Skip to content

LLM Gateway

Six months after the assistant launched, BoxOffice has five features that call models: the support assistant, a review summarizer, an event-description translator, a fraud-signal classifier and the nightly job that embeds the catalog. Each team picked its own provider, wrote its own retry loop and pasted an API key into its own configuration. Then three things happened in the same week: a provider had a two-hour outage and three features went down with it; the translator, stuck in a retry loop, burned a month of budget in one night; and nobody could answer the CFO's question, "how much does the assistant cost per conversation?".

The fix is the same one the course applied to HTTP traffic in Traffic & Edge: put one gateway in front of every model, and move the cross-cutting concerns into it.

flowchart LR
    subgraph Before["Before: every service talks to every provider"]
        a1["assistant"] --> p1["provider A"]
        a2["summarizer"] --> p1
        a2 --> p2["provider B"]
        a3["translator"] --> p2
        a4["embed job"] --> p3["self-hosted"]
    end
    subgraph After["After: one LLM gateway"]
        b1["assistant"] --> gw["LLM Gateway"]
        b2["summarizer"] --> gw
        b3["translator"] --> gw
        b4["embed job"] --> gw
        gw --> q1["provider A"]
        gw --> q2["provider B"]
        gw --> q3["self-hosted"]
    end

What the gateway does

An LLM gateway is a reverse proxy specialized for model APIs. Clients send requests in one format to a model alias (assistant-default, not a vendor model name); the gateway decides which real deployment serves it, and records what happened.

Responsibility What it means Classic equivalent
Unified API one request/response format for every provider; clients do not import vendor SDKs adapter / anti-corruption layer
Credentials provider keys live only in the gateway (secret store); clients authenticate with their own service identity API gateway + secrets management
Routing alias β†’ model and deployment, by feature, difficulty, cost, latency or data residency L7 routing
Fallbacks and retries on 429, 5xx or a slow first token, retry with backoff or move to the next deployment retry + circuit breaker
Quotas and budgets requests and tokens per minute per user, team and feature; currency budgets per month rate limiter
Caching exact-match and semantic response caches; prompt-cache-friendly requests cache
Observability one trace span per call with model, tokens, cost, TTFT; dashboards per feature access log + metrics
Policy hooks calls the Safety Mesh before and after the model (redaction, filters) WAF / filters

The gateway is on the critical path of every AI feature, so it must itself be stateless, horizontally scaled and highly available β€” the quota counters and caches live in Redis, exactly like a rate-limiting API gateway (Edge).


Routing

The same prompt can be served by models that differ by an order of magnitude in price and speed. Routing chooses, per request, the cheapest deployment that is good enough.

Strategy How it decides Good for Watch out
Static, per feature the alias maps to one model (translator β†’ small model) most features; simple and predictable over-paying for easy requests
By task a rule or a small classifier labels the request (FAQ, booking, complaint) and picks a model assistants with mixed traffic the classifier is one more model to evaluate
Cascade try the small model first; escalate when its answer fails validation or a confidence check1 high volume, mostly easy requests worst-case latency = small + large
Learned router a model trained on preference data predicts whether the small model suffices2 very high volume needs training data and monitoring
Latency/cost aware send to the deployment with the lowest current TTFT or price multi-provider, multi-region oscillation; keep some stickiness
Compliance personal data stays in deployments in an approved region, or self-hosted LGPD, contractual constraints fewer fallbacks available
flowchart TD
    req["request: alias assistant-default"] --> pii{"contains personal data?"}
    pii -- yes --> region["only approved-region deployments"]
    pii -- no --> all["all deployments"]
    region --> task{"task class"}
    all --> task
    task -- "FAQ / small talk" --> small["small model"]
    task -- "booking with tools" --> large["large model"]
    small --> check{"answer valid?"}
    check -- no --> large
    check -- yes --> done(["respond"])
    large --> done

Aliases decouple applications from model names. When a new model version appears, the platform team points assistant-default at it for 5% of traffic (a canary, as in Cloud-native delivery), compares online metrics and offline evals, then rolls forward or back β€” with no change in the assistant's code. Pin model versions behind the alias: a provider silently updating "latest" is an untested deploy.


Fallbacks and retries

A model call has no side effects: retrying it is always safe (tool calls are a different story β€” see Agents & Tools). But not every error deserves a retry, and some deserve a different model.

Error Meaning Gateway action
429 rate limited your quota at that deployment is exhausted honor Retry-After if short; otherwise fall back to another deployment
5xx, "overloaded" provider-side failure retry with exponential backoff + jitter, then fall back; count it in the circuit breaker
No first token within the TTFT deadline queueing or an overloaded region cancel and fall back (or hedge: send a second request after the p95 TTFT and keep the first to answer)
Stream breaks after tokens were sent connection lost mid-answer cannot retry transparently β€” the user already saw part of it; retry the whole answer and tell the client to replace it
400 context too long the prompt does not fit do not retry the same deployment; trim history, or route to a long-context model
Refusal / content filter the model or provider declined do not fall back to a "less strict" model; return the policy answer
Invalid JSON / schema output did not match the requested format one repair attempt with the validation error, then fail; prefer constrained decoding (structured outputs)

A simplified, provider-agnostic sketch of the fallback chain, combining the retry and circuit-breaker patterns of Reliability:

router.py
import random
import time

CHAIN = {  # model alias -> ordered list of deployments
    "assistant-default": ["large@provider-a", "large@provider-b", "medium@self-hosted"],
    "assistant-cheap": ["small@self-hosted", "small@provider-a"],
}
RETRYABLE = {429, 500, 502, 503, 504}


def complete(alias, request, clients, breakers, ttft_deadline_s=3.0):
    """clients and breakers are this sketch's own interfaces, not a real SDK."""
    last_error = None
    for deployment in CHAIN[alias]:
        if breakers[deployment].is_open():
            continue  # known to be failing: skip it without waiting
        for attempt in range(2):
            try:
                reply = clients[deployment].chat(request, first_token_timeout=ttft_deadline_s)
                breakers[deployment].record_success()
                return reply
            except ProviderError as error:
                breakers[deployment].record_failure()
                last_error = error
                if error.status not in RETRYABLE:
                    raise  # 400, context too long, refusal: another try will not help
                if error.status == 429 and not error.retry_after:
                    break  # quota gone here: go straight to the next deployment
                wait = error.retry_after or 0.2 * 2**attempt
                time.sleep(wait + random.uniform(0, wait))  # backoff with jitter
    raise AllDeploymentsFailed(alias) from last_error

Two traps specific to models:

  • A fallback model is a different program. Prompts tuned for one model, its tool-calling format and its refusal style may not transfer. Run the eval suite against every model in the chain, not only the primary β€” otherwise the fallback silently lowers quality exactly during an incident.
  • Retries multiply token spend. The retry storm of the reliability class now has a price tag: cap attempts, use a retry budget (e.g. retries ≀ 10% of calls), and count retried tokens against the caller's quota.

In the BoxOffice code: the gateway of the assistant (lab 12)

Hands-on 12 β€” The service and the LLM gateway uses LiteLLM's in-process Router as the gateway. The assistant asks for a route (an alias), never a model name: local, a small open model on Ollama, always exists; claude exists only when an API key does. The fallback claude β†’ local keeps the assistant answering when the provider fails or the quota runs out β€” and it is the first trap above in practice: the fallback is a much smaller model, so the evaluations must run against it too. One retry and a 120 s timeout, because an LLM call is the slowest dependency BoxOffice has. One service does not need the separate LiteLLM proxy; with several AI services, the proxy becomes the shared gateway of this page. The lab estimates one question at about 1,000 input and 100 output tokens, β‰ˆ US$ 0.006 on the Claude route.

services/assistant/app/llm.py
"""The LLM gateway: one place that knows models, routes, fallbacks, timeouts and costs."""
import litellm
from litellm import Router

from .config import settings

litellm.drop_params = True  # drop parameters a provider does not support instead of failing

model_list = [{
    "model_name": "local",
    "litellm_params": {"model": settings.local_model, "api_base": settings.ollama_url},
}]
fallbacks = []
if settings.claude_model:  # optional: only when ANTHROPIC_API_KEY is set
    model_list.append({"model_name": "claude", "litellm_params": {"model": settings.claude_model}})
    fallbacks = [{"claude": ["local"]}]  # provider down or out of quota: answer with the local model

router = Router(model_list=model_list, fallbacks=fallbacks, num_retries=1, timeout=120)


def real_model(route: str) -> str:
    """The provider model behind a route name (for metrics and costs)."""
    return settings.claude_model if route == "claude" and settings.claude_model else settings.local_model


async def embed(texts: list[str]) -> list[list[float]]:
    response = await litellm.aembedding(model=settings.embed_model, input=texts, api_base=settings.ollama_url)
    return [item["embedding"] for item in response.data]

Try it

Load the AI assistant scenario with the spike profile and chaos set to medium. Watch the LLM Gateway when the provider throttles and fails: without a fallback, most questions end in errors. Select the LLM Gateway and turn on Fallback model: the error rate falls by half or more, but the cost per month more than doubles and p99 grows by several seconds β€” the fallback answers what the quota rejected, slower. Compare the token rate the gateway reports with the provider's limit. Open it in the full playground to combine the fallback with fewer agent steps and a semantic cache.


Quotas and token budgets

Provider limits are expressed in requests per minute (RPM), tokens per minute (TPM) β€” sometimes input and output separately β€” and concurrent requests, per account and per region. A single feature with a bug can exhaust the account's TPM and take every other feature down with it: the noisy neighbor problem, now with an invoice. OWASP lists it as unbounded consumption3; some call it denial of wallet.

The gateway enforces its own, finer limits:

Scope Example limit (BoxOffice) Why
Per user 20 requests and 60,000 tokens per hour stop abuse and runaway loops from one account
Per feature assistant ≀ 60% of the account TPM; nightly jobs ≀ 20% protect interactive traffic from batch jobs
Per request max_tokens ≀ 600 output tokens; ≀ 8 model calls per task bound the worst case of a single request
Per month US$ budget per feature with alerts at 50/80/100% finance can plan; no surprises

Counting tokens before you know them. A token bucket counts tokens instead of requests, but output tokens are only known after the call. The usual approach is reserve, then reconcile: before the call, count the input tokens with the model's tokenizer and reserve input + max_tokens from the bucket; after the call, return the unused part. Keep the counters in Redis with atomic scripts, as in the rate-limiter design of Edge.

What happens when a budget runs out is a product decision, decided in advance:

  1. Degrade: route to a smaller model or answer from the cache/FAQ.
  2. Queue: background work (summaries, embeddings) waits for the next window β€” the async queue pattern.
  3. Shed: return 429 with Retry-After and a friendly message; interactive traffic has priority over batch, and paying flows over browsing.

BoxOffice at 10:00

The flash sale makes the assistant's traffic 50Γ— normal for 15 minutes. The gateway reserves 70% of the account's TPM for the assistant alias with priority interactive, pauses the nightly embedding job (priority batch), routes questions classified as FAQ to the small model and the semantic cache, and caps each fan at 5 assistant requests per minute. When the reserved TPM is 90% used, new conversations get a link to the static FAQ page instead of a model answer: the sale goes on; the assistant degrades.


Caching: three different things

"Cache the LLM" means three different mechanisms with very different risks.

Exact-match response cache Prompt caching (prefix cache) Semantic cache
Key hash of the full request (model, prompt, parameters) the longest identical prefix of the prompt embedding of the user question; hit if similarity β‰₯ threshold
What is reused the final answer the model's internal state for the prefix (the KV cache) a stored answer to a similar question
Where it lives gateway (Redis) inside the model server / provider gateway (Redis or a vector index)
Saves the whole call input-token cost (cached tokens billed at a discount) and TTFT the whole call
Correctness risk none (same input) none β€” the answer is computed normally false hits: a similar question that needs a different answer
Hit ratio in a chat low (questions rarely repeat byte-for-byte) high (system prompt + tools + history repeat on every call) medium for FAQ-like traffic; ~0 for personal, tool-using requests

Prompt caching

Processing the input (the prefill, below) produces internal key/value tensors for every token. When the next request starts with the same tokens, the server can reuse them instead of recomputing. Most major providers and open-source servers (e.g. vLLM's automatic prefix caching) support this; providers bill cached input tokens at a fraction of the normal price, require a minimum prefix length, and keep entries for minutes of inactivity. Details β€” automatic or explicit cache markers, discount, lifetime β€” differ by provider: read their documentation.

Because the match is on an exact prefix, the order of the prompt matters:

flowchart LR
    s["1. system prompt<br/>(static)"] --> t["2. tool definitions<br/>(static)"] --> k["3. retrieved documents<br/>(per conversation)"] --> h["4. conversation history<br/>(grows)"] --> u["5. new user message<br/>(changes every call)"]

Cache busters

A timestamp, a request ID or the user's name at the top of the system prompt changes the prefix on every call and silently drops the hit ratio to zero. So does re-ordering tool definitions or re-serializing JSON with unstable key order. Put everything that varies at the end, and monitor the cached-token ratio per feature.

Semantic caching

A semantic cache embeds the user question (see RAG & Memory) and looks for a previous question whose embedding is close enough. "What time do the gates open at Arena Paulista?" and "when do doors open at Arena Paulista?" should hit. The danger is the reverse case: "can I get a refund for the Saturday show?" and "can I get a refund for the Sunday show?" are almost identical as vectors and may need different answers.

Rules that keep a semantic cache safe:

  • Scope the key: tenant, language, event or venue ID, and policy version are part of the key, not only the vector.
  • Only for generic answers: never cache answers that used tools, personal data or the user's permissions.
  • Tune the threshold with data: label a sample of (question, cached question) pairs as same/different and pick the similarity threshold for the precision you need; a hit ratio gained with wrong answers is a bug.
  • Short TTL and invalidation when the source document changes (the same problem as in Caching).

Inference performance: TTFT, TPOT and throughput

Generating an answer has two phases with opposite profiles:

  • Prefill β€” the model processes all input tokens in parallel. It is compute-bound and ends when the first output token appears: it sets the time to first token (TTFT). TTFT grows with the uncached prompt length and with queueing at the server.
  • Decode β€” the model generates output tokens one at a time, each step reading all the model weights from GPU memory. It is memory-bandwidth-bound and sets the time per output token (TPOT), also called inter-token latency (ITL).
\[ \text{latency}_{\text{e2e}} = \text{TTFT} + \text{TPOT} \times (N_{out} - 1) \qquad \text{speed per user} = \frac{1}{\text{TPOT}}\ \text{tokens/s} \]

With TTFT = 0.6 s and TPOT = 20 ms (50 tokens/s), a 250-token answer takes 0.6 + 0.02 Γ— 249 β‰ˆ 5.6 s; a 1,000-token answer, ~20.6 s. Answer length is a latency decision.

2026-11-01T21:54:20.157964 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

import matplotlib.pyplot as plt
from io import StringIO

# Left: end-to-end latency = TTFT + TPOT x (output tokens - 1)
models = {"large model (TTFT 0.6 s, 50 tok/s)": (0.6, 0.020),
          "small model (TTFT 0.2 s, 150 tok/s)": (0.2, 1 / 150)}
tokens = list(range(1, 1001, 10))

# Right: one self-hosted replica decoding a batch of b sequences per step.
# Step time = fixed weight read + a small per-sequence cost (illustrative).
W_READ_MS, PER_SEQ_MS = 12.0, 0.15
batch = list(range(1, 257))
per_stream = [1000 / (W_READ_MS + PER_SEQ_MS * b) for b in batch]
aggregate = [b * s for b, s in zip(batch, per_stream)]

fig, ax = plt.subplots(1, 2, figsize=(11, 4))
for label, (ttft, tpot) in models.items():
    ax[0].plot(tokens, [ttft + tpot * (n - 1) for n in tokens], label=label)
ax[0].set_xlabel("output tokens")
ax[0].set_ylabel("end-to-end latency (s)")
ax[0].set_title("Latency grows with the answer length")
ax[0].legend()
ax[0].grid(alpha=.3)

ax[1].plot(batch, aggregate, color="tab:green", label="replica throughput (tok/s)")
ax[1].set_xlabel("concurrent sequences in the batch")
ax[1].set_ylabel("replica throughput (tokens/s)", color="tab:green")
twin = ax[1].twinx()
twin.plot(batch, per_stream, color="tab:red", label="per-user speed (tok/s)")
twin.set_ylabel("per-user speed (tokens/s)", color="tab:red")
ax[1].axvline(64, ls="--", color="gray")
ax[1].text(66, max(aggregate) * 0.1, "KV-cache full\n(8k-token contexts)", color="gray")
ax[1].set_title("Batching: throughput vs per-user speed")
ax[1].grid(alpha=.3)
plt.tight_layout()

buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())
plt.close()

Streaming

Since the answer arrives token by token, the gateway streams it to the client β€” usually with Server-Sent Events (SSE) over HTTP, as seen in Protocols. The fan starts reading after the TTFT instead of after the full latency, which is why TTFT is the interactive SLO (e.g. p95 TTFT < 1.5 s) and TPOT only needs to beat reading speed (people read roughly 4–8 words per second, so ~15–20 tokens/s already feels fluent).

Streaming has architectural consequences:

  • Long-lived connections. By Little's law, 33 requests/s Γ— 12 s per streamed answer β‰ˆ 400 open connections per instance pool. Use asynchronous servers (FastAPI/uvicorn, Netty), and raise idle timeouts on load balancers and proxies that would cut a slow stream.
  • Tool calls and JSON cannot be acted on until complete. Only the final natural-language answer benefits from streaming.
  • Output guardrails and streaming conflict. Checking the whole answer before showing it removes the TTFT benefit; checking chunk by chunk may show a problematic sentence before it is caught. Common compromise: stream, check in windows of a few sentences, and retract if needed β€” and never stream content of high-risk actions before validation.

Batching and the KV cache

If you self-host a model (or just want to understand provider limits), one number explains most behavior: decode is limited by memory bandwidth, not by compute. Every decode step reads all the weights. An 8-billion-parameter model in 16-bit precision is ~16 GB; a GPU with ~3 TB/s of memory bandwidth needs at least ~5 ms to read it β€” so a single sequence cannot exceed ~200 tokens/s however powerful the GPU.

Batching amortizes that read: one pass over the weights advances b sequences at once. Replica throughput grows almost linearly with b while each user's speed degrades slowly (right plot above). Two techniques made this practical:

  • Continuous (in-flight) batching4: instead of waiting for a whole batch to finish, the server adds and removes sequences at every token step, so a short answer does not wait for a long one.
  • PagedAttention5: the KV cache of each sequence is stored in fixed-size pages, like virtual memory, eliminating fragmentation and allowing prefix sharing across requests (which is how self-hosted prompt caching works).

The limit is memory for the KV cache, the key/value tensors kept for every token of every active sequence:

\[ \text{KV bytes per token} = 2 \times \text{layers} \times \text{KV heads} \times \text{head dim} \times \text{bytes per value} \]

For a Llama-3-8B-class model (32 layers, 8 KV heads, head dimension 128, 16-bit values): 2 Γ— 32 Γ— 8 Γ— 128 Γ— 2 = 128 KiB per token. An 8,000-token conversation needs ~1 GiB. On an 80 GB GPU with ~16 GB of weights, ~64 GB remain: ~64 concurrent 8k-token conversations, or ~512 of 1k tokens. Concurrency is bounded by context length Γ— users, which is why long prompts are expensive even when they are cheap per token.

Other serving techniques you will see in 2026 (all trade something for throughput or latency):

Technique Idea Trade-off
Quantization (8-bit, 4-bit weights) fewer bytes per weight β†’ less memory, faster decode some quality loss; must be evaluated
Speculative decoding6 a small draft model proposes several tokens; the large model verifies them in one step extra compute; gain depends on how often the draft is right
Chunked prefill split long prefills so they do not stall ongoing decodes slightly higher TTFT for long prompts
Prefill/decode disaggregation7 run the two phases on different GPU pools KV cache must move between pools; more moving parts

Self-hosted or API?

Criterion Provider API Self-hosted (vLLM, SGLang, Ollama for dev)
Cost model per token; zero when idle per GPU-hour; paid when idle
Best when traffic is spiky or small; best-quality models needed traffic is high and steady; smaller open-weight models suffice
Latency control limited (shared infrastructure, rate limits) full (batch size, hardware, region)
Data control contractual (retention, region) full
Operations none GPUs, drivers, autoscaling, model updates, on-call

A back-of-the-envelope check: a GPU that costs US$ c per hour and sustains T output tokens/s at your batch size costs \(c / (3600 \cdot T \cdot u)\) per token at utilization u. At 10% utilization it is 10Γ— more expensive per token than at 100%. Self-hosting wins only with steady load β€” or when data cannot leave your network. For offline work (embedding the catalog, running evals), many providers also offer batch APIs: asynchronous, results within hours, at a discount.


Observability at the gateway

Because every call passes through it, the gateway is the natural place to emit the LLM telemetry described in Safety, Evals & Observability: one span per call with model, tokens (input, cached, output), cost, TTFT and finish reason, plus metrics aggregated per alias, feature and user tier. The dashboards answer the CFO's question directly: US$ per conversation, by feature, per day.

Products

The gateway is a pattern, not a product. Examples, to explore β€” check each product's current documentation for capabilities:

  • LiteLLM: unified API, routing, fallbacks, budgets, spend tracking, as a proxy server or as an in-process SDK β€” its SDK Router is the gateway of Hands-on 12.
  • Envoy AI Gateway: LLM traffic management built on Envoy Gateway.
  • Serving engines behind the gateway: vLLM, SGLang, Ollama (local development).
  • Cloud model platforms with routing and cross-region capacity: Amazon Bedrock, Google Vertex AI, Azure AI Foundry.
  • API-management products with LLM policies (token limits, semantic caching): Azure API Management, Kong AI Gateway, Cloudflare AI Gateway.

In the interview

Put an LLM gateway between every service and the models, and name four of its jobs: routing by alias, fallbacks with circuit breakers, token quotas with priorities, and cost/latency telemetry. Separate TTFT (interactive SLO, streamed) from TPOT (speed) in your latency estimate, and distinguish prompt caching (safe, cuts input cost and TTFT) from semantic caching (risky, only for generic answers). If the interviewer asks about self-hosting, answer with the KV-cache memory and utilization arguments.

Best practices

  • Set a TTFT deadline per alias and fail over on it β€” for a fan staring at an empty bubble, a provider that queues is as bad as one that is down; for example 3 s at p95 for the interactive alias.
  • Reserve tokens with priorities, not only limits β€” interactive traffic keeps ~70% of the account's TPM at 10:00 and batch jobs pause first (quotas).
  • Run the eval suite on every model of the fallback chain β€” in BoxOffice the fallback of claude is a 1.5-billion-parameter local model (lab 12 β€” llm.py): it must pass the same golden set, or the fallback is a silent quality drop during an incident.
  • Alert on the cached-token ratio per feature β€” cached input is billed at a fraction of the price (10% in the overview's example); a prompt edit that moves a variable to the top multiplies the input cost overnight.
  • Attribute cost per feature and per user tier at the gateway β€” "US$ per conversation" should be a dashboard panel, not an investigation. In BoxOffice: metrics.price() turns the tokens of every answer into dollars (lab 12 β€” Guardrails and metrics).

Common pitfalls

  • Model names hard-coded in services β€” every model change is a multi-team deploy. Use aliases.
  • Unpinned "latest" models β€” quality changes without a deploy, and nobody knows why the evals dropped.
  • Retrying 400s and refusals, or falling back to a weaker-guarded model on a refusal.
  • Rate limiting by requests only β€” one 100k-token request costs as much as a thousand small ones.
  • Variable data at the top of the prompt β€” the prompt cache never hits.
  • A semantic cache keyed only by the vector β€” leaks answers across events, users or tenants.
  • Proxy idle timeouts shorter than the longest stream β€” answers cut in the middle under load.

How it connects

flowchart LR
    SVC["assistant, summarizer,<br/>translator, embed job"] --> GW["LLM gateway<br/>routing by alias"]
    GW --> FB["fallbacks + circuit breaker<br/>β†’ Reliability"]
    GW --> Q["token quotas, priorities<br/>β†’ API gateway rate limiting"]
    GW --> PC["prompt caching<br/>(stable prefix)"]
    GW --> SC["semantic caching<br/>β†’ Caching invalidation"]
    GW --> ST["streaming over SSE, TTFT<br/>β†’ Edge protocols"]
    GW --> CAN["canary by alias<br/>β†’ Cloud-native delivery"]
    GW --> OBS["tokens, cost, TTFT<br/>β†’ Safety, Evals & Observability"]
    Q --> BG["background work waits<br/>β†’ Messaging queues"]

Palette components: llmgw Β· apigw (its classic cousin) Β· thirdparty (the model providers behind it) Β· cache (semantic cache) Β· queue (background work waiting for quota) Β· metrics and tracing (tokens, cost, TTFT). Scenarios: ai-assistant, cascading-failure (a slow provider without a breaker), rate-limiter (quotas as rate limits). Continues in: Edge β€” API gateway, Reliability β€” patterns, Caching β€” invalidation, Edge β€” protocols, Agents & Tools.



  1. CHEN, L.; ZAHARIA, M.; ZOU, J. FrugalGPT: How to Use Large Language Models While Reducing Cost and Improving Performance. arXiv, 2023. ↩

  2. ONG, I. et al. RouteLLM: Learning to Route LLMs with Preference Data. arXiv, 2024. ↩

  3. OWASP. Top 10 for LLM Applications 2025. LLM10: Unbounded Consumption. ↩

  4. YU, G.-I. et al. Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI, 2022. ↩

  5. KWON, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP, 2023. ↩

  6. LEVIATHAN, Y.; KALMAN, M.; MATIAS, Y. Fast Inference from Transformers via Speculative Decoding. ICML, 2023. ↩

  7. ZHONG, Y. et al. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving. OSDI, 2024. ↩