--- 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`.