Skip to Content
Rius

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 global init() 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() returns False on timeout or when the most recent export was rejected, for example a 401 on a bad API key. True means 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

  1. 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
  2. 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 as service.version. If you compare versions of the agent in the Agents view, set main_agent_version, which is stamped as rius.main_agent.version. Read the API key from your function’s environment variables, either as RIUS_API_KEY or by passing it in.

  3. Wrap the agent with rius.observe and flush when the invocation ends. run_invocation below is your own code: call it from the entrypoint Vercel invokes, such as a route of your ASGI app or the handler class 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")
  4. 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, raise OTEL_BSP_MAX_QUEUE_SIZE (default 2048) before you lower the sample rate.

  5. 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() returns False and you can log it.
  • One agent name across deploys, with the release in service.version for 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 stale and gone while 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.

Still stuck? Get help

Last updated on