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 thatinitinstalls. 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 globalinit()slot. Useflush()per invocation and leaveshutdown()out of the handler.
The Reliability page explains the export pipeline and the heartbeat in more detail.
Set it up
-
Add the packages to your function’s dependencies:
pip install "glassflow-rius[openai]" "autogen-agentchat" "autogen-ext[openai]" -
Set
RIUS_API_KEYas an environment variable on the function, so the key stays out of your code. Your key starts withri_. -
Initialize at module level with
heartbeat=False. Then wrap the async entrypoint withobserveand theAGENTkind, and flush in afinallyblock 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) -
Choose the names.
service_nameis the agent’s name in Rius. The agent is grouped under it unless you setRIUS_AGENT_NAMEto something else. Inside the trace, theobservename (handle-ticket) labels the root span, and the AutoGen agent’sname(support_agent) labels thecreate_agentandinvoke_agentspans. -
Leave
observeoff your tool functions. AutoGen runs tools in a detached context, so a wrapper would split off as its own single-span trace. The framework’sexecute_toolspans already cover them. -
To group a conversation across invocations, scope each run with
rius.session()and reuse your conversation id. -
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_tooland theChatCompletiongeneration.
What you get
- One trace per invocation, with the root
AGENTspan, 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.