How do I trace a customer support agent built with the OpenAI Agents SDK?
Rius traces an OpenAI Agents SDK support agent by capturing every model call through its openai integration and adding observe spans for the run, tools and retrieval. You install one extra, wrap the entrypoint and the tool bodies, and scope each ticket as a session and each customer as a user. Every model call arrives with tokens and cost, and the run structure arrives from your wrappers.
How it works
The OpenAI Agents SDK drives the OpenAI client underneath, so the openai integration records each model call as a generation with model, tokens, cost and time to first token. You write no framework-specific code for that. This integration is Python only. See Integrations for the full matrix and the OpenAI Agents SDK page for its details.
The agent loop, tool runs and handoffs happen inside the framework, so a thin layer of observe wrappers gives them shape. A support agent usually needs these spans:
- A run root. Wrap the function that handles one ticket with
observeandkind=SpanKind.AGENT. Every model call and tool call nests under it. - A span per tool. Put
observewithkind=SpanKind.TOOLon the body of each function tool, such as an account lookup. Rius names itexecute_toolplus the tool name. - A retrieval span. Open a
RETRIEVERspan around the knowledge base search. Passdata_source_idandtop_k, and record the documents withset_retrieved_documents. It stores identifiers and scores, not document text. - A step per stage. For stages such as classifying the ticket or drafting a reply, wrap each stage function with a bare
observe. It makes a span named after the function.
The framework also uploads its own tracing to the OpenAI platform. Call set_tracing_disabled(True) if Rius should be the only telemetry path. Leaving it on does not interfere with Rius.
Set it up
- Install the integration and the framework:
pip install "glassflow-rius[openai]" openai-agents- Initialize Rius, turn off the framework’s own tracing, and wrap the tool body. Put
@function_tooloutermost andobserveon the function body, because the framework reads the function signature for the tool schema.
import rius
from agents import Agent, Runner, function_tool, set_tracing_disabled
from rius import SpanKind
rius.init(api_key="ri_...", 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",
)- Add the run root and the retrieval span.
search,lookup,ticket_idandcustomer_idstand for your own code and values.
@rius.observe(name="handle-ticket", kind=SpanKind.AGENT)
def handle_ticket(ticket: str) -> str:
with rius.start_as_current_span(
kind=SpanKind.RETRIEVER, data_source_id="support-kb", top_k=5
) as obs:
obs.set_input(ticket)
docs = search(ticket)
obs.set_retrieved_documents([{"id": d.id, "score": d.score} for d in docs])
result = Runner.run_sync(support_agent, ticket)
return str(result.final_output)- Scope each ticket as a session and each customer as a user. Reuse your ticket or conversation id for the session, so every turn on one conversation groups together. Use an opaque customer id rather than an email.
with rius.session(ticket_id):
with rius.user(customer_id):
handle_ticket(ticket)- Run one ticket that calls a tool, then open the trace in the console. You should see the root
AGENTspan, theLLMgenerations, the retrieval span and your namedTOOLspan.
Name agents with underscores, as in support_agent. A hyphen is rewritten in the generated handoff tool name and the framework warns on every run. A streamed run carries token counts only when usage is enabled on the underlying OpenAI client.
What you get
- Every model call as a generation with model, tokens, cost and time to first token. Cost is a list-price estimate in US dollars.
- One trace per ticket, with the model calls, tool calls, retrieval and stage steps nested under the
handle-ticketroot. - Named tool spans, and a retrieval span that records which documents scored what.
- Tickets grouped by
session.id, so a multi-turn conversation reads as one session. - Each customer’s traces, failures and cost on the Users page, because
user.idis stamped on every span in scope, including the spans the integration creates.
The tool spans are kind TOOL, so the Tool kind filter in the Spans tab and the Tool Loop alert see them. A tool traced with a bare observe would be a CHAIN span and would be missed.
When something else fits
- A handoff produces no span of its own. It shows only as consecutive generations from different agents. If handoff boundaries matter, add an
observewrapper per agent entry. - Generation spans are all named
Response, so the span name does not say which agent spoke. - If your support agent is in TypeScript, this integration does not apply. Trace its model calls with manual generations, described in Tracing your code.