ai.experimental_telemetryis experimental: it may change or be removed.
Enable telemetry
- Nothing is reported until an adapter is registered or a sink is routed;
with telemetry off, spans are no-ops (
id="", no clock reads,push()does nothing). Check withis_enabled(). - OpenTelemetry export: perform the standard OpenTelemetry ceremony (tracer
provider + OTLP exporter), then
ai.experimental_telemetry.register(otel.OtelAdapter()). Requires theai[otel]extra. - Message content capture is opt-in:
otel.OtelAdapter(capture_content=True)orOTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true. - Call
OtelAdapter.flush()before a checkpoint andOtelAdapter.shutdown()at process exit; the exporter buffers.
Framework spans
- The framework opens spans automatically:
RunSpanData,LoopTurnSpanData,AiStreamSpanData,AiGenerateSpanData,ToolExecutionSpanData,HookSpanData. - Dispatch on the type of
span.datain adapters; span events includeFIRST_TOKEN,RESPONSE_COMPLETE,HOOK_DEFERRED,HOOK_RESOLVED,HOOK_CANCELLED.
User spans
async with span("name") as sp:— stamps, pushes, nests, and records exceptions automatically.sp.set_attrs(...)for attributes;sp.add_event(name)for milestones.- Typed spans: any Pydantic model with a
kindfield isSpanData; open withspan(MyData(...))and assignsp.datafields directly.
Custom adapters
- Protocol:
on_span_start/on_span_event/on_span_end(all required), registered withregister()— it raisesTypeErrorotherwise. Failures are logged and skipped. - Prefer the
@adapterdecorator for vendor bridges: one async generator per span, yield loop untilNone, then readspan.data/span.error. Also works on a class whose__call__is an async generator method, for adapters with configuration or state. - Subclass
OtelAdapterand overridespan_name()/span_attrs()to adjust OpenTelemetry output.
Durable and serverless execution
- Inside a workflow body, route spans to a sink instead of the adapters and
keep delivery in steps; with deterministic clock and random installed,
agent runs and the
span()context manager work in the body. Use the low-level API for spans that cross process boundaries. - Collect in the body:
sink = DictSink(), run underasync with use_sink(sink):, then serializesink.finished_spanswithmodel_dump(mode="json"). - Deliver from a step:
await push_all(payload). - Manual lifecycle:
create_span()thenstamp_start()/stamp_end()/push(); nothing is reported except by pushing. - Continue a trace across processes: restore with
Span.model_validate(...), parent under it withasync with use_span(restored):orspan(..., parent=restored). - Deterministic timestamps:
use_time(workflow.time_ns); read the ambient clock withnow_ns(). use_sink,use_span, anduse_timeare async context managers; they also work as decorators on async functions, but never with a plainwith.- Errors serialize as
SpanError(SpanError.from_exception(exc)), so spans that failed in another process still report.