Skip to Content

How do I trace a TypeScript AI agent running in Vercel functions?

Rius traces a TypeScript agent in a Vercel function when you call init() once at module scope and call await client.flush() at the end of every invocation. The flush matters because the TypeScript SDK installs no process-exit hook, and a serverless runtime freezes the process between invocations, so queued spans would otherwise not leave. Turn the heartbeat off, since a frozen process cannot send it. If your app uses pnpm, you may also need to pass your instrumentation modules to init().

How it works

  • Initialize once per process. Put init() at the top of the module that holds your handler. Module-level code runs once per process, and later invocations that reuse the process reuse the same client. A second global init() only logs a warning and returns the existing client.
  • Spans export in the background. The SDK queues spans and sends them in batches on a timer. A frozen process runs no timer, so a batch that has not been sent yet waits until the process thaws, if it ever does.
  • Flush at the invocation boundary. client.flush() forces the export and returns false if the most recent export failed, so true means your spans actually left. Call it once, as the last step of the invocation, never inside a hot path.
  • Instrumentation attaches asynchronously. init() returns at once, and client.ready resolves with the integrations that attached. Await it before your first model call so that call is traced.
  • The heartbeat does not fit. The heartbeat is a long-lived-process signal. Between invocations the process is suspended, pings stop, and the instance would flap between alive and stale or gone. Set heartbeat: false.
  • pnpm needs extra care. The SDK finds the OpenAI and Anthropic instrumentation by importing it relative to its own files. A file-tracing deployment of a pnpm app may not ship those links, so no model-call spans appear while everything else looks fine. instrumentModules fixes that.

Set it up

  1. Install the SDK and the instrumentation for the client library your agent calls. This example uses Anthropic. For OpenAI, install @arizeai/openinference-instrumentation-openai instead. Details are in Installation and configuration.
npm install @glassflow-ai/rius @opentelemetry/api npm install @arizeai/openinference-instrumentation-anthropic
  1. Add RIUS_API_KEY (your key starts with ri_) to the environment of your function. The SDK reads it, so it stays out of your code.
  2. Initialize at module scope, wrap the work in a named function, and flush in a finally block so spans are exported even when the run throws:
import Anthropic from '@anthropic-ai/sdk' import { init, observe } from '@glassflow-ai/rius' const rius = init({ serviceName: 'my-agent', heartbeat: false }) const claude = new Anthropic() // reads ANTHROPIC_API_KEY const handle = observe(async function handle(query: string) { const message = await claude.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 200, messages: [{ role: 'user', content: query }], }) const [block] = message.content return block.type === 'text' ? block.text : '' }) export async function run(query: string) { await rius.ready // auto-instrumentation is attached before the first call try { return await handle(query) } finally { const delivered = await rius.flush() // export before the function freezes if (!delivered) console.warn('the most recent span export failed') } }
  1. If you deploy a pnpm app and rius.ready leaves out anthropic, import the modules yourself and hand them to init(). Your own imports are what file tracing follows. The sdk module must be the copy your client calls go through. This needs @glassflow-ai/rius 1.1.0 or later:
import * as anthropicSdk from '@anthropic-ai/sdk' import * as anthropicInstrumentation from '@arizeai/openinference-instrumentation-anthropic' import { init } from '@glassflow-ai/rius' init({ serviceName: 'my-agent', heartbeat: false, instrumentModules: { anthropic: { instrumentation: anthropicInstrumentation, sdk: anthropicSdk }, }, })

The Integrations page covers the pnpm case in full. The Reliability page covers flushing, retries and the serverless rules.

What you get

  • One trace per invocation, with a span for each model call, its token counts and a cost estimate. Costs are list-price estimates in US dollars.
  • Your own handle function as the root of the trace, so model calls sit under a named step.
  • A clear signal when export fails: flush() returns false instead of failing silently.
  • Optional partial spans. With partialSpans: true (and a partialSpansDelay to keep volume low), each span sends a content-free snapshot when it starts, so a run that crashes leaves a record of what it was doing.

When something else fits

  • On a long-running Node server rather than a function, skip the per-request flush and call await client.shutdown() on graceful shutdown, for example in a SIGTERM handler.
  • If a request is refused with 402 or 429, flushing cannot help. Check your plan and ingest rate as described in Reliability.
  • If your agent calls a library with no TypeScript integration, the Integrations page points to manual generations for those calls.

Still stuck? Get help

Last updated on