> ## Documentation Index
> Fetch the complete documentation index at: https://tracevell.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# LangChain and LangGraph observability

> Trace LangChain chains and LangGraph agents in TypeScript and Python: every graph node, tool call, and LLM call, with token usage and cost, read as a transcript.

## Overview

Tracevell is an open-source, OpenTelemetry-native observability platform for AI agents. Trace, debug, and monitor every [LangChain](https://www.langchain.com/) chain and [LangGraph](https://www.langchain.com/langgraph) agent in TypeScript and Python: each graph node, tool call, retriever query, and LLM call is recorded with its inputs, outputs, token usage, and cost. Self-host via Helm or use managed cloud.

Your LangChain and LangGraph code stays as it is. Tracevell builds the run tree from LangChain's own callbacks. Calls to OpenAI and Anthropic models are recorded by Tracevell's [OpenAI](https://tracevell.com/docs/integrations/openai.md) and [Anthropic](https://tracevell.com/docs/integrations/anthropic.md) integrations, so each call is one LLM span with the provider's full request and response.

## Getting started

> [tab] TypeScript
>
> **1. Install Tracevell and LangChain**
>
> ```bash theme={null}
> npm install @lmnr-ai/lmnr @langchain/core @langchain/langgraph @langchain/openai openai zod
> ```
>
> This needs `@lmnr-ai/lmnr` 0.8.53 or later. Install the model provider's SDK (`openai` here, `@anthropic-ai/sdk` if you use `@langchain/anthropic`) as a direct dependency, because you pass it to Tracevell in step 3.
>
> **2. Set up your environment variables**
>
> ```bash .env theme={null}
> LMNR_PROJECT_API_KEY=your-tracevell-project-api-key
> OPENAI_API_KEY=your-openai-api-key
> ```
>
> Load them with a package like [dotenv](https://www.npmjs.com/package/dotenv), or `node --env-file=.env`.
>
> **3. Initialize Tracevell with your LangChain modules**
>
> Tracevell can't patch an ES module that your code has already imported, so pass the LangChain modules, and the SDK of each model provider you call, to `instrumentModules`. Do it once, in its own file:
>
> ```typescript instrumentation.ts theme={null}
> import { Tracevell } from '@lmnr-ai/lmnr';
> import * as callbackManagerModule from '@langchain/core/callbacks/manager';
> import * as chatModelsModule from '@langchain/core/language_models/chat_models';
> import * as llmsModule from '@langchain/core/language_models/llms';
> import * as singletonsModule from '@langchain/core/singletons';
> import * as langgraphModule from '@langchain/langgraph';
> import OpenAI from 'openai';
>
> Laminar.initialize({
>   instrumentModules: {
>     OpenAI,
>     langchain: {
>       callbackManagerModule,
>       chatModelsModule,
>       llmsModule,
>       singletonsModule,
>       langgraphModule,
>     },
>   },
> });
> ```
>
> Then import it first in your entry point, before any LangChain code runs:
>
> ```typescript index.ts theme={null}
> import './instrumentation.js';
> ```
>
> Each module turns on part of the trace. The LangChain modules go under `instrumentModules.langchain`, and the provider SDKs go at the top level:
>
> | Key | What you lose without it |
> | - | - |
> | `callbackManagerModule` | Nothing from LangChain is recorded. |
> | `singletonsModule` | Spans created inside a chain step, tool, or graph node (`observe()`, direct SDK calls) don't nest under it. |
> | `chatModelsModule`, `llmsModule` | OpenAI and Anthropic calls are recorded twice, once by LangChain and once by the provider integration. |
> | `langgraphModule` | The graph's nodes and edges aren't recorded, so the trace has no [LangGraph view](https://tracevell.com/docs/tracing/langgraph-visualization.md). |
> | `OpenAI` | OpenAI calls are recorded by LangChain as a `ChatOpenAI.chat` span instead of the OpenAI integration's span. |
> | `anthropic` | Anthropic calls are recorded by LangChain as a `ChatAnthropic.chat` span instead of the Anthropic integration's span. Pass it as `import * as anthropic from '@anthropic-ai/sdk'`. |
>
> > [!WARNING]
> >
> > Pass the same copy of the provider SDK that the LangChain package loads. `@langchain/anthropic` and `@langchain/openai` each depend on a specific version of their provider's SDK, and if your app depends on a different major version, npm installs two copies and Tracevell patches the one LangChain doesn't use. Run `npm ls openai @anthropic-ai/sdk` and check that each package resolves to a single version. When the copies differ, the calls are still recorded once, as the LangChain-side span.
>
> > [!NOTE]
> >
> > In a CommonJS app, calling `Laminar.initialize()` before LangChain is loaded instruments it without `instrumentModules`. Passing the modules works in both module systems, so it's the setup we recommend.
>
> **4. Use LangChain and LangGraph as usual**
>
> A chain:
>
> ```typescript chain.ts {1-2,17} theme={null}
> import './instrumentation.js';
> import { Tracevell } from '@lmnr-ai/lmnr';
> import { StringOutputParser } from '@langchain/core/output_parsers';
> import { ChatPromptTemplate } from '@langchain/core/prompts';
> import { ChatOpenAI } from '@langchain/openai';
>
> const prompt = ChatPromptTemplate.fromMessages([
>   ['system', 'You are a helpful assistant. Answer in one sentence.'],
>   ['human', '{question}'],
> ]);
> const chain = prompt
>   .pipe(new ChatOpenAI({ model: 'gpt-5.6-terra' }))
>   .pipe(new StringOutputParser());
>
> console.log(await chain.invoke({ question: 'What is the capital of France?' }));
>
> await Laminar.shutdown();
> ```
>
> A LangGraph agent with a tool:
>
> ```typescript agent.ts {1-2,37} theme={null}
> import './instrumentation.js';
> import { Tracevell } from '@lmnr-ai/lmnr';
> import { HumanMessage } from '@langchain/core/messages';
> import { tool } from '@langchain/core/tools';
> import { MemorySaver, MessagesAnnotation, START, StateGraph } from '@langchain/langgraph';
> import { ToolNode, toolsCondition } from '@langchain/langgraph/prebuilt';
> import { ChatOpenAI } from '@langchain/openai';
> import { z } from 'zod';
>
> const getWeather = tool(
>   async ({ city }) => `It is 18C and sunny in ${city}.`,
>   {
>     name: 'get_weather',
>     description: 'Get the current weather for a city.',
>     schema: z.object({ city: z.string() }),
>   },
> );
>
> const model = new ChatOpenAI({ model: 'gpt-5.6-terra' }).bindTools([getWeather]);
>
> const agent = new StateGraph(MessagesAnnotation)
>   .addNode('agent', async (state) => ({
>     messages: [await model.invoke(state.messages)],
>   }))
>   .addNode('tools', new ToolNode([getWeather]))
>   .addEdge(START, 'agent')
>   .addConditionalEdges('agent', toolsCondition)
>   .addEdge('tools', 'agent')
>   .compile({ checkpointer: new MemorySaver() });
>
> const result = await agent.invoke(
>   { messages: [new HumanMessage('What is the weather in Paris?')] },
>   { configurable: { thread_id: 'user-42' } },
> );
> console.log(result.messages.at(-1)?.text);
>
> await Laminar.shutdown();
> ```
>
> Agents built with `createAgent` from the `langchain` package run on LangGraph, so they're traced the same way. `await Laminar.shutdown()` sends the last spans before a short-lived script exits; a long-running server doesn't need it.

> [tab] Python
>
> **1. Install Tracevell and LangChain**
>
> ```bash theme={null}
> pip install -U lmnr langchain-openai langgraph
> ```
>
> This needs `lmnr` 0.8.2 or later.
>
> **2. Set up your environment variables**
>
> ```bash .env theme={null}
> LMNR_PROJECT_API_KEY=your-tracevell-project-api-key
> OPENAI_API_KEY=your-openai-api-key
> ```
>
> Load them with [python-dotenv](https://pypi.org/project/python-dotenv/) or export them in your shell.
>
> **3. Initialize Tracevell**
>
> Call `Laminar.initialize()` once at startup, before any LangChain or LangGraph code runs. LangChain and LangGraph are instrumented automatically.
>
> ```python theme={null}
> from lmnr import Tracevell
>
> Laminar.initialize()
> ```
>
> **4. Use LangChain and LangGraph as usual**
>
> A chain:
>
> ```python chain.py {1,6,15} theme={null}
> from lmnr import Tracevell
> from langchain_core.output_parsers import StrOutputParser
> from langchain_core.prompts import ChatPromptTemplate
> from langchain_openai import ChatOpenAI
>
> Laminar.initialize()
>
> prompt = ChatPromptTemplate.from_messages([
>     ("system", "You are a helpful assistant. Answer in one sentence."),
>     ("human", "{question}"),
> ])
> chain = prompt | ChatOpenAI(model="gpt-5.6-terra") | StrOutputParser()
>
> print(chain.invoke({"question": "What is the capital of France?"}))
> Laminar.flush()
> ```
>
> A LangGraph agent with a tool:
>
> ```python agent.py {1,9,41} theme={null}
> from lmnr import Tracevell
> from langchain_core.messages import HumanMessage
> from langchain_core.tools import tool
> from langchain_openai import ChatOpenAI
> from langgraph.checkpoint.memory import MemorySaver
> from langgraph.graph import START, MessagesState, StateGraph
> from langgraph.prebuilt import ToolNode, tools_condition
>
> Laminar.initialize()
>
>
> @tool
> def get_weather(city: str) -> str:
>     """Get the current weather for a city."""
>     return f"It is 18C and sunny in {city}."
>
>
> model = ChatOpenAI(model="gpt-5.6-terra", use_responses_api=True).bind_tools([get_weather])
>
>
> def call_model(state: MessagesState):
>     return {"messages": [model.invoke(state["messages"])]}
>
>
> agent = (
>     StateGraph(MessagesState)
>     .add_node("agent", call_model)
>     .add_node("tools", ToolNode([get_weather]))
>     .add_edge(START, "agent")
>     .add_conditional_edges("agent", tools_condition)
>     .add_edge("tools", "agent")
>     .compile(checkpointer=MemorySaver())
> )
>
> result = agent.invoke(
>     {"messages": [HumanMessage("What is the weather in Paris?")]},
>     {"configurable": {"thread_id": "user-42"}},
> )
> print(result["messages"][-1].text)
>
> Laminar.flush()
> ```
>
> `Laminar.flush()` sends the last spans before a short-lived script exits; a long-running server doesn't need it.
>
> > [!NOTE]
> >
> > To see Tracevell inside a FastAPI application, check out the [FastAPI guide](https://tracevell.com/docs/guides/fastapi.md).

## What Tracevell records

Each invocation of a chain or graph is one trace:

| Span | What it holds |
| - | - |
| The run, named after the chain or graph (`RunnableSequence`, `LangGraph`, or the `runName` you set) | The input you passed and the final output or state. For a graph, also its nodes and edges, which power the [LangGraph view](https://tracevell.com/docs/tracing/langgraph-visualization.md). |
| `execute_task <name>` for every chain step and graph node | The step's input and output, nested under the run. |
| A tool span, named after the tool | The tool's arguments, result, and tool call ID. Tool spans show up as tool calls in the transcript. |
| `vector_db_retrieve <name>` | The retriever's query and the documents it returned. |
| An LLM span for every model call | The messages, the response with any tool calls, the model, token usage, and cost. |

### Which integration records each LLM call

* **OpenAI (including Azure OpenAI) and Anthropic chat models** are recorded by Tracevell's provider integrations, as `openai.chat`, `openai.response`, or `anthropic.chat` spans. In Python, Google GenAI (`langchain-google-genai` 4 or later) and Groq models are handed off the same way.
* **Every other chat model** (Gemini in TypeScript, Mistral, Amazon Bedrock, Ollama, and so on) is recorded by LangChain as an LLM span named after the model class, such as `ChatGoogleGenerativeAI.chat` or `ChatMistralAI.chat`. It carries the same messages, tool calls, model, token usage, and cost. Any provider SDK instrumentation underneath is silenced for that call, so nothing is recorded twice.

### Group a LangGraph thread into a session

LangGraph's `thread_id` is recorded on the graph and node spans. To see every run of a thread together in Tracevell's [sessions view](https://tracevell.com/docs/tracing/structure/sessions.md), wrap the call in `observe` with the thread ID as the session ID:

> [tab] TypeScript
>
> ```typescript theme={null}
> import { observe } from '@lmnr-ai/lmnr';
>
> const threadId = 'user-42';
> const result = await observe({ name: 'weather_agent', sessionId: threadId }, () =>
>   agent.invoke(
>     { messages: [new HumanMessage('What is the weather in Paris?')] },
>     { configurable: { thread_id: threadId } },
>   ),
> );
> ```

> [tab] Python
>
> ```python theme={null}
> from lmnr import observe
>
>
> @observe(name="weather_agent", session_id="user-42")
> def run(question: str):
>     return agent.invoke(
>         {"messages": [HumanMessage(question)]},
>         {"configurable": {"thread_id": "user-42"}},
>     )
> ```

The graph's spans nest under the `weather_agent` span, and the whole trace joins the session.

## See what happened in a trace

Open a trace in Tracevell and the default view is the transcript: the agent's auto-extracted input, every LLM turn with a one-line preview of its response, and each tool call with its arguments and result, in the order they ran. You read the conversation instead of clicking through a tree of `execute_task` spans. Switch to tree view for the span-by-span structure, or open the [LangGraph view](https://tracevell.com/docs/tracing/langgraph-visualization.md) to see which nodes ran.

More on the trace UX: [Viewing traces](https://tracevell.com/docs/platform/viewing-traces.md).

## Track outcomes with Signals

Traces answer *what happened on this run*. **[Signals](https://tracevell.com/docs/signals/introduction.md) answer the cross-trace question**: *how often does the agent answer without calling the tool it needed, when does a graph loop between the same two nodes, which runs end with an apology instead of an answer*. A Signal pairs a plain-language instruction with a JSON output schema. Tracevell runs it live on new traces (Triggers) or backfills it across history (Jobs) and records a structured event every time it matches. From there you [query](https://tracevell.com/docs/platform/sql-editor.md), [cluster](https://tracevell.com/docs/signals/clusters.md), and [alert](https://tracevell.com/docs/signals/alerts.md) on events across every trace.

> [!NOTE]
>
> Every new project ships with a **Failure Detector** Signal that categorizes issues on any trace over 1000 tokens. Open it from the Signals sidebar to see events as soon as your LangChain traces arrive.

## Query across traces

* **[SQL editor](https://tracevell.com/docs/platform/sql-editor.md)** for ad-hoc queries across traces, spans, signals, and evals.
* **SQL API** for programmatic access from scripts and pipelines.
* **[CLI](https://tracevell.com/docs/platform/cli.md)** (`lmnr-cli sql query`) for terminal-driven queries and piping JSON into shell tools or coding agents.
* **[MCP server](https://tracevell.com/docs/platform/mcp.md)** to query Tracevell from Claude Code, Cursor, or Codex.

## Troubleshooting

> [accordion] I don't see any traces in Tracevell (TypeScript)
>
> * Pass the LangChain modules to `instrumentModules` as shown in step 3. In an ES module app, `Laminar.initialize()` without them records nothing from LangChain.
> * Import your `instrumentation` file before any file that runs LangChain code.
> * Confirm `LMNR_PROJECT_API_KEY` is set in the process that runs LangChain.
> * Call `await Laminar.shutdown()` before a short-lived script exits.

> [accordion] Every OpenAI or Anthropic call shows up twice (TypeScript)
>
> Add `chatModelsModule` and `llmsModule` to `instrumentModules.langchain`. Without them, LangChain can't hand the call to the provider integration, so both record it. Tracevell logs a warning at startup when `chatModelsModule` is missing.

> [accordion] I see `ChatOpenAI.chat` instead of an `openai.*` span (TypeScript)
>
> The OpenAI integration didn't record the call, so LangChain recorded it instead. Pass `OpenAI` from `openai` to `instrumentModules`, and check with `npm ls openai` that `@langchain/openai` uses the same copy of `openai` that you import. The same applies to `ChatAnthropic.chat` and `@anthropic-ai/sdk`.

> [accordion] Self-hosting Tracevell
>
> Point the SDK at your instance when initializing. For a local OSS deployment:
>
> > [tab] TypeScript
> >
> > ```typescript theme={null}
> > Laminar.initialize({
> >   baseUrl: 'http://localhost',
> >   httpPort: 8000,
> >   grpcPort: 8001,
> >   instrumentModules: { /* as in step 3 */ },
> > });
> > ```
>
> > [tab] Python
> >
> > ```python theme={null}
> > Laminar.initialize(base_url="http://localhost", http_port=8000, grpc_port=8001)
> > ```

## What's next

> [card] [Viewing traces](https://tracevell.com/docs/platform/viewing-traces.md)
>
> Read the transcript view, filter, and search across traces.

> [card] [Signals](https://tracevell.com/docs/signals/introduction.md)
>
> Detect behaviors and failures across every run, then query, cluster, and alert on them.

> [card] [SQL editor and MCP server](https://tracevell.com/docs/platform/sql-editor.md)
>
> Query traces programmatically from the UI, API, or your IDE.

> [card] [Tracing structure](https://tracevell.com/docs/tracing/structure/overview.md)
>
> Sessions, user IDs, metadata, and tags.

## Related integrations

> [card] [deepagents](https://tracevell.com/docs/integrations/deepagents.md)
>
> Trace deepagents runs, subagents, and tool calls.

> [card] [Pydantic AI](https://tracevell.com/docs/integrations/pydantic-ai.md)
>
> Trace Pydantic AI agent runs and typed tool calls.

> [card] [LiteLLM](https://tracevell.com/docs/integrations/litellm.md)
>
> One integration for every model LiteLLM routes to.

> [card] [OpenAI](https://tracevell.com/docs/integrations/openai.md)
>
> Trace the OpenAI SDK directly in TypeScript and Python.

> [card] [Langfuse](https://tracevell.com/docs/integrations/langfuse.md)
>
> Keep Langfuse and send the same spans to Tracevell too.

> [card] [All integrations](https://tracevell.com/docs/integrations.md)
>
> Browse every provider, framework, coding agent, and browser integration Tracevell supports.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.