How do I trace an OpenAI Agents SDK agent deployed to Vercel functions?
Rius traces an OpenAI Agents SDK agent on Vercel functions when you call rius.init once at module level, wrap the entrypoint and tools with observe, and flush spans at the end of every invocation. Model calls arrive as generations with tokens and cost. The flush matters because a serverless runtime freezes the process between invocations, so spans still queued can go unsent. Use underscores in the framework’s Agent(name=...).
How it works
- Model calls. The framework drives the OpenAI client underneath, so the openai integration records each model call with model, tokens, cost and time to first token. You add no framework-specific code for that. See the OpenAI Agents SDK page for the full coverage table.
- Run structure. The agent loop and tool executions run inside the framework. An
observewrapper with the AGENT kind on your entrypoint, and one with the TOOL kind on each tool body, puts that structure in the trace. - Export. Spans are queued in memory and sent in batches from a background thread. A serverless runtime suspends the process between invocations, so that thread exports nothing while frozen, and the exit hook that normally drains the queue may never run. An explicit
flush()at the end of each invocation sends what is queued. The Reliability page covers the pipeline in detail. - Heartbeat. The SDK pings Rius for the process’s whole lifetime by default. A suspended process cannot keep pinging, so in serverless runtimes you turn it off.
Set it up
- Install the SDK with the OpenAI extra, and the framework. The integrations overview covers installing in full.
pip install "glassflow-rius[openai]" openai-agents-
Call
rius.initonce, at the top of the module that defines your agent, before the agent and the handler. Code at module level runs once per process, not on every request. Passheartbeat=False, and keep the returned client so you can flush it. Disable the framework’s own tracing withset_tracing_disabled(True)if Rius should be the single telemetry path. -
Wrap the entrypoint with
observeas an AGENT span, and each tool body withobserveas a TOOL span. Keep@function_tooloutermost on tools. -
Flush in a
finallyblock at the end of every invocation, so spans leave even when the run raises.
import rius
from agents import Agent, Runner, function_tool, set_tracing_disabled
from rius import SpanKind
client = rius.init(api_key="ri_...", service_name="my-agent", heartbeat=False)
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)
def handle_request(ticket: str) -> str:
# Call this from your Vercel function's handler.
try:
return handle_ticket(ticket)
finally:
client.flush(timeout_millis=30_000)-
Name things deliberately.
- The agent. Use underscores in
Agent(name=...). Hyphens are rewritten in the generated handoff tool name, and the framework warns on every run. - The root span. The
nameyou giveobserveis how the run appears in the trace, herehandle-ticket. - The service.
service_nameis the name the agent is grouped under by default. SetRIUS_AGENT_NAMEif you want a different grouping name.
- The agent. Use underscores in
-
Group turns of one conversation with
rius.session(), reusing your conversation or ticket id.
Flush once per invocation, at the end. Do not flush inside the agent loop or other hot paths, because flush() blocks until the export completes.
What you get
- A root AGENT span per request, with an LLM generation for each model call showing model, tokens, cost and time to first token, and a TOOL span for each tool call.
- Spans that leave the process before the runtime freezes it, so a short invocation does not lose its trace.
- Tool spans you named yourself, under a root named after your entrypoint.
- Cost as a list-price estimate in US dollars, computed by Rius from the model and token counts.
- Because the heartbeat is off, the agent is marked no heartbeat and lists no Instances. Its traces still appear.
- Handoffs produce no span of their own. They show as consecutive generations from different agents, so add an
observewrapper per agent entry if you need the boundaries. - Streamed runs carry token counts only when usage is enabled on the underlying OpenAI client. See the OpenAI Agents SDK page for the
include_usagenote.
When something else fits
- The OpenAI Agents SDK integration is Python only. If your Vercel functions are written in TypeScript, the Integrations page lists what each language supports, and calls with no integration are traced with manual generations.
- If your agent runs in a long-lived process rather than a function, keep the heartbeat on and flush at graceful shutdown instead, as the Reliability page describes.