> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://respan.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://respan.ai/_mcp/server.

# LangGraph (tracing)

> Trace LangGraph stateful agent workflows with Respan — callback spans, gateway routing, and full observability.

[LangGraph](https://langchain-ai.github.io/langgraph/) is a framework for building stateful, multi-step agent workflows as graphs. Nodes represent operations such as LLM calls, tools, and routing decisions, while edges define the flow between them. Respan gives you full observability over every graph run, node, tool call, and LLM generation — and gateway routing through the OpenAI-compatible Respan endpoint.

#### Set up Respan

Create an account at [platform.respan.ai](https://platform.respan.ai) and grab an [API key](https://platform.respan.ai/platform/api/api-keys).

Run `npx @respan/cli setup` to set up with your coding agent.

#### Use Respan Gateway

See [LangGraph gateway setup](/docs/gateway/langgraph) to route this integration through the Respan gateway.

#### Example projects

* [Example projects](https://github.com/respanai/respan-example-projects)

## Setup

#### Install packages

```bash Python
pip install respan-ai respan-instrumentation-langchain langgraph langchain-openai python-dotenv
```

```bash TypeScript
npm install @respan/respan @respan/instrumentation-langchain @langchain/langgraph zod
```

#### Set environment variables

```bash
export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"
export RESPAN_BASE_URL="https://api.respan.ai/api"
```

`RESPAN_API_KEY` is used to export traces to Respan. Set `OPENAI_API_KEY` too when graph nodes call provider-backed models.

#### Initialize and run

LangGraph runs through LangChain callback managers, so use `respan-instrumentation-langchain` and pass `LangChainInstrumentor()` to `Respan(instrumentations=[...])`.

**Python** — the instrumentor patches the LangChain and LangGraph callback managers on init, so the compiled graph, every node, and their nested LLM and tool calls are traced **automatically**. No per-call setup.

**TypeScript** — the instrumentor does not patch globally. Attach `langgraph.addCallback(...)` at the compiled graph's top-level `app.invoke()` (or `app.stream()`), not inside a node; LangGraph propagates it to every node. Attaching it only inside a single node yields just that node's spans.

```python Python
from typing import TypedDict

from langgraph.graph import StateGraph, START, END
from respan import Respan
from respan_instrumentation_langchain import LangChainInstrumentor

# Activates global LangChain/LangGraph instrumentation — graph and node runs are traced automatically.
respan = Respan(instrumentations=[LangChainInstrumentor()])

class State(TypedDict):
    topic: str
    joke: str

def generate_joke(state: State) -> dict:
    return {"joke": f"A short joke about {state['topic']}"}

graph = StateGraph(State)
graph.add_node("generate", generate_joke)
graph.add_edge(START, "generate")
graph.add_edge("generate", END)

app = graph.compile()
result = app.invoke({"topic": "AI tracing"})
print(result)
```

```typescript TypeScript
import { END, START, StateGraph, StateSchema } from "@langchain/langgraph";
import { Respan } from "@respan/respan";
import { LangChainInstrumentor } from "@respan/instrumentation-langchain";
import { z } from "zod";

const langgraph = new LangChainInstrumentor();
const respan = new Respan({
  apiKey: process.env.RESPAN_API_KEY,
  baseURL: process.env.RESPAN_BASE_URL,
  instrumentations: [langgraph],
});
await respan.initialize();

const State = new StateSchema({
  topic: z.string(),
  joke: z.string().default(""),
});

const graph = new StateGraph(State)
  .addNode("generate", (state: typeof State.State) => {
    return { joke: `A short joke about ${state.topic}` };
  })
  .addEdge(START, "generate")
  .addEdge("generate", END)
  .compile();

const result = await graph.invoke(
  { topic: "AI tracing" },
  langgraph.addCallback({
    runName: "joke_graph",
    tags: ["langgraph", "quickstart"],
    metadata: { framework: "langgraph" },
  })
);
console.log(result);
```

#### View your trace

Open the [Traces page](https://platform.respan.ai/platform/traces) to see the graph execution with node spans, LLM calls, and state transitions.

## Configuration

| Parameter             | Type           | Default | Description                                                          |
| --------------------- | -------------- | ------- | -------------------------------------------------------------------- |
| `api_key`             | `str \| None`  | `None`  | Falls back to `RESPAN_API_KEY` env var.                              |
| `base_url`            | `str \| None`  | `None`  | Falls back to `RESPAN_BASE_URL` env var.                             |
| `instrumentations`    | `list`         | `[]`    | Plugin instrumentations to activate, e.g. `LangChainInstrumentor()`. |
| `include_content`     | `bool`         | `True`  | Includes graph inputs, node inputs, and outputs on spans.            |
| `include_metadata`    | `bool`         | `True`  | Includes LangGraph tags, metadata, and serialized runnable details.  |
| `customer_identifier` | `str \| None`  | `None`  | Default customer identifier for all spans.                           |
| `metadata`            | `dict \| None` | `None`  | Default metadata attached to all spans.                              |
| `environment`         | `str \| None`  | `None`  | Environment tag, e.g. `"production"`.                                |

## Attributes

### In Respan()

Set defaults at initialization — these apply to all spans.

```python Python
from respan import Respan
from respan_instrumentation_langchain import LangChainInstrumentor

respan = Respan(
    instrumentations=[LangChainInstrumentor()],
    customer_identifier="user_123",
    metadata={"service": "graph-api", "version": "1.0.0"},
)
```

```typescript TypeScript
import { Respan } from "@respan/respan";
import { LangChainInstrumentor } from "@respan/instrumentation-langchain";

const respan = new Respan({
  instrumentations: [new LangChainInstrumentor()],
});
```

### With propagate\_attributes

Override per-request using a context scope.

```python Python
from respan import propagate_attributes
from respan_instrumentation_langchain import add_respan_callback

def handle_request(user_id: str, topic: str):
    with propagate_attributes(
        customer_identifier=user_id,
        thread_identifier="conv_abc_123",
        metadata={"plan": "pro"},
    ):
        result = app.invoke(
            {"topic": topic},
            config=add_respan_callback({"run_name": "user_graph"}),
        )
        print(result)
```

```typescript TypeScript
async function handleRequest(userId: string, topic: string) {
  await respan.propagateAttributes(
    {
      customer_identifier: userId,
      thread_identifier: "conv_abc_123",
      metadata: { plan: "pro" },
    },
    async () => {
      const result = await graph.invoke(
        { topic },
        langgraph.addCallback({ runName: "user_graph" })
      );
      console.log(result);
    }
  );
}
```

| Attribute             | Type   | Description                                           |
| --------------------- | ------ | ----------------------------------------------------- |
| `customer_identifier` | `str`  | Identifies the end user in Respan analytics.          |
| `thread_identifier`   | `str`  | Groups related messages into a conversation.          |
| `metadata`            | `dict` | Custom key-value pairs. Merged with default metadata. |

## Decorators (optional)

Decorators are not required. Graph and node runs are traced automatically by the instrumentor in Python, and when you attach the Respan callback config to the graph invocation in TypeScript. Use `@workflow` and `@task` (Python) or `withWorkflow` and `withTask` (TypeScript) to group graph runs inside larger application workflows.

```python Python
from respan import workflow, task
from respan_instrumentation_langchain import add_respan_callback

@task(name="run_joke_graph")
def run_joke_graph(topic: str):
    return app.invoke(
        {"topic": topic},
        config=add_respan_callback({"run_name": "decorated_graph"}),
    )

@workflow(name="joke_pipeline")
def pipeline(topic: str):
    print(run_joke_graph(topic))

pipeline("AI tracing")
```

```typescript TypeScript
await respan.withWorkflow({ name: "joke_pipeline" }, async () => {
  const result = await respan.withTask({ name: "run_joke_graph" }, async () => {
    return graph.invoke(
      { topic: "AI tracing" },
      langgraph.addCallback({ runName: "decorated_graph" })
    );
  });
  console.log(result);
});
```

## Examples

### Streaming updates

Stream graph node updates while keeping the same trace.

```python
for update in app.stream(
    {"topic": "AI tracing"},
    config=add_respan_callback({"run_name": "streaming_graph"}),
    stream_mode="updates",
):
    print(update)
```

### Tool nodes

Tool calls inside graph nodes are captured as tool spans.

```python
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode

@tool
def search_docs(query: str) -> str:
    """Search docs."""
    return f"Results for {query}"

tools = ToolNode([search_docs])
```