> 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.

# LangChain (tracing)

> Trace LangChain workflows with Respan — callback spans, gateway routing, and full observability.

[LangChain](https://www.langchain.com/) is a framework for building applications with language models. It provides chains, agents, tools, retrievers, and provider integrations. Respan gives you full observability over every chain run, agent step, retriever call, 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 [LangChain gateway setup](/docs/gateway/langchain) 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 langchain langchain-openai python-dotenv
```

```bash TypeScript
npm install @respan/respan @respan/instrumentation-langchain langchain @langchain/core @langchain/openai
```

#### 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 your LangChain run uses a provider-backed model instead of a fake/local model.

#### Initialize and run

Pass `LangChainInstrumentor()` to `Respan(instrumentations=[...])`. The instrumentor captures every chain, model, tool, retriever, and agent run.

**Python** — the instrumentor patches LangChain's callback manager on init, so every run (chains, agents, graphs, and their nested LLM, tool, and retriever calls) is traced **automatically**. No per-call setup.

**TypeScript** — the instrumentor does not patch globally. Attach `langchain.addCallback(...)` at the **outermost** `.invoke()` (your chain, agent, or graph); LangChain propagates it to nested runs. Attaching it only to an inner model yields just that one LLM span.

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

# Activates global LangChain instrumentation — every run is traced automatically.
respan = Respan(instrumentations=[LangChainInstrumentor()])

llm = ChatOpenAI(model="gpt-4.1-nano")
response = llm.invoke("Say hello in three languages.")
print(response.content)

```

```typescript TypeScript
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { FakeListChatModel } from "@langchain/core/utils/testing";
import { Respan } from "@respan/respan";
import { LangChainInstrumentor } from "@respan/instrumentation-langchain";

const langchain = new LangChainInstrumentor();
const respan = new Respan({
  apiKey: process.env.RESPAN_API_KEY,
  baseURL: process.env.RESPAN_BASE_URL,
  appName: "typescript-langchain-quickstart",
  instrumentations: [langchain],
  logLevel: "error",
  silenceInitializationMessage: true,
});
await respan.initialize();

const model = new FakeListChatModel({
  responses: ["Hello from a traced TypeScript LangChain run."],
});

try {
  const response = await model.invoke(
    [
      new SystemMessage("Reply in one short sentence."),
      new HumanMessage("Say hello to Respan tracing."),
    ],
    langchain.addCallback({
      runName: "quickstart",
      tags: ["respan-langchain-example", "quickstart"],
      metadata: { example: "quickstart" },
    })
  );
  console.log(response.content);
} finally {
  await respan.shutdown().catch(() => undefined);
}
```

Optionally attach a callback config to label a specific run with a `run_name`, tags, or metadata. In Python this is only for labeling — tracing already happens through the instrumentor. In TypeScript it is also what activates tracing for the run.

```python Python
from langchain_openai import ChatOpenAI
from respan_instrumentation_langchain import add_respan_callback

llm = ChatOpenAI(model="gpt-4.1-nano")
response = llm.invoke(
    "Say hello in three languages.",
    config=add_respan_callback(
        {
            "run_name": "hello_langchain",
            "tags": ["langchain", "quickstart"],
            "metadata": {"framework": "langchain"},
        }
    ),
)
print(response.content)
```

```typescript TypeScript
import { ChatOpenAI } from "@langchain/openai";

const llm = new ChatOpenAI({
  model: "gpt-4.1-nano",
  apiKey: process.env.OPENAI_API_KEY,
});

const response = await llm.invoke(
  "Say hello in three languages.",
  langchain.addCallback({
    runName: "hello_langchain",
    tags: ["langchain", "quickstart"],
    metadata: { framework: "langchain" },
  })
);
console.log(response.content);
```

#### View your trace

Open the [Traces page](https://platform.respan.ai/platform/traces) to see your LangChain workflow with chain runs, LLM calls, retriever spans, and tool calls.

## 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 inputs and outputs on LangChain spans.                      |
| `include_metadata`    | `bool`         | `True`  | Includes LangChain 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": "langchain-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 langchain_openai import ChatOpenAI
from respan import Respan, propagate_attributes
from respan_instrumentation_langchain import (
    LangChainInstrumentor,
    add_respan_callback,
)

respan = Respan(instrumentations=[LangChainInstrumentor()])
llm = ChatOpenAI(model="gpt-4.1-nano")

def handle_request(user_id: str, question: str):
    with propagate_attributes(
        customer_identifier=user_id,
        thread_identifier="conv_abc_123",
        metadata={"plan": "pro"},
    ):
        response = llm.invoke(
            question,
            config=add_respan_callback({"run_name": "user_question"}),
        )
        print(response.content)
```

```typescript TypeScript
async function handleRequest(userId: string, question: string) {
  await respan.propagateAttributes(
    {
      customer_identifier: userId,
      thread_identifier: "conv_abc_123",
      metadata: { plan: "pro" },
    },
    async () => {
      const response = await llm.invoke(
        question,
        langchain.addCallback({ runName: "user_question" })
      );
      console.log(response.content);
    }
  );
}
```

| 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. LangChain spans are emitted automatically by the instrumentor in Python, and when you attach the Respan callback config in TypeScript. Use `@workflow` and `@task` (Python) or `withWorkflow` and `withTask` (TypeScript) to add structure when you want to group related runs into a named workflow with nested tasks.

```python Python
from langchain_openai import ChatOpenAI
from respan import Respan, workflow, task
from respan_instrumentation_langchain import (
    LangChainInstrumentor,
    add_respan_callback,
)

respan = Respan(instrumentations=[LangChainInstrumentor()])
llm = ChatOpenAI(model="gpt-4.1-nano")

@task(name="generate_outline")
def outline(topic: str) -> str:
    return llm.invoke(
        f"Create a brief outline about: {topic}",
        config=add_respan_callback({"run_name": "outline_model"}),
    ).content

@workflow(name="content_pipeline")
def pipeline(topic: str):
    plan = outline(topic)
    response = llm.invoke(
        f"Write content from this outline: {plan}",
        config=add_respan_callback({"run_name": "draft_model"}),
    )
    print(response.content)

pipeline("Benefits of API gateways")
```

```typescript TypeScript
async function outline(topic: string) {
  return respan.withTask({ name: "generate_outline" }, async () => {
    const response = await llm.invoke(
      `Create a brief outline about: ${topic}`,
      langchain.addCallback({ runName: "outline_model" })
    );
    return response.content;
  });
}

await respan.withWorkflow({ name: "content_pipeline" }, async () => {
  const plan = await outline("Benefits of API gateways");
  const response = await llm.invoke(
    `Write content from this outline: ${plan}`,
    langchain.addCallback({ runName: "draft_model" })
  );
  console.log(response.content);
});
```

## Examples

### Chains

Chains are traced as workflow/task spans with nested LLM and tool spans.

```python
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from respan_instrumentation_langchain import add_respan_callback

prompt = ChatPromptTemplate.from_messages([
    ("system", "Translate the user text to {language}."),
    ("human", "{text}"),
])
chain = prompt | ChatOpenAI(model="gpt-4.1-nano") | StrOutputParser()

result = chain.invoke(
    {"language": "French", "text": "Hello, Respan."},
    config=add_respan_callback({"run_name": "translation_chain"}),
)
print(result)
```

### Tools

Tool calls are captured with tool name, arguments, result, and timing.

```python
from langchain_core.tools import tool
from respan_instrumentation_langchain import add_respan_callback

@tool
def get_weather(city: str) -> str:
    """Get the weather for a city."""
    return f"It is sunny in {city}."

result = get_weather.invoke(
    {"city": "San Francisco"},
    config=add_respan_callback({"run_name": "weather_tool"}),
)
print(result)
```

### Streaming

Streaming responses are traced like regular calls.

```python
for chunk in llm.stream(
    "Write a haiku about Python.",
    config=add_respan_callback({"run_name": "streaming_model"}),
):
    print(chunk.content, end="", flush=True)
```