Trace every agent run, LLM call, tool call and flow node with OpenTelemetry, into any OTLP backend.
Gwenflow ships with optional tracing built on OpenTelemetry and the OpenInference semantic conventions, so traces flow into any OTLP-compatible backend: Jaeger, Grafana Tempo, Arize Phoenix, Langfuse, Honeycomb…It is off by default and costs nothing until you enable it: without the telemetry packages, or without a Telemetry() configured, every traced call is a plain pass-through.
pip install "gwenflow[telemetry]"
from gwenflow import Agent, ChatOpenAI, TelemetryTelemetry(organization="acme") # OTLP/HTTP export to http://localhost:4318 by defaultagent = Agent(name="Researcher", llm=ChatOpenAI(model="gpt-4o-mini"))agent.run("What is the capital of France?")
One top-level call (agent.run, flow.run) is one trace. Spans nest into the tree of the run:
Flow Run└── Agent Run / Agent Arun agent name, finish reason, usage, delegated usage ├── LLM Invoke / LLM Stream model, provider, input/output messages, token counts, latency ├── Tool Call tool name, arguments, result, status │ └── Agent Run a team member or a sub-agent, nested under its tool call ├── Retriever Search query, documents └── compact_messages tiers applied, message and token counts (never the content)
Streams are traced as Agent Stream / Agent Astream; a stream closed early ends with the interrupted finish reason and what it spent. A tool error handed back to the model is marked on the tool’s span, not on the whole run. Async calls get their own names (Agent Arun, LLM Async Invoke…). Embeddings (Embed Documents, Embed Query) and rerankers (Rerank) have their spans too, and outgoing HTTP calls (httpx) are instrumented unless you turn it off.
OTEL_SDK_DISABLED=true turns everything off. When your application already configures an OpenTelemetry TracerProvider, Gwenflow reuses it and its exporter; the export parameters of Telemetry() are then ignored.Spans are exported in batches: call telemetry.flush() before a short-lived process exits, and telemetry.shutdown() to stop.
Traces can be organized from the organization down to a single operation: organization → project → session → trace → span.
Organization.Telemetry(organization=...), reported as the standard resource attribute service.name, which Jaeger and Phoenix use natively. Fixed per process.
Anything else. Attach attributes to every span of a block with tracer.context(metadata={...}): a project, an environment, a tenant. Nothing is required or privileged.
record llm.token_count.* on LLM spans (also Telemetry(report_llm_usage=...))
GWENFLOW_TELEMETRY_PROPAGATE_BAGGAGE
false
propagate tracer.context metadata as W3C baggage to downstream services (also Telemetry(propagate_baggage=...))
When the model is served behind a gateway that emits its own span with token counts (Gwenlake’s API, a proxy, an inference service), set report_llm_usage=False on one side: otherwise a backend summing llm.token_count.* counts the same tokens twice.
Team handoffs (ask_*) and sub-agents (spawn_agent) record gwenflow.delegation.finish_reason and are marked as failed when the delegated run hits max_turns or needs an approval. The spawn_agent span also records the request: gwenflow.subagent.description, .tools, .skills, .model, .model_name. The Agent Run span reports gwenflow.agent.llm_requests and gwenflow.agent.tool_calls, with gwenflow.agent.delegated_llm_requests and gwenflow.agent.delegated_tool_calls for the delegated share.