Vibe Engines
YouTube
AI System Design

Design a Conversational AI

Step 1 / 9

Learn AI system design by building a production conversational AI like ChatGPT step by step.

The numbers to beat128kwindow (tokens)~4 chars≈ 1 tokenin + outboth billed

The whole design, in writing

Learn AI system design by building a production conversational AI like ChatGPT step by step. An interactive guide covering the inference gateway, context-window assembly and token budgeting, conversation memory, model serving with batching and KV cache, token streaming, and prompt/output guardrails.

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is a conversational AI, really?

Strip away the chat bubble and a system like ChatGPT does one deceptively simple thing: take your message plus everything said so far, run it through a language model, and stream back an answer — for millions of people at once.

Userweb / app
New in this step: User.

That sentence hides the hard parts. What exactly do we feed the model (and how much fits)? How does it remember earlier turns? How do we serve a giant model fast and affordably? And how do we keep the output safe?

What the new pieces do

Userclient
A person typing a message. Sends a turn, then watches the answer stream back token by token.

Step 1 · The skeleton

A client, a gateway, a model

Your browser can’t hold a 100-billion-parameter model, and it can’t be trusted with the keys, the rate limits, or other users’ data. So where does the request go?

Userweb / appInference Gatewayentry point
New in this step: Inference Gateway.

You type a message. Where does the thinking happen?

  1. Frontier models are tens to hundreds of gigabytes and need GPUs. Even if it fit, you’d ship the weights to every user and lose all control over cost and safety.

  2. A neutral backend holds the keys, enforces limits, assembles the prompt and routes to the GPU fleet. This is the spine of every LLM product.

  3. Letting clients hit inference directly means no auth boundary, no rate limiting, no prompt assembly, and your model endpoint exposed to the world.

We put an Inference Gateway in the middle. The browser sends a turn; the gateway authenticates, rate-limits, and orchestrates everything behind it. This is the client → gateway → model backbone under every chat product.

What the new pieces do

Inference Gatewaybackend
The single front door for every chat request. Authenticates, rate-limits, then orchestrates context, guardrails and model serving.

Step 2 · What the model sees

Assemble the context window

A language model has no memory between calls — it only sees the text you hand it this request. So what exactly do we put in front of it for each turn?

Inference Gatewayentry pointContext Builderprompt assembly
New in this step: Context Builder.

The model is stateless. What do you send it for each new message?

  1. Then it forgets the system instructions and everything said earlier — every turn starts from scratch. The "conversation" disappears.

  2. Right instinct, wrong limit. Conversations grow past the context window and cost scales with every token sent. You need to assemble and budget.

  3. A Context Builder composes the model input each turn — the system prompt, the recent/relevant history, and the new message — shaped to fit the window.

A Context Builder composes the model input for every turn: the system prompt (who the assistant is), the conversation history, and the new message. The model is stateless, so the full context is rebuilt and resent on each request.

What the new pieces do

Context Builderservice
Assembles the model input: system prompt + recent history + the new message, trimmed to fit the context window.

Step 3 · Budget the tokens

It won’t all fit — now what?

The window is finite (say 128k tokens) and every token costs money and latency. A long chat — or a big document — blows past it. How do you decide what makes the cut?

Context Builderprompt assemblyVector / KBretrieval
New in this step: Vector / KB.

The conversation is longer than the context window. What gets sent?

  1. Overflowing the window errors or silently drops the start — usually the system prompt, the most important part. You must decide what to keep on purpose.

  2. Allocate the token budget deliberately — pin the system prompt, keep recent turns, compress older ones, and pull in only the relevant facts (RAG) instead of everything.

  3. Recency usually matters most in a conversation, and the oldest turns are often stale. Blindly keeping the start wastes budget on what no longer matters.

The builder works to a token budget. It pins the system prompt, keeps the most recent turns, and summarizes or drops older ones. For knowledge beyond the chat, it retrieves only the relevant passages from a vector store (RAG) instead of stuffing everything in.

  • 128kwindow (tokens)
  • ~4 chars≈ 1 token
  • in + outboth billed

What the new pieces do

Vector / KBindex
Optional knowledge source. The context builder can retrieve relevant facts to ground the answer (RAG).

Back of the envelope

system prompt
pinned — always sent
recent turns
kept verbatim while they fit
older turns
summarized into a running recap
external knowledge
retrieved on demand (RAG), not preloaded

Step 4 · Make it remember

Where does history live?

The model is stateless and the browser can’t be trusted to hold the true history. Yet tomorrow the user reopens the chat and expects it all to be there. Where does the conversation actually live?

Memory ServiceconversationConversation DBturns & threads
New in this step: Memory Service, Conversation DB.

The model forgets between calls. Where is the real conversation stored?

  1. Inference doesn’t update the weights. The model never remembers anything between requests; memory must live outside it.

  2. Close the tab and it’s gone; switch devices and it’s gone; you can’t rebuild context server-side. The client is a cache, not the source of truth.

  3. Persist every turn server-side. The builder loads history from it each request and saves the new turn — survives reloads, devices and crashes.

A Memory Service reads and writes the Conversation DB — the durable source of truth for every turn. Each request, the builder loads recent history from it; after each answer, the new turn is appended. The browser is just a fast cache of what the database already knows.

What the new pieces do

Memory Serviceservice
Loads and saves conversation history so a chat remembers earlier turns across requests.
Conversation DBstore
Durable storage of every message in every conversation — the source of truth for history.

Step 5 · The real cost

Inference is the expensive part

Assembling a prompt is cheap. Running a hundred-billion-parameter model to generate each token is not — it needs GPUs, and a single request can hold one for seconds. How should the system treat this?

UserInference GatewayContext BuilderMemory ServiceConversation DBVector / KB
The system as it stands at this step. · swipe to pan the diagram

What dominates the cost and latency of a chat turn?

  1. A few small reads are microseconds. Real, but a rounding error next to generation.

  2. Each output token is a full forward pass through the model on scarce GPUs. This dominates both cost and latency, so it gets its own carefully-managed tier.

  3. Bytes are tiny; latency here is milliseconds. The seconds you wait are the model thinking, not the wire.

Generation runs on a dedicated Model Servers tier — a fleet of GPUs holding the model weights. Because each output token is a full pass through the model, this tier is the bottleneck we design everything else around: it gets its own queue, its own scaling, and its own failure handling.

Step 6 · Serve it efficiently

Batching and the KV cache

GPUs are most efficient when fully fed, but chat requests arrive one at a time and each generates tokens one at a time. Run them naively and the GPUs sit half-idle while users wait. How do you serve a giant model fast?

Model ServersGPU inferenceModel Weightssharded · GPUEvent Streamusage · evals
New in this step: Model Servers, Model Weights, Event Stream.

How do you keep scarce GPUs busy across many concurrent chats?

  1. A single chat can’t saturate a GPU, and others queue behind it. You pay for hardware that mostly idles while users wait.

  2. Pack concurrent requests into shared batches and cache the attention state (KV cache) so each new token is cheap. This is how real serving stacks hit high throughput.

  3. Re-running attention over the entire context for each output token is quadratic waste — exactly what the KV cache exists to avoid.

The model servers use continuous batching — packing many in-flight chats into shared GPU passes — and a KV cache that stores attention state so each new token reuses prior work instead of recomputing the whole prompt. Every turn also emits usage events (tokens, latency) to the stream for billing and evals.

What the new pieces do

Model Serversservice
The GPU fleet running the LLM. Batches concurrent requests and reuses the KV cache to generate tokens efficiently.
Model Weightsstore
The model parameters, loaded across the GPU fleet. Large models are sharded over many GPUs.
Event Streambus
Every turn emits events — tokens used, latency, safety flags — feeding billing, evals and abuse detection.

Back of the envelope

continuous batching
merge concurrent requests into shared GPU passes
KV cache
reuse attention state — no recompute per token
weights sharded across GPUs
big models span many devices
emit usage events
tokens + latency → billing, evals, abuse detection

Step 7 · Don’t make them wait

Stream the tokens back

A long answer can take many seconds to finish. If the user stares at a blank screen until it’s done, the product feels broken — even when it’s working perfectly. How do you make it feel instant?

UserInference GatewayMemory ServiceModel Servers
New in this step: Model Servers → Inference Gateway, Inference Gateway → User. · swipe to pan the diagram

A full answer takes 6 seconds to generate. What does the user see?

  1. Six seconds of blank screen reads as "frozen." You’re hiding progress the model is already making.

  2. Stream over a persistent connection (SSE/WebSocket) so words appear as they’re produced. Time-to-first-token is what users actually feel.

  3. Polling adds latency and load and still lags the model. A push stream delivers each token the moment it exists.

The model generates tokens incrementally, and the gateway streams them straight to the browser over a persistent connection (server-sent events or WebSocket). The user sees the answer appear word by word. The metric that matters is time-to-first-token, not total time.

Step 8 · Keep it safe

Guard the input and the output

Users will send prompts that try to jailbreak, extract secrets, or produce harmful content — and even a well-behaved model can generate something unsafe. Where do you put the checks?

Inference Gatewayentry pointGuardrailsin + outModel Serversbatch + stream
New in this step: Guardrails.

Where do safety checks belong in the request path?

  1. Models can be jailbroken and make mistakes. Safety can’t be the model’s job alone — you need an independent gate.

  2. Input filtering catches malicious prompts but not unsafe generations. A clean prompt can still yield output you must block. You need both sides.

  3. A Guardrails layer checks the prompt before generation and the answer before it reaches the user — defense on both edges, independent of the model.

A Guardrails layer screens both edges: the incoming prompt (block jailbreaks, abuse, PII leaks) before it reaches the model, and the outgoing answer before it streams to the user. Safety flags are emitted to the event stream so abuse can be detected and rate-limited over time.

What the new pieces do

Guardrailsservice
Screens the incoming prompt and the outgoing answer for unsafe or disallowed content before either is acted on.

The payoff

You built a conversational AI

From one box to a production LLM system: a gateway that orchestrates, a context builder that budgets tokens, durable memory, a batched GPU serving tier, streaming, and guardrails on both edges.

UserAPI GatewayContext BuilderMemory ServiceGuardrailsModel ServersConversation DBModel WeightsVector / KBEvent Stream
The finished design, end to end. · swipe to pan the diagram

Now overload the model servers and watch how the serving tier — isolated behind its own queue — sheds load and degrades to a smaller model instead of taking the whole product down.

Everything you assembled, in order

  • Inference Gateway — one front door — auth, rate limits, orchestration
  • Context Builder — assembles system prompt + history + message
  • Token budget — pin, keep recent, summarize, retrieve (RAG)
  • Memory Service + DB — history lives outside the stateless model
  • Model Servers — batched GPU inference + KV cache
  • Streaming — tokens pushed as generated — fast time-to-first-token
  • Guardrails — screen both the prompt and the answer
  • Event stream — usage + safety events → billing, evals, abuse detection

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why does the Context Builder rebuild the full prompt on every single turn instead of somehow keeping the model "warm" with prior context?

    Inference is stateless by construction — a model server has no persistent memory of a specific user's session between HTTP requests, and even if a specific GPU replica happened to still hold state from the last turn, the NEXT turn could easily be routed to a different replica by the load balancer. Rebuilding the full context every request is what makes the system horizontally scalable: any replica can serve any turn, because everything the model needs is self-contained in that one request's prompt. The cost is resending tokens every turn (which the token budget in Step 3 manages); the benefit is a serving tier with no per-user sticky state to coordinate.

  2. Why summarize or drop older turns instead of just always retrieving relevant history via RAG, the same way external knowledge is handled?

    RAG retrieval works well for FACTS — discrete, indexable pieces of information you can search for by similarity. Conversation history is different: it's sequential, and often what matters is the THREAD of reasoning (what was already tried, what was already rejected) rather than any single retrievable fact. A running summary preserves that narrative continuity in a compact form; pure retrieval could pull an isolated old message without the surrounding context that made it make sense. Most production systems use a combination: recent turns verbatim, a running summary for the middle, and RAG only for genuinely fact-like content (documents, a knowledge base) rather than for the conversation itself.

  3. The chaos scenario shows model-server overload causing a "busy, retry" response. Why not just queue every request indefinitely instead of shedding load?

    An unbounded queue during sustained overload means EVERY request — including ones from users who'd be happy to retry in a few seconds — waits behind an ever-growing backlog, and the wait time itself becomes unpredictable and can exceed what any client-side timeout tolerates anyway. Shedding load (explicitly telling some requests "busy, retry shortly" rather than silently queuing them) keeps the SYSTEM's behavior predictable and lets the client decide how to handle the delay (retry with backoff, show a friendly message) — an unbounded queue just defers the failure to an unpredictable later moment instead of communicating it honestly now.

  4. Why do both the incoming prompt AND the outgoing answer need separate guardrail checks — couldn't one sufficiently strict input filter prevent unsafe outputs entirely?

    A clean, entirely reasonable-looking prompt can still produce an unsafe generation — the model might hallucinate harmful content, leak something from its training data, or simply make a mistake that has nothing to do with anything malicious in the input. Conversely, an input filter alone catches attempts to elicit bad behavior but can't catch bad behavior that emerges without an adversarial prompt at all. The two checks are looking for genuinely different things (is the REQUEST trying to misuse the system vs. is the RESPONSE actually safe to show), which is why defense needs to sit at both edges of the model, independently.

  5. How would this design need to change to support multiple simultaneous conversations per user (e.g. several open chat tabs), rather than one linear thread?

    The Conversation DB already models history as discrete turns rather than one global blob, so the core schema doesn't need to change much — each conversation just needs its own thread/session identifier, and the Memory Service loads and saves against that specific thread id rather than a single per-user history. The more interesting design question is at the Context Builder: for a genuinely multi-thread product, you'd want to make sure a message sent in thread A never accidentally pulls context from thread B (a scoping discipline similar to tenant isolation elsewhere in this series, just scoped to conversation-id instead of tenant-id) — the gateway and context builder both need to consistently thread that id through every step, not just the database schema.

Check yourself — the answers, and why

Nine steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. The Inference Gateway sits between the client and the model servers mainly to…

    • Make the UI render faster
    • Hold auth/rate-limiting/orchestration outside an untrusted client, and keep the model endpoint from being exposed directly
    • Store the conversation history

    A browser can't be trusted with keys or limits — the gateway is the neutral backend every chat product needs.

  2. The Context Builder assembles a fresh prompt every turn because…

    • It's required by the UI framework
    • The model is stateless — nothing persists between calls unless it's explicitly resent in the next prompt
    • It reduces GPU cost

    Inference never updates the weights, so anything the model needs to "remember" has to be re-included in every request.

  3. When a conversation exceeds the context window, the right approach is to…

    • Send everything and let the model truncate
    • Deliberately budget the window — pin the system prompt, keep recent turns, summarize or retrieve the rest
    • Always keep only the oldest messages

    Treating the window as a budget to allocate on purpose avoids losing the system prompt or wasting tokens on stale content.

  4. Continuous batching and the KV cache exist together to…

    • Simplify the codebase
    • Keep GPUs busy across many concurrent chats while avoiding redundant recomputation of attention state per token
    • Reduce the size of the model weights

    Batching raises throughput across the fleet; the KV cache makes each new token cheap by reusing prior attention state.

  5. Guardrails screen both the incoming prompt and the outgoing answer because…

    • One check would be too slow
    • A clean prompt can still produce an unsafe answer, and vice versa — the two checks catch different failure modes
    • The model can't be trusted to generate any text at all

    Input filtering and output filtering are independent defenses against different risks, not redundant copies of the same check.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Answer a turn: take the user’s message plus the conversation so far, run the model, and stream back a reply.
  • Remember the chat: persist every turn so a conversation survives reloads, new devices, and crashes.
  • Fit the window: assemble system prompt + relevant history + the new message within a fixed token budget.
  • Ground with knowledge: retrieve only the relevant passages from a vector store (RAG) instead of stuffing everything in.
  • Stay safe: screen the incoming prompt and the outgoing answer for unsafe or disallowed content.

The qualities that shape everything

Each one names the mechanism that buys it.

Cost and latency don’t blow up per turn
The context builder works to a token budget — pin the system prompt, keep recent turns, summarize the rest — since cost and latency both scale with tokens in + out.
A stateless model that still remembers
History lives in a durable Conversation DB, reloaded per turn — the model is fed its memory as text each request rather than holding it.
Isolate the expensive bottleneck
GPU inference gets its own queued, independently-scaled tier so it can be batched, autoscaled, and protected from the cheap stateless work around it.
Serve a giant model economically
Continuous batching packs concurrent chats into shared GPU passes and a KV cache reuses attention state, so each new token is cheap.
Feel instant even on a slow answer
Tokens stream to the browser over a persistent connection as they’re generated — time-to-first-token, not total time, is what the user feels.
Safe on both edges of the model
An independent guardrails layer screens the prompt before generation and the answer before it streams — a clean prompt can still yield unsafe output.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Budget the context window over send the whole conversation every time

Overflowing the window errors or silently drops the start — usually the system prompt, the most important part — and cost scales with every token. Pin, keep recent, summarize or retrieve the rest.

A durable Conversation DB over keep history only in the browser tab

Close the tab and it’s gone; switch devices and it’s gone. The client is a fast cache, not the source of truth; the stateless model needs history reloaded from a durable store each turn.

Continuous batching + KV cache over one request per GPU, start to finish

A single chat can’t saturate a GPU and others queue behind it. Batching raises throughput across the fleet, and the KV cache avoids re-running attention over the whole prompt per token.

Stream tokens as generated over wait for the whole answer

Six seconds of blank screen reads as frozen. Streaming doesn’t speed up generation — it shows progress immediately, and optimizing time-to-first-token beats optimizing total time.

Guardrails on both edges over filtering only the user’s input

Input filtering catches malicious prompts but not unsafe generations — a clean prompt can still produce output you must block. Safety is an independent layer around the model, not a property of it.

What this teaches

Learn AI system design by building a production conversational AI like ChatGPT step by step. An interactive guide covering the inference gateway, context-window assembly and token budgeting, conversation memory, model serving with batching and KV cache, token streaming, and prompt/output guardrails.

Key takeaways

  • Inference Gateway — one front door — auth, rate limits, orchestration
  • Context Builder — assembles system prompt + history + message
  • Token budget — pin, keep recent, summarize, retrieve (RAG)
  • Memory Service + DB — history lives outside the stateless model
  • Model Servers — batched GPU inference + KV cache
  • Streaming — tokens pushed as generated — fast time-to-first-token
  • Guardrails — screen both the prompt and the answer
  • Event stream — usage + safety events → billing, evals, abuse detection

Concepts covered

  • What is a conversational AI, really?
  • A client, a gateway, a model
  • Assemble the context window
  • It won’t all fit — now what?
  • Where does history live?
  • Inference is the expensive part
  • Batching and the KV cache
  • Stream the tokens back
  • Guard the input and the output
built to be reasoned about, not memorized — make the calls, trip the guardrail, run the quiz.
Finished this one? 0 / 61 AI System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs