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.
Oodle is a managed observability platform for metrics, logs, traces and agent traces. It ingests OpenTelemetry natively, so the snippet on this page is the complete setup.
Each trace shows the full transcript, the token counts and cost of every call, the agent structure (runs, steps and tool calls) and Signals: labels for loops, rate limits, refusals and tool failures, detected as the trace arrives.
Sign up for free to get an instance ID and an API key, or see the Agent Observability overview first.
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
Add the Oodle destination from the eve registry (eve 0.62.0 or later). It installs @vercel/otel and writes agent/instrumentation/oodle.ts:
npx eve add instrumentation/oodle
Instrument
Remove the exportPolicy from the generated agent/instrumentation/oodle.ts: it redacts every prompt and reply. Then add otel.ts to authorize content. eve owns the tracer provider, names the service after the agent, and traces every turn, step and tool call:
// 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';
// The registry file also sets an exportPolicy that redacts
// every prompt and reply. Without it the Transcript is kept.
export default otelIntegration({
traceExporter: new OTLPHttpProtoTraceExporter({
url: `https://${process.env.OODLE_OTLP_ENDPOINT}/v1/traces`,
headers: {
'X-API-KEY': process.env.OODLE_API_KEY!,
'X-OODLE-INSTANCE': process.env.OODLE_INSTANCE!,
},
}),
});
Environment
Point the application at Oodle:
# eve add puts these names in .env.local.
# The endpoint is the host only, without https://
export OODLE_OTLP_ENDPOINT=<OTLP_ENDPOINT>
export OODLE_API_KEY=<OODLE_API_KEY>
export OODLE_INSTANCE=<OODLE_INSTANCE>
# Compress the export: prompt payloads are large
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
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]