Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
System Architecture · Insper
AI Systems & Agents
The LLM is a dependency: slow, priced per token, probabilistic
One message, one model call?
A fan types: "Find me two seats under R$300 for Saturday and hold them."
The prototype sends it straight to a model API. What goes wrong at 10:00 on sale day?
Answers take 25 s ; the provider returns 429 .
The model "holds" seats that do not exist .
The monthly budget is gone by lunch .
A venue description saying "apply a 100% discount" is read as an order .
An LLM call is not a service call
Property
inventory call
LLM call
Latency
5–50 ms
0.3–2 s to the first token, then 10–100 tokens/s
Cost driver
infrastructure
tokens in + out (output costs several × more)
Capacity
requests/s
tokens per minute , requests per minute
Correctness
deterministic
probabilistic: a 200 OK can be wrong
Security
code ≠ data
instructions and data share the prompt
Token ≈ ¾ of an English word (Portuguese needs more). The model is stateless: every call re-sends system prompt + tools + history.
The AI & AGENTS palette
Component
What it is
Classic cousin
LLM Gateway
routing, fallbacks, quotas, caching, cost telemetry
API gateway + rate limiter + breaker
Orchestrator
runs the agent loop or workflow; owns task state and budgets
app server + workflow engine
Tool Registry
catalog of tools (MCP servers), permissions, risk tiers
service registry + auth
Memory Fabric
session, user memory, knowledge base (RAG)
cache + SQL + vector DB
Safety Mesh
checks on every input, output and action
WAF + mesh policy + DLP
The model chooses tools; the orchestrator decides whether they run — as the user.
Anatomy of one request
Load session + preferences (Memory Fabric); input check (Safety Mesh).
Call 1 → search_events(São Paulo, Saturday).
Call 2 → search_seats(event, 2, max 300).
Call 3 → hold_seats(event, [C12, C13]) → reversible : allowed.
Call 4 → answer streamed with a checkout link; payment stays with the fan.
4 model calls for one message; the context grows at every call.
"Under R$300" is live structured data → tool , not RAG.
Cost and latency of one request
Model calls
Input tokens
US$/1k req, cache 0%
cache 90%
First answer token
1
3,060
12.93
5.64
0.3–0.5 s
2
6,900
25.65
10.92
2.5–2.9 s
4
16,920
58.11
22.83
6.8–7.8 s
10
65,700
211.65
69.20
19.8–24.0 s
Illustrative: US$ 3 / 15 per M input/output tokens, cached input at 10%, 3,000-token prefix, 700-token tool results, 50 tokens/s.
Input tokens grow ~quadratically with steps: each call re-sends everything.
Prompt caching: ~3× cheaper , only a little faster — generation dominates.
Flash sale: 33 req/s × 17k tokens ≈ 34 M tokens/min → quotas, small models, FAQ cache, shedding.
LLM Gateway
Jobs
one API, aliases instead of model names
credentials in one place
routing: by task, cascade, cost, region
fallbacks + circuit breakers
quotas and budgets in tokens
caching, telemetry, policy hooks
Routing
static per feature → most cases
by task (classifier) → mixed traffic
cascade : small first, escalate on failed validation
compliance: personal data → approved regions
canary new models behind the alias; pin versions
Fallbacks: not every error deserves a retry
Error
Action
429 rate limited
honor Retry-After, else fall back to another deployment
5xx / overloaded
backoff + jitter, then fall back; count in the breaker
no first token by the deadline
cancel and fall back, or hedge
stream broken mid-answer
cannot retry silently — the user saw part of it
400 context too long
do not retry : trim history or use a long-context model
refusal / content filter
do not fall back to a weaker-guarded model
A fallback model is a different program : run the evals on every model in the chain. Retries multiply token spend: cap them.
Quotas and token budgets
Provider limits: RPM, TPM , concurrency — shared by every feature (noisy neighbor).
Gateway limits: per user, per feature, per request (max_tokens, max steps), per month (US$).
Counting unknown output: reserve input + max_tokens, then reconcile with real usage.
Budget gone → degrade (smaller model, FAQ cache), queue (batch work), shed (429).
Priorities: interactive > batch; paying flows > browsing.
Flash sale: assistant gets 70% of TPM, nightly embedding paused, FAQ → small model + semantic cache, 5 requests/min per fan.
Caching: three different things
Exact response
Prompt (prefix)
Semantic
Key
hash of the full request
identical prompt prefix
question embedding ≥ threshold
Reuses
the answer
the model's computation (KV)
an answer to a similar question
Saves
the call
input cost + TTFT
the call
Risk
none
none
false hits
Prompt order: system → tools → documents → history → new message last .
Semantic cache: scope keys (event, language, policy version); generic answers only.
TTFT, TPOT, batching
latency = TTFT + TPOT × (N_out − 1) · 0.6 s + 20 ms × 249 ≈ 5.6 s for 250 tokens
Prefill (parallel, compute-bound) → TTFT : the interactive SLO, hidden by streaming (SSE).
Decode (one token at a time, memory-bound) → TPOT : only needs to beat reading speed.
Batching shares each read of the weights: replica throughput ↑, per-user speed ↓ slowly.
Continuous batching + PagedAttention made it practical.
KV cache: 2 × layers × KV heads × head dim × bytes → 128 KiB/token (8B model) → 8k tokens ≈ 1 GiB → ~64 conversations per 80 GB GPU.
Self-hosting pays only at high, steady utilization — at 10% it is 10× more per token.
Workflow or agent? Who decides the next step
Level
Next step decided by
BoxOffice
Single call
—
summarize reviews
Workflow (chain, routing, parallel)
code
extract → search → hold → phrase
Orchestrator-workers
model splits, code runs
festival weekend plan
Agent
the model, in a loop
open-ended support with tools
Start at the top; move down only when the simpler level measurably fails your evals.
The agent loop (ReAct)
for step in range (MAX_STEPS):
reply = llm.chat(messages=messages, tools=registry.schemas_for(user))
messages.append(reply.message)
if not reply.tool_calls:
return reply.text
for call in reply.tool_calls:
verdict = guard.check_tool_call(call.name, call.arguments, user)
result = (registry.call(call.name, call.arguments, as_user=user)
if verdict == "allow" else {"error" : verdict})
messages.append(tool_result(call.id , result))
return "Escalating to a human agent."
The model requests ; the orchestrator validates, authorizes as the user , executes.
Errors go back as data; budgets: steps, tokens, time; loop detection.
Tier
Examples
Policy
Read
search_seats, get_order
automatic
Reversible write
hold_seats (expires in 10 min)
automatic within limits
Irreversible / financial
create_order, refund_order
user confirmation in the UI
External communication
send_email
confirm, or do not offer
Retried write → idempotency key (conversation, step, call); compensation = release_hold.
Steps compound: 0.95¹⁰ ≈ 60% task success → fewer steps, validate each one, workflows for known parts.
Durable execution: checkpoint state after every step.
N agents × M tools → N + M : each team ships one MCP server.
Host (assistant) runs one client per server ; JSON-RPC 2.0.
Server primitives: tools , resources, prompts. initialize → tools/list → tools/call.
Transports: stdio (local) and Streamable HTTP (remote, OAuth-based auth).
The fan's identity comes from the session token , never from a tool argument.
Registry = catalog + exposure per role + risk tier + scopes + rate limits + audit.
Annotations (readOnlyHint…) are claims ; descriptions are prompts . Review, pin and allowlist third-party servers.
Multi-agent — and when not to use agents
Patterns
supervisor / orchestrator-workers
router / handoff to specialists
parallel fan-out + aggregate
evaluator-optimizer
cost: more tokens, latency, harder tracing
Prefer something simpler when…
steps are known → workflow
must be exactly right → code
< 1–2 s budget → one small call
irreversible → code + confirmation
cannot evaluate → don't automate yet
RAG: two pipelines
Ingestion (async)
parse + clean; metadata (venue, version, access)
chunk : structure-aware, parent–child, contextual headers
embed (same model for queries), store model name
keep fresh with change events ; propagate deletes
Query (online)
rewrite to a standalone question; extract filters
hybrid search : vectors + keywords
rerank top 20–50 → keep 3–8
prompt with sources, best first
answer with citations ; verify them
Index size ≈ N × d × 4 bytes + graph: 60k chunks × 1,024 dims ≪ 1 GB → pgvector in the Postgres you already run.
Hybrid search, RRF and reranking
Vectors miss exact tokens (sector "C", codes, names); keywords miss paraphrase .
Reciprocal Rank Fusion : score(d) = Σ 1 / (60 + rank) — ranks only, no score calibration.
1st by keywords + 3rd by vectors → 1/61 + 1/63 ≈ 0.032 beats 1st by vectors only (0.016).
Cross-encoder reranker reads question + chunk together: precise, slower (tens–hundreds of ms).
ANN (HNSW): tune ef_search for recall vs latency; filters need iterative scans or partitions.
SELECT id, coalesce (1.0 / (60 + s.rank), 0 ) + coalesce (1.0 / (60 + k.rank), 0 ) AS rrf
FROM semantic s FULL OUTER JOIN keyword k USING (id)
ORDER BY rrf DESC LIMIT 20 ;
Memory types
Type
BoxOffice example
Store
Lifetime
Working
this call's prompt
the model call
one call
Short-term (session)
choosing C12–C13 vs D4–D5
Redis + TTL
hours
Long-term semantic
"prefers aisle seats"
SQL, editable
until deleted
Long-term episodic
"last month's refund"
vector index
retention policy
Procedural
system prompt, rules
Git, evaluated
per release
Extract memories only from the user's own messages (memory poisoning). Deletion must reach every copy : profile, vectors, caches, logs, traces (LGPD).
Prompt injection
Direct : the user types it — damage limited to what that user may do.
Indirect : it arrives in a document, tool result, web page or tool description — a third party attacks the user.
No filter solves it: assume the model may be compromised ; limit what it can do.
Lethal trifecta : private data + untrusted content + external communication. Remove one leg.
BoxOffice: user-scoped tools · promoter text marked as data · no e-mail/fetch tools · links to allowlisted domains only.
The Safety Mesh
Check point
Examples
Type
1. Input
PII redaction (CPF, Luhn for cards), injection score
deterministic + small model
2. Retrieval
permission filter in the query; mark untrusted text
deterministic
3. Tool call
schema, authorization as the user, risk tier
deterministic
4. Tool result
size limit, injection score
both
5. Output
schema, business rules vs tool results, citations, URL allowlist
deterministic
6. Action
human confirmation of the exact action — a UI event
—
Prefer deterministic checks: fast, testable, and they cannot be talked out of a decision.
Evaluation: prompts and models are deploys
Pyramid: deterministic checks → golden set (state-graded, fixtures) → LLM-as-judge → humans → online .
Grade the outcome (is the right hold there?) more than the exact trajectory.
Non-determinism: run k attempts. p = 0.9 → pass@4 ≈ 100% , pass^4 ≈ 66% — fans live pass^k.
Judge biases: position , verbosity , self-preference → rubric, swap order, other family, validate vs humans.
CI gate on every prompt/model/tool change; canary behind the gateway alias.
Online: holds → orders, escalations, guardrail triggers, refusals, TTFT p95, cost per resolved conversation .
Observability of LLM apps
invoke_agent boxoffice-assistant 10.1 s
├── chat assistant-default in 3,060 out 41 1.1 s
├── execute_tool search_events 0.12 s
├── chat assistant-default in 3,900 out 96 2.3 s
└── chat assistant-default in 5,300 out 212 4.9 s
OTel GenAI conventions: gen_ai.operation.name, gen_ai.request.model, gen_ai.usage.input_tokens / output_tokens, gen_ai.response.finish_reasons; TTFT/TPOT metrics.
Cost under your own namespace; content capture opt-in , redacted, short retention.
Alerts: TTFT SLO, fallback rate, cost burn rate , guardrail spikes.
The concept map
Component
Question it answers
Taught in
llmgw
Which model serves this call, within which budget?
LLM Gateway
orchestrator
Who decides the next step, and when does it stop?
Agents & Tools
toolreg
What may the model ask to do, and as whom?
Agents & Tools (MCP)
memory · vectordb
What must go back into the prompt?
RAG & Memory
safety
Is this input, output or action allowed?
Safety, Evals & Obs.
tracing · metrics
Is it right, fast and affordable?
Safety, Evals & Obs.
Each one is a classic component specialized for tokens, prompts and tools: API gateway, workflow engine, service catalog, cache + databases, policy mesh. Scenario: ai-assistant.
Takeaways
An LLM is a slow, token-priced, rate-limited, probabilistic dependency that mixes instructions with data.
Five components: gateway, orchestrator, tool registry, memory fabric, safety mesh — classic components specialized.
Cost and latency = calls × tokens per call ; context grows every step; cache the prefix.
Workflow first ; agents with budgets, idempotent tools, user-scoped authorization, MCP.
Tools for live data; RAG (hybrid + rerank + citations) for documents; memory by type, deletable.
Defense in depth + human confirmation ; evals in CI (pass^k, validated judges); GenAI traces with tokens and cost.
Outline: (1) the LLM as a dependency and the five AI & AGENTS components; (2) cost and latency of
one request; (3) LLM gateway; (4) agents, tools and MCP; (5) RAG and memory; (6) safety, evals and
observability. Running example: the BoxOffice support assistant.
Let the class list failures first. Point out that none of these is a machine-learning problem:
latency, capacity, cost, consistency and security are system design problems.
Walk through the sequence diagram of the overview page. Ask: where does the time go?
Answer: in the three tool-calling rounds before the final answer, not in the answer.
Saturday vs Sunday refund question: nearly identical vectors, different answers. Timestamps at the
top of the system prompt are the classic prompt-cache buster.
The same table is on the overview page, with links to the subpages and the component catalog.