Skip to content

    AI Architect

    LLMOps and Observability for AI Systems

    How AI architects trace one request, version prompts and models as releases, gate canaries with confidence intervals and attribute cost by team and feature.

    By Published 26 min read

    Share

    This lesson is part of AI Architect: From AI Engineer to Expert. Enroll for free, then sign in, to read it in full.

    What you'll learn: how one request leaves a trace you can debug from, why a prompt or model change is a versioned release, how a shadow, canary and A/B ladder limits damage, how to size a canary gate, and how to tag cost at the gateway so a bill can be explained.

    At a glance

    Figure 1. The LLMOps loop: trace, evaluate, release, monitor

    flowchart LR accTitle: The LLMOps loop accDescr: A production request leaves a trace in the trace store. The eval service judges a sample of traces. A release gate combines that evidence with the release record. A passing release climbs the shadow, canary and A/B ladder. Monitoring watches symptoms and cost tags and can roll back to the previous release. PR["Production request"] --> TR["Trace (one per request)"] TR --> TS["Trace store"] TS --> EV["Eval service (sampled judging)"] EV --> GT{"Release gate"} RR["Release record (prompt, model, params, tools, retrieval)"] --> GT GT -- promote --> LD["Shadow, canary, A/B ladder"] LD --> MN["Monitor symptoms and cost tags"] MN --> PR MN -- regression or alert --> RB["Roll back to previous release"]

    How to read this: evidence flows left to right from a real request to a release decision, and the arrow from monitoring back to production is what makes it a loop rather than a pipeline.

    • A trace is the record of one request: timed steps, their parent-child links and the facts that explain them.
    • Anything that shapes an answer (prompt, model, settings, tools, retrieval) is part of a release and gets one ID.
    • Releases climb a ladder: offline gate, shadow, canary, A/B, full. Each rung buys evidence at a different price.
    • A small stage can only detect a big problem. The number of judged items decides what a gate can see.
    • Cost tags stamped at the gateway turn a token total into "which team, which feature, which release".
    • Traces are evidence and also a data store: decide what they may hold before the first request.

    The real-world problem

    A team ships a one-line prompt edit on Friday. By Tuesday, support tickets mention wrong return windows from one region. The dashboard shows normal latency and error rates, and the logs show only status 200. Nobody can say which prompt version produced the answer a customer saw, so the team can neither find the cause nor undo the change with confidence.

    After this page you will be able to

    • Read a trace of one AI request and name the span that holds the cause of a bad answer.
    • List what a release pins (prompt, model, parameters, tools, retrieval configuration) and describe a rollback.
    • Size a canary: the items needed, the traffic share and the three gate outcomes (promote, hold, block).
    • Design cost tags stamped at the gateway and state what they cannot attribute.
    • Decide what a trace may store about prompts and answers.

    Core concepts

    What one request leaves behind

    Figure 2. One request as a tree of spans

    flowchart TD accTitle: One request as a tree of spans accDescr: A root span for handling a question lasts 4.1 seconds. Its child, the orchestrate span, lasts 4.0 seconds and has three children that run one after another. Retrieve lasts 0.9 seconds and records passage identifiers. Rerank lasts 0.3 seconds. The model call lasts 2.6 seconds and records the release identifier, token counts and finish reason. R["handle_question: 4.1 s"] --> O["orchestrate: 4.0 s"] O --> RT["retrieve: 0.9 s, passage IDs, top_k 5"] O --> RK["rerank: 0.3 s"] O --> MD["chat model: 2.6 s, release rel-0142, 812 tokens in, 164 out, finish: stop"]

    How to read this: each box is a span, an arrow means "is the parent of", and the facts after the duration are attributes that make the span searchable.

    A trace is a set of spans that share a trace ID, arranged as a hierarchy. OpenTelemetry defines a span as "a unit of work or operation" and attributes as key-value metadata that annotate it (OpenTelemetry, Traces, as of 2026-10-22). The architect's job is to decide which attributes every AI span must carry.

    Check yourself: the orchestrate span lasts 4.0 s and its three children run one after another (0.9 s, 0.3 s, 2.6 s). How much time is spent in the orchestrator's own code?

    0.2 s (4.0 minus 0.9 + 0.3 + 2.6 = 3.8). If the children overlapped, their durations would not simply add up, so the figure's wording "one after another" matters.

    Prompts and config as versioned releases

    What changed Is it a release? Why
    Prompt text, one sentence Yes It changes every answer
    Model ID or provider Yes Different weights, different behaviour
    Temperature, output limit, reasoning effort Yes Changes length, cost and quality
    Tool names or schemas Yes Changes what the model can call
    Retrieval: index, top-k, reranker, filters Yes Changes the evidence the model sees

    Anthropic's 23 April 2026 post on Claude Code quality reports traces them to three separate changes. Two were a default setting and a prompt line: the default reasoning effort moved from high to medium, and a system prompt instruction added length limits. The third cleared older reasoning from idle sessions. The post says "The API was not impacted" (Anthropic Engineering, 23 April 2026).

    The release ladder

    Figure 3. The release ladder with its exits

    stateDiagram-v2 accTitle: The release ladder accDescr: A candidate release starts at the offline gate. If it passes it moves to shadow, then canary, then A/B, then full release. A failure at any rung sends it to rolled back, which returns to the previous release. state "Offline gate" as OG state "Shadow" as SH state "Canary" as CA state "A/B test" as AB state "Full release" as FR state "Rolled back" as RB [*] --> OG OG --> SH: gate passes SH --> CA: errors and cost within limits CA --> AB: gate promotes AB --> FR: decision rule met OG --> RB: gate fails SH --> RB: failure CA --> RB: gate blocks AB --> RB: failure FR --> RB: alert RB --> [*]

    How to read this: every rung has an exit to "Rolled back", and the exit rule for each rung is written before the rung starts.

    Stage Who sees the output Can detect Cannot detect
    Offline gate Nobody Regressions on the golden set Cases the set lacks, the real traffic mix
    Shadow Nobody (a copy of traffic, responses discarded) Latency, cost and format errors on real inputs User reaction, behaviour that needs real side effects
    Canary A small share of users Hard failures and large quality drops Small quality drops (too few items)
    A/B Randomly assigned users A user-outcome difference of a size chosen in advance Rare harms below the sample size
    Full Everyone Drift and incidents, through monitoring Anything monitoring does not measure

    Try the quiz below; each answer follows from the table.

    Which rung of the ladder answers this? (interactive when JavaScript is on)

    You want to know whether a new prompt raises latency and malformed outputs on real inputs, with no user seeing its answers. Which stage?

    • Offline gate
    • Shadow
    • A/B test
    Answer

    Shadow. Shadow copies real traffic to the candidate and discards its responses, so users never see them. Tools with side effects must be stubbed or read-only.

    You need to know whether users resolve more issues with the new answers, measured on randomly assigned users. Which stage?

    • Canary
    • Shadow
    • A/B test
    Answer

    A/B test. Random assignment lets you compare user outcomes between variants. A canary is not randomised for measurement and a shadow has no user reaction.

    A 1% canary yields 70 judged answers per day. What can it reliably tell you?

    • Whether quality dropped by 4 points
    • Whether errors or malformed outputs jumped
    • Nothing at all
    Answer

    Whether errors or malformed outputs jumped. Seventy items cannot detect a 4-point quality drop (the later sections show about 349 are needed). Hard signals such as errors and format failures are visible at small volume.

    Symptoms versus causes in dashboards

    Google's SRE book says a monitoring system should answer two questions: "what's broken, and why?" The first is the symptom, the second the cause (Google SRE Book, Monitoring Distributed Systems). Its four golden signals are latency, traffic, errors and saturation. None of them says whether an answer was right.

    Symptom Likely causes Look at
    p95 latency up, errors flat Longer prompts or outputs, slow retrieval, a new model route Span durations by stage, token attributes
    Cost per request up, traffic flat Prompt grew, cache misses, retries, longer agent loop Tokens per span, retry count, release ID
    Thumbs-down up in one segment Prompt or retrieval change that hurts that slice, stale document Sampled judge score per slice, passage versions
    Answers cut off Output limit reached Finish-reason attribute on the model span
    Errors up Rate limit, timeout, provider incident Error type and status per span

    Attributing cost and quality

    Both questions need the same stamp on every request: who asked, for which feature, on which release. Cost then splits by team and feature. A sampled quality score splits by release and slice. The mechanism is in the cost-attribution section below.

    Dimension Question it answers Rule
    Team Who pays? Derive from authenticated identity
    Feature What is it for? Derive from the route
    Release ID Did the change help or hurt? Stamp from the serving pointer
    Slice (region, tenant class) Who is affected? Few values, never a user ID

    Debugging one bad answer

    Figure 4. From a bad-answer report to a cause

    flowchart TD accTitle: Debugging one bad answer accDescr: Start from a trace ID attached to the report. If any span has Error status, fix by error type. If not, check whether the retrieval span holds the right passages. If it does, read the release ID on the model span and test the same input on the previous release. A good answer there means a regression in the release. A bad answer means a weakness to fix by design. Both end as golden-set items. S["Bad answer reported with trace ID"] --> T1["Open the trace, read root span status and duration"] T1 --> Q1{"Any span with Error status?"} Q1 -- yes --> E1["Fix by error type: timeout, rate limit, tool failure"] Q1 -- no --> Q2{"Right passages in the retrieval span?"} Q2 -- no --> E2["Retrieval fault: index, filter, chunking, stale document"] Q2 -- yes --> Q3{"Previous release answers this input well?"} Q3 -- yes --> E3["Regression in the release: roll back"] Q3 -- no --> E4["Weakness on this input: fix by design"] E3 --> G["Add the case to the golden set"] E4 --> G E2 --> G

    How to read this: the questions run from cheapest to most expensive to answer, and every branch ends by feeding the golden set so the same failure is caught next time.

    Step through the same bad answer twice, first with only dashboards and logs, then with a trace.

    Debugging one bad answer: logs only versus one trace (illustrative) (interactive when JavaScript is on)

    Dashboard and logs only

    1. Dashboard. p95 latency and error rate are normal, so nothing alerts.
    2. Logs. One log line says answered, status 200. It does not say which prompt, passages or model were used.
    3. Reproduce by hand. The same question typed in later gets a correct answer, because retrieval or sampling differs.
    4. Outcome. Closed as cannot reproduce. The cause is still live.

    With one trace

    1. Find the trace. The feedback form stored the trace ID, so the engineer opens that one request.
    2. Read the root span. Status is Unset and duration is 4.1 s. Nothing failed at the top level, so look inside.
    3. Open the retrieval span. It lists five passage IDs. The current returns policy is not among them, an older page is.
    4. Name the cause. A retrieval fault, not a prompt fault: the model span shows release rel-0142 and finish reason stop, so the model followed what it was given. The fix is in the index or filters, and the request becomes a golden-set item.

    Privacy of traces

    Figure 5. Where a trace may hold sensitive text

    flowchart LR accTitle: Where a trace may hold sensitive text accDescr: Inside your trust boundary the orchestrator emits spans to a collector that drops content attributes and redacts text. The trace store receives identifiers, token counts and redacted text. Raw prompts and answers, if kept at all, go to a separate content store with short retention that a policy engine guards. Engineers open traces freely but content only through the policy engine. subgraph B["Your trust boundary"] ORC["Orchestrator"] --> COL["Collector: redact, drop content attributes"] COL --> TS["Trace store: IDs, token counts, redacted text"] ORC -.-> CS["Content store: opt-in, short retention"] PE["Policy engine"] --> CS end ENG["Engineer on call"] --> TS ENG --> PE

    How to read this: the solid path is what every engineer can open, the dotted path is optional and guarded, and nothing carrying raw text leaves the boundary by default.

    The OpenTelemetry GenAI conventions mark the message-content attributes as opt-in. They warn that the input and output messages are "likely to contain sensitive information including user/PII data" (OpenTelemetry GenAI conventions, client inference, as of 2026-10-22). Page 12 covers redaction methods and their limits.

    Privacy also constrains debugging. In its September 2025 postmortem Anthropic says its privacy and security controls limit when engineers can view user interactions, which "protects user privacy but prevents engineers from examining the problematic interactions" needed to reproduce bugs (Anthropic Engineering, 17 September 2025). Design the trace so identifiers and structure answer most questions without raw text.

    Case files

    • Canary release of a prompt change. In a composite SaaS scenario, a prompt change went to everyone and quality fell for one customer segment. Pattern: version pin, shadow then canary with a confidence-interval gate, and a one-click rollback.
    • Explaining a shared model bill. In a composite telecom scenario, three teams shared one model account and the monthly bill could not be explained. Pattern: tags at the gateway, per-team budgets and a weekly report.
    • Anthropic's September 2025 quality postmortem. A public case based on Anthropic's engineering post about three infrastructure bugs that degraded Claude responses, which users reported before the causes were found. Pattern: the evaluation, deployment-test and privacy-safe debugging changes the post lists.

    Under the hood: The trace and span data model

    Intuition

    A trace is a family tree of timed operations for one request. Every span records its parent, so the tree can be rebuilt after the fact. The only things that must cross a network call to make this work are two IDs.

    Mechanism

    Figure 6. Context propagation across one AI request, with a provider timeout

    sequenceDiagram accTitle: Context propagation across one AI request accDescr: The client app calls the API gateway, which starts the root span. The gateway, orchestrator and model gateway each pass a traceparent header to the next hop so that every span shares one trace ID. The model gateway calls the LLM provider. If the provider times out, the model gateway marks its span as an error and the orchestrator records the failure. Finished spans are exported to the trace store without blocking the request. participant CA as Client app participant GW as API gateway participant OR as Orchestrator participant MG as Model gateway participant LP as LLM provider participant TS as Trace store CA->>GW: Question GW->>GW: Start root span, create trace ID GW->>OR: Call with traceparent header OR->>MG: Call with traceparent header MG->>LP: Provider request inside a client span alt Provider timeout LP--xMG: No response MG->>MG: End span with Error status and error type MG-->>OR: Failure OR-->>GW: Degraded answer else Answer returned LP-->>MG: Answer and token counts MG-->>OR: Answer OR-->>GW: Answer end GW-->>CA: Response GW--)TS: Export spans OR--)TS: Export spans MG--)TS: Export spans Note over GW,MG: One trace ID on every span. Each child parent is its caller span.

    How to read this: the header travels with the request while the spans travel separately to the trace store, so a slow or down store cannot slow the answer.

    1. The first service creates the root span and a trace ID.
    2. Before every outgoing call it writes the IDs into a header. OpenTelemetry's default propagator uses the W3C traceparent header (OpenTelemetry, Context propagation).
    3. The receiver reads the header and creates its own span in the same trace, with the caller's span as parent.
    4. Spans are exported separately. The backend joins them by trace ID.
    5. For asynchronous work, a producer span creates a job and a consumer span processes it, possibly long after. Span links associate spans across traces (OpenTelemetry, Traces).

    The W3C Trace Context Recommendation (23 November 2021) fixes the header format (W3C Trace Context).

    Field Hex characters Example from the specification Meaning
    version 2 00 Format version
    trace-id 32 4bf92f3577b34da6a3ce929d0e0e4736 Shared by every span in the trace
    parent-id 16 00f067aa0ba902b7 The caller's span, used as the parent
    trace-flags 2 01 Lowest bit: the caller may have recorded trace data

    An all-zero trace-id or parent-id is invalid. The companion tracestate header carries vendor-specific information and allows at most 32 list members.

    Check yourself: a service receives 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01. Which ID does its new span use as parent, and which does it keep?

    The parent is 00f067aa0ba902b7 (the caller's span). It keeps the trace ID 4bf92f3577b34da6a3ce929d0e0e4736 so both spans join one trace.

    Span part Holds Use in an AI system
    Name The operation Searching by stage
    Parent span ID Link to the caller Rebuilding the tree
    Start and end time Duration Latency by stage
    Attributes Key-value metadata Release ID, tokens, passage IDs
    Events Timestamped notes Guardrail verdict at a moment
    Links Causal links to other spans Async hops, batch jobs
    Status Unset, Error or Ok Failure counts

    The GenAI semantic conventions. As of 2026-10-22 OpenTelemetry's generative-AI conventions carry the status "Development". The pages on opentelemetry.io are notices that say the content moved to the open-telemetry/semantic-conventions-genai repository. OpenTelemetry's document-status page says the support guarantees and allowed changes depend on a document's maturity level, so plan for names and requirement levels to change.

    Attribute Requirement level Note
    gen_ai.operation.name Required For example chat, embeddings, execute_tool, invoke_agent
    gen_ai.provider.name Required The provider as the client sees it
    gen_ai.request.model Conditionally required If available
    gen_ai.response.model Recommended The model that answered
    gen_ai.usage.input_tokens, gen_ai.usage.output_tokens Recommended Prompt and completion tokens
    gen_ai.response.finish_reasons Recommended One stop reason per generation
    error.type Conditionally required If the operation ended in an error
    gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, gen_ai.tool.definitions Opt-in Likely or possibly sensitive

    The inference span name should be {gen_ai.operation.name} {gen_ai.request.model} and its kind should be CLIENT, with INTERNAL allowed for in-process models.

    What this means for architects

    What changes What you do
    One lost header splits a trace in two Make propagation a platform requirement. Test that a request through gateway, orchestrator, retriever and model gateway yields one trace. Put traceparent in message headers on every queue
    Attribute names are still in Development Wrap all instrumentation in one module, pin versions, and keep your own facts under your own prefix such as app.*
    Content attributes are sensitive Leave them off by default. Record passage IDs, token counts and the release ID instead of text
    Telemetry must not become an outage Export asynchronously, drop on overflow and alert on dropped spans, because the store is for diagnosis and not for business logic
    The provider's internal time is invisible Treat the model gateway span as the only measurement of provider latency and put the model ID on it

    Under the hood: Prompt registry and versioning

    Intuition

    A release is the whole recipe for an answer: prompt, model, settings, tools and retrieval. If any piece can change without a new version, nobody can say what produced a bad answer or what "roll back" restores.

    Mechanism

    Figure 7. What one release record pins

    erDiagram accTitle: What one release record pins accDescr: A release references one prompt version, one model pin, one parameter set, one toolset and one retrieval configuration, and has gate results. A pointer such as an alias or flag selects which release serves a traffic slice and can be moved without changing the release. RELEASE ||--|| PROMPT_VERSION : uses RELEASE ||--|| MODEL_PIN : calls RELEASE ||--|| PARAMETERS : sets RELEASE ||--|| TOOLSET : exposes RELEASE ||--|| RETRIEVAL_CONFIG : reads RELEASE ||--o{ GATE_RESULT : records POINTER }o--|| RELEASE : selects RELEASE { string release_id string change_note } MODEL_PIN { string model_id string retirement_date } RETRIEVAL_CONFIG { string index_version int top_k } GATE_RESULT { string golden_set_version string judge_version string outcome }

    How to read this: the release is a bundle of immutable parts, and the pointer is the only mutable thing, which is why a rollback is a pointer move.

    1. Assemble the release from immutable parts and give it an ID.
    2. Run the gate against that ID and store the outcome with it.
    3. A pointer (an alias or a flag) says which release serves each traffic slice.
    4. Every span carries the release ID, so a bad answer names its release.
    5. Rolling back moves the pointer to the previous release. Its parts have not changed.

    MLflow's prompt registry documents the same split: prompt versions "are immutable", each has a sequential number, and an alias is a "mutable named reference to the prompt" that teams can use for the production version and for roll-backs. Its model_config is "mutable", unlike the prompt template (MLflow, Prompt Registry, as of 2026-10-22). So a registry entry alone is not a release record: pin the settings in the release too.

    Anthropic's models page says every Claude model ID is a pinned snapshot and lists a "not sooner than" retirement date per current model for the platforms Anthropic operates (Anthropic, Models overview, as of 2026-10-22). Amazon Bedrock and Google Cloud set their own dates, and other providers differ, so check each one.

    Part Illustrative value If left unpinned
    Prompt template Hash of the text An edit changes answers with no record
    Model A snapshot ID and its retirement date A silent provider change, or a retired rollback target
    Parameters Temperature, output limit, effort Length, cost and quality drift
    Tools Hash of the tool schemas The model calls a changed contract
    Retrieval Index version, top-k, reranker Evidence changes under a constant prompt

    What this means for architects

    What changes What you do
    Rollback only works if old parts survive Keep the previous release's model snapshot, index version and tool schemas available, and store each model's retirement date in the record
    Some registries let settings change after creation Treat prompt version plus settings as one release record that nobody edits
    The index is data, and data changes Pin an index version or snapshot, not "latest"
    Rolling back cannot undo actions already taken Design compensations for side-effecting tools (page 7)
    The gate result belongs to the release Store golden-set and judge versions with it, so a pass can be audited

    Under the hood: Shadow, canary and A/B releases

    Intuition

    A shadow is a rehearsal, a canary is a small real exposure, and an A/B test is a randomised experiment. Google's SRE Workbook defines canarying as "a partial and time-limited deployment of a change in a service and its evaluation" (Google SRE Workbook, Canarying Releases).

    Mechanism

    Figure 8. Shadow, then canary, with a gate and a rollback

    sequenceDiagram accTitle: Shadow then canary with a gate and rollback accDescr: In the shadow phase the API gateway sends each request to the stable orchestrator and a copy to the candidate, and the candidate answer is discarded but traced. In the canary phase a small share of users receive the candidate answer. The eval service judges a sample from the trace store, and the release controller promotes, holds or blocks. A block sets the candidate weight to zero. participant CA as Client app participant GW as API gateway participant ST as Orchestrator stable participant CD as Orchestrator candidate participant TS as Trace store participant EV as Eval service participant RC as Release controller Note over CA,RC: Phase 1, shadow CA->>GW: Request GW->>ST: Forward ST-->>GW: Stable answer GW-->>CA: Stable answer GW--)CD: Copy of request, response discarded CD--)TS: Spans tagged with candidate release Note over CA,RC: Phase 2, canary at 5 percent CA->>GW: Request from a canary user GW->>CD: Forward CD-->>GW: Candidate answer GW-->>CA: Candidate answer EV->>TS: Read judged sample for both releases EV->>RC: Pass rate and interval alt Lower bound meets the bar RC->>GW: Promote to next stage else Interval straddles the bar RC->>GW: Hold at current stage else Evidence below the bar or hard failure RC->>GW: Set candidate weight to zero end

    How to read this: in shadow nobody sees the candidate, in canary a few do, and the same controller reads the same evidence and picks one of three outcomes.

    1. Shadow. The gateway copies requests to the candidate. Istio documents the same mechanism: mirrored traffic happens "out of band of the critical request path" and is "fire and forget", so responses are discarded (Istio, Mirroring).
    2. Canary. A small share of users get the candidate's answer. The Workbook says a canary "should be sizeable and last long enough to be representative of the overall deployment", and that its authors "strongly advise running only one canary deployment at a time".
    3. Judge a sample. Judging every canary answer is rarely worth the spend, so a share is judged (35% here, an assumption).
    4. Decide with an interval. Compute the Wilson interval from page 8 and apply the three-outcome rule.
    5. A/B. Assign users randomly and consistently to a variant, then compare a user-outcome metric with a sample size chosen in advance.

    The exposure arithmetic uses 20,000 requests per day, a judged share of 35% and a drop from 92% to 88% that needs 349 items to detect (computed in the worked example).

    Canary share Requests per day Judged per day Days to 349 judged Extra bad answers per day if quality drops 4 points
    1% 200 70 5.0 8
    5% 1,000 350 1.0 40
    25% 5,000 1,750 0.2 200
    100% 20,000 7,000 not applicable 800

    The Workbook's own example shows the principle: a release that fails 20% of requests, on a 5% canary, produces a 1% overall error rate.

    Check yourself: at the 5% stage a bad release makes 4% more answers fail. How many extra bad answers does the canary cause per day, and how many after a full rollout?

    40 per day (1,000 canary requests x 0.04), against 800 per day at 20,000 requests. Exposure is proportional to traffic.

    The starting interval is 89.2% to 94.1%, so the gate says fail. Set passes to 324, then to 305 and 304, and watch the bounds move.

    Canary gate: 349 judged answers, bar 90 percent, one-sided 95 percent (interactive when JavaScript is on)
    Observed pass rate92.0%
    Interval (z = 1.645)89.2% to 94.1%
    Gate: lower bound at least 90.0%fail
    Passes of 349 Pass rate One-sided 95% interval Outcome
    324 or more 92.8% or more Lower bound at least 90.0% Promote: the bar is demonstrated
    305 to 323 87.4% to 92.6% Straddles 90% Hold: inconclusive
    304 or fewer 87.1% or less Upper bound below 90% Block: evidence below the bar

    The starting state, 321 of 349 (92.0%), matches the baseline and still does not clear the bar. This is the proof-gate behaviour that page 8 describes. For early stages, use the regression rule instead: block when passes fall to 312 or fewer of 349. This is the one-sided test at n = 349 whose exact binomial size and power page 8 computes: it blocks 5.0% of the time at a true 92% and detects a true 88% about 81% of the time.

    One caution comes from a simulation made for this page, not a sourced method. Testing against 92% after every 50 items up to 350, and blocking if any look fell below the one-sided 5% line, raised the false-block rate at a true 92% from about 5% to about 15% (20,000 simulated runs). Fix the sample size or plan the looks.

    What this means for architects

    What changes What you do
    Small stages give little evidence Use the 1% stage for hard signals (errors, latency, malformed output) as the Workbook advises, and a larger share for quality
    Concurrent changes cannot be told apart Run one canary at a time. Anthropic's April postmortem says three changes hitting different traffic slices on different schedules made the combined effect look like broad, inconsistent degradation
    Shadow repeats side effects Stub tools or make them read-only in the shadow path
    Mirrored data is still data Apply the same retention and redaction rules to shadow traces
    Peeking inflates false alarms Fix n in advance, or plan the number of looks and tighten the threshold
    Rollback must be fast and pre-agreed Let hard failures roll back automatically and put quality holds in front of a named owner

    Under the hood: Cost attribution tags

    Intuition

    A provider bill has one line per model. The business asks per team and per feature. A tag stamped on every call at the gateway is what turns tokens into "who and why".

    Mechanism

    Figure 9. Tags stamped at the gateway and reconciled against the provider

    flowchart LR accTitle: Tags stamped at the gateway and reconciled against the provider accDescr: The client app calls the API gateway, which stamps team and feature from the caller identity. The model gateway records tokens with those tags and calls the LLM provider. Usage events go to the trace store and a daily rollup. The provider usage report is reconciled with the rollup, and the gap is untagged spend. A direct provider call bypasses the gateway and appears only in the gap. CA["Client app"] --> GW["API gateway: stamp team, feature, release"] GW --> MG["Model gateway: record tokens with tags"] MG --> LP["LLM provider"] MG --> TS["Trace store: usage events"] TS --> RU["Daily rollup by tag"] LP --> PU["Provider usage report"] RU --> RC["Reconcile: gap is untagged spend"] PU --> RC DC["Direct provider call"] -.-> LP

    How to read this: the left path stamps and records, the right path is the provider's own number, and the difference between them is the spend your tags cannot explain.

    1. Derive tags from authenticated identity and the route, not from text a caller supplies. This is the course's rule.
    2. Record per call: tags, model, uncached input, cached input and output tokens, outcome and retry count. Record tokens, and apply a price table later. This page states no prices.
    3. Roll up daily by tag.
    4. Reconcile with the provider. Anthropic's Usage API groups usage by dimensions such as model, workspace, API key and service tier, and its Cost API groups costs by workspace or description (Anthropic, Usage and Cost API, as of 2026-10-22). The page lists no per-request custom tag, so a team-and-feature split comes from your gateway or from mapping keys and workspaces to teams.
    5. Report weekly and compare with each team's budget. A gateway can do the tagging: LiteLLM's proxy documentation describes spend tracking for keys, users, teams and tags (LiteLLM, Spend tracking, as of 2026-10-22).
    Team Feature Tokens per month (millions, illustrative) Share of 1,200
    Care returns-help 380 31.7%
    Care call-summary 120 10.0%
    Field triage 330 27.5%
    Sales email-draft 214 17.8%
    Untagged none 156 13.0%
    Check yourself: using the table, what share of the month's tokens belongs to team Care, and what share cannot be attributed to any team?

    Care is 380 + 120 = 500 of 1,200 million, or 41.7%. The untagged 156 million is 13.0%, and it is the number to push toward zero.

    What a tag cannot attribute Why What you do
    Calls that bypass the gateway Nothing stamped them Remove direct provider keys, and watch the reconciliation gap
    Shared costs (index, embeddings pipeline, human review) They are not model calls Agree an allocation rule in advance and label it as an allocation
    Failed attempts and retries Tokens were spent with no useful answer Tag attempts with the retry count and charge the calling feature
    Value Tokens say nothing about outcomes Pair each feature's cost with an outcome metric (page 11)
    Individual users High-cardinality tags overwhelm metrics and are personal data Keep tags to a few values and look up one request by trace ID

    What this means for architects

    What changes What you do
    Teams cannot self-report honestly under budget pressure Stamp from identity at the gateway and make direct keys unavailable
    Tag values drift (spelling, new features) Keep an allow-list in gateway config and reject unknown values
    Cost without release ID cannot show a regression Include the release ID, so a prompt change that grew tokens is visible
    Budgets need an owner and an action Alert at a share of budget and name who decides: throttle, route to a smaller model or fund it

    Decision framework

    Figure 10. Choosing the next step for a proposed change

    flowchart TD accTitle: Choosing the next step for a proposed change accDescr: A decision tree. If the change touches nothing in the release record it ships through normal CI. Otherwise it gets a new release ID and must pass the offline gate. If tools have side effects shadow runs with stubs, otherwise with live read-only tools. A small canary checks hard signals, then a larger canary applies the quality gate and the outcome is promote, hold or block. A{"Touches prompt, model, params, tools or retrieval?"} -- no --> B["Normal CI"] A -- yes --> C["New release ID"] C --> D{"Offline gate passes per slice?"} D -- no --> E["Fix and re-run"] D -- yes --> F{"Tools with side effects?"} F -- yes --> G["Shadow with stubbed tools"] F -- no --> H["Shadow with live read-only tools"] G --> I["Small canary: hard signals"] H --> I I --> J["Larger canary: quality gate"] J --> K{"Outcome"} K -- promote --> L["Next stage"] K -- hold --> M["More items or review"] K -- block --> N["Roll back"]

    How to read this: only one question lets a change skip the ladder, and every other path ends in one of three named outcomes.

    If Then Because Revisit when
    Any answer-shaping part changes Mint a release ID and stamp it on every span You cannot debug or roll back what you cannot name Parts live in separate systems that version independently
    A change has side-effecting tools Shadow with stubbed tools and canary behind approval Mirrored traffic repeats actions Tools become idempotent or sandboxed
    The canary yields fewer than the needed judged items per day Use it for hard signals and widen the share or the duration for quality The item count decides what can be detected Traffic changes
    Traces could hold personal data Store identifiers and redacted text with short retention A trace store is a second data store to protect Retention rules or the data classification change
    Teams share a model account Require gateway tags and reconcile with the provider each month Self-reported attribution drifts A team needs a direct key for a documented reason

    Comparison of the main options

    Option Strength Weakness Choose when
    Provider usage console or API No build, authoritative totals Splits by key, workspace and model, not feature A single team, one feature
    Gateway logs with tags Per-call tags and tokens No span tree, no cause inside a request Cost questions, rate-limit tracking
    OpenTelemetry traces with GenAI conventions Vendor-neutral, a full request tree Conventions in Development, you own storage and privacy Several services, a platform team
    Vendor LLM-observability platform Fast to adopt, built-in dashboards Sensitive data leaves your boundary, lock-in risk Small team, data classification allows it
    Sampled judge on traces Measures answer quality Judge cost, needs calibration (page 8) Quality gates and monitoring

    Failure scenarios to design for

    Failure What the system does Why
    Trace context is lost at a queue hop Spans split into two traces. The platform alerts on root spans with no downstream spans, and the message header carries traceparent A queue is the usual place a header is dropped
    The trace store or collector is down Requests are unaffected. Spans are dropped, counted and alerted Diagnosis must never cause an outage
    The canary result is inconclusive The release holds at its stage. Add items or review, never re-run until green Re-running until green is peeking
    The rollback target is retired or its index is gone The record stores the retirement date, and an alert fires ahead of it An unrestorable rollback is not a rollback
    Untagged spend rises Direct keys are revoked and the gap alert goes to the platform owner Self-reporting drifts
    Sensitive text appears in traces Content attributes are off by default, redaction runs at the collector, and tests send seeded fake personal data The trace store is a data store

    Worked example

    Release rel-0143 changes only the system prompt. The current release rel-0142 passes 92% on the rubric, and the requirement is a pass rate of at least 90%.

    Assumptions:

    • Traffic is 20,000 requests per day, 5% go to the canary, and 35% of canary answers are judged.
    • The change is worth catching if the pass rate falls from 92% to 88%.
    • One-sided alpha is 0.05 and power is 0.80 (z = 1.645 and 0.8416).
    • All numbers are illustrative.
    Step Calculation Result
    1. Items needed ((1.645 x sqrt(0.92 x 0.08) + 0.8416 x sqrt(0.88 x 0.12)) / 0.04)^2 323.8, so 324 items
    2. With continuity correction 323.8 + 1/0.04 = 323.8 + 25 348.8, so 349 items
    3. Canary traffic per day 20,000 x 0.05 1,000
    4. Judged per day 1,000 x 0.35 350
    5. Days to 349 349 / 350 1.0 day, one full daily cycle
    6. Exposure if the change is bad 1,000 x 0.04 40 extra bad answers per day
    7. Observed after day one 307 of 349 pass 88.0%
    8. Decision 307 is at or below 312 Block: weight set to zero, rel-0142 keeps serving

    The block rule in step 8 is the regression rule above. The 95% one-sided interval for 307 of 349 is about 84.8% to 90.5%: it straddles the 90% bar, so this is evidence of a drop from the baseline, not proof of falling below the bar.

    Judged items needed to detect a drop from 92 percent to 88 percent (interactive when JavaScript is on)
    Items to detect 92.0% falling to 88.0%323.8
    With continuity correction (n + 1/delta)348.8

    Change p1 to 0.90 and the items needed rise to about 1,221 (1,271 with the correction), because halving the drop roughly quadruples the sample.

    Code sample: tracing a model call and passing the context

    This sample uses opentelemetry-sdk (1.45.1 was the latest release on PyPI on 2026-10-22). It records the GenAI attributes (status Development) beside our own app.* attributes, marks an error properly, and writes a traceparent header for the next hop.

    from opentelemetry import trace
    from opentelemetry.propagate import extract, inject
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
    from opentelemetry.trace import SpanKind, Status, StatusCode
    
    provider = TracerProvider()
    provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
    trace.set_tracer_provider(provider)
    tracer = trace.get_tracer("support-assistant")
    
    
    def call_model(release_id: str, model_id: str, question: str) -> str:
        attrs = {
            "gen_ai.operation.name": "chat",
            "gen_ai.provider.name": "example-provider",
            "gen_ai.request.model": model_id,
            "app.release.id": release_id,  # our own namespace, not OpenTelemetry's
            "app.feature": "returns-help",
        }
        with tracer.start_as_current_span(f"chat {model_id}", kind=SpanKind.CLIENT, attributes=attrs) as span:
            try:
                span.set_attribute("gen_ai.usage.input_tokens", 812)  # from the provider response
                span.set_attribute("gen_ai.usage.output_tokens", 164)
                return "answer"
            except Exception as exc:
                span.record_exception(exc)
                span.set_status(Status(StatusCode.ERROR))
                span.set_attribute("error.type", type(exc).__name__)
                raise
    
    
    with tracer.start_as_current_span("handle_question", kind=SpanKind.SERVER):
        call_model("rel-0142", "model-x", "What is the returns window?")
        headers: dict[str, str] = {}
        inject(headers)  # writes a traceparent header for the next hop
    
    with tracer.start_as_current_span("worker", context=extract(headers), kind=SpanKind.CONSUMER):
        pass  # same trace ID; its parent is the span that injected the header

    The token counts are fixed numbers here, standing in for the provider response. Message content is deliberately not recorded. In production, read the counts from the response and add the content attributes only behind an opt-in.

    Common mistakes

    • Versioning the prompt but not the settings or retrieval config. Pin the whole release record, not the text.
    • Logging 200 and calling it observed. Record release ID, passage IDs, token counts and finish reason on spans.
    • Judging the canary on too few items. Compute the items needed first and use small stages for hard signals only.
    • Checking the gate until it turns green. Fix n, or plan the looks, and treat "hold" as a result.
    • Letting teams label their own spend. Stamp tags from identity at the gateway.
    • Storing raw prompts in the trace store by default. Keep content opt-in, redacted and short-lived.

    Try it

    Time: 30 minutes. Run the code sample with the console exporter. Add a second function that stands in for your retriever and records passage IDs as an attribute. Write your own release record as a small file: prompt hash, model ID and retirement date, parameters, tool schema hash and retrieval settings. Then compute how many judged items you need to detect a 4-point drop from your own baseline and how many days your canary share takes to produce them.

    Done when one trace ID appears on spans from two functions, a release record exists for your current setup, and you can state the smallest drop your canary can detect in one day.

    Key takeaways

    • A trace is a tree of spans joined by a trace ID that travels in the traceparent header.
    • The GenAI semantic conventions are in Development as of 2026-10-22: isolate them behind one module.
    • A release pins prompt, model, parameters, tools and retrieval, and a rollback is a pointer move.
    • Shadow shows real inputs without exposing users, canary exposes few, and A/B measures user outcomes.
    • A gate has three outcomes: promote, hold or block. The number of judged items sets the smallest detectable drop.
    • Stamp cost tags at the gateway from identity and reconcile with the provider to find the untagged share.
    • Traces hold personal data unless you design them not to.

    Self-check

    1. Which header carries trace context between services, and what does the receiving service use as its span's parent?

    The W3C traceparent header, whose fields are version, trace-id, parent-id and trace-flags. The receiver keeps the trace ID and uses the parent-id, the caller's span, as its parent.

    2. Why is a prompt registry entry alone not a release record?

    A release also needs the model, settings, tools and retrieval configuration. Some registries let settings change after a version is created, as MLflow's model_config can, so the release must pin them itself.

    3. With 349 judged canary answers and a 90% bar, what do 330, 310 and 300 passes mean?

    330 passes (94.6%) clear the bar, since 324 or more is needed, so promote. 310 passes lie between 305 and 323, so the result is inconclusive and the release holds. 300 passes are 304 or fewer, so the upper bound is below 90% and the release is blocked.

    4. What status do the OpenTelemetry generative-AI conventions carry as of 2026-10-22, and what should you do about it?

    They carry the status Development. Wrap instrumentation in one module, pin versions, keep your own facts under your own prefix and expect names to change.

    5. Why stamp cost tags at the gateway, and what does an untagged share of 13% tell you?

    Tags derived from identity at one point cannot drift or be skipped by teams. An untagged 13% means that much spend reached the provider without passing the gateway or without a valid tag, and it is the first thing to investigate.

    Further reading

    Last verified: 2026-10-22

    Next

    Page 10: Reliability and scaling for AI systems

    Enroll to keep reading

    Lesson 9 of 13 · AI Architect: From AI Engineer to Expert

    How AI architects trace one request, version prompts and models as releases, gate canaries with confidence intervals and attribute cost by team and feature.

    In the rest of this lesson

    • Case files
    • Under the hood: The trace and span data model
    • Under the hood: Prompt registry and versioning
    • Under the hood: Shadow, canary and A/B releases
    • Under the hood: Cost attribution tags
    • Decision framework

    Enrolling is free. It unlocks every lesson in AI Architect: From AI Engineer to Expert, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments