How do I trace a customer support agent built with LangChain?
Rius traces a LangChain support agent by hooking LangChain’s callbacks, and you add a run root, a retrieval span, and the session and user. After init(), your chains, tools, retrievers and model calls arrive as a nested trace with tokens and cost. A thin layer of observe wrappers gives each support turn one root and one span per stage. A session id for the conversation and a user id for the customer tie the traces together.
How it works
The LangChain integration hooks LangChain’s callback system, so anything that runs through it is traced: LCEL chains, agents and LangGraph nodes. Each step becomes one span, typed by what it is:
- Chains are CHAIN spans.
- Tool calls are TOOL spans.
- Retrievers are RETRIEVER spans.
- Model calls are LLM spans with model, token usage and messages. Rius computes cost from them on the server.
The nesting follows your chain structure, so the trace reads like your execution graph.
A support agent usually needs more than that, and you add it with the tracing helpers:
- A run root. Wrap the function that handles one customer message with
observeandSpanKind.AGENT. Every LangChain span from that turn nests under it, so one turn is one trace with one top span. - A step per stage. Wrap each stage that LangChain does not run for you, such as classifying the ticket, checking an order or drafting the reply, with
observe. Each becomes a CHAIN span by default. - Retrieval. A LangChain retriever already arrives as a RETRIEVER span. If you search your help center outside LangChain, open a retriever span yourself with
data_source_idandtop_k, and record the returned document ids and scores withset_retrieved_documents(). These are metadata, so they stay when content capture is off. - Session and user.
rius.sessionstampssession.idon every span in scope, andrius.userstampsuser.id. Both also reach the spans the LangChain integration creates.
Set it up
- Install the integration and call
init()once at startup. The extra installs the instrumentation, not LangChain, so the command nameslangchainfor a fresh environment. For the full SDK setup, see Integrations.
pip install "glassflow-rius[langchain]" langchain- Wrap the turn handler as the run root, wrap each stage you own, and open a retriever span for any search that runs outside LangChain. The names below (
handle_turn,classify_ticket,search_help_center,help-center) are yours to replace.
import rius
from rius import SpanKind
rius.init(api_key="ri_...", service_name="support-agent")
@rius.observe
def classify_ticket(message: str) -> str:
...
def search_help_center(query: str):
with rius.start_as_current_span(
kind=SpanKind.RETRIEVER, data_source_id="help-center", 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 docs
@rius.observe(name="support-turn", kind=SpanKind.AGENT)
def handle_turn(message: str) -> str:
topic = classify_ticket(message)
docs = search_help_center(message)
return agent_executor.invoke({"input": message, "topic": topic, "docs": docs})- Scope the session and user around each customer message. Use your conversation id as the session and an opaque customer id as the user:
with rius.user(customer_id), rius.session(conversation_id):
reply = handle_turn(message)- Run one conversation with a few turns and open the traces in the console. Each turn shows the run root, your stage spans, the retrieval span, and the LangChain chain with its model calls as leaves carrying model, tokens and cost.
In TypeScript, install @arizeai/openinference-instrumentation-langchain and keep @langchain/core, which a LangChain.js app already has. Use observe, startAsCurrentSpan, withSession and withUser from @glassflow-ai/rius for the same structure. In TypeScript, give each wrapped function a name, because an anonymous one traces as anonymous.
Two details on scoping:
observetakes no user argument, because a decorator is bound once and a user belongs to each request. Scope the user around the call instead.- Do not set a process-wide session with
RIUS_SESSION_IDin a server that handles many customers, because their traces would merge into one session.
What you get
- One trace per customer message, with your own run root on top and the LangChain chain, tools, retriever and model calls nested below it.
- Tokens and a cost estimate on each model call. Costs are list-price estimates in US dollars.
- Retrieval spans that show which help-center documents came back and with what scores, so you can tell a bad answer from bad retrieval.
- Traces grouped into a session per conversation. With no session id, each trace is its own session.
- Traces tied to each customer through
user.id, with no user id leaving a trace anonymous. Prefer an opaque id to an email, since the value is stored on every span and shown in the console. - Step inputs, outputs and messages recorded as content, so the privacy controls apply to the whole tree.
When something else fits
- If you call a provider client directly with no LangChain in the path, the OpenAI or Anthropic integration on the Integrations page is the closer fit.
- If you use text-completion models (
BaseLLMsubclasses such aslangchain_openai.OpenAI), their calls are still counted twice. The LangChain integration page links the layer to keep.