---
title: LangChain
description: Add the AgentPing callback handler to any LangChain chain, agent or LangGraph graph so model calls, tool calls and errors land on one run.
section: frameworks
order: 2
---

# LangChain

LangChain reports what a chain does through callbacks. AgentPing ships a callback handler for Python (`langchain-core`) and TypeScript (`@langchain/core`); pass it in `callbacks` or `config` and every model call, tool call and error inside the chain lands on the active run. LangGraph graphs use the same callback system, so the same handler works there.

## Install / enable

Python:

```bash
pip install "agentping-io[langchain]"
```

```python
import agentping
from agentping import AgentPingCallbackHandler
from langchain_openai import ChatOpenAI

agentping.init()

llm = ChatOpenAI(model="gpt-4o-mini")

with agentping.run("support-triage", customer_id="acme-corp"):
    reply = llm.invoke(
        "classify this ticket",
        config={"callbacks": [AgentPingCallbackHandler()]},
    )
```

`AgentPingCallbackHandler(run=None, capture_tool_payloads=True, tool_payload_max_chars=4000)`. Supported `langchain-core` versions: 0.3 up to, but not including, 2.0. The handler is plain `BaseCallbackHandler`, so it goes anywhere LangChain accepts callbacks: `invoke(config=...)`, `with_config`, an agent executor, or a LangGraph `graph.invoke(state, config={"callbacks": [...]})`.

TypeScript:

```bash
npm install @agentping/sdk @langchain/core
```

```typescript
import { ChatOpenAI } from "@langchain/openai";
import * as agentping from "@agentping/sdk";

agentping.init({ apiKey: process.env.AGENTPING_API_KEY });

const run = agentping.run("support-triage", { customerId: "acme-corp" });
const handler = new agentping.AgentPingLangChainCallbackHandler(run);

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const reply = await llm.invoke("classify this ticket", { callbacks: [handler] });

await run.finish({ status: "success" });
```

`new AgentPingLangChainCallbackHandler(run?, { captureToolPayloads?, toolPayloadMaxChars? })`.

## Events

| LangChain callback | AgentPing event | What is recorded |
|---|---|---|
| `on_llm_end` / `handleLLMEnd` | `llm_call` | Provider, model, input and output tokens from `usage_metadata` (cached and reasoning tokens when the provider reports them), latency. |
| `on_llm_error` / `handleLLMError` | `llm_call` with `status: "error"` | Provider, model, the exception message and class, latency. |
| `on_tool_end` / `handleToolEnd` | `tool_call` | Tool name, `tool_invocation_id` (LangChain's `run_id`), input and output (capped at `tool_payload_max_chars`), latency. |
| `on_tool_error` / `handleToolError` | `tool_call` with `status: "error"` | Tool name, input, the exception message and class, latency. |
| `on_chain_error` / `handleChainError` | `error` | The exception message and class. The run stays open; finish it with the status you want. |

`provider` is inferred from the model class name (`ChatAnthropic` gives `anthropic`), the `ls_provider` metadata LangChain attaches, or the model name prefix, and falls back to `langchain` when none of those identify it. Token counts come from LangChain's normalised `usage_metadata`, so what you see is what the provider reported to LangChain; Anthropic's cache split is passed through as the provider gives it.

Streamed model calls are recorded the same way, from the final `on_llm_end`, but without a `stream` flag. Callbacks that are not listed (`on_retriever_end`, `on_chain_end`, `on_agent_action`) emit nothing.

## Naming and runs

One handler instance per AgentPing run is the simplest pattern: create the handler after `agentping.run(...)` and pass it to the top-level `invoke`. LangChain propagates callbacks down to every nested chain, tool and model call, so one handler at the top covers the whole graph.

In Python, `tool_call` and `error` events honour an explicit `run=`; `llm_call` events go to the run that is active in the current context. Inside a `with agentping.run(...)` block those are the same run. If you build handlers for runs that are not the active one (a worker fanning out several runs on one thread), keep the model calls inside the run's `with` block rather than relying on `run=` alone.

## Source / notes

- Python: `agentping.AgentPingCallbackHandler` in [agent-ping-python](https://github.com/agent-ping/agent-ping-python)
- TypeScript: `AgentPingLangChainCallbackHandler` in [agent-ping-typescript](https://github.com/agent-ping/agent-ping-typescript)

For span-level traces of a LangGraph graph (node timing, state at each step), export LangChain's OpenTelemetry traces to the [OpenTelemetry](/docs/integrations/opentelemetry) endpoint; the callback handler and the OTLP route can run side by side.
