# LangChain and LangGraph

Source: https://immiscible.fly.dev/docs/sdks/integrations/langchain

# LangChain and LangGraph

A guarded tool asks Immiscible before it runs and settles after. It keeps its name, description and schema, so it drops into `ToolNode`, `bindTools` / `bind_tools` and the prebuilt agents unchanged.

Tested against `@langchain/core` 1.2 with `@langchain/langgraph` 1.4, and `langchain-core` 1.6 with `langgraph` 1.2, through a real `ToolNode`.

## TypeScript

```shell
npm install @immiscible/sdk @langchain/core @langchain/langgraph zod
```

```ts
import { tool } from '@langchain/core/tools';
import { ToolNode } from '@langchain/langgraph/prebuilt';
import { ChatOpenAI } from '@langchain/openai';
import { z } from 'zod';
import { Immiscible } from '@immiscible/sdk';
import { guardLangChainTools } from '@immiscible/sdk/langchain';

const immiscible = new Immiscible().run({ client: 'langchain' });

const buy = tool(async ({ pence, domain }) => placeOrder(pence, domain), {
  name: 'buy', description: 'Buy groceries', schema: z.object({ pence: z.number().int(), domain: z.string() }),
});

const tools = guardLangChainTools([buy, search], {
  client: immiscible,
  mapToAction: ({ name, args }) => name === 'search' ? null
    : Immiscible.paymentAction({ amount: args.pence, currency: 'GBP', merchant: args.domain, provenance: [{ source: 'user' }] }),
});

const model = new ChatOpenAI({ model: 'gpt-5-mini', configuration: immiscible.gateway.openai() }).bindTools(tools);
const toolNode = new ToolNode(tools);
```

## Python

```shell
python3 -m venv .venv && . .venv/bin/activate
pip install immiscible langchain-core langgraph
```

```python
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode
from immiscible import Immiscible
from immiscible.integrations import guard_langchain_tools

immiscible = Immiscible().run(client="langchain")

@tool
def buy(pence: int, domain: str) -> str:
    """Buy groceries from a supermarket."""
    return place_order(pence, domain)

tools = guard_langchain_tools(
    [buy, search],
    client=immiscible,
    map_to_action=lambda call: None if call.name == "search"
        else Immiscible.payment_action(call.args["pence"], "GBP", call.args["domain"], provenance=[{"source": "user"}]),
)
node = ToolNode(tools)                       # in a StateGraph, or create_react_agent(model, tools)
```

Both `invoke` and `ainvoke` are guarded; the async path makes its HTTP calls in a worker thread. When `ToolNode` hands over a tool call, a refusal comes back as a `ToolMessage` with `status="error"` and the tool call's id, as `ToolNode` requires. Point a chat model at the gateway with `ChatOpenAI(**immiscible.gateway.openai())` (base URL, key and headers) or `ChatOpenAI(http_client=...)` with the run's hooks.

You can also guard the function under `@tool`:

```python
from immiscible.integrations import guarded

@tool
@guarded(to_action, client=immiscible)
def buy(pence: int, domain: str) -> str:
    """Buy groceries from a supermarket."""
```

## A person in the loop

The default waits for the person inside the tool call, up to ten minutes. In a LangGraph graph with a checkpointer you may prefer to pause the graph instead:

- `wait: false` / `wait=False`: refuse at once with "waiting for the person to approve"; route on it, and retry the tool call after approval with the same tool call id (it is the idempotency key, so the retry finds the same action, now allowed).
- `onApprovalRequired` / `on_approval_required`: call LangGraph's `interrupt({ approve: d.approval.url })` there to suspend the graph until the person answers.

Options are the same as for the [OpenAI Agents SDK](https://immiscible.fly.dev/docs/sdks/integrations/openai-agents.md#options).

Examples: [`packages/immiscible-js/examples/langchain.mjs`](https://github.com/efr7-7/immiscible/blob/main/packages/immiscible-js/examples/langchain.mjs), [`packages/immiscible-py/examples/langchain_example.py`](https://github.com/efr7-7/immiscible/blob/main/packages/immiscible-py/examples/langchain_example.py). Both run against the fake with `--demo`.
