soev_agents.observability
Optional observers. Import an implementation only when the server enables it.
This package needs the otel extra: install soev-agents[otel]. Without the extra, its import raises an
ImportError that names the extra.
OpenTelemetry
The OpenTelemetry observer exports spans of turns, model calls and tool calls, with no content.
OtelObserver
classOtelObserver(tracer=None, *, tracer_provider=None, model_for_endpoint=None)An Observer that exports turns, model calls and tool calls as OpenTelemetry spans, with no content.
Each turn is a span soev.agents.turn. Each model call is a child span soev.agents.model, and each tool call a
child span soev.agents.tool. The observer also records a model span for each compaction. The spans hold only
metadata: the thread id, the agent, the turn id, the model names, the token usage, the finish reasons, the tool
name and the status. They hold no prompt, text, reasoning, tool argument, tool result or error message.
A turn span has the attributes soev.thread.id, soev.turn.id, soev.agent and gen_ai.request.model. A model
span has soev.endpoint.id, gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens,
gen_ai.usage.output_tokens, soev.usage.total_tokens and gen_ai.response.finish_reasons. A tool span has
soev.tool.name. An attribute is absent when the observer does not know its value. A turn span and a tool span
end with soev.status: ok, error, cancelled or interrupted. A model span gets soev.status only when the
observer ends it before the output of its call.
The server owns the tracer provider, its exporter and its shutdown. The observer installs no global provider. It keeps the open spans in memory: after a restart of the process, a resumed turn gets a new span with the same turn id.
Example
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from soev_agents.adapters import InMemoryTraceStore
from soev_agents.entrypoints.http import create_app
from soev_agents.observability import OtelObserver
exporter = ConsoleSpanExporter()
store = InMemoryTraceStore()
agents = {"assistant": agent} # agent is an Agent of your own
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
app = create_app(store, agents, version="1.0", observer=OtelObserver(tracer_provider=provider))Make an observer that writes spans with a tracer of the server.
paramtracerTracer | None= NoneThe tracer to write the spans with. Give this or tracer_provider, not both.
paramtracer_providerTracerProvider | None= NoneThe provider to get the tracer from. With neither argument, the observer uses the server's global OpenTelemetry configuration.
parammodel_for_endpointCallable[[str], str] | None= NoneA function that maps the name of an endpoint to the name of the provider's model,
for the gen_ai.request.model and gen_ai.response.model attributes. None uses the endpoint's
name.
Raises:
ValueError: Bothtracerandtracer_providerare given.
methodrequest(headers) -> AbstractContextManager[None]Attach the W3C traceparent context of the caller for the time of one HTTP request.
The observer reads traceparent and tracestate from the headers, in upper or lower case. A turn that the HTTP
request starts gets the span of the caller as its parent. The previous context comes back when the HTTP
request ends.
paramheadersMapping[str, str]The headers of the HTTP request.
Returns
AbstractContextManager[None]A context manager that holds the traceparent context of the caller while it is entered.
methodrun(thread_id, agent, opening) -> AbstractContextManager[None]Attach the span of the thread's turn for the time of one run.
A run that resumes a turn that this observer has no span for starts a new turn span from opening. When the
run stops, the observer ends each model span and tool span that is still open, with the soev.status
interrupted. The turn span stays open, so a turn that waits for a response continues under it.
paramthread_idstrThe thread that runs.
paramopeningInput | NoneThe input that opened the current turn when that turn is not over, else None.
Returns
AbstractContextManager[None]A context manager that holds the turn span as the current span while it is entered.
methodevent(thread_id, agent, event) -> NoneStart, continue or end spans from one sealed event.
An input of the root stream starts a new turn span, and ends an unfinished earlier one as interrupted. An
input that responds to a request continues the turn. A model output or a compaction ends its model span, and
a tool output ends its tool span. A failure marks the turn as an error. A Finished or Cancelled of the
root stream ends the turn span.
methodcall(thread_id, stream, node, kind, name, call_id) -> NoneStart a model span or a tool span under the thread's turn span.
A call of a thread without an open turn span gives no span. The span ends when the output of the call is recorded, or when the run stops.
methodclose() -> NoneEnd every open span with the soev.status interrupted.
The observer does not flush or shut down the tracer provider: the server does.