Skip to Content

How do I trace a research agent built with the OpenAI Agents SDK?

Rius traces a research agent built with the OpenAI Agents SDK by capturing model calls automatically and adding observe wrappers for the run, each stage and each tool. Every model call arrives as a generation with model, tokens and cost. A retrieval span records the index you searched and the documents it returned. Setting a session and a user groups the traces by conversation and by person.

How it works

The framework drives the OpenAI client underneath, so the openai integration captures its model calls with no framework-specific code. Each call becomes an LLM generation. The agent loop, tool runs and handoffs happen inside the framework, so you add that structure yourself. The OpenAI Agents SDK integration is Python only. See OpenAI Agents SDK for its full coverage, and Integrations for the other frameworks.

A research agent usually needs four things on top of the model calls:

  • A run root. Wrap the entrypoint with observe and SpanKind.AGENT. Every other span nests under it.
  • A span per stage. Wrap each stage, such as planning and investigating, with a bare observe. Each becomes a CHAIN span named after the function.
  • A retrieval span. Open a RETRIEVER span around each search with data_source_id and top_k. It is named retrieval plus the data source id, and set_retrieved_documents records document ids and scores, not text.
  • A TOOL span per tool. Use observe with SpanKind.TOOL on the tool body. The Tool kind in the Spans tab and the Tool Loop alert only see spans of that kind, and a bare observe makes a CHAIN span they miss.

The framework’s own tracing uploads to the OpenAI platform and is independent of OpenTelemetry. Call set_tracing_disabled(True) if Rius should be the single telemetry path.

Set it up

  1. Install the SDK with the openai extra and the framework. OpenAI Agents SDK covers setup in full.

    pip install "glassflow-rius[openai]" openai-agents
  2. Initialize Rius and turn off the framework’s own tracing. Then write the search tool with a retrieval span inside it. Put @function_tool outermost and observe on the function body, because the framework reads the function signature for its 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="research-agent") set_tracing_disabled(True) @function_tool @rius.observe(kind=SpanKind.TOOL) def search_papers(query: str) -> str: """Search the paper index.""" with rius.start_as_current_span( kind=SpanKind.RETRIEVER, data_source_id="papers", top_k=5 ) as obs: obs.set_input(query) docs = search(query) obs.set_retrieved_documents([{"id": d.id, "score": d.score} for d in docs]) return format_docs(docs)
  3. Define the agents and wrap each stage and the run. Use underscores in agent names, because hyphens are rewritten in the generated handoff tool name and the framework warns on every run.

    planner = Agent(name="planner", instructions="Break the question into sub-questions.", model="gpt-5-mini") researcher = Agent( name="researcher", instructions="Answer with the search tool and cite sources.", tools=[search_papers], model="gpt-5-mini", ) @rius.observe def plan(question: str) -> str: return str(Runner.run_sync(planner, question).final_output) @rius.observe def investigate(plan_text: str) -> str: return str(Runner.run_sync(researcher, plan_text).final_output) @rius.observe(name="research-run", kind=SpanKind.AGENT) def run_research(question: str) -> str: return investigate(plan(question))
  4. Scope the session and the user around each run. Use your conversation id as the session, so every turn of one conversation groups together. Use an opaque user id rather than an email.

    with rius.session(conversation_id): with rius.user(request.user_id): report = run_research(question)

    Both scopes stamp session.id and user.id on every span started inside them, including the spans the integration creates. See Tracing your code for the span options, sessions and users.

  5. Run one question that searches, then open the trace. You should see the research-run root, a CHAIN span per stage, LLM generations, the TOOL span and the RETRIEVER span under it.

What you get

  • One trace per question, shaped like the research itself: the run root, then plan and investigate, with each model call, tool call and search nested where it happened.
  • Model, tokens and cost on every model call, with time to first token where the integration records it. Cost is a list-price estimate that Rius computes, so you never send it.
  • Each search shows its index, the number of documents requested and the ids and scores of those returned.
  • Traces grouped into sessions by conversation and attributed to users, so you can follow one person across several research questions.
  • Tool Loop and Tool kind filtering that count your search tool, because it carries the TOOL kind.

Two blind spots to plan around. A handoff produces no span of its own and shows only as consecutive generations from different agents. Model calls are all named Response, so the span name does not say which agent spoke. Add an observe wrapper at each agent entry if you need those boundaries.

When something else fits

  • If your research agent is not built on the OpenAI Agents SDK, check Integrations for the framework you use.
  • For TypeScript, this framework has no integration. Trace its calls with manual generations and observe from Tracing your code.

Still stuck? Get help

Last updated on