OpenTelemetry Bridge
The OTel bridge emits every framework span through the OpenTelemetry API, so any collector that speaks OTLP β Jaeger, Honeycomb, Datadog, Phoenix, Langwatch, Grafana Tempo, New Relic β ingests Augments agent traces without a vendor-specific SDK.
Installation
OTel is an optional extra. The core framework has zero runtime
dependency on opentelemetry.
pip install 'augments-adk[otel]'If the extra is missing and application code tries to construct an
OTelTracer or call setup_otel(...), the framework raises
TracingDependencyError with the install command β not a confusing
low-level ImportError.
from augments.adk.exceptions import TracingDependencyError
from augments.adk.tracing.otel import OTelTracer
try:
OTelTracer()
except TracingDependencyError as e:
# e.missing == "opentelemetry"
# str(e) contains the "pip install 'augments-adk[otel]'" command
...setup_otel β fluent installer
from augments.adk.tracing import set_tracer
from augments.adk.tracing.otel import setup_otel
tracer = setup_otel(
endpoint="http://localhost:4317", # optional; reads OTEL_EXPORTER_OTLP_ENDPOINT when None
service_name="my-agent", # shows up in collector UIs
console=True, # also print spans to stdout
headers={"x-honeycomb-team": "..."}, # vendor API keys
)
set_tracer(tracer)What it installs:
- A
TracerProviderwithservice.name=<service_name>as the resource attribute. - A
BatchSpanProcessor(OTLPSpanExporter)for background shipping. - When
console=True, aSimpleSpanProcessor(ConsoleSpanExporter)so spans are also printed to stdout for inspection. - Any extra
SpanProcessorinstances supplied viaadditional_processors=[...]β used to coexist with OpenInference / Phoenix processors, or to tap spans into an in-memory recorder for test assertions.
Vendor walkthroughs
Jaeger (local dev, Docker)
docker run -d --name jaeger -p 16686:16686 -p 4317:4317 jaegertracing/all-in-onetracer = setup_otel(endpoint="http://localhost:4317", service_name="my-agent")
set_tracer(tracer)Open http://localhost:16686, pick the my-agent service, and
verify the trace tree shape: agent.<name> β llm.generation (per
turn) β tool.<name> / mcp.<name> (per tool call) β agent.handoff
/ guardrail.<name> where applicable.
Honeycomb
tracer = setup_otel(
endpoint="https://api.honeycomb.io:443",
service_name="my-agent",
headers={"x-honeycomb-team": os.environ["HONEYCOMB_API_KEY"]},
)
set_tracer(tracer)Datadog
Deploy the Datadog OTel collector as a sidecar or DaemonSet, then:
tracer = setup_otel(
endpoint="http://datadog-agent:4317",
service_name="my-agent",
)
set_tracer(tracer)Phoenix / Arize / Langwatch
These platforms ingest GenAI semconv attributes directly. Point
endpoint at their OTLP receiver and the spans will be correlated
into LLM-dashboard views automatically β no adapter required.
Span name and attribute mapping
| Span kind | OTel name | Key attributes |
|---|---|---|
agent_span | agent.<agent_name> | augments.agent.name, augments.agent.handoffs, augments.agent.tools, augments.agent.output_type, augments.metadata.<key> |
function_span (tool) | tool.<tool_name> | augments.tool.name, augments.tool.input, augments.tool.output |
function_span (MCP) | mcp.<tool_name> | augments.mcp.server_name, augments.mcp.tool_name, plus the tool.* set above |
generation_span | llm.generation | gen_ai.system="augments", gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.request.<k> (from model_config) |
response_span | llm.response | gen_ai.system="augments", gen_ai.response.id |
handoff_span | agent.handoff | augments.handoff.from, augments.handoff.to |
guardrail_span | guardrail.<name> | augments.guardrail.name, augments.guardrail.triggered |
custom_span | caller-provided name | augments.span.name, augments.custom.<k> (from data) |
GenAI semconv keys follow the OpenTelemetry GenAI
semantic-convention
draft, so spans are ingestable by Phoenix / Langwatch / Honeycomb LLM
dashboards without an adapter. Framework-specific fields use the
augments.* namespace.
Nested values
Nested dicts flatten with dotted prefixes:
model_config={"temperature": 0.7, "top_p": 0.9} becomes
gen_ai.request.temperature=0.7, gen_ai.request.top_p=0.9. Values
that cannot be expressed as OTel scalars (unknown types, heterogeneous
lists) are JSON-encoded into a string attribute.
Errors
Span.set_error(message, data={"type": ...}) maps to:
Status(StatusCode.ERROR, message)on the OTel span, and- an
exceptionevent withexception.message/exception.typeattributes, matching the OTel exception semconv.
Parent-child semantics
The bridge relies on OTel's own context propagation
(opentelemetry.context) to auto-parent children. A child span started
while an outer span is the current span is automatically attached as
its child β no framework bookkeeping. This means OTelSpan does not
register itself on the framework _current_span ContextVar (see
src/augments/adk/tracing/otel/otel_span.py for the rationale).
Using an existing TracerProvider
If the host application already configures its own TracerProvider
(typical in larger services), pass it explicitly instead of calling
setup_otel:
from opentelemetry import trace as otel_trace
from augments.adk.tracing import set_tracer
from augments.adk.tracing.otel import OTelTracer
provider = otel_trace.get_tracer_provider() # installed elsewhere
set_tracer(OTelTracer(provider=provider, service_name="my-agent"))Coexisting with OpenInference / Phoenix
OpenInference ships its own TracerProvider processors. The bridge
plays nicely with them β they just read the GenAI semconv attributes
the bridge emits. Either:
- Install OpenInference's processor as an
additional_processorinsetup_otel(...), or - Construct the provider yourself with OpenInference's recipe and
hand it to
OTelTracer(provider=...).
Examples
examples/tracing/otel_console.pyβ minimal local run withconsole=True.examples/tracing/otel_otlp.pyβ ship to an OTLP collector (Jaeger default).examples/tracing/multi_tracer.pyβ fan-out to OTel plus an in-memory recorder.