OpenTelemetry Tracing
Last updated 1 day ago · fc28b6a
@reactive-agents/observe bridges the agent event bus to OpenInference-compliant OpenTelemetry spans. Every agent run automatically emits a span hierarchy — workflow → LLM calls → tool calls — that any OTLP-compatible backend can ingest.
Install
Section titled “Install”npm install @reactive-agents/observe# orbun add @reactive-agents/observeZero-config auto-export
Section titled “Zero-config auto-export”Set OTEL_EXPORTER_OTLP_ENDPOINT and call autoConfigureExporter before running agents:
import { autoConfigureExporter } from "@reactive-agents/observe"import { OpenInferenceTracerLayer } from "@reactive-agents/observe"// OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 already set in env
const handle = autoConfigureExporter({ serviceName: "my-agent" })
// Wire tracer layer into your Effect runtime...// (see "Effect integration" section below)
await handle.shutdown() // flush before process exitautoConfigureExporter is a no-op when OTEL_EXPORTER_OTLP_ENDPOINT is not set — safe to ship in all environments.
Span hierarchy
Section titled “Span hierarchy”Each agent run produces a nested span tree:
agent:my-agent-id ← openinference.span.kind = AGENT llm:anthropic/claude-... ← openinference.span.kind = LLM tool:web-search ← openinference.span.kind = TOOL llm:anthropic/claude-...All child spans share the workflow trace ID, so backends show the full call graph per invocation.
Span attributes
Section titled “Span attributes”Workflow span
Section titled “Workflow span”| Attribute | Description |
|---|---|
openinference.span.kind | AGENT |
llm.model_name | Model at start |
llm.provider | Provider name |
agent.id | Agent identifier |
task.id | Task correlation ID |
agent.iterations | Total reasoning loop iterations |
llm.token_count.total | Aggregate tokens across run |
agent.success | Boolean — false on error |
LLM span
Section titled “LLM span”| Attribute | Description |
|---|---|
openinference.span.kind | LLM |
llm.model_name | Model name |
llm.provider | Provider |
llm.token_count.prompt | Input tokens |
llm.token_count.completion | Output tokens |
llm.token_count.total | Total tokens |
llm.estimated_cost_usd | Estimated cost |
llm.cached | true when served from prompt cache |
llm.duration_ms | Round-trip latency |
Tool span
Section titled “Tool span”| Attribute | Description |
|---|---|
openinference.span.kind | TOOL |
tool.name | Tool identifier |
tool.parameters | JSON-serialized arguments |
tool.output | JSON-serialized result |
agent.iteration | Reasoning loop iteration |
tool.duration_ms | Execution latency |
tool.success | Boolean — false on error |
Effect integration
Section titled “Effect integration”OpenInferenceTracerLayer is an Effect Layer that subscribes to the EventBus. Provide it alongside your other layers:
import { Effect, Layer } from "effect"import { EventBusLive } from "@reactive-agents/core"import { OpenInferenceTracerLayer } from "@reactive-agents/observe"import { autoConfigureExporter } from "@reactive-agents/observe"
const handle = autoConfigureExporter({ serviceName: "my-agent" })
const AppLayer = Layer.merge( EventBusLive, OpenInferenceTracerLayer, // ... other layers)
await Effect.runPromise( myAgentProgram.pipe(Effect.provide(AppLayer)))
await handle.shutdown()Explicit OTLP config
Section titled “Explicit OTLP config”import { setupOpenInferenceExporter } from "@reactive-agents/observe"
const handle = setupOpenInferenceExporter({ endpoint: "http://my-collector:4318", serviceName: "production-agent", headers: { Authorization: `Bearer ${process.env.BACKEND_TOKEN}`, },})Langfuse
Section titled “Langfuse”Langfuse ingests OpenTelemetry traces natively over OTLP HTTP. setupLangfuseExporter is a thin preset over setupOpenInferenceExporter — it computes the /api/public/otel endpoint and the HTTP Basic auth header from your project keys, so spans arrive with OpenInference attributes and render as agent/LLM/tool traces in the Langfuse UI.
import { setupLangfuseExporter } from "@reactive-agents/observe"
// Reads LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_HOST from env.const handle = setupLangfuseExporter()
// ... run agents (with OpenInferenceTracerLayer wired in) ...
await handle.shutdown()Or pass credentials explicitly:
import { setupLangfuseExporter } from "@reactive-agents/observe"
const handle = setupLangfuseExporter({ publicKey: "pk-lf-...", secretKey: "sk-lf-...", host: "https://us.cloud.langfuse.com", // regional or self-hosted; defaults to https://cloud.langfuse.com serviceName: "production-agent",})host must match the region your Langfuse project lives in — pick the base URL and set it via host or LANGFUSE_HOST:
- EU (default) —
https://cloud.langfuse.com - US —
https://us.cloud.langfuse.com - Japan —
https://jp.cloud.langfuse.com - Self-hosted — your own base URL (e.g.
http://localhost:3000)
A region mismatch surfaces as 401 Invalid credentials. Confirm that you've configured the correct host. — the keys are fine, the host is wrong.
The preset base64-encodes publicKey:secretKey into Authorization: Basic ... and sets x-langfuse-ingestion-version: 4 (Langfuse v4 real-time ingestion — without it, directly-ingested OTel data can be delayed up to 10 minutes). Both are overridable via headers.
Backends
Section titled “Backends”@reactive-agents/observe emits standard OTLP HTTP spans with OpenInference semantic attributes. Works out of the box with:
- Jaeger —
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 - Grafana Tempo —
OTEL_EXPORTER_OTLP_ENDPOINT=https://tempo.example.com - Langfuse — use the
setupLangfuseExporterpreset above - Arize Phoenix —
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:6006 - Any OTLP HTTP-compatible collector
Stability
Section titled “Stability”@reactive-agents/observe is @stable as of v0.11. The OpenInferenceTracerLayer, setupOpenInferenceExporter, setupLangfuseExporter, and autoConfigureExporter exports are stable. A dedicated Braintrust preset and sampling support are not yet built — any OTLP-compatible backend works today via the standard OTLP endpoint config above.