Skip to Content

How do I trace an agent built with AutoGen and deployed to AWS Lambda?

Rius traces an AutoGen agent on AWS Lambda when you call rius.init once at module level, wrap the entrypoint in one observe span, and call client.flush() at the end of every invocation. AutoGen’s own spans and the OpenAI generations then arrive as a single trace per invocation. Name the agent with service_name, and turn the heartbeat off, because Lambda freezes the process between invocations.

How it works

AutoGen emits create_agent, invoke_agent and execute_tool spans through the global tracer provider, which rius.init() installs. Model calls go through the OpenAI client, so the openai integration records them as ChatCompletion generations with model, tokens and cost. The AutoGen integration page covers this coverage in full.

Three things are specific to Lambda:

  • Where init goes. Call rius.init() before any AutoGen code runs, because the framework’s spans go to the tracer provider that init installs. Put it at the top of your handler module, so it runs once when the execution environment starts and not on every request.
  • Why you flush. Spans are exported by a background thread. Lambda freezes the process between invocations, and a frozen thread exports nothing, so the exit hook may never run. client.flush() at the end of every invocation forces the export.
  • Why you do not shut down. shutdown() releases the global init() slot. Use flush() per invocation and leave shutdown() out of the handler.

The Reliability page explains the export pipeline and the heartbeat in more detail.

Set it up

  1. Add the packages to your function’s dependencies:

    pip install "glassflow-rius[openai]" "autogen-agentchat" "autogen-ext[openai]"
  2. Set RIUS_API_KEY as an environment variable on the function, so the key stays out of your code. Your key starts with ri_.

  3. Initialize at module level with heartbeat=False. Then wrap the async entrypoint with observe and the AGENT kind, and flush in a finally block so spans leave even when the run raises:

    import asyncio import rius from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from rius import SpanKind client = rius.init(service_name="my-agent", heartbeat=False) 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: agent = AssistantAgent( name="support_agent", model_client=OpenAIChatCompletionClient(model="gpt-5-mini"), tools=[account_status], system_message="Answer support questions; check accounts with the tool.", ) result = await agent.run(task=ticket) return str(result.messages[-1].content) def lambda_handler(event, context): try: return asyncio.run(handle_ticket(event["ticket"])) finally: client.flush(timeout_millis=30_000)
  4. Choose the names. service_name is the agent’s name in Rius. The agent is grouped under it unless you set RIUS_AGENT_NAME to something else. Inside the trace, the observe name (handle-ticket) labels the root span, and the AutoGen agent’s name (support_agent) labels the create_agent and invoke_agent spans.

  5. Leave observe off your tool functions. AutoGen runs tools in a detached context, so a wrapper would split off as its own single-span trace. The framework’s execute_tool spans already cover them.

  6. To group a conversation across invocations, scope each run with rius.session() and reuse your conversation id.

  7. Invoke the function with a request that calls a tool, then open the trace in the console. You should see the root span, invoke_agent, execute_tool and the ChatCompletion generation.

What you get

  • One trace per invocation, with the root AGENT span, the agent construction and run spans, each tool call and each model call with tokens and cost.
  • Spans that leave the function before Lambda freezes it, instead of waiting for a thread that never runs.
  • A stable agent name in Rius, set by service_name, with your own span names inside the trace.
  • Sessions that follow your conversation id when you scope runs with rius.session().

With the heartbeat off, the agent is marked no heartbeat and lists no instances. Its traces still arrive. flush() returns False on a timeout or when the last export was rejected, for example because of a bad API key, so you can log that result to see whether your spans left.

When something else fits

If your AutoGen agent runs in a long-lived server or container instead of Lambda, Python needs no explicit flush, because the exit hook drains the queue on a clean shutdown, and you can leave the heartbeat on. AutoGen has no TypeScript integration, so for TypeScript agents see the Integrations page for the other options.

Still stuck? Get help

Last updated on