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 globalinit()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 cleanshutdown()sends a finalstoppedping, 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
-
Install the SDK with the extra for the library your agent calls. Details are in Installation and configuration.
pip install "glassflow-rius[openai]" -
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 -
Call
init()once when the app starts, and callshutdown()when it stops. Theappandlifespannames 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) -
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. -
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 withheartbeat=FalseorRIUS_HEARTBEAT=false, because pings stop while the process is suspended. -
Check the settings that matter on a server:
- Do not set
session_idorRIUS_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 asrius.sessioninstead. - Raise
OTEL_BSP_MAX_QUEUE_SIZEbefore loweringsample_rateif a busy service logs dropped spans. LowerOTEL_BSP_SCHEDULE_DELAYif you need faster delivery on shutdown. - Keep
heartbeat_intervalat 15 seconds or below. Rius marks an instance stale after 30 seconds without a ping.
For more on delivery rules and limits, read Reliability.
- Do not set
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 reportsgone, 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 inservice.versionlets 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_PROVIDERso 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 passinstrumentsexplicitly.