Skip to Content

How do I trace a multi-agent workflow built with AutoGen?

Rius traces an AutoGen workflow as one trace per run, with each named agent as its own span, once you call rius.init() and wrap your entrypoint in one observe call. AutoGen emits its own agent and tool spans, and the openai integration adds each model call with tokens and cost. You read the order of agents in the trace waterfall, from the span names you gave your agents.

How it works

AutoGen (AgentChat, Python) emits OpenTelemetry spans for agents and tools through the global tracer provider, which rius.init() installs. Rius adds the model calls through the OpenAI integration, since AutoGen calls models through the OpenAI client. The AutoGen integration page has the full recipe.

The pieces of a run map to spans like this:

  • Your entrypoint is the root span, kind AGENT, from your observe wrapper. It makes the whole run one trace.
  • Each agent you construct gets a create_agent span followed by its name.
  • Each agent run gets an invoke_agent span followed by its name.
  • Each model call is a generation named ChatCompletion, with model, tokens and cost.
  • Each tool call is an execute_tool span followed by the tool name.

Spans nest by parent context, so the waterfall shows each agent’s model and tool calls under that agent’s invoke_agent span. That nesting is how you tell which agent made a call, because the generation names are all the generic ChatCompletion. For the vocabulary of traces, spans and kinds, see Concepts.

Set it up

  1. Install the SDK with the openai extra, plus AutoGen:
pip install "glassflow-rius[openai]" "autogen-agentchat" "autogen-ext[openai]"
  1. Give every agent a distinct name. The name you pass to AssistantAgent is the name that appears in its create_agent and invoke_agent spans, so choose names you want to read in the trace. In the example, researcher and writer are your own names.

  2. Wrap the entrypoint that runs all your agents in one observe call with the AGENT kind, so every agent lands in the same trace:

import rius from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from rius import SpanKind rius.init(api_key="ri_...", service_name="my-agent") def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) @rius.observe(name="handle-ticket", kind=SpanKind.AGENT) async def handle_ticket(ticket: str) -> str: researcher = AssistantAgent( name="researcher", model_client=OpenAIChatCompletionClient(model="gpt-5-mini"), tools=[account_status], system_message="Check the customer's account with the tool.", ) writer = AssistantAgent( name="writer", model_client=OpenAIChatCompletionClient(model="gpt-5-mini"), system_message="Write the reply to the customer.", ) findings = await researcher.run(task=ticket) result = await writer.run(task=str(findings.messages[-1].content)) return str(result.messages[-1].content)
  1. Do not wrap your AutoGen tool functions with observe. AutoGen runs tools in a detached context, so the wrapper’s span cannot find the run and splits off as its own single-span trace. The framework’s execute_tool spans already cover tools.

  2. Scope each run with rius.session(), reusing your conversation id, so the turns of one conversation group into one session.

  3. Run one request that calls a tool, then open the trace in the console. You should see the root span, an invoke_agent span for each agent, the ChatCompletion generations and the execute_tool span.

What you get

  • One trace per run, so a whole multi-agent job reads as one tree with a single duration and status at the root.
  • A span for each agent by its name, so you see which agent ran, in what order, and how long each took.
  • The model calls under each agent with model, tokens and cost. Cost is a list-price estimate in US dollars that Rius computes from the model and token counts, so you never send it.
  • Tool calls as execute_tool spans under the agent that made them.
  • Conversation grouping through sessions, when you set a session id.

A few quirks to know when you read a trace. Framework spans arrive with an UNSET status rather than OK on success, and the trace status still reads Ok. Generation names stay generic, so identify the agent from the parent span.

When something else fits

If your agents call a provider other than the OpenAI client, read the integrations overview to see which instrumentation captures those model calls. For a framework with no integration, trace the calls with manual generations and observe wrappers instead.

Still stuck? Get help

Last updated on