OpenAI Agents SDK
Trace agents built with the OpenAI Agents SDK
with Rius: every model call the framework makes arrives as a generation with
tokens and cost, and a thin layer of observe wrappers gives the run and its
tools their shape. This guide covers the Python SDK; the runnable example
behind it lives in
agent-observability-examples .
How coverage works
The framework drives the OpenAI client underneath, so the
openai integration captures its model
calls without any framework-specific code. Two things the framework keeps to
itself need one line each:
- The run structure. The agent loop, tool executions, and handoffs run
inside the framework; auto-instrumentation only sees the client calls that
come out of it. Wrapping the entrypoint and the tool bodies with
observeputs that structure in the trace. - The built-in tracing. The framework ships its own tracing, which
uploads to the OpenAI platform and is independent of OpenTelemetry.
Disable it with
set_tracing_disabled(True)if Rius should be the single telemetry path; leaving it on double-reports to two backends but does not interfere with Rius.
Setup
pip install "glassflow-rius[openai]" openai-agentsimport rius
from agents import Agent, Runner, function_tool, set_tracing_disabled
from rius import SpanKind
rius.init(api_key="gf_...", service_name="my-agent")
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)The decorator order on tools matters: @function_tool outermost, observe
on the function body. The framework reads the function signature for its
tool schema, and the TOOL span parents under the run through ambient
context.
What lands where
| In the framework | In the trace |
|---|---|
Your entrypoint around Runner.run() | The root AGENT span (your observe wrapper) |
| A model call | An LLM generation with model, tokens, cost, TTFT |
| A function tool | A TOOL span (your observe wrapper on the body) |
| A handoff | No span of its own; see blind spots |
A run that crosses a handoff looks like this (real trace from the example,
openai-agents 0.22.0):
handle-ticket AGENT 9.8s
├─ Response LLM 2.3s triage decides to hand off
├─ Response LLM 2.8s billing agent decides to call the tool
├─ account_status TOOL 0ms
└─ Response LLM 4.6s billing agent answersBlind spots
Verified against openai-agents 0.22.0:
- Handoffs produce no span. A handoff is visible only as consecutive
generations belonging to different agents. If handoff boundaries matter
to you, add an
observewrapper per agent entry or use the framework’sRunHooksto open spans at agent boundaries. - Generation span names are generic. Responses API calls are all named
Response; which agent spoke is not visible from the span name. - Hyphens in agent names get rewritten in the generated handoff tool name and the framework warns on every run; use underscores.
Sessions
One conversation is one session: scope each run with
rius.session(), reusing your conversation or
ticket id, so every turn on the same conversation groups together.
Verify
Run one request that calls a tool, then open the trace in the console: the
root AGENT span, the LLM generations with model, tokens, and cost, and
your named TOOL spans should all be there.
Streamed runs need usage enabled on the underlying client to carry token
counts; see the OpenAI integration for
the include_usage note that applies here too.