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

# LangGraph / LangChain

> Automatic tracing for LangGraph and LangChain agents with Bitfab

Bitfab integrates with [LangGraph](https://langchain-ai.github.io/langgraph/) and [LangChain](https://python.langchain.com/) via a callback handler that automatically captures graph node execution, LLM calls, tool invocations, and retriever queries as traced spans, with no manual `withSpan` or `@span` decorators needed on your graph nodes.

**Canonical signatures:** [TypeScript reference](/reference/typescript#framework-integrations) · [Python reference](/reference/python#framework-integrations)

## Supported Languages

| Language   | Method                             | Status            |
| ---------- | ---------------------------------- | ----------------- |
| TypeScript | `getLangGraphCallbackHandler()`    | ✅ Supported       |
| Python     | `get_langgraph_callback_handler()` | ✅ Supported       |
| Ruby       | -                                  | Not yet supported |
| Go         | -                                  | Not yet supported |

Working with plain LangChain (no LangGraph)? The same handler serves both, and the SDK exposes it under a LangChain name too: `getLangChainCallbackHandler()` / `get_langchain_callback_handler()` and the `BitfabLangChainCallbackHandler` class are aliases of their LangGraph counterparts. Use whichever name matches your stack; the behavior is identical.

## Quick Start

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Bitfab } from "@bitfab/sdk"

  const bitfab = new Bitfab({ apiKey: process.env.BITFAB_API_KEY })
  const handler = bitfab.getLangGraphCallbackHandler("my-agent")

  const result = await agent.invoke(
    { messages: [{ role: "user", content: "What's the weather?" }] },
    { callbacks: [handler] },
  )
  ```

  ```python Python theme={null}
  import os
  from bitfab import Bitfab

  bitfab = Bitfab(api_key=os.environ["BITFAB_API_KEY"])
  handler = bitfab.get_langgraph_callback_handler("my-agent")

  result = agent.invoke(
      {"messages": [{"role": "user", "content": "What's the weather?"}]},
      config={"callbacks": [handler]},
  )
  ```
</CodeGroup>

### Plain LangChain Chains

The handler works the same way on LangChain chains and runnables; pass it in `callbacks` when invoking:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const handler = bitfab.getLangChainCallbackHandler("summarize-doc")

  const result = await chain.invoke(
    { document: docText },
    { callbacks: [handler] },
  )
  ```

  ```python Python theme={null}
  handler = bitfab.get_langchain_callback_handler("summarize-doc")

  result = chain.invoke(
      {"document": doc_text},
      config={"callbacks": [handler]},
  )
  ```
</CodeGroup>

## What Gets Captured

The callback handler hooks into LangChain's callback system and creates spans automatically:

| Event                | Span Type  | Captured Data                                                                               |
| -------------------- | ---------- | ------------------------------------------------------------------------------------------- |
| Graph nodes (chains) | `agent`    | Configured run name or node name, inputs, outputs, LangGraph metadata                       |
| Chat model calls     | `llm`      | Configured run name or model name, messages (role/content), token usage, LangGraph metadata |
| LLM calls            | `llm`      | Configured run name or model name, prompts, token usage, LangGraph metadata                 |
| Tool invocations     | `function` | Configured run name or tool name, input, output                                             |
| Retriever queries    | `function` | Configured run name or retriever name, query, documents                                     |

### Trace Lifecycle

The first callback in a framework invocation becomes the trace root. For full LangGraph runs this is usually a chain/graph node; for plain LangChain usage it can also be a direct chat model, LLM, tool, or retriever call. The handler registers that root immediately as a pending external trace, then completes it when the root callback ends, so long-running invocations can appear as in progress before their final output is available.

If the handler runs inside an active Bitfab `withSpan` / `@span` root, its spans attach to that outer trace instead. In that case the handler still records the framework span tree, but the outer Bitfab root owns final trace completion.

You do **not** need an outer `withSpan` / `@span` root when the workflow is just the LangGraph or LangChain invocation. Add one only when there is meaningful application work around `invoke()` that should be part of the same trace, such as input preparation, retrieval outside LangChain, post-processing, persistence, or downstream service calls. When you do add an outer root, use the same trace function key for the outer span and the callback handler.

### LangGraph Metadata

LangGraph-specific metadata is automatically extracted and stored as span context:

* `langgraph_step`: Current step number
* `langgraph_node`: Current node name
* `langgraph_triggers`: What triggered this node
* `langgraph_path`: Execution path
* `langgraph_checkpoint_ns`: Checkpoint namespace

### Token Usage

For LLM spans, token usage is captured from the LLM result. The handler prefers LangChain's standardized `usage_metadata` on each generation's message (which is also how usage arrives on the final aggregated chunk of streaming runs), then falls back to provider-native `response_metadata` shapes (OpenAI, Anthropic, Google Gemini / Vertex), and finally the legacy `llm_output.token_usage` location:

* `inputTokens`: Prompt tokens. For Anthropic, cache reads and cache creation are added back so the value reflects the true prompt size.
* `outputTokens`: Completion tokens
* `totalTokens`: Total tokens
* `cachedInputTokens`: Cached prompt tokens, from `usage_metadata.input_token_details.cache_read`, OpenAI `prompt_tokens_details.cached_tokens`, or Anthropic `cache_read_input_tokens`
* `model`: Model name (extracted from serialized config or metadata)

When a result has multiple generations, usage is summed across them. Only provider-reported numbers are recorded: if the provider reports nothing (for example OpenAI streaming without `stream_usage: true` / `stream_options: {"include_usage": true}`), the fields are left unset rather than estimated.

## TypeScript

### Installation

```bash theme={null}
npm install @bitfab/sdk @langchain/core @langchain/langgraph @langchain/openai zod
```

### Method Signature

```typescript theme={null}
bitfab.getLangGraphCallbackHandler(traceFunctionKey: string): BitfabLangGraphCallbackHandler
```

**Parameters:**

* `traceFunctionKey` (string, required): Groups all traces from this handler under one key in Bitfab

**Returns:** A `BitfabLangGraphCallbackHandler` that implements the LangChain callback handler interface (duck-typed, no `@langchain/core` dependency required).

### Usage

```typescript theme={null}
import { Bitfab } from "@bitfab/sdk"
import { ChatOpenAI } from "@langchain/openai"
import { createReactAgent } from "@langchain/langgraph/prebuilt"
import { tool } from "@langchain/core/tools"
import { z } from "zod"

const bitfab = new Bitfab({ apiKey: process.env.BITFAB_API_KEY })

const getWeather = tool(
  async ({ city }) => {
    return `Sunny, 72°F in ${city}`
  },
  {
    name: "getWeather",
    description: "Get the current weather for a city.",
    schema: z.object({ city: z.string() }),
  },
)

const model = new ChatOpenAI({ model: "gpt-4o-mini" })
const agent = createReactAgent({ llm: model, tools: [getWeather] })

// Create the callback handler
const handler = bitfab.getLangGraphCallbackHandler("weather-agent")

// Pass as a callback: all graph execution is traced automatically
const result = await agent.invoke(
  { messages: [{ role: "user", content: "What's the weather in SF?" }] },
  { callbacks: [handler] },
)
```

### Callback Hooks

LangChain invokes these callback hooks on the handler:

| Method                      | Creates Span               | Type       |
| --------------------------- | -------------------------- | ---------- |
| `handleChainStart(...)`     | Yes                        | `agent`    |
| `handleChainEnd(...)`       | Completes span             | -          |
| `handleChainError(...)`     | Completes span with error  | -          |
| `handleChatModelStart(...)` | Yes                        | `llm`      |
| `handleLLMStart(...)`       | Yes                        | `llm`      |
| `handleLLMEnd(...)`         | Completes span with tokens | -          |
| `handleLLMError(...)`       | Completes span with error  | -          |
| `handleToolStart(...)`      | Yes                        | `function` |
| `handleToolEnd(...)`        | Completes span             | -          |
| `handleToolError(...)`      | Completes span with error  | -          |
| `handleRetrieverStart(...)` | Yes                        | `function` |
| `handleRetrieverEnd(...)`   | Completes span             | -          |
| `handleRetrieverError(...)` | Completes span with error  | -          |

### Nesting with Core Tracing

The handler integrates with Bitfab's span stack. If the application has meaningful work around the graph or chain invocation, create a same-key `withSpan` wrapper around that outer workflow and the LangGraph spans nest as children. Handler-only instrumentation is enough for a plain LangGraph / LangChain call:

```typescript theme={null}
const pipeline = bitfab.getFunction("my-pipeline")

const tracedRun = pipeline.withSpan(
  { name: "RunAgent", type: "agent" },
  async (query: string) => {
    const handler = pipeline.getLangGraphCallbackHandler() // same key as the root
    return agent.invoke(
      { messages: [{ role: "user", content: query }] },
      { callbacks: [handler] },
    )
  },
)

await tracedRun("What's the weather?")
// LangGraph spans appear nested under the "RunAgent" span
```

**Use the same trace function key in both places.** Both `bitfab.getFunction(...)` and `bitfab.getLangGraphCallbackHandler(...)` take a key; pass the **same key** (`"my-pipeline"` above) to both. If you use two different keys here, the same flow will register as two separate overlapping trace functions in the dashboard, an anti-pattern to avoid.

### Error Handling

* **GraphBubbleUp**: LangGraph's internal interrupt mechanism. Detected automatically and completed silently (no error recorded).
* **All other errors**: Error message captured in the span. The handler never throws; all callbacks are wrapped in try/catch.
* **Missing serialized callback data**: Some framework versions can pass nullish serialized payloads. The handler still records the span using fallback names such as `chain`, `llm`, `tool`, or `retriever`.
* **Reusability**: The handler resets after each root span completes and can be reused across multiple invocations.

## Python

### Installation

```bash theme={null}
pip install bitfab-py langchain-core langgraph langchain-openai
```

### Method Signature

```python theme={null}
bitfab.get_langgraph_callback_handler(trace_function_key: str) -> BitfabLangGraphCallbackHandler
```

**Parameters:**

* `trace_function_key` (str, required): Groups all traces from this handler under one key in Bitfab

**Returns:** A `BitfabLangGraphCallbackHandler` instance that extends `BaseCallbackHandler` from `langchain-core`.

### Usage

```python theme={null}
import os
from bitfab import Bitfab
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent

bitfab = Bitfab(api_key=os.environ["BITFAB_API_KEY"])

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"Sunny, 72°F in {city}"

model = ChatOpenAI(model="gpt-4o-mini")
agent = create_react_agent(model, tools=[get_weather])

# Create the callback handler
handler = bitfab.get_langgraph_callback_handler("weather-agent")

# Pass as a callback: all graph execution is traced automatically
result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's the weather in SF?"}]},
    config={"callbacks": [handler]},
)
```

### Callback Methods

The handler implements these LangChain callback methods:

| Method                                                               | Creates Span               | Type       |
| -------------------------------------------------------------------- | -------------------------- | ---------- |
| `on_chain_start(serialized, inputs, *, run_id, parent_run_id?, ...)` | Yes                        | `agent`    |
| `on_chain_end(outputs, *, run_id, ...)`                              | Completes span             | -          |
| `on_chain_error(error, *, run_id, ...)`                              | Completes span with error  | -          |
| `on_chat_model_start(serialized, messages, *, run_id, ...)`          | Yes                        | `llm`      |
| `on_llm_start(serialized, prompts, *, run_id, ...)`                  | Yes                        | `llm`      |
| `on_llm_end(response, *, run_id, ...)`                               | Completes span with tokens | -          |
| `on_llm_error(error, *, run_id, ...)`                                | Completes span with error  | -          |
| `on_tool_start(serialized, input_str, *, run_id, ...)`               | Yes                        | `function` |
| `on_tool_end(output, *, run_id, ...)`                                | Completes span             | -          |
| `on_tool_error(error, *, run_id, ...)`                               | Completes span with error  | -          |
| `on_retriever_start(serialized, query, *, run_id, ...)`              | Yes                        | `function` |
| `on_retriever_end(documents, *, run_id, ...)`                        | Completes span             | -          |
| `on_retriever_error(error, *, run_id, ...)`                          | Completes span with error  | -          |

### Nesting with Core Tracing

Bind the key once with `get_function` so the root and the handler share it (no repeated string to keep in sync). Use this only when the application has meaningful work around the graph or chain invocation; handler-only instrumentation is enough for a plain LangGraph / LangChain call:

```python theme={null}
pipeline = bitfab.get_function("my-pipeline")


@pipeline.span(type="agent")
def run_agent(query: str):
    handler = pipeline.get_langgraph_callback_handler()  # same key as the root
    return agent.invoke(
        {"messages": [{"role": "user", "content": query}]},
        config={"callbacks": [handler]},
    )


run_agent("What's the weather?")
# LangGraph spans appear nested under the "my-pipeline" span
```

**Use the same trace function key for the root and the handler.** Binding via `get_function` does this for you. The plain `@bitfab.span("my-pipeline")` / `bitfab.get_langgraph_callback_handler("my-pipeline")` forms work too, but you must pass the **same key** to both, otherwise the same flow registers as two separate overlapping trace functions in the dashboard, an anti-pattern to avoid.

### Error Handling

* **GraphBubbleUp**: LangGraph's internal interrupt mechanism. Detected automatically and completed silently (no error recorded).
* **All other errors**: `repr(error)` captured in the span. The handler never raises; all callbacks are wrapped in try/except.
* **Missing serialized callback data**: Some framework versions can pass nullish serialized payloads. The handler still records the span using fallback names such as `chain`, `llm`, `tool`, or `retriever`.
* **Reusability**: The handler resets after each root span completes and can be reused across multiple invocations.

## Replay

Handler-instrumented graphs are fully replayable, even though no `@span` / `withSpan` root exists in the application code. The handler records the graph invocation as the root span, with the initial graph state as the recorded input. To replay, pass the handler's key plus a plain callable that re-invokes the graph (the SDK wraps it internally):

<CodeGroup>
  ```python Python theme={null}
  handler = bitfab.get_langgraph_callback_handler("weather-agent")  # same key


  def replay_weather_agent(state):  # recorded state arrives as one argument
      return agent.invoke(state, config={"callbacks": [handler]})


  result = bitfab.replay("weather-agent", replay_weather_agent, limit=10)
  ```

  ```typescript TypeScript theme={null}
  const handler = bitfab.getLangGraphCallbackHandler("weather-agent")

  const replayWeatherAgent = async (
    state: AgentState,  // recorded state arrives as a single argument
  ) => agent.invoke(state, { callbacks: [handler] })

  const result = await bitfab.replay("weather-agent", replayWeatherAgent, {
    limit: 10,
  })
  ```
</CodeGroup>

The key is the only link between the handler-recorded production traces and the replay callable. Rebuild any runtime environment inside the callable (`config["configurable"]` values, dependency objects, API keys); the trace records only the graph state. Use no-op substitutes for side-effectful wiring (billing callbacks, notification senders). On SDKs that predate explicit-key replay, wrap the callable under the same key yourself (Python: `@bitfab.span("weather-agent")` with a `(**state)` signature; TypeScript: `getFunction("weather-agent").withSpan(...)`). Full details: **Replaying handler-instrumented functions** in the [Python SDK](/python-sdk#replay) and [TypeScript SDK](/typescript-sdk#replay) pages.
