eve
eve traces every turn of an agent session: the model calls, the steps between them, tool executions and subagent runs, with the session id on each span as gen_ai.conversation.id.
You will need:
OODLE_INSTANCE: your Oodle instance ID (ap1, us1)OODLE_API_KEY: an Oodle API key (ap1, us1)OTLP_ENDPOINT: your OTLP collector domain, shown on the tile
Open the eve tile on the ap1, us1 page to
get these filled in for you, or let an agent do the setup with
/oodle-onboarding set up the llm_observability_eve integration.
Install
Install the exporter. eve carries the AI SDK OpenTelemetry integration itself, so only the transport is missing:
npm install @vercel/otel
Instrument
Declare Oodle as a destination under agent/instrumentation/. eve owns the tracer provider, names the service after the agent, and traces every turn, step and tool call:
// agent/agent.ts
import { defineAgent } from 'eve';
export default defineAgent({
model: 'anthropic/claude-opus-4.8',
// `agent/instrumentation/` is read only behind this flag.
// Without it the files below compile and never run.
experimental: { instrumentationProviders: true },
});
// agent/instrumentation/otel.ts
import { otel } from 'eve/instrumentation/otel';
export default otel({
// eve withholds the messages for every conversation its
// channel does not classify as public. Tokens, cost and the
// span tree arrive without this; the transcript does not.
tracePolicy: () => ({
emit: true,
recordInputs: true,
recordOutputs: true,
}),
});
// agent/instrumentation/oodle.ts
import { OTLPHttpProtoTraceExporter } from '@vercel/otel';
import { otelIntegration } from 'eve/instrumentation/otel';
// eve wraps the exporter in its own batching processor and
// names the service after the agent, so there is no tracer
// provider or resource to build here.
export default otelIntegration({
traceExporter: new OTLPHttpProtoTraceExporter({
// The path is explicit: this option is the full URL.
url: 'https://<OTLP_ENDPOINT>/v1/traces',
headers: {
'X-API-KEY': '<OODLE_API_KEY>',
'X-OODLE-INSTANCE': '<OODLE_INSTANCE>',
},
}),
});
Where the transcript comes from
eve withholds model messages for every conversation its channel does
not classify as public. The tracePolicy above authorizes both
directions, but it cannot exceed that ceiling: the base eve HTTP
channel, direct messages and private threads stay metadata-only, so
the spans carry the model, token counts, cost and the tool names with
no prompts or replies on them.
A channel classifies itself through its metadata projection:
import { defineChannel } from 'eve/channels';
export default defineChannel({
metadata: () => ({ audience: 'public' as const }),
routes: [
/* ... */
],
});
Slack's public channels and Chat SDK workspace threads already classify themselves this way, so they need no change. Set it only where the conversation really is visible to a group, and only when Oodle is approved to receive that content.
Verify
Run your application, then open ap1, us1. Spans
carry gen_ai.* attributes: the model, token counts, and the prompt and
response content. Click a trace for the Transcript, the waterfall, and
the cost breakdown.
If nothing arrives, check that the exporter can reach
https://<OTLP_ENDPOINT> and that the instance and key are set: the
OTLP gateway answers 401 without them.
Support
If you need assistance or have any questions, please reach out to us through:
- Email at [email protected]