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.
Prerequisites and cross-links
- Page 8, evaluation: Wilson intervals, the sample-size formula and the regression gate. This page reuses them and does not re-derive them.
- Page 3, diagramming for trust boundaries, and page 4, models and routing for the model gateway.
- Background: Observability for LLM systems, LLM observability in production, Feature flags for AI, Observability and SLOs and AI gateways.
- Terms: trace, span, context propagation, prompt registry, shadow release, cost attribution, canary release, trace store, eval service, model gateway, Wilson interval, SLI and trust boundary.
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
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.
Try the quiz below; each answer follows from the table.
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.
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.
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.
- Dashboard. p95 latency and error rate are normal, so nothing alerts.
- Logs. One log line says answered, status 200. It does not say which prompt, passages or model were used.
- Reproduce by hand. The same question typed in later gets a correct answer, because retrieval or sampling differs.
- Outcome. Closed as cannot reproduce. The cause is still live.
With one trace
- Find the trace. The feedback form stored the trace ID, so the engineer opens that one request.
- Read the root span. Status is Unset and duration is 4.1 s. Nothing failed at the top level, so look inside.
- Open the retrieval span. It lists five passage IDs. The current returns policy is not among them, an older page is.
- 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.
- The first service creates the root span and a trace ID.
- Before every outgoing call it writes the IDs into a header. OpenTelemetry's default propagator uses the W3C
traceparent header (OpenTelemetry, Context propagation).
- The receiver reads the header and creates its own span in the same trace, with the caller's span as parent.
- Spans are exported separately. The backend joins them by trace ID.
- 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).
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.
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.
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
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.
- Assemble the release from immutable parts and give it an ID.
- Run the gate against that ID and store the outcome with it.
- A pointer (an alias or a flag) says which release serves each traffic slice.
- Every span carries the release ID, so a bad answer names its release.
- 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.
What this means for architects
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.
- 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).
- 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".
- Judge a sample. Judging every canary answer is rarely worth the spend, so a share is judged (35% here, an assumption).
- Decide with an interval. Compute the Wilson interval from page 8 and apply the three-outcome rule.
- 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).
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.
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
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.
- Derive tags from authenticated identity and the route, not from text a caller supplies. This is the course's rule.
- 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.
- Roll up daily by tag.
- 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.
- 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).
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 this means for architects
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.
Comparison of the main options
Failure scenarios to design for
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.
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.
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,
"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)
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)
with tracer.start_as_current_span("worker", context=extract(headers), kind=SpanKind.CONSUMER):
pass
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