How do I trace a Python AI agent running in Vercel functions?
Rius traces a Python agent in Vercel functions when you call rius.init once at module level and call client.flush at the end of every invocation. A Vercel function is a serverless runtime, so the process can be frozen between invocations. A frozen process exports nothing, which is why the flush belongs at the end of each invocation. Turn the heartbeat off, because it is a signal for long-lived processes.
How it works
The SDK queues spans in memory and exports them in batches from a background thread. In a long-running server that thread keeps exporting. In a serverless runtime the process is frozen between invocations. A frozen background thread exports nothing, and the exit hook may never run. An explicit flush at the end of each invocation sends the queued spans before that can happen.
Three things matter in this setup:
- Initialize once per process. Call
init()at module level, not inside the handler. A second globalinit()logs a warning and returns the existing client unchanged, so per-request calls only add noise. - Flush at the invocation boundary.
client.flush()blocks until the export completes. Call it once at the end of the invocation, never inside inner loops or other hot paths. - Check the result.
flush()returnsFalseon timeout or when the most recent export was rejected, for example a 401 on a bad API key.Truemeans your spans left the process. A flush is not a guarantee: a batch that fails or is rejected is dropped, not buffered for later, and a process killed hard runs no cleanup.
The heartbeat pings the platform for the whole life of a process. Between invocations a frozen process stops pinging, and the instance flaps between alive and stale or gone in the Agents view. Set heartbeat=False, or RIUS_HEARTBEAT=false, in serverless runtimes.
Set it up
-
Add the SDK and the extra for the model library you call to your function’s requirements. Details are in Installation and configuration.
pip install "glassflow-rius[anthropic]" anthropic -
Initialize at module level, with the heartbeat off and a constant service name. Keep the name the same across deploys. Put the release in
service_version, which is stamped asservice.version. If you compare versions of the agent in the Agents view, setmain_agent_version, which is stamped asrius.main_agent.version. Read the API key from your function’s environment variables, either asRIUS_API_KEYor by passing it in. -
Wrap the agent with
rius.observeand flush when the invocation ends.run_invocationbelow is your own code: call it from the entrypoint Vercel invokes, such as a route of your ASGI app or thehandlerclass of a plain function.import anthropic import rius client = rius.init( api_key="ri_...", service_name="my-agent", heartbeat=False, ) claude = anthropic.Anthropic() # reads ANTHROPIC_API_KEY @rius.observe def run_agent(query: str) -> str: response = claude.messages.create( model="claude-haiku-4-5-20251001", max_tokens=200, messages=[{"role": "user", "content": query}], ) return response.content[0].text def run_invocation(query: str) -> str: try: return run_agent(query) finally: delivered = client.flush(timeout_millis=30_000) if not delivered: print("the most recent span export failed") -
Optionally lower
OTEL_BSP_SCHEDULE_DELAY, the standard OpenTelemetry variable that sets how often a batch is flushed in milliseconds (default 5000). It helps latency-sensitive shutdowns. If a very busy function drops spans because the in-memory queue fills, raiseOTEL_BSP_MAX_QUEUE_SIZE(default 2048) before you lower the sample rate. -
Invoke the function and open the trace list in the console. Delivery rules, retries and the full flush behavior are in Reliability.
What you get
- A trace for each invocation, with the span for each model call carrying tokens and a cost estimate. Costs are list-price estimates in US dollars, not billed amounts.
- A clear signal when an export fails, because
flush()returnsFalseand you can log it. - One agent name across deploys, with the release in
service.versionfor filtering traces. With the heartbeat off, the agent is marked no heartbeat on the Agents page and has no Instances section, while traces, cost and errors still show. - A quiet Agents view, because the heartbeat is off and does not flap between
staleandgonewhile the process is frozen.
Two platform answers drop spans and need action from you. A 402 means billing is blocking ingest, so traces sent in that period are lost even after billing is fixed. A 429 means the workspace is over its plan’s ingest rate, and the platform refuses the whole request. Lower the volume with sampling or move to a plan with a higher rate.
When something else fits
- If your function is TypeScript rather than Python, the SDK installs no exit hook, so you must flush or shut down yourself. A pnpm app on a file-tracing host such as a Vercel function may also need
instrumentModules, covered under Integrations. - If your agent runs in a long-running server or worker, you do not need a flush at every invocation. The exit hook drains the rest on a clean shutdown.
- If a call is slow to export and you would rather not wait at the end of the invocation, lower the schedule delay, or sample fewer traces so each flush has less to send.