Skip to main content
Eve is Vercel’s filesystem-first TypeScript framework for durable backend AI agents. You define an agent as files under an agent/ directory and Eve compiles it into an app that runs on Vercel Functions. Eve emits Vercel AI SDK OpenTelemetry spans for every turn, model call, and tool execution. Phoenix captures them by registering the @arizeai/openinference-vercel span processor in Eve’s agent/instrumentation.ts.

Prerequisites

Install

In your Eve project, add the Arize OpenInference processor and the OpenTelemetry packages it exports through:
npm install @arizeai/openinference-vercel@^3 \
  @arizeai/openinference-semantic-conventions \
  @vercel/otel \
  @opentelemetry/api \
  @opentelemetry/exporter-trace-otlp-proto

Connect to Phoenix

Run a self-hosted Phoenix instance, a local terminal, Kubernetes, etc., then point Eve at it by adding the following to your environment variables.
.local.env
PHOENIX_COLLECTOR_ENDPOINT="http://localhost:6006"

# optional; defaults to the agent name
PHOENIX_PROJECT_NAME="weather-agent"

# optional; use if using Vercel's AI Gateway with Eve
# AI_GATEWAY_API_KEY="<your-ai-gateway-key>"

Setup tracing

Eve auto-discovers agent/instrumentation.ts and runs it once at server startup, before any agent code. Create or edit this file to register an OpenTelemetry provider in the setup callback like so:
agent/instrumentation.ts
import { defineInstrumentation } from "eve/instrumentation";
import { registerOTel } from "@vercel/otel";
import {
  isOpenInferenceSpan,
  OpenInferenceSimpleSpanProcessor,
} from "@arizeai/openinference-vercel";
import { SEMRESATTRS_PROJECT_NAME } from "@arizeai/openinference-semantic-conventions";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";

export default defineInstrumentation({
  setup: ({ agentName }) => {
    // Bail out if the collector endpoint is missing. This callback runs at server
    // startup, so throwing here (an unset endpoint makes an unparseable exporter URL)
    // would crash the agent runner with a "Runner did not become ready in time" error
    // that never mentions Phoenix.
    const endpoint = process.env.PHOENIX_COLLECTOR_ENDPOINT;
    if (!endpoint) {
      console.warn("PHOENIX_COLLECTOR_ENDPOINT not set — skipping Phoenix tracing");
      return;
    }
    // Route to the project named by PHOENIX_PROJECT_NAME, falling back to the agent
    // name. Without a project name, spans land in Phoenix's "default" project.
    const projectName = process.env.PHOENIX_PROJECT_NAME ?? agentName;
    return registerOTel({
      serviceName: projectName,
      attributes: { [SEMRESATTRS_PROJECT_NAME]: projectName },
      spanProcessors: [
        new OpenInferenceSimpleSpanProcessor({
          exporter: new OTLPTraceExporter({
            url: `${endpoint}/v1/traces`,
            // Only needed when your self-hosted Phoenix has auth enabled.
            headers: process.env.PHOENIX_API_KEY
              ? { Authorization: `Bearer ${process.env.PHOENIX_API_KEY}` }
              : undefined,
          }),
          spanFilter: isOpenInferenceSpan,
          reparentOrphanedSpans: true,
        }),
      ],
    });
  },
});
Both options act client-side, before spans are exported, and control which spans reach Phoenix and how each trace is rooted:
  • spanFilter: isOpenInferenceSpan keeps only the AI spans and drops the rest — the raw HTTP/fetch spans and Eve’s Vercel Workflow spans — so your Phoenix Traces view isn’t cluttered with non-AI spans.
  • reparentOrphanedSpans: true re-roots any AI span left orphaned when the filter drops its parent, so it no longer points at a parent that was never exported (which would otherwise show up orphaned on the Phoenix Traces tab). It also recognizes Eve’s ai.eve.turn wrapper as an AI-like root and tags it openinference.span.kind = AGENT, so each turn shows up as a single clean agent root with its steps nested underneath.
OpenInferenceSimpleSpanProcessor exports each span synchronously as it ends, so it is safe on the short-lived serverless functions Eve runs on (no process-exit forceFlush to call). @arizeai/openinference-vercel translates the AI SDK spans into OpenInference before export.

Run Eve

Start the Eve dev server, then open a session against the built-in HTTP channel:
npm run dev
The dev server listens on http://127.0.0.1:2000 by default (pass --port to change it). Open a session against the built-in HTTP channel:
curl -X POST http://127.0.0.1:2000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"What is the weather in Brooklyn?"}'
The response returns a continuationToken in the body and an x-eve-session-id header. Stream the session’s lifecycle events to watch the turn complete:
curl http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream

Expected output

{"type":"session.started","data":{"runtime":{"agentName":"weather-agent"}}}
{"type":"actions.requested","data":{"actions":[{"kind":"tool-call","toolName":"get_weather","input":{"city":"Brooklyn"}}]}}
{"type":"message.completed","data":{"message":"The weather in Brooklyn is **sunny** and **72°F**.","finishReason":"stop"}}

Observe in Phoenix

New to reading traces in Phoenix? Here’s how to make sense of what Eve sends, step by step.
  1. Open your project. Go to localhost:6006 and click the project named weather-agent (or whatever you set in PHOENIX_PROJECT_NAME). That opens the project’s Spans table. New spans show up within ~30 seconds of a turn. By default, only “Root spans” are shown. You can toggle on “All” to see all the spans.
  2. Open a trace. Navigate to the “Traces” tab. Each row in the Traces table is one trace: a full agent turn, from the incoming message to the final reply. Click a row to open a span tree. Each span is one unit of work inside the turn (a model call, a tool run, a reasoning step), nested to show what ran inside what.
  3. Tell spans apart by their kind, not their name. Eve emits Vercel AI SDK spans that follow OpenTelemetry’s GenAI convention, so nearly every span is named gen_ai (or gen_ai.client for the model request) rather than something descriptive like ai.streamText or ai.toolCall. To know what a span actually is, read its span kind, a colored label Phoenix adds to denote what it is:
    • agent a step of the agent’s turn (its reasoning and orchestration).
    • llm a model request (the gen_ai.client span). Open it to see the prompt, the response, and token usage.
    • tool a tool execution, carrying a tool.name attribute such as get_weather.
  4. Find the session context. Click any span and open its Attributes panel. Eve attaches session identifiers under the ai.settings.context.eve.* prefix — ai.settings.context.eve.session.id, ai.settings.context.eve.turn.id, ai.settings.context.eve.step.index, and ai.settings.context.eve.channel.kind — so you can trace any span back to the session and turn it came from.
  5. Read the tree top-down. At the top sits Eve’s ai.eve.turn span, shown as an agent root — reparentOrphanedSpans is promoting Eve’s turn wrapper to a clean root, one per turn. Beneath it you’ll see one or more gen_ai spans, one for each step Eve took in the turn. A step’s gen_ai span often nests another gen_ai span inside it, the outer one being the step itself (either a chain or agent span kind), and the inner one is the actual model request (with a span kind of llm, and sometimes named gen_ai.client). So the “gen_ai inside a gen_ai” you’re seeing is simply a step wrapping its model call. If the step ran a tool, that shows up as its own gen_ai span of kind tool carrying tool.name. Remember: the names are nearly all gen_ai, so lean on the kind label (and the nesting) to read what each one is.
    If you turn off spanFilter/reparentOrphanedSpans in instrumentation.ts, this tree will be cluttered with raw HTTP spans, or with the gen_ai spans floating loose under no root; see the accordion under Setup tracing.
  6. If no traces appear at all, see Troubleshooting.
Phoenix trace view of a Vercel Eve agent turn, showing the ai.eve.turn agent root span with nested agent, llm, and tool spans for each step

Troubleshooting

  • No traces in Phoenix. Confirm the file is exactly agent/instrumentation.ts (Eve discovers it by path), and that PHOENIX_COLLECTOR_ENDPOINT is set in the shell running npm run dev. If your Phoenix has auth enabled, also set PHOENIX_API_KEY. Enable OpenTelemetry debug logs with export OTEL_LOG_LEVEL=debug and re-run.
  • Traces land in the wrong project. Phoenix routes spans to a project by the project-name resource attribute. Set PHOENIX_PROJECT_NAME (or rely on the agent-name fallback above); without it, spans land in Phoenix’s default project.
  • Model auth errors. Eve routes models through AI Gateway, so set AI_GATEWAY_API_KEY, or run vercel link to use a VERCEL_OIDC_TOKEN. To skip the gateway, switch the agent to a direct provider model (e.g. @ai-sdk/openai with OPENAI_API_KEY). A brand-new AI Gateway key also fails until you add a payment method. The turn errors with GatewayInternalServerError: AI Gateway requires a valid credit card on file to service requests, even if you only plan to use the free credits. Add a card in your Vercel AI Gateway dashboard to unlock them.
  • Version mismatch. Pin the OpenTelemetry packages to Eve’s @vercel/otel major: @vercel/otel@1.x requires @opentelemetry/* 1.x; @vercel/otel@2.x requires 2.x. Mismatches surface as silently missing traces.
  • gen_ai spans orphaned on the Traces tab. This happens when spanFilter: isOpenInferenceSpan drops Eve’s ai.eve.turn workflow span without re-rooting its children, leaving the per-step gen_ai spans with no parent. Set reparentOrphanedSpans: true as shown in Setup tracing for a single clean agent root per turn, and confirm @arizeai/openinference-vercel is 3.0.0 or later (the v3 major maps Eve’s v7 GenAI-convention spans and carries the ai.eve.turn reparenting fix first shipped in 2.8.1).

Resources

Vercel AI SDK Tracing (JS)

Eve Observability Docs

OpenInference Vercel Span Processor

Eve Getting Started