How do I trace tool calls in LangChain?
Rius traces LangChain tool calls as TOOL spans through the LangChain integration, nested under the chain or agent run that invoked them. Install the LangChain extra, call init, and run your chains as before. If a tool runs outside LangChain’s callback system, wrap it with observe and the TOOL kind so it still appears in the trace. Both paths are covered below.
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 the step is:
- Tool invocations are TOOL spans.
- Chains are CHAIN spans.
- Retrievers are RETRIEVER spans.
- Model calls are LLM spans with model, token usage and messages. Rius computes cost on the server.
Nesting follows your chain structure. A tool the agent calls sits under the chain or agent step that called it, and the model calls around it sit beside it in the same tree, so the trace reads like your execution graph.
A tool that does not go through LangChain’s callbacks, for example a plain function you call yourself, is not seen by the integration. You trace that step with observe, which opens a span for each call. A span you open inside a traced run is parented to the span that is current when it starts, so it lands in the same tree.
This is one of the integrations on the Integrations page, which lists every library and framework Rius supports.
Set it up
- Install the instrumentation for LangChain. The extra does not install
langchainitself, so a fresh environment needs it named too. Without it,init()logs aDependencyConflictand the integration stays off.
pip install "glassflow-rius[langchain]" langchain- Call
init()once at startup, then build and run your chains exactly as before. Tool spans appear with no other code.
import rius
rius.init(api_key="ri_...", service_name="my-agent")In TypeScript, run npm install @arizeai/openinference-instrumentation-langchain. The integration also needs @langchain/core, which a LangChain.js application already has, and init() patches its callback manager for you. await rius.ready resolves with the integrations that attached, so you can check that langchain is among them.
-
Run one chain or agent that calls a tool, then open the trace in the console. You should see the tool as a TOOL span inside the run, next to the LLM spans.
-
If a tool does not appear, wrap it yourself. Give the span the TOOL kind and the tool’s name. Here
search_ordersis your own function:
import rius
from rius import SpanKind
@rius.observe(kind=SpanKind.TOOL, tool_name="search_orders")
def search_orders(customer_id: str) -> list[dict]:
...The TypeScript equivalent wraps the function with observe and passes kind: SpanKind.TOOL and toolName. Use a named function expression, because an anonymous arrow function traces as anonymous unless you pass a name.
- Check how the wrapped span is named. Without a
name, a TOOL span is calledexecute_toolfollowed by the tool name, such asexecute_tool search_orders. Withouttool_name,observeuses the function’s name as the tool name. If you pass a spannamebut no tool name, the span name doubles as the tool name and the SDK warns, so passtool_namewhen the two should differ.
The tracing page covers observe, generic spans and the other span kinds in full.
What you get
- A TOOL span for each tool invocation that runs through LangChain, nested under the chain or agent run that triggered it.
- Chain, retriever and model spans in the same tree, with model, tokens and messages on the LLM spans and cost computed by Rius.
- Tool spans for code the integration does not see, using
observewith the TOOL kind. They carry the tool’s name ingen_ai.tool.nameand the function’s arguments and return value as input and output. - Failures recorded on the span. A tool that raises an exception under
observegets ERROR status anderror.typeset to the exception class, and the exception is re-raised. - Tool, chain and model steps in one trace, so you can see which tool call preceded which model call.
Step inputs, outputs and messages are content attributes, so Rius’s privacy controls apply to the whole tree, tool spans included.
When something else fits
- If a tool runs outside LangChain’s callbacks, such as a function you call directly from your own code, wrap it with
observeand the TOOL kind instead of expecting the integration to trace it. - If you need time to first token on a LangChain stream, note that most LangChain streams do not record it, except a Python stream over the
openaiintegration, which gets it on its nested OpenAI span. Trace other streamed calls as a manual generation, described on the tracing page. - If your tools are served over MCP, the MCP integration traces those calls for you.