Expand description
Lightweight OpenTelemetry GenAI-style span instrumentation, built on the
tracing crate, plus optional GenAI metrics behind the otel-metrics
feature.
This is a dependency-light port of the Python observability.py
instrumentation. It emits tracing spans that follow the OpenTelemetry
GenAI semantic conventions, so that an OTel bridge (e.g.
tracing-opentelemetry) can export them without any additional glue:
- span names:
chat {model},invoke_agent {agent},execute_tool {tool}— the human-readable name is carried in theotel.namefield (the statictracingmetadata name is the bare operation, sincetracingrequires a literal span name). - chat-span attributes:
gen_ai.operation.name, the provider tag as eithergen_ai.provider.nameorgen_ai.system(whichever the active GenAI semantic-convention version defines — seeObservabilityConfig::use_latest_experimental_gen_ai_semconv),gen_ai.request.model,gen_ai.response.model,gen_ai.response.id,gen_ai.response.finish_reasons,gen_ai.usage.{input,output}_tokens, the request parameters (gen_ai.request.{temperature,top_p,max_tokens, seed,frequency_penalty,presence_penalty,stop_sequences},gen_ai.conversation.id),error.typeplus thetracing-opentelemetry“special fields”otel.status_code/otel.status_message— and, only when content capture is explicitly enabled,gen_ai.input.messages/gen_ai.output.messages,gen_ai.system_instructions, andgen_ai.tool.definitions. - tool-span attributes:
gen_ai.tool.name,gen_ai.tool.call.id,gen_ai.tool.description,gen_ai.tool.type, and (content-capture-gated)gen_ai.tool.call.arguments/gen_ai.tool.call.result.
The main entry point is ObservableChatClient, a ChatClient decorator.
Tool execution inside FunctionInvokingChatClient and the
Agent run paths are instrumented directly by
those types using the span constructors here.
§Metrics (otel-metrics feature)
With the otel-metrics feature enabled, ObservableChatClient also
records two histograms through the opentelemetry API crate only
(mirroring observability.py:788-803, bucket boundaries at :65-96):
[metrics::TOKEN_USAGE_METRIC] (gen_ai.client.token.usage, unit
"tokens") and [metrics::OPERATION_DURATION_METRIC]
(gen_ai.client.operation.duration, unit "s"). A third histogram,
[metrics::FUNCTION_INVOCATION_DURATION_METRIC]
(agent_framework.function.invocation.duration), times tool calls and is
recorded by FunctionInvokingChatClient around each tool invocation.
This crate never depends on an OTel SDK:
without an application-installed MeterProvider (via
[opentelemetry::global::set_meter_provider]) the instruments are no-ops,
so the feature is safe to enable unconditionally.
§Wiring to a real OTel backend
By default neither the tracing spans nor the otel-metrics histograms are
exported anywhere by this crate — that stays the application’s job, so that
an OTel SDK and its version churn never reach consumers who don’t ask for
them.
Two ways to close that loop:
otel-exportfeature — [export::OtelExport] builds an OTLP pipeline (exporter, tracer provider, meter provider, and thetracing↔OTel bridge) in one call, already wired to the conventions above. This is the only feature that pulls an OTel SDK.- Wire it yourself — compose
tracing-opentelemetryfor spans with anopentelemetry_sdkMeterProviderinstalled via [opentelemetry::global::set_meter_provider] for the histograms. Theotel_exportexample in this repository’sexamples/observability/shows both routes end to end, and is compiled by CI so it cannot drift out of date.
Without either, spans are still emitted to whatever plain tracing
subscriber you do have (e.g. for structured logging), and otel-metrics
instruments silently drop their measurements — zero required setup either
way.
§Environment configuration
ObservabilityConfig::from_env reads ENABLE_SENSITIVE_DATA (mirrors
Python’s enable_sensitive_data, observability.py:347-394) and
OTEL_SEMCONV_STABILITY_OPT_IN (which GenAI semantic-convention version to
emit — see
ObservabilityConfig::use_latest_experimental_gen_ai_semconv) for use
with ObservableChatClient::from_env. Unlike Python, there is no
ENABLE_OTEL equivalent to read: this crate’s spans are plain tracing
spans, already effectively free without a subscriber attached, so there is
no separate “enable observability” switch.
Modules§
Structs§
- Observability
Config - Observability configuration read from the process environment, mirroring
(a subset of) Python’s
ObservabilitySettings(observability.py:347-394). - Observable
Chat Client - A
ChatClientdecorator that emits achatspan per request following the OpenTelemetry GenAI semantic conventions.
Constants§
- GEN_
AI_ LATEST_ EXPERIMENTAL_ OPT_ IN - The token recognized in
OTEL_SEMCONV_STABILITY_OPT_INthat selects the GenAI semantic conventions above the v1.36.0 baseline. Mirrors upstream’sGEN_AI_LATEST_EXPERIMENTAL_OPT_IN.
Functions§
- agent_
span - Build an
invoke_agent {agent}span for an agent run. - chat_
span - Build a
chat {model}span for a chat-completion request. - error_
type - The
error.typevalue for a frameworkError: its variant discriminant. - record_
error - Record an error onto a span following the existing
error.typepattern:error.type(the frameworkErrorvariant tag), plus thetracing-opentelemetry“special fields”otel.status_code("ERROR") andotel.status_message(the exception’sDisplaytext) so that a bridge sets real OTel span status. This mirrors Python’scapture_exception(record_exception+set_status,observability.py:1407-1411) as far as baretracingfields allow — there is no span-events API here without also taking on an SDK dependency. - record_
request - Record request-side attributes onto a
chatspan fromChatOptions, mirroring_get_span_attributes(observability.py:1345-1404). System instructions and the serialized tool list are additionally gated bycapture_content(mirrors Python’sSENSITIVE_DATA_ENABLEDgate). - record_
response - Record the response-side attributes (finish reason, usage, id, model) onto
span, mirroring_get_response_attributes(observability.py:1488-1512). - record_
tool_ arguments - Record tool-call arguments onto a tool span, gated by content capture
(mirrors the
SENSITIVE_DATA_ENABLED-gatedgen_ai.tool.call.argumentscapture in Python’sAIFunction.invoke,_tools.py:751-759). - record_
tool_ result - Record a tool-call result onto a tool span, gated by content capture
(mirrors the
gen_ai.tool.call.resultcapture in Python’sAIFunction.invoke,_tools.py:779-787). - tool_
span - Build an
execute_tool {tool}span (source-compatible two-argument form). - tool_
span_ ex - Build an
execute_tool {tool}span with the full OTel GenAI tool attribute set: name, call id, description, a fixed"function"tool type (the only kind executed through this in-process loop — mirrors Python’sget_function_span_attributes,observability.py:1284-1302), and placeholders for the content-capture-gated call arguments/result (fill withrecord_tool_arguments/record_tool_result) and the error/status fields (fill withrecord_error).