Skip to Content

How do I trace a Python AI agent running in a FastAPI service?

Rius traces a Python agent in a FastAPI service when you call rius.init() once per process, as early as possible, and let the SDK’s background exporter send spans while the server runs. In a long-running FastAPI server you add no flush code, because batches export continuously and the exit hook drains the rest on a clean shutdown. Set service_name and your API key, and every installed integration traces your model calls automatically.

How it works

  • One init per process. init() registers the global OpenTelemetry provider, which can be set only once. A second global init() logs a warning and returns the existing client unchanged. Put the call at the top of your app module or in your startup code, not inside a request handler.
  • Export runs off the request path. Spans queue in memory and leave in batches from a background thread, so creating a span never waits on the network. By default a batch is flushed every 5 seconds, and the queue holds 2048 spans.
  • Auto-instrumentation is on by default. With a global init(), every integration whose library is installed turns on, so your OpenAI or Anthropic calls arrive as spans with tokens and cost. See Integrations for the full list.
  • Heartbeat shows the process. The SDK pings Rius from init() until the process exits, so the Agents view lists your service. It needs an API key. A clean shutdown() sends a final stopped ping, which is how Rius tells a clean exit from a crash.
  • Workers each count. With gunicorn or other fork-based workers, the exporter re-creates its background thread in each forked child. Each worker shows as its own instance of the same agent.

Set it up

  1. Install the SDK with the extra for the library your agent calls. Details are in Installation and configuration.

    pip install "glassflow-rius[openai]"
  2. Put your key and a stable name in the environment, so the same code runs everywhere. Keep the name constant across replicas and deploys, and put the release in the version setting instead.

    export RIUS_API_KEY=ri_... export RIUS_SERVICE_NAME=my-agent export RIUS_SERVICE_VERSION=2026-10-26
  3. Call init() once when the app starts, and call shutdown() when it stops. The app and lifespan names below are your own FastAPI code.

    from contextlib import asynccontextmanager import rius from fastapi import FastAPI client = rius.init() @asynccontextmanager async def lifespan(app: FastAPI): yield client.shutdown() # drain, stop the thread, release init() app = FastAPI(lifespan=lifespan)
  4. Leave flushing out of your route handlers. client.flush() blocks until the export completes, which is the stall the background exporter exists to avoid. Flush only at process or invocation boundaries.

  5. If you run the app on a serverless runtime such as Lambda, the process freezes between invocations and a frozen exporter sends nothing. Call client.flush() at the end of every invocation, and turn the heartbeat off with heartbeat=False or RIUS_HEARTBEAT=false, because pings stop while the process is suspended.

  6. Check the settings that matter on a server:

    • Do not set session_id or RIUS_SESSION_ID. It stamps one session on every span, so it fits one session per process, not a server handling many users. Use a per-request session scope such as rius.session instead.
    • Raise OTEL_BSP_MAX_QUEUE_SIZE before lowering sample_rate if a busy service logs dropped spans. Lower OTEL_BSP_SCHEDULE_DELAY if you need faster delivery on shutdown.
    • Keep heartbeat_interval at 15 seconds or below. Rius marks an instance stale after 30 seconds without a ping.

    For more on delivery rules and limits, read Reliability.

What you get

  • Each request your agent handles appears as a trace, with a span for every model call, tool call and step, plus tokens and an estimated cost in US dollars.
  • Your service shows in the Agents view with one instance per process or worker, so you can see replicas starting, running and stopping.
  • A clean redeploy reports stopped, and a killed process reports gone, so a crash is distinguishable from a normal exit.
  • Traces from every replica share one service.name, so you filter and break down cost for the service as a whole. The release in service.version lets you tell deploys apart.
  • Failures in export never raise into your request code. If the backend is unreachable, spans are dropped and errors are logged while your API keeps answering.

When something else fits

  • If your FastAPI app is deployed as a serverless function, treat each invocation like a short-lived job: flush at the end of every one and disable the heartbeat.
  • If another OpenTelemetry SDK already owns the global tracer provider in your service, set RIUS_BRIDGE_FOREIGN_PROVIDER so its spans also reach Rius.
  • If you need an isolated pipeline beside an existing one, use a scoped client with set_global=False. It does not auto-instrument unless you pass instruments explicitly.

Still stuck? Get help

Last updated on