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

# LangChain (Beta)

> Automatic tracing for LangChain applications with Raindrop

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @raindrop-ai/langchain @langchain/core @langchain/openai
  ```

  ```bash pnpm theme={null}
  pnpm add @raindrop-ai/langchain @langchain/core @langchain/openai
  ```

  ```bash pip theme={null}
  pip install raindrop-langchain langchain-core langchain-openai
  ```
</CodeGroup>

## Quick Start

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createRaindropLangChain } from "@raindrop-ai/langchain";
  import { ChatOpenAI } from "@langchain/openai";
  import { HumanMessage } from "@langchain/core/messages";

  const raindrop = createRaindropLangChain({
    writeKey: "your-write-key",
    userId: "user-123",
  });

  const model = new ChatOpenAI({ model: "gpt-4o" });

  const result = await model.invoke(
    [new HumanMessage("What is the capital of France?")],
    { callbacks: [raindrop.handler] },
  );

  await raindrop.flush();
  ```

  ```python Python theme={null}
  from raindrop_langchain import create_raindrop_langchain
  from langchain_openai import ChatOpenAI
  from langchain_core.messages import HumanMessage

  raindrop = create_raindrop_langchain(
      api_key="your-write-key",
      user_id="user-123",
  )

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

  result = model.invoke(
      [HumanMessage(content="What is the capital of France?")],
      config={"callbacks": [raindrop.handler]},
  )

  raindrop.flush()
  ```
</CodeGroup>

## What Gets Traced

The LangChain integration automatically captures:

* **LLM calls** — model name, input messages, output text, token usage (prompt/completion/total), finish reason
* **Tool calls** — tool name, input arguments, output, duration (via `interaction.track_tool()` spans)
* **Chains** — chain execution with nested spans
* **Retrievers** — query text and document count
* **Agent actions** — tool selection and execution
* **Errors** — captured with error status on the span
* **Tags and metadata** — LangChain tags and metadata are forwarded to Raindrop event properties
* **Extended token categories** — cached tokens (`ai.usage.cached_tokens`) and reasoning tokens (`ai.usage.thoughts_tokens`) when available from the provider (e.g. OpenAI)
* **Finish reason** — captured as `ai.finish_reason` in event properties (e.g. `"stop"`, `"length"`)

All operations are linked with parent-child relationships, so you can see the full execution tree in the Raindrop dashboard.

## Configuration

<CodeGroup>
  ```typescript TypeScript theme={null}
  const raindrop = createRaindropLangChain({
    writeKey: "your-write-key",                // Optional: your Raindrop write key (omit to disable telemetry)
    endpoint: "...",                   // Optional: custom API endpoint
    userId: "user-123",               // Optional: associate events with a user
    convoId: "convo-456",             // Optional: conversation/thread ID
    projectId: "support-prod",        // Optional: route events to a specific project (slug)
    eventName: "support_agent",       // Optional: event name for every event (default: "ai_generation")
    debug: false,                      // Optional: enable verbose logging
    traceChains: true,                 // Optional: create spans for chain execution (default: true)
    traceRetrievers: true,             // Optional: create spans for retriever calls (default: true)
    filterLangGraphInternals: true,    // Optional: filter LangGraph noise + dedup (default: true)
  });
  ```

  ```python Python theme={null}
  raindrop = create_raindrop_langchain(
      api_key="your-write-key",                    # Optional: your Raindrop API key (omit to disable telemetry)
      user_id="user-123",                 # Optional: associate events with a user
      convo_id="convo-456",               # Optional: conversation/thread ID
      project_id="support-prod",          # Optional: route events to a specific project (slug)
      event_name="support_agent",          # Optional: event name for every event (default: "ai_generation")
      trace_chains=True,                   # Optional: track chain execution (default: True)
      trace_retrievers=True,               # Optional: track retriever calls (default: True)
      filter_langgraph_internals=True,     # Optional: filter LangGraph noise + dedup (default: True)
      tracing_enabled=True,                # Optional: enable distributed tracing (default: True)
      bypass_otel_for_tools=True,          # Optional: bypass OTEL for tool spans (default: True)
      debug=False,                         # Optional: enable debug logging (default: False)
  )
  ```
</CodeGroup>

## Event name

Every event this integration ships carries an event name (the `event` field in
the dashboard). It defaults to `ai_generation`. Set `eventName` (`event_name` in
Python) to label LangChain traffic — e.g. distinguish a support agent from a
billing agent:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const raindrop = createRaindropLangChain({
    writeKey: "your-write-key",
    eventName: "support_agent",
  });
  ```

  ```python Python theme={null}
  raindrop = create_raindrop_langchain(
      api_key="your-write-key",
      event_name="support_agent",
  )
  ```
</CodeGroup>

## Projects

Route events to a specific [project](/platform/projects) by passing its slug as `projectId` (`project_id` in Python):

<CodeGroup>
  ```typescript TypeScript theme={null}
  const raindrop = createRaindropLangChain({
    writeKey: "your-write-key",
    projectId: "support-prod",
  });
  ```

  ```python Python theme={null}
  raindrop = create_raindrop_langchain(
      api_key="your-write-key",
      project_id="support-prod",
  )
  ```
</CodeGroup>

This sets the `X-Raindrop-Project-Id` header on every event. Omit it (or pass `"default"`) to use your org's default **Production** project, which is the existing behavior. Single-project orgs need nothing new.

### Multiple projects in one process

Available in `raindrop-ai>=0.0.56`. When one service runs several LangChain
agents that should report to **different** projects, create one
`RaindropLangchain` wrapper per project. Each wrapper owns its own
`raindrop.Raindrop` client, so the two route independently — there is no shared
module-level state:

```python theme={null}
from raindrop_langchain import RaindropLangchain

# One long-lived wrapper per project, created at startup and reused.
rd_support = RaindropLangchain(api_key="your-write-key", project_id="support-prod")
rd_billing = RaindropLangchain(api_key="your-write-key", project_id="billing-prod")

# Pass each wrapper's handler to the corresponding chain/agent.
support_result = support_chain.invoke(
    "How do I reset my password?",
    config={"callbacks": [rd_support.handler]},   # -> support-prod
)
billing_result = billing_chain.invoke(
    "Why was I charged twice?",
    config={"callbacks": [rd_billing.handler]},    # -> billing-prod
)

rd_support.flush()
rd_billing.flush()
```

Each wrapper owns its configuration and delivery pipeline, so concurrent
requests handled by different agents route independently. To share a single
client across wrappers (or with the module-level API), construct a
`raindrop.Raindrop` yourself and pass it via `client=`:

```python theme={null}
from raindrop import Raindrop

client = Raindrop(api_key="your-write-key", project_id="support-prod")
rd = RaindropLangchain(client=client)
```

## Using with LangGraph

The Raindrop handler works with LangGraph out of the box. It automatically filters
LangGraph-internal chain events (graph executor, `__start__`, `__end__`, channel nodes)
and deduplicates LLM callbacks that LangGraph may fire multiple times.

Pass the handler both at graph invocation time and inside your LLM node:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createRaindropLangChain } from "@raindrop-ai/langchain";
  import { ChatOpenAI } from "@langchain/openai";
  import { HumanMessage } from "@langchain/core/messages";
  import { StateGraph, MessagesAnnotation, END } from "@langchain/langgraph";
  import { ToolNode } from "@langchain/langgraph/prebuilt";
  import { tool } from "@langchain/core/tools";
  import { z } from "zod";

  const raindrop = createRaindropLangChain({
    writeKey: "your-write-key",
    userId: "user-123",
    convoId: "convo-456",
  });

  const getWeather = tool(
    async ({ city }) => `The weather in ${city} is 22°C.`,
    { name: "get_weather", description: "Get weather", schema: z.object({ city: z.string() }) },
  );

  const model = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools([getWeather]);

  const callModel = async (state: typeof MessagesAnnotation.State) => {
    const response = await model.invoke(state.messages, {
      callbacks: [raindrop.handler],
    });
    return { messages: [response] };
  };

  const graph = new StateGraph(MessagesAnnotation)
    .addNode("agent", callModel)
    .addNode("tools", new ToolNode([getWeather]))
    .addEdge("__start__", "agent")
    .addConditionalEdges("agent", (state) => {
      const last = state.messages[state.messages.length - 1];
      if ("tool_calls" in last && Array.isArray(last.tool_calls) && last.tool_calls.length > 0) return "tools";
      return END;
    })
    .addEdge("tools", "agent")
    .compile();

  // Pass callbacks only to the model inside the node — NOT to graph.invoke().
  // This ensures clean, deduplicated events without LangGraph internal noise.
  const result = await graph.invoke(
    { messages: [new HumanMessage("What's the weather in Paris?")] },
  );

  await raindrop.shutdown();
  ```

  ```python Python theme={null}
  from raindrop_langchain import create_raindrop_langchain
  from langchain_openai import ChatOpenAI
  from langchain_core.messages import HumanMessage
  from langchain_core.tools import tool
  from langgraph.graph import StateGraph, MessagesState, END
  from langgraph.prebuilt import ToolNode

  raindrop = create_raindrop_langchain(
      api_key="your-write-key",
      user_id="user-123",
      convo_id="convo-456",
  )

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

  model = ChatOpenAI(model="gpt-4o-mini").bind_tools([get_weather])

  def call_model(state: MessagesState):
      response = model.invoke(
          state["messages"],
          config={"callbacks": [raindrop.handler]},
      )
      return {"messages": [response]}

  def should_continue(state: MessagesState):
      last = state["messages"][-1]
      if hasattr(last, "tool_calls") and last.tool_calls:
          return "tools"
      return END

  graph = (
      StateGraph(MessagesState)
      .add_node("agent", call_model)
      .add_node("tools", ToolNode([get_weather]))
      .add_edge("__start__", "agent")
      .add_conditional_edges("agent", should_continue)
      .add_edge("tools", "agent")
      .compile()
  )

  # Pass callbacks only to the model inside the node — NOT to graph.invoke().
  result = graph.invoke(
      {"messages": [HumanMessage(content="What's the weather in Paris?")]},
  )

  raindrop.shutdown()
  ```
</CodeGroup>

### LangGraph Best Practices

* **Pass callbacks to the model, not the graph** — use `callbacks: [raindrop.handler]` inside your LLM node function only. Do NOT pass callbacks to `graph.invoke()` — LangGraph's internal callback propagation causes duplicate events and noisy chain spans when callbacks are on the graph level.
* **Create a new handler per request** in server environments to avoid state collisions between concurrent graph executions.
* **LangGraph-internal filtering is on by default** — the `filterLangGraphInternals` option (default: `true`) skips noisy internal chain events from the graph executor and node wrappers. Set to `false` if you want full visibility into LangGraph internals.

## Using with LangSmith

Raindrop and LangSmith can coexist — both use LangChain's callback system and receive
the same events independently. There is no conflict, but you should be aware of the
following:

* **Both tracers are active simultaneously** when `LANGSMITH_TRACING=true` is set. Each
  builds its own trace tree from the same callback events. This is safe but means LLM
  calls are traced twice (once by each system).
* **To use Raindrop only**, disable LangSmith tracing:
  ```bash theme={null}
  LANGSMITH_TRACING=false
  ```
* **To use both**, no changes needed. Both handlers receive callbacks and ship data
  independently. Raindrop traces appear in the Raindrop dashboard; LangSmith traces
  appear in LangSmith.
* **Performance**: having two tracers adds minimal overhead since both operate
  asynchronously and don't block the LLM pipeline.

## Usage with Chains

The same handler works with plain LangChain chains:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { ChatPromptTemplate } from "@langchain/core/prompts";
  import { StringOutputParser } from "@langchain/core/output_parsers";

  const prompt = ChatPromptTemplate.fromTemplate("Tell me about {topic}");
  const chain = prompt.pipe(model).pipe(new StringOutputParser());

  const result = await chain.invoke(
    { topic: "quantum computing" },
    { callbacks: [raindrop.handler] },
  );

  await raindrop.flush();
  ```

  ```python Python theme={null}
  from langchain_core.prompts import ChatPromptTemplate
  from langchain_core.output_parsers import StrOutputParser

  prompt = ChatPromptTemplate.from_template("Tell me about {topic}")
  chain = prompt | model | StrOutputParser()

  result = chain.invoke(
      {"topic": "quantum computing"},
      config={"callbacks": [raindrop.handler]},
  )

  raindrop.flush()
  ```
</CodeGroup>

## Passing Tags and Metadata

LangChain tags and metadata are forwarded to Raindrop event properties:

```typescript theme={null}
const result = await model.invoke(
  [new HumanMessage("Hello")],
  {
    callbacks: [raindrop.handler],
    tags: ["production", "v2"],
    metadata: { experimentId: "exp-123" },
  },
);
```

## Identify Users

Associate events with a user identity after initialization:

<CodeGroup>
  ```typescript TypeScript theme={null}
  raindrop.users.identify({ userId: "user-123", traits: { name: "Alice", plan: "pro" } });
  ```

  ```python Python theme={null}
  raindrop.identify("user-123", {"name": "Alice", "plan": "pro"})
  ```
</CodeGroup>

## Track Signals

Send feedback, edits, or custom signals tied to a specific event:

<CodeGroup>
  ```typescript TypeScript theme={null}
  raindrop.signals.track({
    eventId: "evt-abc",
    name: "thumbs_up",
    type: "feedback",
    sentiment: "POSITIVE",
  });
  ```

  ```python Python theme={null}
  raindrop.track_signal(
      event_id="evt-abc",
      name="thumbs_up",
      signal_type="feedback",
      sentiment="POSITIVE",
  )
  ```
</CodeGroup>

## Flushing and Shutdown

Always call `flush()` before your process exits to ensure all telemetry is shipped:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await raindrop.flush();     // flush pending data
  await raindrop.shutdown();  // flush + release resources
  ```

  ```python Python theme={null}
  raindrop.flush()     # flush pending data
  raindrop.shutdown()  # flush + release resources
  ```
</CodeGroup>
