Skip to Content

How do I trace an agent built with the OpenAI Agents SDK and deployed to a FastAPI service?

Rius traces an OpenAI Agents SDK agent in a FastAPI service when you call rius.init once at process start, wrap the run and its tools with observe, and name the agent with underscores. In Python a long-running server needs no manual flush, because the exit hook drains pending spans on a clean shutdown. Rius records every model call the framework makes with tokens and cost.

How it works

The OpenAI Agents SDK drives the OpenAI client underneath, so the openai integration captures each model call without framework-specific code. With a global init(), every installed integration turns on automatically, so one call at startup covers every request your service handles.

The agent loop, tool executions and handoffs run inside the framework, so Rius sees only the client calls that come out of it. Two small additions give the run its shape:

  • An observe wrapper with the AGENT kind around the function that calls the runner becomes the root span of each request.
  • An observe wrapper with the TOOL kind on each tool body becomes a tool span under that root.

The framework also ships its own tracing, which uploads to the OpenAI platform. Call set_tracing_disabled(True) if Rius should be your only telemetry path. Leaving it on reports to two backends and does not interfere with Rius.

Spans are queued in memory and exported in batches from a background thread, so tracing never sits on your request path. For the export pipeline and its tuning, see Reliability.

Set it up

  1. Install the SDK with the OpenAI extra next to the framework. The extra installs the instrumentation, not the library itself.
pip install "glassflow-rius[openai]" openai-agents
  1. Call rius.init once, at module level, before you build the agent and before the first request arrives. Do not call it inside a request handler, so it runs once per process. Right after it, disable the framework’s own tracing.

  2. Name things deliberately. service_name is the name of your service. The Rius agent name defaults to the service name, and you can set a different one with RIUS_AGENT_NAME. Give the framework’s Agent a name with underscores, such as support_agent. Hyphens in agent names are rewritten in the generated handoff tool name, and the framework warns on every run.

  3. Put @function_tool outermost on each tool and observe on the function body, so the framework still reads the signature for its tool schema. Wrap the entrypoint that calls the runner with observe and give it a readable name.

from contextlib import asynccontextmanager import rius from fastapi import FastAPI from agents import Agent, Runner, function_tool, set_tracing_disabled from rius import SpanKind client = rius.init(api_key="ri_...", service_name="support-service") set_tracing_disabled(True) @function_tool @rius.observe(kind=SpanKind.TOOL) def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) support_agent = Agent( name="support_agent", instructions="Answer support questions; check accounts with the tool.", tools=[account_status], model="gpt-5-mini", ) @rius.observe(name="handle-ticket", kind=SpanKind.AGENT) def handle_ticket(ticket: str) -> str: result = Runner.run_sync(support_agent, ticket) return str(result.final_output) @asynccontextmanager async def lifespan(app: FastAPI): yield client.shutdown() # drain spans, send the final stopped heartbeat app = FastAPI(lifespan=lifespan) @app.post("/tickets") def create_ticket(ticket: str) -> dict: return {"answer": handle_ticket(ticket)}
  1. Decide how the service ends. A long-running Python server needs nothing extra: batches export continuously and the exit hook drains the rest on a clean shutdown. For a clean stop signal on the Agents page, the lifespan above calls client.shutdown() when the service shuts down gracefully. It drains the queue, stops the export thread and sends the final stopped ping. Rius installs no signal handlers, so an unhandled SIGTERM or a SIGKILL sends no stopped ping and the instance reports gone. Never call client.flush() inside a request handler, because it blocks until the export completes. Flush only at process boundaries, for example if workers are killed hard.

  2. Scope each conversation with rius.session(), reusing your conversation or ticket id, so every turn groups into one session.

If you run the service under gunicorn, each worker process re-creates the export thread and shows as its own instance. For the full agent recipe, see OpenAI Agents SDK.

What you get

  • One trace per request, with the AGENT span from your wrapper at the root.
  • A generation span for each model call, with model, tokens, cost and time to first token. Cost is a list-price estimate in US dollars.
  • A named tool span for each function tool, under the run.
  • A readable agent name on the Agents page, with a running instance for each worker process.
  • Sessions that group the turns of one conversation.

Handoffs produce no span of their own. They appear as consecutive generations that belong to different agents, and every Responses API call is named Response. If handoff boundaries matter, add an observe wrapper per agent entry. Streamed runs carry token counts only when usage is enabled on the underlying OpenAI client.

When something else fits

If your FastAPI service calls the OpenAI client directly and does not use the Agents SDK, the OpenAI integration page is the shorter path. For other frameworks, the Integrations page lists each one and its setup. For a short-lived job or a serverless function instead of a long-running server, flush at the end of every invocation as described in Reliability.

Still stuck? Get help

Last updated on