How do I trace an agent built with LangChain and deployed to AWS Lambda?
Rius traces a LangChain agent on AWS Lambda when you call init once before you build your chains, name the agent with service_name, and call flush at the end of every invocation. Lambda freezes the process between invocations, so a flush at the end of each one is what sends the spans. Chains, tools, retrievers and model calls then arrive as one nested trace per run, with tokens and cost on each model call. Heartbeats are a long-lived-process signal, so you also turn them off in Lambda.
How it works
- Tracing hooks LangChain’s callbacks. Anything that runs through them is traced: LCEL chains, agents and LangGraph nodes. Each step becomes a span typed by what it is (chain, tool, retriever or model call), nested like your graph. See LangChain for what lands in the trace.
- Spans export from a background thread. The SDK queues spans in memory and sends them in batches. Lambda suspends the process between invocations, so a frozen background thread exports nothing and the exit hook may never run. That is why Reliability says to call
client.flush()at the end of every invocation in Lambda. - Do not use
shutdown()as the per-invocation flush.shutdown()also releases the globalinit()slot. Flush at the end of each invocation and keep the client you created. - Heartbeats do not suit Lambda. The SDK pings the platform for the process’s whole lifetime. A frozen process stops pinging, so the instance flaps between alive and
staleorgoneon the Agents page. Setheartbeat=Falsein Python,heartbeat: falsein TypeScript, orRIUS_HEARTBEAT=false. - The agent name comes from the service name.
service_name(orRIUS_SERVICE_NAME) is theservice.nameof the agent.RIUS_AGENT_NAMEsets the name the agent is grouped under, and it defaults to the service name.
Set it up
-
Install the instrumentation and LangChain into the package you deploy. The extra installs the instrumentation, not
langchainitself.pip install "glassflow-rius[langchain]" langchain -
Call
initat the top of your handler module, before you build chains, and keep the client it returns. Pick a stable name, because it is what the agent is called in Rius. Turn the heartbeat off. -
Flush in a
finallyblock, so spans are sent even when the run raises. Flush at the invocation boundary only, never inside your chain code.import rius client = rius.init( api_key="ri_...", service_name="support-agent", heartbeat=False, ) def handler(event, context): try: # build and run your chains exactly as before ... finally: client.flush(timeout_millis=30_000) -
On Node, install the LangChain instrumentation. It also needs
@langchain/core, which a LangChain.js app already has.npm install @arizeai/openinference-instrumentation-langchain -
In TypeScript, the SDK installs no process-exit hook, so the flush is not optional.
flush()resolvesfalsewhen the most recent export failed, so you can log it. Integrations attach asynchronously, so awaitclient.readybefore the first run of a cold start. LangChain.js runs callbacks in the background by default, so awaitawaitAllCallbacks()before you flush, or every span may not have ended yet.import { init } from '@glassflow-ai/rius' import { awaitAllCallbacks } from '@langchain/core/callbacks/promises' const client = init({ apiKey: 'ri_...', serviceName: 'support-agent', heartbeat: false, }) export const handler = async (event: unknown) => { try { await client.ready // build and run your chains exactly as before } finally { await awaitAllCallbacks() const delivered = await client.flush() if (!delivered) console.warn('the most recent span export failed') } } -
Invoke the function once, then open the trace in the console. You should see the chain as nested spans, with the model call carrying model, tokens and cost. The Integrations page lists every framework integration, and Reliability covers the exporter variables.
What you get
- One trace per invocation that mirrors your LangChain graph, with a span for each chain, tool, retriever and model call.
- Tokens and cost on each model call. Rius computes cost from the model and token counts, so you never send it. Costs are list-price estimates in US dollars.
- A stable agent name in Rius, taken from
service_name, so every invocation groups under one agent. - Spans that leave the function before Lambda freezes it, because you flush at the end of every invocation.
- No false alarms on the Agents page from a heartbeat that stops whenever the runtime suspends the process. The agent is marked no heartbeat and has no Instances section, which is expected in Lambda.
When something else fits
- If your Lambda function sends spans from another OpenTelemetry setup instead of the Rius SDK, use the OTLP ingest route instead of
init. The same rule applies there: export before the invocation ends. - If you run LangChain in a long-lived server, you do not need the per-invocation flush. In Python the exit hook drains the rest on clean shutdown, and heartbeats work as designed.