@anvia/otel
OpenTelemetry tracing for Anvia agents.
@anvia/otel sends agent lifecycle events to any OpenTelemetry-compatible backend (Jaeger, Zipkin, Grafana Tempo, Datadog, etc.). Use it when you want full control over your tracing pipeline.
Install
pnpm add @anvia/otel@opentelemetry/api is a transitive dependency. You need to configure an OpenTelemetry exporter separately.
Quick Start
import { AgentBuilder } from "@anvia/core";
import { OpenAIClient } from "@anvia/openai";
import { otel } from "@anvia/otel";
const tracing = otel.create({
tracerName: "my-app",
serviceName: "support-service",
});
const client = new OpenAIClient({ apiKey });
const agent = new AgentBuilder("support", client.completionModel())
.instructions("Answer support questions.")
.observe(tracing)
.build();
const response = await agent
.prompt("How do I reset my password?")
.withTrace({
name: "support-question",
userId: "user_123",
sessionId: "session_456",
})
.send();Configuration
type OtelTracingOptions = {
tracer?: Tracer; // pre-configured OpenTelemetry Tracer
tracerName?: string; // tracer name (default: "@anvia/otel")
tracerVersion?: string; // tracer version
serviceName?: string; // service.name resource attribute
};// Minimal
const tracing = otel.create();
// With service name
const tracing = otel.create({
tracerName: "my-app",
serviceName: "support-service",
});
// Pre-configured tracer
import { trace } from "@opentelemetry/api";
const tracing = otel.create({
tracer: trace.getTracer("my-app"),
});Setting Up an Exporter
@anvia/otel creates spans but does not configure an exporter. You need to set up the OpenTelemetry SDK exporter in your application:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces",
}),
});
sdk.start();Or use environment variables:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_SERVICE_NAME=my-appSpan Structure
Each agent run produces this span tree:
agent.{name} (root span)
├── model.turn.0 (generation span)
├── tool.{tool_name} (tool span)
│ ├── {child_agent}.run (child agent span, if applicable)
│ │ ├── {child}.model.turn.0
│ │ └── {child}.{tool}
│ └── {child}.{tool_name} (child tool span)
└── model.turn.1 (final generation)Span Attributes
The observer sets these attributes on spans:
Run spans (agent.*)
anvia.agent.name,anvia.agent.description,anvia.agent.instructionsanvia.run.max_turns,anvia.run.prompt,anvia.run.historyanvia.run.output,anvia.run.messagesanvia.trace.name,anvia.trace.user_id,anvia.trace.session_id,anvia.trace.tagsanvia.usage.input_tokens,anvia.usage.output_tokens,anvia.usage.total_tokens
Generation spans (model.turn.*)
anvia.generation.turn,anvia.generation.modelanvia.generation.input,anvia.generation.output,anvia.generation.output_textanvia.generation.tool_count,anvia.generation.has_output_schemaanvia.generation.temperature,anvia.generation.max_tokens,anvia.generation.tool_choiceanvia.generation.first_delta_msanvia.usage.*(per-generation usage)
Tool spans (tool.*)
anvia.tool.name,anvia.tool.turn,anvia.tool.args,anvia.tool.resultanvia.tool.call,anvia.tool.call_id,anvia.tool.internal_call_idanvia.tool.skipped
Parent Trace Linking
If a traceId is provided via .withTrace(...), the observer creates a remote parent context so the agent run span links to an existing trace. This is useful when the agent is called from within a larger distributed trace.
Child Agent Tracing
When an agent uses another agent as a tool, the observer creates nested spans under the tool span. Child agent events (generations, tool calls) are automatically captured as sub-spans.
Error Handling
Observer failures are swallowed by default so tracing does not break application behavior. Use strict mode in tests:
const agent = new AgentBuilder("support", model)
.observe(tracing, { failOnObserverError: true })
.build();Langfuse vs OTEL
| Aspect | @anvia/langfuse | @anvia/otel |
|---|---|---|
| Backend | Langfuse only | Any OTEL-compatible backend |
| Setup | One-line langfuse.create(...) | Requires OTEL SDK + exporter config |
| Scoring | Built-in tracing.score(...) | Use your backend's scoring API |
| Eval reporting | Built-in createLangfuseEvalReporter | Custom reporter needed |
| UI | Langfuse dashboard | Your chosen backend's UI |
Use @anvia/langfuse for a batteries-included experience. Use @anvia/otel when you already have an OpenTelemetry pipeline or need vendor flexibility.
Related
- OTEL Guide for setup walkthrough
- Observers for the observer pattern
- OTEL Reference for full API types
@anvia/langfusefor the Langfuse-specific adapter
