AI-powered support ticket Interview Questions | JiQuest

add

#

AI-powered support ticket

Mini project / POC · end to end

AI Support Ticket Triage System: a complete Spring AI mini project.

Three services, Retrieval-Augmented Generation over a pgvector knowledge base, tool-calling against live customer data, confidence-based human escalation, Kafka event routing, JWT RBAC, and AWS S3 attachments — with full diagrams, real code, and a responsible-AI guardrails section.

3Microservices
4Kafka topics
1pgvector store
Customer Ticket Serviceintake + S3 AI Triage ServiceRAG + tool calls Agent Serviceroute + notify LLM providerOpenAI / Claude pgvector KBknowledge base Kafka broker4 topics low-confidence classifications escalate to a human, never auto-close

Project overview and requirements

Support teams drown in ticket volume that needs to be read, categorized, prioritized, and routed before anyone can actually help the customer. This project automates that first triage step with an LLM, while keeping a human in the loop wherever the model isn't confident — the central design tension of any production AI feature.

Functional requirements

Ticket intakeCustomers submit tickets with text and optional file attachments.
AI classificationEvery new ticket is auto-categorized, prioritized, and sentiment-scored using RAG over a knowledge base.
Smart routingHigh-confidence tickets auto-assign to the right specialist; low-confidence ones escalate to a human review queue.
Agent workflowAgents see assigned tickets with an AI-suggested response they can edit, not a black-box auto-reply.

Non-functional requirements

Never auto-closeThe AI never resolves or closes a ticket on its own — only classifies and suggests.
Bounded costOne classification call per ticket, capped tokens, cached KB embeddings — not a call per message.
Explainable routingEvery triage decision stores its confidence score and the KB chunks that informed it.
Service autonomyDatabase-per-service, same as the e-commerce project, for the same independent-deployability reasons.

Technology stack and why each piece is there

TechnologyRole in this projectWhy this one
Java 21 + Spring Boot 3.xRuntime and framework for all three services.Same reasoning as the e-commerce project — virtual threads, records, auto-configuration.
Spring AIChatClient, RAG advisors, tool calling, structured output.Portable across model providers; the advisor chain is the natural place for RAG and guardrails.
PostgreSQL + pgvectorOne database per service; the Triage Service's database also stores KB embeddings.Avoids standing up a separate vector database for a moderate-sized knowledge base — one less moving part.
Apache KafkaAsync events for triage results, escalation, and assignment.Durable, replayable log fits a multi-step, partially-automated workflow better than direct calls.
Spring Security + JWTStateless auth with CUSTOMER / AGENT / ADMIN roles.Identical pattern to the e-commerce project — every service verifies its own JWT.
AWS S3Ticket attachment and knowledge-base source document storage.Presigned URLs again, so large files never pass through application memory.
OpenFeignTriage Service → Ticket Service sync lookups.Fetches full ticket text and customer tier only when the event payload isn't enough.

Jump to a section

High-level architecture

The Ticket Service owns intake and is the source of truth for ticket state. The AI Triage Service is the only service that talks to the LLM provider and the only one that reads the pgvector knowledge base; it never writes ticket state directly — it publishes an opinion as an event, and the Agent Service decides what to do with it. That separation keeps "the model said so" from ever being the same thing as "the system did it."

Client API GatewaySpring Cloud Gateway Ticket Service/tickets /files AI Triage ServiceRAG + classify Agent Serviceroute + notify ticket_dbPostgreSQL triage_dbPostgres + pgvector agent_dbPostgreSQL LLM providerOpenAI / Claude AWS S3 Kafka broker4 topics
Why isn't the Triage Service allowed to write to ticket_db directly? Even though it would be a shorter path, it would let an LLM-driven process mutate another service's system-of-record state without going through that service's own validation and audit path. Publishing an event and letting Ticket Service apply the update itself keeps the same invariant-enforcement boundary the service has for every other caller.

Service breakdown

Ticket Service — intake and system of record

Owns ticket creation, status, and the audit trail of comments. It's the only service a customer or agent writes ticket data through, and the only place attachment metadata lives.

  • POST /tickets — CUSTOMER, creates a ticket, publishes ticket.created
  • GET /tickets/{id} — CUSTOMER (own), AGENT/ADMIN (any)
  • POST /tickets/{id}/attachment-upload-url — presigned S3 PUT URL
  • consumes ticket.triaged / ticket.assigned — updates category, priority, and assignee
Owns ticket_dbPublishes ticket.createdTalks to AWS S3

AI Triage Service — RAG classification and tool calling

Consumes every new ticket, retrieves relevant knowledge-base context, classifies category/priority/sentiment via structured output, optionally calls a tool to check the customer's account tier, and publishes its result as an event. It owns the pgvector knowledge base and never writes ticket state.

  • consumes ticket.created → runs RAG + classification
  • publishes ticket.triaged (confident) or ticket.escalated (low confidence)
  • POST /kb/articles — ADMIN, ingest a new knowledge-base article
  • sync calls Ticket Service (OpenFeign) for full ticket text when the event payload is insufficient
Owns triage_db + pgvectorCalls the LLM providerNever writes ticket state directly

Agent Service — routing and notification

Owns the agent roster and workload, and decides who a ticket goes to. High-confidence ticket.triaged events auto-assign to the least-loaded matching specialist; ticket.escalated events go straight to a senior-review queue instead of the normal load-balancing logic.

  • consumes ticket.triaged → auto-assign by specialty and current load
  • consumes ticket.escalated → assign to senior review queue, flag for manual look
  • publishes ticket.assigned; sends the agent and customer a notification
  • GET /agents/{id}/queue — AGENT, view assigned tickets with the AI-suggested response
Owns agent_dbKafka consumer + producerNo dependency on the LLM provider

Database design

Same database-per-service discipline as the e-commerce project, with one addition: the Triage Service's database has the pgvector extension enabled so knowledge-base embeddings live alongside its relational data instead of in a separate vector database.

ticket_db tickets(id PK, customer_id,  subject, status, category,  priority, s3_key, created_at) ticket_comments(id PK, ticket_id FK,  author_id, body, created_at) triage_db (pgvector) kb_articles(id PK, title, s3_key) kb_chunks(id PK, article_id FK,  chunk_text, embedding VECTOR(1536)) triage_results(id PK, ticket_id,  category, priority, confidence,  sentiment, suggested_response) agent_db agents(id PK, name, email,  specialty, active, current_load) ticket_assignments(id PK,  ticket_id, agent_id FK, status) ticket_id in triage_results and ticket_assignments is a stored value, never a cross-database foreign key CREATE EXTENSION IF NOT EXISTS vector; CREATE INDEX ON kb_chunks USING hnsw (embedding vector_cosine_ops); HNSW index makes cosine similarity search fast at knowledge-base scale.

RAG knowledge base pipeline

Support articles, past resolved-ticket writeups, and product docs are ingested once into pgvector; every incoming ticket then runs a similarity search against that store to ground the classification in your actual support history instead of the model's generic training knowledge.

Ingestion (one-time / admin-triggered) KB articlefrom S3 / admin API TextSplittertoken-aware chunks EmbeddingModelchunk to vector pgvector storekb_chunks table Retrieval (per incoming ticket) Ticket textsubject + description QuestionAnswerAdvisortop-K similarity search ChatClientgrounded classification TriageResultcategory, priority, confidence
// KnowledgeBaseIngestionService.java
public void ingestArticle(String s3Key, String title) {
    Resource resource = s3ResourceLoader.load(s3Key);
    List docs = new TikaDocumentReader(resource).get();
    List chunks = new TokenTextSplitter().apply(docs);
    chunks.forEach(c -> c.getMetadata().put("articleTitle", title));
    vectorStore.add(chunks); // PGVectorStore, backed by triage_db
}
Why pgvector instead of a dedicated vector database? At the scale of a support knowledge base (typically hundreds to low thousands of articles, not billions of vectors), pgvector's HNSW index is fast enough, and keeping it inside the same Postgres instance as the rest of the Triage Service's data means one less system to operate, back up, and secure.

AI classification and tool calling

Classification uses structured output so the result is a typed Java record, not a string to parse, and one tool call lets the model factor in live account data (a Tier-1 enterprise customer's "login broken" ticket should outrank the same words from a free-tier trial account).

public record TriageResult(
    TicketCategory category,      // enum: BILLING, TECHNICAL, ACCOUNT, FEATURE_REQUEST, OTHER
    Priority priority,            // enum: LOW, MEDIUM, HIGH, URGENT
    Sentiment sentiment,          // enum: NEUTRAL, FRUSTRATED, ANGRY
    double confidence,            // 0.0 - 1.0
    String suggestedResponse
) {}

@Service
public class TriageService {
    private final ChatClient chatClient; // built with QuestionAnswerAdvisor over the pgvector store
    private final CustomerTools customerTools;

    public TriageResult classify(Ticket ticket) {
        return chatClient.prompt()
            .system("""
                You triage customer support tickets. Use the retrieved knowledge-base
                context to ground your category and suggested response. Never invent
                policy details that aren't in the retrieved context.
                """)
            .user(u -> u.text("Ticket subject: {subject}\nDescription: {description}")
                        .param("subject", ticket.subject())
                        .param("description", ticket.description()))
            .tools(customerTools) // exposes getCustomerTier(customerId)
            .advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'kb_article'"))
            .call()
            .entity(TriageResult.class);
    }
}

public class CustomerTools {
    @Tool(description = "Get the account tier (FREE, PRO, ENTERPRISE) for a customer id")
    public String getCustomerTier(String customerId) {
        return ticketClient.getCustomerTier(customerId); // OpenFeign call
    }
}
Confidence-based routing, not confidence-based auto-resolution The confidence field only ever decides who handles the ticket next (auto-assign vs. senior review queue) — it never decides whether the ticket gets closed or a reply gets sent automatically. That boundary is what keeps this "AI-assisted" rather than "AI-autonomous."

Security and role-based access control

Identical JWT/RBAC pattern to the e-commerce project: every service independently validates the signed token rather than trusting an upstream header. See that project's security section for the full JwtAuthFilter code — the only thing that changes here is the role set.

RoleCan do
ROLE_CUSTOMERSubmit tickets, view own tickets, add comments to own tickets.
ROLE_AGENTView assigned tickets, view the AI-suggested response, reply, change status.
ROLE_ADMINIngest/manage knowledge-base articles, view all tickets, reassign, view triage confidence scores.
// SecurityConfig.java (AI Triage Service)
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(HttpMethod.POST, "/kb/articles").hasRole("ADMIN")
            .requestMatchers(HttpMethod.GET, "/kb/articles/**").hasAnyRole("ADMIN", "AGENT")
            .anyRequest().authenticated())
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
}
Security note Knowledge-base ingestion is ADMIN-only for a reason beyond access control: anyone who can add "authoritative" KB content can steer what the RAG-grounded classifier treats as ground truth — an under-protected ingestion endpoint is a prompt-injection vector into every future ticket classification.

Service communication: Kafka topics and the triage sequence

TopicProducerConsumer(s)Payload
ticket.createdTicket ServiceAI Triage ServiceticketId, customerId (event-notification style, not full text)
ticket.triagedAI Triage ServiceTicket Service, Agent ServiceticketId, category, priority, confidence, suggestedResponse
ticket.escalatedAI Triage ServiceAgent ServiceticketId, reason ("low_confidence")
ticket.assignedAgent ServiceTicket ServiceticketId, agentId, assignedAt
Customer Ticket Service Kafka Triage Service Agent Svc 1. POST /tickets 2. ticket.created 3. consume 4. RAG retrieve + classify (tool call) 5a. confidence ≥ 0.75 → ticket.triaged 5b. confidence < 0.75 → ticket.escalated 6a. auto-assign by specialty/load 6b. route to senior review queue 7. ticket.assigned 8. consume, update ticket
Event-notification vs. event-carried-state-transfer Notice ticket.created carries only IDs (event-notification), forcing the Triage Service to fetch full text via a sync call when needed, while ticket.triaged carries the full classification result (event-carried-state-transfer) since Agent Service needs all of it. The choice per event is deliberate: keep the broker payload small when only one consumer needs the detail and can fetch it on demand; embed it when multiple consumers need the same data and a round trip would be wasteful.

AWS S3: ticket attachments and KB source documents

Same presigned-URL pattern as the e-commerce project's product images, reused here for two purposes: customer-uploaded ticket attachments (screenshots, logs) and the source documents an admin uploads before they're ingested into the knowledge base.

  • POST /tickets/{id}/attachment-upload-url — CUSTOMER, presigned PUT for a screenshot or log file
  • POST /kb/source-upload-url — ADMIN, presigned PUT for a new KB source document before ingestion
Production note Attachments can contain customer PII or secrets pasted into a log file — scan uploads (a virus/malware scan, and ideally a basic PII detector) before an attachment is ever referenced from a prompt sent to the LLM provider.

AI safety and guardrails

This is the section that separates a demo from something a real support team could trust. Every guardrail here maps to a concrete failure mode this system would otherwise have.

Never auto-resolvesThe model only classifies and suggests; a human agent sends every reply and closes every ticket.
Confidence thresholdBelow 0.75, tickets skip auto-routing entirely and go to senior human review.
Grounded-only responsesThe system prompt forbids inventing policy details not present in retrieved KB context.
Explainability storedEvery triage_results row stores which KB chunks and confidence produced the decision.
PII-aware ingestionKB ingestion and attachments are scanned before reaching a prompt sent to a third-party model.
Bounded tool accessThe only tool the classifier can call is a read-only customer-tier lookup — no write or destructive tools.
Cost ceilingOne classification call per ticket, capped output tokens, no retry storms on ambiguous input.
Human override always winsAn agent can change category/priority; the AI's fields are a starting suggestion, not the final record.
The single most important guardrail is the confidence-based escalation path, because it's the one guardrail that fails safe: when the model is unsure, the ticket doesn't get a possibly-wrong automatic decision — it gets a human, which is exactly the behavior you'd want from a human triage assistant who genuinely doesn't know.

Project folder structure

ai-ticket-triage-poc/
├── api-gateway/
│  ├── src/main/java/com/jiquest/gateway/GatewayApplication.java
│  ├── src/main/resources/application.yml
│  ├── Dockerfile
│  └── pom.xml
│
├── ticket-service/
│  ├── src/main/java/com/jiquest/ticket/
│  │  ├── TicketServiceApplication.java
│  │  ├── config/SecurityConfig.java, KafkaProducerConfig.java, KafkaConsumerConfig.java
│  │  ├── controller/TicketController.java, FileController.java
│  │  ├── service/TicketService.java, S3StorageService.java
│  │  ├── listener/TriageResultListener.java, AssignmentListener.java
│  │  ├── repository/TicketRepository.java, CommentRepository.java
│  │  └── entity/Ticket.java, TicketComment.java
│  ├── src/main/resources/application.yml
│  ├── Dockerfile
│  └── pom.xml
│
├── ai-triage-service/
│  ├── src/main/java/com/jiquest/triage/
│  │  ├── TriageServiceApplication.java
│  │  ├── config/SecurityConfig.java, VectorStoreConfig.java
│  │  ├── client/TicketClient.java          // OpenFeign
│  │  ├── controller/KnowledgeBaseController.java
│  │  ├── service/TriageService.java, KnowledgeBaseIngestionService.java
│  │  ├── tools/CustomerTools.java          // @Tool methods
│  │  ├── listener/TicketCreatedListener.java
│  │  ├── repository/KbArticleRepository.java, TriageResultRepository.java
│  │  └── entity/KbArticle.java, TriageResult.java
│  ├── src/main/resources/application.yml
│  ├── Dockerfile
│  └── pom.xml
│
├── agent-service/
│  ├── src/main/java/com/jiquest/agent/
│  │  ├── AgentServiceApplication.java
│  │  ├── config/KafkaConsumerConfig.java, KafkaProducerConfig.java
│  │  ├── controller/AgentController.java
│  │  ├── service/RoutingService.java, NotificationService.java
│  │  ├── listener/TicketTriagedListener.java, TicketEscalatedListener.java
│  │  ├── repository/AgentRepository.java, AssignmentRepository.java
│  │  └── entity/Agent.java, TicketAssignment.java
│  ├── src/main/resources/application.yml
│  ├── Dockerfile
│  └── pom.xml
│
├── docker-compose.yml
├── .env.example
└── README.md

Local development setup

# docker-compose.yml (excerpt)
services:
  postgres-ticket:
    image: postgres:16
    environment: { POSTGRES_DB: ticket_db, POSTGRES_PASSWORD: postgres }
    ports: ["5433:5432"]

  postgres-triage:
    image: pgvector/pgvector:pg16   # ships the vector extension pre-installed
    environment: { POSTGRES_DB: triage_db, POSTGRES_PASSWORD: postgres }
    ports: ["5434:5432"]

  postgres-agent:
    image: postgres:16
    environment: { POSTGRES_DB: agent_db, POSTGRES_PASSWORD: postgres }
    ports: ["5435:5432"]

  kafka:
    image: apache/kafka:3.7.0
    ports: ["9092:9092"]

  ai-triage-service:
    build: ./ai-triage-service
    depends_on: [postgres-triage, kafka]
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres-triage:5432/triage_db
      SPRING_AI_OPENAI_API_KEY: ${OPENAI_API_KEY}
    ports: ["8082:8080"]

  # ticket-service and agent-service follow the same shape as the e-commerce project's services

For fully offline development, point spring.ai.openai.base-url at a local Ollama instance instead of a paid provider — the ChatClient code doesn't change.

Testing strategy

@Test
void lowConfidenceClassificationTriggersEscalationNotAutoAssign() {
    ChatModel mockModel = mock(ChatModel.class);
    when(mockModel.call(any(Prompt.class)))
        .thenReturn(fakeResponse("""
            {"category":"TECHNICAL","priority":"HIGH","sentiment":"FRUSTRATED",
             "confidence":0.42,"suggestedResponse":"..."}"""));
    ChatClient client = ChatClient.builder(mockModel).build();

    TriageResult result = new TriageService(client, customerTools).classify(sampleTicket());

    assertThat(result.confidence()).isLessThan(0.75);
    // and assert the service published to ticket.escalated, not ticket.triaged
}
Mocked ChatModel for deterministic unit testsTestcontainers pgvector/pgvector for RAG integration testsTestcontainers Kafka for the triage-to-assignment flow

Observability and cost monitoring

The Triage Service is the only service with a variable, usage-based cost, so it gets extra instrumentation the other two don't need.

Token usage per ticketLogged via Spring AI's Micrometer integration, tagged by category so you can see which ticket types are expensive to classify.
Confidence distributionA histogram of confidence scores over time flags model or prompt drift before it becomes a support-quality problem.
Escalation rateA sudden spike in ticket.escalated volume is an early warning the KB is missing coverage for a new issue.
RAG retrieval latencyTraced as a separate span from the LLM call itself, so a slow pgvector query doesn't get misattributed as "the model is slow."

Path to production on AWS

Local (this POC)AWS equivalent
3 services in Docker ComposeECS Fargate or EKS, one service per task definition
pgvector/pgvector containerAmazon RDS PostgreSQL with the pgvector extension enabled
Single-broker Kafka containerAmazon MSK
S3 with local credentialsS3 + IAM task roles, no static keys
Direct OpenAI/Anthropic API keySame provider via Spring AI, key sourced from AWS Secrets Manager

Key design decisions and interview talking points

Why does the AI only classify and never auto-resolve tickets?

An LLM can be confidently wrong, and a wrong auto-resolution or auto-reply directly damages a customer relationship with no human check in the loop. Limiting the model's authority to classification and suggestion keeps the failure mode of a bad AI call bounded to "an agent has to fix a mis-tagged ticket," not "a customer got a wrong or unauthorized answer."

How is the confidence threshold of 0.75 chosen, and is it fixed forever?

It starts as a reasonable default and should be tuned empirically: track how often auto-assigned (high-confidence) tickets get reassigned or corrected by an agent versus escalated ones, and adjust the threshold to minimize both wasted human review and incorrect auto-routing. It's a live operational parameter, not a constant to set once and forget.

Why pgvector inside the Triage Service's own database instead of a shared vector store used by multiple services?

The knowledge base is exclusively the Triage Service's concern — no other service needs semantic search over support articles — so a shared vector store would violate the same database-per-service boundary the rest of the system follows, for a capability only one service actually uses.

What happens if the LLM provider is down or times out when a ticket needs triage?

The consumer catches the failure, and rather than dropping the ticket, publishes directly to ticket.escalated with reason "triage_unavailable" so it still reaches a human queue — a triage outage degrades to "everything needs manual review," not to tickets silently getting stuck untriaged.

How would you prevent a malicious ticket description from manipulating the classifier via prompt injection?

Ticket text is always treated as untrusted user content in the user message, never elevated into the system message; the system prompt explicitly instructs the model to classify the ticket rather than follow instructions found within it; and because the model's only available tool is a read-only lookup, even a successful injection has nothing destructive to reach for.

Why give the classifier a tool call to check customer tier instead of just including it in the prompt payload?

Both work, but a tool call keeps the customer-tier lookup live and authoritative at classification time rather than baked into whatever the Ticket Service happened to send in the event, and it's the same tool-calling pattern used elsewhere in the system, so there's one consistent way services expose live data to the model instead of two.

Why does ticket.created carry only IDs while ticket.triaged carries the full classification result?

Only the Triage Service needs full ticket text, and it can fetch that on demand, so keeping the event thin avoids duplicating potentially large description text into every message on the broker. ticket.triaged, by contrast, is needed identically by both Ticket Service and Agent Service, so embedding the full result once avoids two redundant fetches.

How would you evaluate whether the RAG-grounded classifier is actually more accurate than a non-RAG baseline?

Build a labeled evaluation set of real historical tickets with agent-confirmed correct categories, run both a RAG-enabled and a RAG-disabled version of the classifier against it, and compare category accuracy and confidence calibration — if RAG isn't measurably improving results, the added retrieval latency and complexity isn't earning its cost.

What stops an ADMIN from poisoning the knowledge base with incorrect information that the classifier then treats as authoritative?

Nothing at the technical layer beyond the RBAC restriction itself — this is fundamentally a process control, not a code control. A real deployment would add a review/approval step before a new KB article goes live, and an audit log of who ingested what, the same governance you'd want over any authoritative internal documentation.

Why is the suggested response shown to the agent for editing rather than sent directly to the customer?

Even a well-grounded RAG response can be subtly wrong, tonally off, or missing account-specific context the model doesn't have access to; routing it through an agent as a draft captures most of the time-saving benefit of AI assistance while keeping a human accountable for what the customer actually receives.

How would you handle a ticket in a language the knowledge base has no content for?

Detect this indirectly through the confidence score: a query with no relevant KB matches produces low-quality retrieved context, which should legitimately lower the model's classification confidence and route the ticket to escalation — the same guardrail that handles "the model doesn't know" also handles "the knowledge base doesn't cover this," without needing separate language-detection logic.

Why is Agent Service kept free of any dependency on the LLM provider?

Routing and notification are core operational functions that must keep working even during an AI outage or a provider API change; isolating the LLM dependency entirely inside the Triage Service means an AI-related incident degrades triage quality, not the ability to route and notify on tickets that are already classified or escalated.

How would you extend this system to learn from agent corrections over time?

Log every case where an agent changes the AI's category or priority as a labeled correction, and periodically review that correction log both to refine the prompt and confidence threshold, and to identify systematic KB gaps — this is evaluation-driven prompt iteration (see the Spring AI guide's golden-dataset discussion) rather than model fine-tuning, which is unnecessary at this scale.

What would break first if ticket volume grew 100x overnight?

The AI Triage Service's LLM calls: unlike the relational writes in the other two services, each classification is a network call to an external provider with its own rate limits and latency, making it the natural bottleneck and the first place you'd add a queue-depth-based autoscaling policy and provider-side rate-limit handling.

Why database-per-service here too, given this project is smaller than the e-commerce one?

Consistency of architectural discipline matters more than the absolute size of the project — applying the same boundary here keeps the pattern reusable and teachable across projects, and specifically prevents a future "just add a foreign key, it's faster" shortcut that would recouple the Triage Service to Ticket Service's schema.

If an interviewer asks you to justify the single biggest architectural decision in this project, what do you say?

Separating "the AI's opinion" (a published event) from "the system's action" (a state change made by a different service) — because it's the decision that turns an LLM from something the system blindly trusts into something the system treats as one more, fallible input, which is the core judgment production AI system design actually tests.

Related guides

Update these hrefs to your published Blogger post URLs once each page is live.

No comments
Leave a Comment