How do I trace a document processing pipeline built with the OpenAI Agents SDK?
Rius traces a document processing pipeline built with the OpenAI Agents SDK in Python: the openai extra captures model calls and observe wrappers add your stages. Every model call arrives as an LLM span with model, tokens and cost. Your wrappers add the run root, a span for each stage and a retrieval span. Set a session per document and a user per requester so related traces group together.
How it works
The framework drives the OpenAI client underneath, so the openai integration captures each model call with no framework-specific code. The agent loop, tool executions and handoffs run inside the framework, so the structure of your pipeline comes from observe wrappers. The OpenAI Agents SDK integration page covers the framework in full, and the tracing guide covers the wrappers.
For a document pipeline, the trace needs these layers on top of the model calls:
- A run root. Wrap the entrypoint that handles one document with
observeandSpanKind.AGENT. It becomes the root span, under thenameyou pass. - A span per stage. Wrap each stage, such as extraction, chunking or summarizing, with a bare
observe. Each becomes a span named after its function. - A retrieval span. Open a span with
SpanKind.RETRIEVERaround each search. Passdata_source_idfor the index andtop_kfor how many documents you asked for. It records document ids and scores, not document text. - Tool spans. Wrap each tool body with
observeandSpanKind.TOOL, so the span is namedexecute_toolplus the tool name.
The framework’s own tracing uploads to the OpenAI platform and is independent of OpenTelemetry. Turn it off with set_tracing_disabled(True) if Rius should be your only telemetry path.
Set it up
-
Install the SDK with the openai extra and the framework. Integrations lists the install line for every framework.
pip install "glassflow-rius[openai]" openai-agents -
Initialize Rius once at startup, then wrap your pipeline. Put
@function_tooloutermost on a tool andobserveon 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="doc-pipeline") set_tracing_disabled(True) @rius.observe def extract_text(path: str) -> str: ... @function_tool @rius.observe(kind=SpanKind.TOOL) def lookup_clause(query: str) -> str: """Find the clause that matches the query.""" with rius.start_as_current_span( kind=SpanKind.RETRIEVER, data_source_id="contract-kb", 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 render(docs) summary_agent = Agent( name="summary_agent", instructions="Summarize the document; check clauses with the tool.", tools=[lookup_clause], model="gpt-5-mini", ) @rius.observe(name="process-document", kind=SpanKind.AGENT) def process_document(path: str) -> str: text = extract_text(path) result = Runner.run_sync(summary_agent, text) return str(result.final_output) -
Set the session and the user around each document. Reuse your document or job id as the session id, so every trace for that document groups together. Pass an opaque id for the user rather than an email.
with rius.session(document_id), rius.user(requester_id): process_document(path) -
Use underscores in agent names. Hyphens are rewritten in the generated handoff tool name, and the framework warns on every run.
-
Run one document that calls a tool, then open the trace in the console. You should see the root
AGENTspan, your stage spans, the retrieval span, theTOOLspan and theLLMgenerations.
What you get
- One trace per document, with the run root on top and your stages, retrieval, tool calls and model calls nested under it.
- A generation for each model call with model, tokens and cost. Cost is a list-price estimate in US dollars, computed by Rius, so you never send it.
- Retrieval spans that show which index was searched, how many documents were asked for and which ids came back with what score.
- Sessions that group every trace for one document, and a Users view that ties work to each requester.
- The tool spans carry the kind that Rius uses to recognize tool calls, because you marked them with
SpanKind.TOOL.
When something else fits
- The OpenAI Agents SDK integration is Python only. In TypeScript, trace the model calls with manual generations.
- A handoff produces no span of its own, and responses are all named
Response. If you need to see which agent spoke, add anobservewrapper at each agent entry. - Streamed runs carry token counts only when usage is enabled on the underlying client.