Skip to main content
GitHub

LangChain

Auto-instrument LangChain chains and agents.

Risicare automatically instruments LangChain for comprehensive chain and agent observability.

Installation

pip install 'risicare[langchain]'
# or
pip install risicare langchain langchain-openai

Version Compatibility

Requires langchain-core >= 0.2.0.

The extra installs langchain-core only. The samples on this page also use langchain-openai, and the agent sample uses langchain: the second install line installs both.

Auto-Instrumentation

import risicare
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
 
risicare.init()
 
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant."),
    ("user", "{input}")
])
 
chain = prompt | llm
 
# Automatically traced
response = chain.invoke({"input": "Hello!"})

What's Captured

FeatureDescription
Chain ExecutionFull chain invoke/ainvoke calls
LLM CallsEach chat model or LLM run, as langchain.chat/{model} or langchain.llm/{model}. The provider call underneath it gets no second span
Prompt TemplatesTemplate formatting
Output ParsersParsing operations
Tool CallsTool/function executions
Retriever CallsRAG retrieval operations (retriever.document_count; retriever.query when content capture is on)

Span Hierarchy

langchain.chain/{class}
├── langchain.chat/{model}
├── langchain.tool/{name}
└── langchain.retriever/{name}

With the classic AgentExecutor, the tool choice is a langchain.agent_action/{tool} span — see Agents.

Provider Deduplication

Provider Deduplication

The provider call that a LangChain model makes for its own run gets no span of its own, so its tokens and cost count once. A provider call that you make yourself, for example with the openai client inside a chain step, keeps its provider span. That span is a trace of its own, unless a risicare.trace() or a graph span is active: then it is a child of that span.

Agents

A LangChain agent is traced as chain, chat-model and tool spans:

from langchain.agents import create_agent
from langchain_core.tools import tool
 
@tool
def search(query: str) -> str:
    """Search for information."""
    return f"Results for: {query}"
 
agent = create_agent(llm, tools=[search], system_prompt="You are a helpful assistant.")
 
# The agent run is traced
result = agent.invoke({"messages": [{"role": "user", "content": "Search for AI news"}]})
print(result["messages"][-1].content)

This sample needs langchain 1.0 or later and langchain-openai (the second install line above).

The classic agent API (create_react_agent and AgentExecutor) is not in langchain.agents in LangChain 1.0. Import it from langchain_classic.agents (package langchain-classic). create_react_agent needs a ReAct prompt that has {tools}, {tool_names} and {agent_scratchpad}.

A run of this sample sends one trace: a langgraph.graph/LangGraph span at the root, langchain.chain spans for the graph and its model and tools steps, a langchain.chat/{model} span for each model call, and a langchain.tool/search span.

With the classic AgentExecutor (Python 0.5.1 and later), a run that calls a tool is one trace. The langchain.chain/AgentExecutor span is the root, and the model calls, the tool calls and the langchain.agent_action/{tool} spans are below it. An agent_action span is a point in time: it starts and ends when the agent chooses the tool, so its duration is near zero. Measured with Python 0.6.0 and langchain-classic 1.0.8: two model calls, one tool call, one trace. In Python 0.5.0, such a run had no AgentExecutor span and arrived as one trace for each tool call plus one, and only the last agent_action span was sent.

LCEL Chains

LangChain Expression Language (LCEL) chains are automatically traced:

from langchain_core.output_parsers import StrOutputParser
 
chain = prompt | llm | StrOutputParser()
 
# Each component is a span
result = chain.invoke({"input": "Hello"})

Streaming

async for chunk in chain.astream({"input": "Write a story"}):
    print(chunk, end="")

RAG Chains

Retrieval chains capture document retrieval:

from langchain_core.runnables import RunnablePassthrough
 
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)
 
# Retriever calls are captured with documents
result = rag_chain.invoke("What is Risicare?")

JavaScript / TypeScript

The JS SDK provides a RisicareCallbackHandler for LangChain.js:

import { RisicareCallbackHandler } from 'risicare/langchain';
 
const handler = new RisicareCallbackHandler();
 
// Use withSuppression() to prevent duplicate spans when using patchOpenAI()
const result = await handler.withSuppression(() =>
  chain.invoke(input, { callbacks: [handler] })
);

Deduplication

If you use both patchOpenAI() and RisicareCallbackHandler, wrap your chain calls with handler.withSuppression() to prevent duplicate LLM spans.

Next Steps