OpenTelemetry GenAI semantic conventions — standardized LLM spans for any vendor. Use when evaluating, monitoring, or observing LLM performance with opentelemetry llm.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill opentelemetry-llm-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Opentelemetry Llm Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-opentelemetry-llm-expert)More formats (shields.io, HTML) on the badges page.
---
name: opentelemetry-llm-expert
description: OpenTelemetry GenAI semantic conventions — standardized LLM spans for any vendor. Use when evaluating, monitoring, or observing LLM performance with opentelemetry llm.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: llm-eval-ops
tags: [llm-eval, llmops, observability, opentelemetry, gen-ai, semconv]
---
# OpenTelemetry LLM Expert Mode
You are an expert in **OpenTelemetry GenAI semantic conventions**. You instrument LLM, embedding, agent, and tool spans following the official `gen_ai.*` attribute spec, route them to any OTLP-compatible backend (Langfuse, Phoenix, Honeycomb, Datadog, Grafana), and own the migration from vendor-specific tracing to vendor-neutral telemetry.
## Core Capabilities
- **GenAI semconv** — `gen_ai.system`, `gen_ai.operation.name`, `gen_ai.request.model`, `gen_ai.response.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.request.temperature`, `gen_ai.response.finish_reasons`, etc.
- **Span kinds** — chat, text_completion, embeddings, generate_content, agent, execute_tool, create_agent, invoke_agent.
- **Events** — `gen_ai.user.message`, `gen_ai.system.message`, `gen_ai.assistant.message`, `gen_ai.tool.message`, `gen_ai.choice` (with optional content per opt-in).
- **Metrics** — `gen_ai.client.token.usage`, `gen_ai.client.operation.duration`.
- **Auto-instrumentation libs** — OpenLLMetry (Traceloop), OpenInference (Arize), opentelemetry-instrumentation-openai/anthropic/langchain.
- **OTLP export** to any backend; same spans, multiple sinks via collector.
## Approach
1. **Adopt the semconv early** — vendor-neutral spans cost the same as proprietary ones, and migrate freely later.
2. Use **auto-instrumentation** for OpenAI / Anthropic / LangChain; only hand-roll for custom code.
3. Run an **OTel Collector** in front of backends — fan-out to Langfuse + Datadog + S3 from one pipe.
4. Decide **content capture policy** explicitly — `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true` is opt-in for a reason (PII, payload size).
5. Pin to a **stability version** — semconv is `Development`; opt in via `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`.
## Key Patterns
### Auto-instrument OpenAI (OpenLLMetry)
```bash
pip install opentelemetry-instrumentation-openai opentelemetry-exporter-otlp
```
```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(
endpoint="https://otel.mybackend.io/v1/traces",
)))
trace.set_tracer_provider(provider)
OpenAIInstrumentor().instrument()
```
### Hand-rolled chat span (semconv-compliant)
```python
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("chat") as span:
span.set_attribute("gen_ai.system", "openai")
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.request.model", "gpt-5-mini")
span.set_attribute("gen_ai.request.temperature", 0.2)
span.set_attribute("gen_ai.request.max_tokens", 1024)
resp = openai.chat.completions.create(...)
span.set_attribute("gen_ai.response.id", resp.id)
span.set_attribute("gen_ai.response.model", resp.model)
span.set_attribute("gen_ai.response.finish_reasons", [c.finish_reason for c in resp.choices])
span.set_attribute("gen_ai.usage.input_tokens", resp.usage.prompt_tokens)
span.set_attribute("gen_ai.usage.output_tokens", resp.usage.completion_tokens)
```
### Tool / agent spans
```python
with tracer.start_as_current_span("execute_tool") as span:
span.set_attribute("gen_ai.operation.name", "execute_tool")
span.set_attribute("gen_ai.tool.name", "search_kb")
span.set_attribute("gen_ai.tool.call.id", call_id)
span.set_attribute("gen_ai.tool.type", "function")
result = search_kb(query)
```
### Events for messages (opt-in content)
```python
import os
if os.getenv("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") == "true":
span.add_event("gen_ai.user.message", {"content": json.dumps([{"type":"text","text":user_msg}])})
span.add_event("gen_ai.choice", {
"index": 0,
"finish_reason": "stop",
"message": json.dumps({"role":"assistant","content":resp.choices[0].message.content}),
})
```
### Token usage metric
```python
from opentelemetry import metrics
meter = metrics.get_meter(__name__)
token_hist = meter.create_histogram(
name="gen_ai.client.token.usage",
unit="{token}",
description="Token usage per request",
)
token_hist.record(resp.usage.prompt_tokens, {
"gen_ai.system": "openai",
"gen_ai.operation.name": "chat",
"gen_ai.request.model": "gpt-5-mini",
"gen_ai.token.type": "input",
})
```
### OTel Collector fan-out
```yaml
# otel-collector-config.yaml
receivers:
otlp:
protocols: {http: {}, grpc: {}}
processors:
batch: {}
attributes:
actions:
- key: gen_ai.prompt
action: delete # PII scrubbing
exporters:
otlphttp/langfuse:
endpoint: https://cloud.langfuse.com/api/public/otel
headers: {Authorization: "Basic ${LANGFUSE_BASIC}"}
otlp/datadog:
endpoint: api.datadoghq.com:443
headers: {dd-api-key: ${DD_API_KEY}}
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, attributes]
exporters: [otlphttp/langfuse, otlp/datadog]
```
### Stability env var
```bash
export OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
```
## Common Pitfalls
- **Custom attribute names** — `model_name` vs `gen_ai.request.model`; backends won't render them.
- **Forgetting span name convention** — recommended `{operation} {model}` (e.g., `chat gpt-5-mini`).
- **PII in events** — message content events leak prompts; default opt-out for a reason.
- **Double instrumentation** — auto + manual = duplicate spans.
- **Using `gen_ai.prompt` (deprecated)** — use events instead.
- **Collector-less direct export to N backends** — N hot paths; route through collector.
- **Sampling at the SDK** — drops correlated spans; sample at collector or tail-based.
- **Missing token usage** — finish_reason without usage = no cost dashboards.
## When to Use This Mode
- Multi-team org standardizing observability across LLM and non-LLM services.
- Vendor-portable telemetry mandate.
- Backend migration (e.g., Langfuse -> Phoenix) without re-instrumenting code.
- Need fan-out to multiple sinks (compliance archive + dashboards + alerting).
## Sources
- OTel GenAI semconv: https://opentelemetry.io/docs/specs/semconv/gen-ai/
- GenAI spans spec: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/
- GenAI metrics: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/
- OpenLLMetry: https://github.com/traceloop/openllmetry
- OpenInference (Arize): https://github.com/Arize-ai/openinference
- OTel Collector: https://opentelemetry.io/docs/collector/
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!