Reliability, Evaluation and Safety
Observability and tracing
Seeing inside the agent
When an agent misbehaves it rarely throws a stack trace. It just quietly does the wrong thing across ten steps. You cannot debug, cost, or improve what you cannot see. This lesson makes an agent run legible: the trace, the span, the OpenTelemetry standard that names them, and how to read a waterfall to spot a bug in seconds.
Why agents need their own observability
Traditional monitoring (APM) assumes deterministic code paths and error codes: a request either returns 200 or throws. Agents break both assumptions. They are non-deterministic, so the same input can produce a different sequence of tool calls. They run a multi-step, stateful loop of reason, act, observe, reason again. And their worst failures are silent: goal drift, context loss, and quality degradation emit no error code at all.[9]
5xx alerts are necessary but nowhere near sufficient
So the unit of observability is not the log line, it is the . Hamel Husain's single most contrarian piece of advice: get data flowing first, "it doesn't matter whether you use an observability tool or just log to files," because you cannot do error analysis or write evals on runs you never recorded.[1]
Trace and span: the two words that matter
A trace is one whole agent run. It is a tree of , each one a unit of work with a start time, an end time, and a bag of attributes. For agents, three span types cover almost everything: the agent turn, an LLM call, and a tool call. Every span records the same six load-bearing facts, and reading a trace is mostly a matter of scanning them.
| Fact on every span | What it tells you |
|---|---|
| input tokens | The prompt this call saw. It grows every step as history accumulates, so it is your context and cost meter. |
| output tokens | What the model produced this call. |
finish_reason | The model's intent: tool_calls (wants to act), stop (done), length (truncated mid-thought). |
| latency | Start to end of this span. |
| model | Which model served the call, for example claude-sonnet-4. |
| cost | Tokens times price, rolled up over the run. |
Two attributes do outsized work. finish_reason tells you the model's intent: tool_calls means "I want to act" (an execute_tool span should follow); stop means "I'm done"; length means it was truncated mid-thought. And the per-span input token count is your context and cost meter: it grows every step as history accumulates, so a sudden jump flags a bloated payload.
The standard: OpenTelemetry GenAI conventions
You do not want a bespoke logging format per vendor. (OTel) formed a GenAI Special Interest Group in April 2024 and now defines semantic conventions for agent telemetry: a shared vocabulary of span names and attributes.[2] This is the vendor-neutral wire format that Langfuse, Arize Phoenix, Datadog, Honeycomb, and New Relic all consume: instrument once, view anywhere.
The canonical agent trace is a three-level tree. Learn this shape; it is the mental model for every tool in this lesson.
Tap a node to see what it does.
The attribute gen_ai.operation.name is the discriminator that names each span. The rest are the facts above, with exact standardized keys:[3]
| Attribute / metric | Meaning |
|---|---|
gen_ai.request.model | Model requested (for example gpt-4o, claude-sonnet-4) |
gen_ai.usage.input_tokens | Prompt / input token count for that call |
gen_ai.usage.output_tokens | Completion / output token count |
gen_ai.response.finish_reasons | Why generation stopped: stop, tool_calls, length |
gen_ai.input.messages / output.messages | The actual content, opt-in only (may hold PII or secrets) |
gen_ai.client.token.usage | Metric: token histogram, split by input / output |
gen_ai.client.operation.duration | Metric: latency histogram, filterable by model |
Structural telemetry is safe; content is gated
gen_ai.input.messages can log customer data, API keys, and PII straight into your observability backend, so content capture is opt-in and should be redacted.[2] You often want it in dev, gated in prod.A parallel spec, Arize's OpenInference, powers Phoenix and predates the OTel effort. The two are converging but not identical, worth knowing both names exist when you compare tools.[8]
# structural attributes (safe) - the OTel GenAI convention
{
"gen_ai.operation.name": "chat",
"gen_ai.request.model": "claude-sonnet-4",
"gen_ai.usage.input_tokens": 2400,
"gen_ai.usage.output_tokens": 88,
"gen_ai.response.finish_reasons": ["tool_calls"],
"start_time": 2.1, "end_time": 3.0 # -> 0.9s latency for this call
}What every agent span should capture
A span is only as useful as the attributes you attach to it. For agent work the high-value fields are consistent across the ecosystem, and capturing them is the difference between a trace you can debug and a pretty timeline you cannot.
- Inputs and outputs. The full prompt sent to the model (system, history, and retrieved context) and the raw completion it returned. This is the single most important thing to record and the one most often truncated away.
- Tool calls. The tool name, the exact arguments the model chose, and the raw result or error that came back. Most agent bugs are a wrong argument or a tool result the model then misreads.
- Model metadata. Model id, temperature and sampling settings, and the stop reason, so a run is reproducible and you can tell a truncation from a real stop.
- Tokens and cost. Prompt and completion token counts per call, which drive both the bill and the latency and let you find the step that is quietly blowing the budget.
- Status and latency. Success or error, the error type on failure, and wall-clock duration, so you can rank spans by what is slow or breaking.
Reading a trace waterfall
Every backend renders a trace the same way: a waterfall, spans laid out on a horizontal time axis, indented to show nesting. Width is duration; the tree is the agent's control flow. Here is one real-shaped run of a research agent answering "Compare the battery life of the top 3 flagship phones." Read top to bottom, and watch the input token count climb.
| Span | In -> Out tokens | finish_reason | Duration |
|---|---|---|---|
invoke_agent research-agent (root) | 14.2k tok total, $0.06 | - | 7.8s |
├ chat #1 | 1.15k -> 95 | tool_calls | 0.9s |
├ execute_tool web_search "phone battery 2026" | - | - | 1.2s |
├ chat #2 | 2.4k -> 88 | tool_calls | 0.9s |
├ execute_tool web_search "phone battery 2026" (duplicate args) | - | - | 1.3s |
├ chat #3 | 3.65k -> 120 | tool_calls | 0.9s |
├ execute_tool fetch_page "gsmarena..." | - | - | 1.2s |
└ chat #4 final | 4.9k -> 210 | stop | 1.4s |
Spotting the bug: a redundant loop
The trace makes the defect obvious. execute_tool #1 already ran web_search(q="phone battery 2026") and got results. Yet chat #2 read those results and re-proposed the identical search, so execute_tool #2 ran the same query again. That is a redundant loop: no step errored, but the model duplicated work it had already done.[9]
What the redundant call cost
chat #3's input to 3.65k and inflating cost on all downstream calls. Left unchecked at scale, this is exactly how agents hit 4x the tokens of chat, and token budget alone explained roughly 80% of performance variance in Anthropic's research system.[4]What the trace tells you to fix
(tool_name, args) and return the prior result on a repeat. Loop detection: abort or warn when the same call recurs; pair it with a . Surface the result better: the model re-searched because the first result read as low-signal, a context-engineering and tool-interface fix, not a model swap.This is the whole game. Anthropic found the same class of bug in their multi-agent system, subagents "duplicating work" and one query spawning "50 subagents," and the fix was the same: make the waste visible in traces, then constrain it.[4] A note of caution for framework users: agent frameworks "create extra layers of abstraction that can obscure the underlying prompts and responses, making them harder to debug,"[5] so keep the raw prompt and response visible in your trace (that is what content capture is for).
A reading checklist
Open any trace and run this scan. It turns a wall of spans into a diagnosis in under a minute.
- Start at the root. Total latency, total tokens, total cost, final
finish_reason. Is the end-state actually correct? - Scan the finish reasons. Every
tool_callsshould be followed by anexecute_tool; a straylengthmeans a truncated call that likely corrupted what came after. - Follow the token curve. Input tokens should grow gently. A sudden jump is a bloated tool payload dumped into context (context rot).[6]
- Hunt for repeats. Identical
(tool, args)spans are a redundant loop; the same error string recurring is an error cascade where one bad step silently corrupts every later step. - Inspect the widest bars. The longest span owns your latency; the largest
chatinput owns your cost. Optimize those, ignore the rest. - Read the messages on the suspect span. Only now open the (redacted) content to confirm the root cause.
The tooling landscape
Hamel's advice stands: do not build custom logging infra, observability is not your product, so adopt a platform.[1] Because they mostly speak OTel/OTLP now, the choice is about workflow fit, not lock-in. The five you will hear most, segmented by job-to-be-done:[8]
| Tool | Open source | Best fit |
|---|---|---|
| LangSmith | No (self-host = Enterprise) | LangChain / LangGraph teams; Studio lets you set breakpoints, edit state mid-run, resume from a checkpoint |
| Langfuse | Yes (MIT) | Framework-agnostic via OTel; prompt-centric workflows, evals, and cost analytics |
| Arize Phoenix | Yes | OTel / OpenInference-native; strong drift and embedding analysis |
| W&B Weave | Partly | Teams already living in Weights & Biases for experiment tracking |
| Braintrust | No | Eval-centric workflows where scoring is the main loop |
Traces feed evals, in that order
Instrument from day one
invoke_agent / chat / execute_tool spans with token and finish-reason attributes on run #1, and treat cost per task as a first-class SLO: a runaway loop is also a runaway bill.[4] The next lesson turns these signals into guardrails.Check yourself
Match each OTel GenAI span to what it records.
the root span for one whole agent turn
one LLM call, request and response
one tool invocation, args and result
In the OpenTelemetry GenAI conventions, which span represents a single LLM call?
Why do plain 5xx / exception alerts miss most real agent failures?
Recall the three-level OTel GenAI span tree, and one key attribute each level carries.
What is the discriminator, and what facts does each level record? Try to state it, then check.
Lock it in
- The unit of agent observability is the trace, one run as a tree of spans, not the log line. Agents fail silently, so error alerts alone are blind.
- Every span records six load-bearing facts: inputs, outputs, model, tokens, latency, cost, plus a
finish_reasonthat reveals the model's intent. - The OpenTelemetry GenAI conventions standardize this:
invoke_agent,chat,execute_toolwithgen_ai.*attributes, so instrument once and view in any backend. Content capture is opt-in. - Read a waterfall by scanning finish reasons, the token curve, repeated
(tool, args)spans, and the widest bars: a redundant loop or cascade jumps out in seconds. - Get traces flowing first, then error-analyze roughly 50 of them, then write evals. Do not build your own logging; adopt Langfuse, Phoenix, LangSmith, Weave, or Braintrust.
Primary source
OpenTelemetry, Inside the LLM Call: GenAI Observability with OpenTelemetryThe clearest walkthrough of the invoke_agent / chat / execute_tool span tree, the exact gen_ai.* attributes and metrics, and why content capture is gated. Pair it with Hamel Husain's "Observability in LLM Applications" for the traces-first mindset.
Sources
- 1.Hamel Husain, Observability in LLM Applications
- 2.OpenTelemetry, Inside the LLM Call: GenAI Observability with OpenTelemetry
- 3.OpenTelemetry, Semantic conventions for GenAI spans
- 4.Anthropic, Building a multi-agent research system
- 5.Anthropic, Building Effective Agents
- 6.Chroma, Context Rot: how increasing input tokens impacts LLM performance
- 7.Chip Huyen, Building A Generative AI Platform
- 8.Laminar, Top agent observability platforms
- 9.Latitude, AI agent failure detection guide