Thomas Piacentini
← Atelier

Fiche

OpenTelemetry GenAI and Agent Observability

Texte original · anglais

Emit gen_ai.* spans, sample traces, export metrics, pick a platform and debug a trace. Stdlib code from the lessons.

Agent span hierarchy

parentSpanIdparentSpanId_meta.traceparentparentSpanIdspansagent.invoke_agentINTERNAL, root spanprocessllm.chatCLIENT, LLM providermodeltool.executeINTERNALprocessmcp.callCLIENT, traceparentprocesssubagent.invokeINTERNALprocessExporterOTLP to any backendstore
Nodes and connections
NodeRoleKind
agent.invoke_agentINTERNAL, root spanprocess
llm.chatCLIENT, LLM providermodel
tool.executeINTERNALprocess
mcp.callCLIENT, traceparentprocess
subagent.invokeINTERNALprocess
ExporterOTLP to any backendstore
FromToConnection
agent.invoke_agentllm.chatparentSpanId
agent.invoke_agenttool.executeparentSpanId
tool.executemcp.call_meta.traceparent
agent.invoke_agentsubagent.invokeparentSpanId
llm.chatExporterspans
Sources

Attributes to set

gen_ai.operation.name
chat, embeddings, execute_tool, invoke_agent.
gen_ai.provider.name
openai, anthropic, aws.bedrock, google.vertex.
gen_ai.request.model
the model that you ask for.
gen_ai.response.model
the model that served the call. Routing can change it.
gen_ai.usage.input_tokens
tokens in. gen_ai.usage.output_tokens gives tokens out.
gen_ai.response.id
the provider response id for correlation.
gen_ai.tool.name
the tool. gen_ai.tool.call.id names one call.
gen_ai.data_source.id
the corpus or store that a RAG step read.
Sources

Instrument in code

TaskCode
Start a child spanstart_span(name, kind, parent=root)
Build a traceparentf"00-{mcp_span.trace_id}-{mcp_span.span_id}-01"
Store content as a referencetracer.add_content(chat, key, prompt)
Wrap work in a spanwith builder.span(name, attributes=..., parent=...)
Write one span per lineJSONLExporter(path=trace_path)
Count tool callsmetrics.counter("tools_called_total")
Render Prometheus textprometheus_exposition(registry)
Sample in an exportermakeSamplingExporter(consoleExporter, 0.05)
Sources

Span builder with metrics

metrics = MetricsRegistry()
builder = SpanBuilder(exporters=[jsonl, in_mem], metrics=metrics)
with builder.span("gen_ai.chat", attributes={GEN_AI_SYSTEM: "anthropic",
GEN_AI_OPERATION_NAME: "chat"}, kind="CLIENT") as chat_span:
chat_span.attributes[GEN_AI_USAGE_INPUT_TOKENS] = 412
with builder.span("gen_ai.tool.execution", parent=chat_span,
attributes={GEN_AI_TOOL_NAME: "read_file"}) as tool1:
tool1.attributes[GEN_AI_TOOL_RESULT_BYTES] = 1024
print(prometheus_exposition(metrics))tools_called_total, tool_latency_ms
Sources

Run the lessons

CommandWhat it does
python code/main.pyLesson 13.20 prints the spans of one trace
OTEL_CAPTURE_CONTENT=1 python code/main.pyAdds prompt and completion events
python3 code/main.pyLesson 14.23 prints a span tree and the content store
python3 -m pytest code/tests/ -vLesson 19.28 checks round trip and buckets
npm startSeeds 1200 synthetic spans, prints the rollup
npm run serveServes ingest and dashboard on PORT
Sources

Debug a trace: agent-trace-debugger

  1. parseTrace one JSON object per line. A bad line stops with its line number.
  2. validateTree rejects a duplicate id, a cycle, a missing parent, a child outside its parent.
  3. unionDuration merges overlapping child intervals. It does not sum them.
  4. analyze exclusive time is inclusive time minus the child union.
  5. analyze slowest names the span with the greatest exclusive time.
  6. render writes standalone HTML with escaped names and error coloring.
  7. cli.ts --baseline adds wall_time_delta_ms, token_delta, error_delta.
Sources

Trace debugger commands

python3 scripts/project_test.py agent-trace-debugger --init learning-artifacts/agent-trace-debugger
python3 scripts/project_test.py agent-trace-debugger --stage 1 --path learning-artifacts/agent-trace-debugger --strict
python3 scripts/project_test.py agent-trace-debugger --all --solution --strict
node cli.ts --input samples/trace.jsonl --baseline samples/before.jsonl --output trace.html --json trace.json
node cli.ts --format otlp --input samples/otlp.json --output otlp-trace.html --json otlp-trace.json
Sources

Defaults and numbers

SettingDefaultNote
OTEL_CAPTURE_CONTENT"0"Set 1 to capture prompt and completion
TailSampler sample_rate0.10Errors and PII evals are always kept
ObservabilityStore capacity10_000Ring buffer of spans
PORT8011Set SERVE=1 to start the HTTP server
High-cost spantotalTokens > 8000Always kept by the sampling exporter
--formatjsonlAlso otlp. Baseline files stay JSONL
--outputtrace.html--json writes the report file
Sources

Troubleshooting

SymptomCauseFix
PII and secrets in tracesFull prompts captured in spansStore content outside, put reference IDs on spans
Multi-provider dashboard breaksNo gen_ai.provider.nameSet it on every LLM span
Orphaned tool spansSpans without parent linksAlways propagate context
Attributes renamed on upgradeNo stability opt-inSet OTEL_SEMCONV_STABILITY_OPT_IN
MCP server not in the traceStdio carries no HTTP headersPut traceparent in _meta of each request
Cannot bisect a regressionPrompt versions not tied to tracesRecord the prompt version on the trace
child outside parentChild interval exceeds the parentNormalize clocks before import
Sources

Pick a platform

FactorLangfusePhoenixOpik
LicenseMITElastic License 2.0Apache 2.0
Strongest forPrompt management, sessionsRAG relevancy, driftOptimization, guardrails
Prompt versioningYesNoA/B experiments
Free tier50K events/monthSelf-hostSelf-host or cloud
Use it whenAll-in-one with promptsDeep RAG eval and driftAutomated optimization
Sources

Questions

CLIENT or INTERNAL?
CLIENT crosses a process boundary. INTERNAL is the agent loop and tool execution.
Which invoke_agent kind?
CLIENT for remote agent services. INTERNAL for in-process frameworks.
What do I keep at scale?
100% errors, 100% high-cost, 5% success.
When is drift real?
PSI above 0.2 typically signals meaningful drift.
Is tracing enough?
No. Tracing without evaluation is just expensive logging.
Which metrics are histograms?
gen_ai.client.token.usage, gen_ai.client.operation.duration, gen_ai.tool.execution.duration.
Sources
Original source
---
id: genai-observability
title: OpenTelemetry GenAI and Agent Observability
code: OG
subtitle: Emit gen_ai.* spans, sample traces, export metrics, pick a platform and debug a trace. Stdlib code from the lessons.
hue: 25
sources: phases/13-tools-and-protocols/20-opentelemetry-genai, phases/14-agent-engineering/23-otel-genai-conventions, phases/14-agent-engineering/24-agent-observability-platforms, phases/17-infrastructure-and-production/13-llm-observability, phases/19-capstone-projects/11-llm-observability-dashboard, phases/19-capstone-projects/28-observability-otel-traces, projects/agent-trace-debugger
source_hash: 413430fbc0d5ea57
---

## Agent span hierarchy
type: architecture
from: phases/13-tools-and-protocols/20-opentelemetry-genai/docs/en.md, phases/13-tools-and-protocols/20-opentelemetry-genai/code/main.py

```arch
node | agent | 1,1 | agent.invoke_agent | INTERNAL, root span | process
node | llm | 2,1 | llm.chat | CLIENT, LLM provider | model
node | tool | 2,2 | tool.execute | INTERNAL | process
node | mcp | 3,2 | mcp.call | CLIENT, traceparent | process
node | sub | 2,3 | subagent.invoke | INTERNAL | process
node | exp | 3,1 | Exporter | OTLP to any backend | store
edge | agent | llm | parentSpanId
edge | agent | tool | parentSpanId
edge | tool | mcp | _meta.traceparent
edge | agent | sub | parentSpanId
edge | llm | exp | spans
```

## Attributes to set
type: concepts
from: phases/13-tools-and-protocols/20-opentelemetry-genai/docs/en.md, phases/14-agent-engineering/23-otel-genai-conventions/docs/en.md

- **`gen_ai.operation.name`**: `chat`, `embeddings`, `execute_tool`, `invoke_agent`.
- **`gen_ai.provider.name`**: `openai`, `anthropic`, `aws.bedrock`, `google.vertex`.
- **`gen_ai.request.model`**: the model that you ask for.
- **`gen_ai.response.model`**: the model that served the call. Routing can change it.
- **`gen_ai.usage.input_tokens`**: tokens in. `gen_ai.usage.output_tokens` gives tokens out.
- **`gen_ai.response.id`**: the provider response id for correlation.
- **`gen_ai.tool.name`**: the tool. `gen_ai.tool.call.id` names one call.
- **`gen_ai.data_source.id`**: the corpus or store that a RAG step read.

## Instrument in code
type: task-table
from: phases/13-tools-and-protocols/20-opentelemetry-genai/code/main.py, phases/14-agent-engineering/23-otel-genai-conventions/code/main.py, phases/19-capstone-projects/28-observability-otel-traces/code/main.py, phases/17-infrastructure-and-production/13-llm-observability/code/main.ts

| Task | Code |
|---|---|
| Start a child span | `start_span(name, kind, parent=root)` |
| Build a traceparent | `f"00-{mcp_span.trace_id}-{mcp_span.span_id}-01"` |
| Store content as a reference | `tracer.add_content(chat, key, prompt)` |
| Wrap work in a span | `with builder.span(name, attributes=..., parent=...)` |
| Write one span per line | `JSONLExporter(path=trace_path)` |
| Count tool calls | `metrics.counter("tools_called_total")` |
| Render Prometheus text | `prometheus_exposition(registry)` |
| Sample in an exporter | `makeSamplingExporter(consoleExporter, 0.05)` |

## Span builder with metrics
type: annotated-code
lang: python
from: phases/19-capstone-projects/28-observability-otel-traces/code/main.py, phases/19-capstone-projects/28-observability-otel-traces/docs/en.md

```python
metrics = MetricsRegistry()
builder = SpanBuilder(exporters=[jsonl, in_mem], metrics=metrics)
with builder.span("gen_ai.chat", attributes={GEN_AI_SYSTEM: "anthropic",
        GEN_AI_OPERATION_NAME: "chat"}, kind="CLIENT") as chat_span:
    chat_span.attributes[GEN_AI_USAGE_INPUT_TOKENS] = 412
    with builder.span("gen_ai.tool.execution", parent=chat_span,
            attributes={GEN_AI_TOOL_NAME: "read_file"}) as tool1:
        tool1.attributes[GEN_AI_TOOL_RESULT_BYTES] = 1024
print(prometheus_exposition(metrics))  # tools_called_total, tool_latency_ms
```

## Run the lessons
type: command-table
from: phases/13-tools-and-protocols/20-opentelemetry-genai/code/main.py, phases/14-agent-engineering/23-otel-genai-conventions/docs/en.md, phases/19-capstone-projects/28-observability-otel-traces/docs/en.md, phases/19-capstone-projects/11-llm-observability-dashboard/code/ts/README.md, phases/19-capstone-projects/11-llm-observability-dashboard/docs/en.md

| Command | What it does |
|---|---|
| `python code/main.py` | Lesson 13.20 prints the spans of one trace |
| `OTEL_CAPTURE_CONTENT=1 python code/main.py` | Adds prompt and completion events |
| `python3 code/main.py` | Lesson 14.23 prints a span tree and the content store |
| `python3 -m pytest code/tests/ -v` | Lesson 19.28 checks round trip and buckets |
| `npm start` | Seeds 1200 synthetic spans, prints the rollup |
| `npm run serve` | Serves ingest and dashboard on `PORT` |

## Debug a trace: agent-trace-debugger
type: flow
from: projects/agent-trace-debugger/solution/main.ts, projects/agent-trace-debugger/solution/cli.ts, projects/agent-trace-debugger/project.json

1. **`parseTrace`**: one JSON object per line. A bad line stops with its line number.
2. **`validateTree`**: rejects a duplicate id, a cycle, a missing parent, a child outside its parent.
3. **`unionDuration`**: merges overlapping child intervals. It does not sum them.
4. **`analyze`**: exclusive time is inclusive time minus the child union.
5. **`analyze`**: `slowest` names the span with the greatest exclusive time.
6. **`render`**: writes standalone HTML with escaped names and error coloring.
7. **cli.ts**: `--baseline` adds `wall_time_delta_ms`, `token_delta`, `error_delta`.

## Trace debugger commands
type: annotated-code
lang: bash
from: projects/agent-trace-debugger/README.md, projects/agent-trace-debugger/solution/cli.ts

```bash
python3 scripts/project_test.py agent-trace-debugger --init learning-artifacts/agent-trace-debugger
python3 scripts/project_test.py agent-trace-debugger --stage 1 --path learning-artifacts/agent-trace-debugger --strict
python3 scripts/project_test.py agent-trace-debugger --all --solution --strict
node cli.ts --input samples/trace.jsonl --baseline samples/before.jsonl --output trace.html --json trace.json
node cli.ts --format otlp --input samples/otlp.json --output otlp-trace.html --json otlp-trace.json
```

## Defaults and numbers
type: defaults
from: phases/13-tools-and-protocols/20-opentelemetry-genai/code/main.py, phases/19-capstone-projects/11-llm-observability-dashboard/code/main.py, phases/19-capstone-projects/11-llm-observability-dashboard/code/ts/src/spans.ts, phases/19-capstone-projects/11-llm-observability-dashboard/code/ts/src/index.ts, phases/19-capstone-projects/28-observability-otel-traces/docs/en.md, phases/17-infrastructure-and-production/13-llm-observability/code/main.ts, projects/agent-trace-debugger/solution/cli.ts, projects/agent-trace-debugger/README.md

| Setting | Default | Note |
|---|---|---|
| `OTEL_CAPTURE_CONTENT` | `"0"` | Set 1 to capture prompt and completion |
| `TailSampler` `sample_rate` | `0.10` | Errors and PII evals are always kept |
| `ObservabilityStore` capacity | `10_000` | Ring buffer of spans |
| `PORT` | `8011` | Set `SERVE=1` to start the HTTP server |
| High-cost span | `totalTokens > 8000` | Always kept by the sampling exporter |
| `--format` | `jsonl` | Also `otlp`. Baseline files stay JSONL |
| `--output` | `trace.html` | `--json` writes the report file |

## Troubleshooting
type: troubleshooting
from: phases/14-agent-engineering/23-otel-genai-conventions/docs/en.md, phases/14-agent-engineering/24-agent-observability-platforms/docs/en.md, phases/17-infrastructure-and-production/13-llm-observability/docs/en.md, phases/13-tools-and-protocols/20-opentelemetry-genai/docs/en.md, projects/agent-trace-debugger/solution/main.ts, projects/agent-trace-debugger/README.md

| Symptom | Cause | Fix |
|---|---|---|
| PII and secrets in traces | Full prompts captured in spans | Store content outside, put reference IDs on spans |
| Multi-provider dashboard breaks | No `gen_ai.provider.name` | Set it on every LLM span |
| Orphaned tool spans | Spans without parent links | Always propagate context |
| Attributes renamed on upgrade | No stability opt-in | Set `OTEL_SEMCONV_STABILITY_OPT_IN` |
| MCP server not in the trace | Stdio carries no HTTP headers | Put traceparent in `_meta` of each request |
| Cannot bisect a regression | Prompt versions not tied to traces | Record the prompt version on the trace |
| `child outside parent` | Child interval exceeds the parent | Normalize clocks before import |

## Pick a platform
type: comparison
from: phases/14-agent-engineering/24-agent-observability-platforms/docs/en.md, phases/17-infrastructure-and-production/13-llm-observability/docs/en.md

| Factor | Langfuse | Phoenix | Opik |
|---|---|---|---|
| License | MIT | Elastic License 2.0 | Apache 2.0 |
| Strongest for | Prompt management, sessions | RAG relevancy, drift | Optimization, guardrails |
| Prompt versioning | Yes | No | A/B experiments |
| Free tier | 50K events/month | Self-host | Self-host or cloud |
| Use it when | All-in-one with prompts | Deep RAG eval and drift | Automated optimization |

## Questions
type: qa
from: phases/13-tools-and-protocols/20-opentelemetry-genai/docs/en.md, phases/14-agent-engineering/23-otel-genai-conventions/docs/en.md, phases/17-infrastructure-and-production/13-llm-observability/docs/en.md, phases/19-capstone-projects/11-llm-observability-dashboard/docs/en.md, phases/14-agent-engineering/24-agent-observability-platforms/docs/en.md

- **CLIENT or INTERNAL?** CLIENT crosses a process boundary. INTERNAL is the agent loop and tool execution.
- **Which `invoke_agent` kind?** CLIENT for remote agent services. INTERNAL for in-process frameworks.
- **What do I keep at scale?** 100% errors, 100% high-cost, 5% success.
- **When is drift real?** PSI above 0.2 typically signals meaningful drift.
- **Is tracing enough?** No. Tracing without evaluation is just expensive logging.
- **Which metrics are histograms?** `gen_ai.client.token.usage`, `gen_ai.client.operation.duration`, `gen_ai.tool.execution.duration`.
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓