# langchain — User Guide

LangChain is a Python framework for building **agents** and LLM-powered applications. It gives you one interface for talking to dozens of model providers, a production-ready agent loop (`create_agent`) built on LangGraph, a composable **middleware** system for retries, guardrails, human approval and context management, and the component library (messages, tools, prompts, output parsers, embeddings, vector stores, text splitters) that everything else is built from.

This guide covers the Python monorepo `langchain-ai/langchain` at commit `a9780cd`. It is organized by what you want to do: get a first result, install the right packages, build agents, add tools and memory, and fix things when they break. Architecture, internals, security and the complete configuration reference live in the companion **Admin Guide**.

| Package (PyPI) | Version in this snapshot | What it is |
|---|---|---|
| `langchain` | 1.4.3 | The actively maintained high-level package: `create_agent`, middleware, `init_chat_model`, `init_embeddings`, MCP adapter |
| `langchain-core` | 1.6.6 | Base abstractions: messages, Runnables, tools, prompts, output parsers, callbacks, vector store and retriever interfaces |
| `langchain-text-splitters` | 1.1.2 | Document chunking utilities |
| `langchain-openai`, `langchain-anthropic`, `langchain-ollama`, … | per package | Provider integrations (16 live in this repo) |
| `langchain-classic` | 1.0.8 | Legacy chains, `AgentExecutor`, memory classes — no new features |
| `langchain-tests` | 1.1.9 | Standard test suites for integration authors |
| `langchain-model-profiles` | 0.0.6 | Maintainer CLI (`langchain-profiles`) that generates model capability data |

> [!NOTE]
> Every package requires **Python 3.10 or newer** (`requires-python = ">=3.10.0,<4.0.0"`). LangChain has no server, no daemon and no configuration file of its own — it is a library you import. "Configuration" means constructor arguments, environment variables for provider credentials, and the per-call `config` dictionary.

---

## 1. Quick Start

The shortest path from zero to a working agent with a tool.

**Step 1 — install** the framework plus one provider package:

```bash
pip install -U langchain langchain-openai
```

```text
Successfully installed langchain-1.4.3 langchain-core-1.6.6 langchain-openai-1.6.6 langgraph-1.2.x openai-2.x ...
```

**Step 2 — set your provider key** in the environment:

```bash
export OPENAI_API_KEY="sk-..."
```

**Step 3 — talk to a model.** `init_chat_model` takes a `provider:model` string and returns a ready chat model:

```python
from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-5.5")
reply = model.invoke("Say hello in five words.")
print(reply.text)
```

```text
Hello there, nice to meet!
```

**Step 4 — build an agent.** Any typed Python function with a docstring can be a tool:

```python
from langchain.agents import create_agent


def get_weather(city: str) -> str:
    """Return the current weather for a city."""
    return f"It's always sunny in {city}!"


agent = create_agent(
    model="openai:gpt-5.5",
    tools=[get_weather],
    system_prompt="You are a helpful assistant.",
)

result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in Paris?"}]})
print(result["messages"][-1].text)
```

```text
It's always sunny in Paris!
```

> [!TIP]
> `result["messages"]` holds the whole conversation: your `HumanMessage`, the model's `AIMessage` with a tool call, the `ToolMessage` your function produced, and the model's final `AIMessage`. Print them all to see the agent loop at work.

> [!IMPORTANT]
> Model reply text varies from run to run. Outputs shown in this guide are representative, not exact.

- [x] Installed `langchain` and a provider package
- [x] Set the provider's API key
- [x] Got a model reply
- [x] Ran an agent that called a tool

---

## 2. Installation

### 2.1 Requirements

| Requirement | Value |
|---|---|
| Python | 3.10, 3.11, 3.12, 3.13 or 3.14 (`>=3.10.0,<4.0.0`) |
| Core runtime dependencies of `langchain` | `langchain-core>=1.6.3,<2.0.0`, `langgraph>=1.2.11,<1.3.0`, `pydantic>=2.7.4,<3.0.0` |
| Core runtime dependencies of `langchain-core` | `langsmith`, `httpx`, `tenacity`, `jsonpatch`, `PyYAML`, `typing-extensions`, `packaging`, `pydantic`, `uuid-utils`, `langchain-protocol` |
| A model provider | An API key for a hosted provider, or a local runtime such as Ollama |
| OS | Any OS that runs CPython; a few optional features are POSIX-only (see `HostExecutionPolicy` limits in the Admin Guide) |

### 2.2 Choose what to install

LangChain is split into a small core plus one package per provider. Install the framework, then add only the providers you use.

=== "uv (recommended by the project)"

    ```bash
    uv add langchain
    uv add langchain-openai        # or langchain-anthropic, langchain-ollama, ...
    ```

=== "pip"

    ```bash
    pip install -U langchain
    pip install -U langchain-openai
    ```

=== "pip with extras"

    ```bash
    # The langchain package declares an extra per provider
    pip install -U "langchain[openai,anthropic]"
    ```

=== "MCP support"

    ```bash
    # langchain.mcp needs FastMCP 4.x
    pip install -U "langchain[mcp]"
    ```

The `langchain` package declares these optional-dependency extras (each simply installs the named integration):

| Extra | Installs | Extra | Installs |
|---|---|---|---|
| `openai` | `langchain-openai` | `groq` | `langchain-groq` |
| `anthropic` | `langchain-anthropic` | `aws` | `langchain-aws` |
| `azure-ai` | `langchain-azure-ai` | `baseten` | `langchain-baseten>=0.2.0` |
| `google-vertexai` | `langchain-google-vertexai` | `deepseek` | `langchain-deepseek` |
| `google-genai` | `langchain-google-genai` | `xai` | `langchain-xai` |
| `fireworks` | `langchain-fireworks` | `perplexity` | `langchain-perplexity` |
| `ollama` | `langchain-ollama` | `meta` | `langchain-meta` |
| `together` | `langchain-together` | `community` | `langchain-community` |
| `mistralai` | `langchain-mistralai` | `mcp` | `fastmcp>=4.0.1,<5.0.0` |
| `huggingface` | `langchain-huggingface` | | |

> [!NOTE]
> Only 16 provider packages live in this repository (Anthropic, Chroma, DeepSeek, Exa, Fireworks, Groq, Hugging Face, Mistral AI, Nomic, Ollama, OpenAI, OpenRouter, Perplexity, Qdrant, TypeSafe, xAI). Google, AWS, Azure AI, Cohere and many others are maintained in their own repositories but are still reachable through `init_chat_model` once installed.

### 2.3 Provider packages in this repository

| Package | Version | Main classes | Extra dependencies it pulls in |
|---|---|---|---|
| `langchain-openai` | 1.6.6 | `ChatOpenAI`, `AzureChatOpenAI`, `OpenAIEmbeddings`, `AzureOpenAIEmbeddings`, `OpenAI`, `AzureOpenAI` | `openai>=2.45.0,<4.0.0`, `tiktoken`; extra `azure-identity` |
| `langchain-anthropic` | 1.7.5 | `ChatAnthropic`, `AnthropicLLM`, `convert_to_anthropic_tool` | `anthropic>=0.120.0,<2.0.0` |
| `langchain-ollama` | 1.1.0 | `ChatOllama`, `OllamaEmbeddings`, `OllamaLLM` | `ollama>=0.6.1,<1.0.0` |
| `langchain-groq` | 1.1.3 | `ChatGroq` | `groq>=0.30.0,<1.0.0` |
| `langchain-mistralai` | 1.1.6 | `ChatMistralAI`, `MistralAIEmbeddings` | `tokenizers`, `httpx`, `httpx-sse` |
| `langchain-fireworks` | 1.7.0 | `ChatFireworks`, `Fireworks`, `FireworksEmbeddings`, `FireworksRerank` | `fireworks-ai`, `openai`, `aiohttp` |
| `langchain-xai` | 1.3.0 | `ChatXAI` | `langchain-openai` |
| `langchain-deepseek` | 1.1.1 | `ChatDeepSeek` | `langchain-openai` |
| `langchain-openrouter` | 0.2.9 | `ChatOpenRouter` | `openrouter>=0.9.2,<1.0.0` |
| `langchain-perplexity` | 1.4.1 | `ChatPerplexity`, `PerplexityEmbeddings`, `PerplexitySearchRetriever`, `PerplexitySearchResults` | `perplexityai` |
| `langchain-huggingface` | 1.2.2 | `ChatHuggingFace`, `HuggingFaceEndpoint`, `HuggingFacePipeline`, `HuggingFaceEmbeddings`, `HuggingFaceEndpointEmbeddings` | `huggingface-hub`, `tokenizers`; extra `full` adds `transformers` + `sentence-transformers` |
| `langchain-nomic` | 1.0.1 | `NomicEmbeddings` | `nomic`, `pillow` |
| `langchain-exa` | 1.1.0 | `ExaSearchRetriever`, `ExaSearchResults`, `ExaFindSimilarResults` | `exa-py` |
| `langchain-chroma` | 1.1.0 | `Chroma` | `chromadb>=1.5.5,<2.0.0` |
| `langchain-qdrant` | 1.1.0 | `QdrantVectorStore`, `FastEmbedSparse`, legacy `Qdrant` | `qdrant-client`; extra `fastembed` |
| `langchain-typesafe` | 0.0.1a3 (alpha) | `TypeSafeClassifier` | `httpx2`; extra `experimental` |

### 2.4 Text splitters and optional helpers

```bash
pip install -U langchain-text-splitters
# Optional back ends are NOT declared as extras; install what you use:
pip install tiktoken            # TokenTextSplitter, from_tiktoken_encoder
pip install beautifulsoup4 lxml # HTML splitters
pip install nltk spacy          # sentence splitters
```

### 2.5 Verify the installation

`langchain-core` ships a system report you can paste into bug reports:

```bash
python -m langchain_core.sys_info
```

```text
System Information
------------------
> OS:  Linux
> OS Version:  ...
> Python Version:  3.12.x

Package Information
-------------------
> langchain_core: 1.6.6
> langchain: 1.4.3
> langsmith: 0.x
> langchain_openai: 1.6.6
> langgraph: 1.2.x
...
```

> [!TIP]
> `print_sys_info(additional_pkgs=[...])` is also importable from `langchain_core.sys_info` if you want to include extra packages in the report.

### 2.6 Upgrading from LangChain 0.x

LangChain 1.0 moved legacy functionality into `langchain-classic`. If an old import stops working, it has usually moved:

| Old usage | Where it lives now |
|---|---|
| `AgentExecutor`, `initialize_agent`, agent types | `langchain_classic.agents` (deprecated; use `langchain.agents.create_agent`) |
| `ConversationBufferMemory` and other memory classes | `langchain_classic.memory` (deprecated since 0.3.1; use a checkpointer with `create_agent`) |
| `LLMChain`, `RetrievalQA`, `APIChain`, SQL chains | `langchain_classic.chains` |
| `CacheBackedEmbeddings`, community embeddings | `langchain_classic` / `langchain-community` |
| `langchain.hub.pull` / `push` | `langchain_classic.hub` (deprecated since 1.0.6; use the LangSmith SDK) |

```bash
pip install -U langchain-classic
```

> [!WARNING]
> `langchain-classic` receives no new features. Its README says: "In most cases, you should be using the main `langchain` package."

---

## 3. Configuration Basics

LangChain reads no config file. You configure it in three places: **environment variables** (mostly credentials), **constructor arguments** on models and middleware, and the per-call **`config`** dict. The exhaustive reference is in the Admin Guide, Section 4.

### 3.1 Environment Variables

Provider packages read their credentials from the environment when you do not pass `api_key=...`:

| Provider | API key | Base URL override | Other |
|---|---|---|---|
| OpenAI (`ChatOpenAI`) | `OPENAI_API_KEY` | `OPENAI_API_BASE` (LangChain), `OPENAI_BASE_URL` (OpenAI SDK) | `OPENAI_ORG_ID` / `OPENAI_ORGANIZATION`, `OPENAI_PROXY` |
| Azure OpenAI (`AzureChatOpenAI`) | `AZURE_OPENAI_API_KEY` (falls back to `OPENAI_API_KEY`) | `AZURE_OPENAI_ENDPOINT` | `OPENAI_API_VERSION` (required — no default for chat), `AZURE_OPENAI_AD_TOKEN` |
| Anthropic (`ChatAnthropic`) | `ANTHROPIC_API_KEY` | `ANTHROPIC_API_URL`, then `ANTHROPIC_BASE_URL` | `ANTHROPIC_PROXY` |
| Groq | `GROQ_API_KEY` | `GROQ_API_BASE` | `GROQ_PROXY` |
| Mistral AI | `MISTRAL_API_KEY` | `MISTRAL_BASE_URL` (chat only) | — |
| xAI | `XAI_API_KEY` | `XAI_API_BASE` | — |
| DeepSeek | `DEEPSEEK_API_KEY` | `DEEPSEEK_API_BASE` | — |
| Fireworks | `FIREWORKS_API_KEY` | `FIREWORKS_API_BASE` | — |
| OpenRouter | `OPENROUTER_API_KEY` | `OPENROUTER_API_BASE` | `OPENROUTER_APP_URL`, `OPENROUTER_APP_TITLE`, `OPENROUTER_SESSION_ID` |
| Perplexity | `PPLX_API_KEY` (chat); `PPLX_API_KEY` or `PERPLEXITY_API_KEY` (embeddings, retriever, tool) | — | — |
| Hugging Face | `HUGGINGFACEHUB_API_TOKEN`, falling back to `HF_TOKEN` | `HF_INFERENCE_ENDPOINT` | — |
| Nomic | `NOMIC_API_KEY` | — | — |
| Exa | `EXA_API_KEY` | — | — |
| Ollama | none | none — pass `base_url=` (default comes from the `ollama` SDK) | — |

Framework-level variables:

| Variable | Effect |
|---|---|
| `LANGSMITH_TRACING=true` + `LANGSMITH_API_KEY` | Sends traces of every run to LangSmith (read by the `langsmith` SDK) |
| `LANGSMITH_PROJECT` | LangSmith project name for traces |
| `LC_OUTPUT_VERSION` | Default `output_version` for chat models: `v0` (provider-native content) or `v1` (standard content blocks) |
| `LANGSMITH_GATEWAY` | `true`/`1`/`yes` routes OpenAI, Anthropic and Fireworks through the LangSmith gateway; any other non-false value is used as a custom gateway URL |

> [!CAUTION]
> Setting `LANGCHAIN_TRACING` or `LANGCHAIN_HANDLER` (the old v1 tracer variables) now raises `RuntimeError: Tracing using LangChainTracerV1 is no longer supported...`. Unset them and use `LANGSMITH_TRACING`.

> [!WARNING]
> `ChatOllama` does **not** read `OLLAMA_HOST`. Pass `base_url="http://host:11434"` explicitly if your Ollama server is not at the SDK's default address.

### 3.2 Choosing a model with a string

Everything that accepts a model (`create_agent`, `ModelFallbackMiddleware`, `SummarizationMiddleware`, …) accepts either a chat model object or a `"provider:model"` string. Strings are resolved by `init_chat_model`:

```python
from langchain.chat_models import init_chat_model

gpt = init_chat_model("openai:gpt-5.5", temperature=0)
claude = init_chat_model("anthropic:claude-sonnet-4-5-20250929", max_tokens=1024)
local = init_chat_model("ollama:llama3.1", base_url="http://localhost:11434")
```

Any extra keyword argument is passed to the provider class constructor, so `temperature`, `max_tokens`, `timeout`, `max_retries`, `base_url` and `rate_limiter` all work.

> [!TIP]
> Prefer the prefixed form (`"anthropic:..."`). A bare name such as `"claude-opus-4-7"` also works because the provider is inferred from the prefix, but inference is best-effort (see Section 10.2 for the exact rules), and names like `o4-mini` are **not** inferred.

### 3.3 Per-call configuration

Every `invoke`, `stream` and `batch` call accepts a `config` dict (`RunnableConfig`):

| Key | Purpose |
|---|---|
| `configurable` | Runtime parameters, e.g. `{"thread_id": "user-42"}` for agent memory or `{"model": "..."}` for configurable models |
| `tags`, `metadata` | Attached to traces and callbacks |
| `callbacks` | Callback handlers for this call |
| `run_name`, `run_id` | Name / ID for the trace of this call |
| `max_concurrency` | Upper bound on parallel work in `batch` and parallel steps |
| `recursion_limit` | Max nested runnable depth (default 25). Agents built with `create_agent` set their own graph limit of 9,999 |

```python
config = {"configurable": {"thread_id": "session-1"}, "tags": ["prod"], "metadata": {"user": "u-17"}}
agent.invoke({"messages": [{"role": "user", "content": "hi"}]}, config)
```

> [!NOTE]
> `RunnableConfig` has no `timeout` key. Unknown top-level keys are silently moved into `configurable`. Set timeouts on the model (`timeout=` / `request_timeout=`).

---

## 4. Everyday Usage

### 4.1 Talking to a chat model

Every chat model implements the same Runnable interface: `invoke`, `stream`, `batch` and their async versions `ainvoke`, `astream`, `abatch`.

**Inputs** can be a string, a list of messages, or message-like dicts and tuples:

```python
from langchain.messages import HumanMessage, SystemMessage

model.invoke("What is 2 + 2?")
model.invoke([SystemMessage("Answer tersely."), HumanMessage("What is 2 + 2?")])
model.invoke([{"role": "system", "content": "Answer tersely."}, {"role": "user", "content": "What is 2 + 2?"}])
model.invoke([("system", "Answer tersely."), ("human", "What is 2 + 2?")])
```

Accepted role strings: `human`/`user`, `ai`/`assistant`, `system`, `developer` (becomes a `SystemMessage` flagged for OpenAI's developer role), `tool`, `function` and `remove`.

**Outputs** are `AIMessage` objects:

```python
msg = model.invoke("Name one planet.")
print(msg.text)             # plain text of the reply
print(msg.content_blocks)   # standardized content blocks
print(msg.usage_metadata)   # token usage
print(msg.response_metadata.get("model_name"))
```

```text
Mars.
[{'type': 'text', 'text': 'Mars.'}]
{'input_tokens': 11, 'output_tokens': 3, 'total_tokens': 14}
gpt-5.5
```

> [!NOTE]
> Use `.text` as a **property**. Calling `.text()` as a method is deprecated since 1.0.0 and will be removed in 2.0.0.

**Streaming** yields `AIMessageChunk` objects that you can add together:

```python
full = None
for chunk in model.stream("Write a haiku about rivers."):
    print(chunk.text, end="", flush=True)
    full = chunk if full is None else full + chunk
print()
print(full.usage_metadata)
```

```text
Silver threads descend
carving patience into stone
the sea waits below
{'input_tokens': 14, 'output_tokens': 18, 'total_tokens': 32}
```

**Batching** runs many inputs in a thread pool:

```python
answers = model.batch(["Capital of France?", "Capital of Japan?"], config={"max_concurrency": 4})
print([a.text for a in answers])
```

```text
['Paris.', 'Tokyo.']
```

> [!TIP]
> Use `return_exceptions=True` in `batch` to get exceptions back in place of results instead of failing the whole batch. It catches `Exception` subclasses only.

> [!NOTE]
> If a provider class does not implement token streaming, `stream()` still works — it falls back to `invoke()` and yields one chunk.

### 4.2 Building an agent

`create_agent` returns a compiled LangGraph graph that calls the model, runs any tool calls, feeds results back and repeats until the model answers without tool calls.

```python
from langchain.agents import create_agent

agent = create_agent(
    model="anthropic:claude-sonnet-4-5-20250929",
    tools=[get_weather],
    system_prompt="You are a concise travel assistant.",
    name="travel_agent",
)
```

The key parameters (full list in Section 10.3):

| Parameter | Type | What it does |
|---|---|---|
| `model` | `str` or `BaseChatModel` | The model; strings go through `init_chat_model` |
| `tools` | list of `BaseTool`, callables, or provider tool dicts | Tools the model may call. `None` or `[]` gives a model-only agent |
| `system_prompt` | `str` or `SystemMessage` | Prepended to every model call |
| `middleware` | sequence of `AgentMiddleware` | Hooks around the loop (Section 6) |
| `response_format` | Pydantic class, dataclass, TypedDict, JSON-schema dict, `ToolStrategy`, `ProviderStrategy` | Structured final answer (Section 4.4) |
| `checkpointer` | LangGraph checkpointer | Enables conversation memory and resumable interrupts |
| `store` | LangGraph `BaseStore` | Cross-thread storage |
| `context_schema` | type | Type of the per-run `context` object passed at invoke time |
| `name` | `str` | Graph name; also stamped onto the agent's `AIMessage.name` |

**Invoking.** The input is a dict with a `messages` list; the output is the final state:

```python
state = agent.invoke({"messages": [{"role": "user", "content": "Weather in Rome?"}]})
for m in state["messages"]:
    print(type(m).__name__, "->", m.text or m.tool_calls)
```

```text
HumanMessage -> Weather in Rome?
AIMessage -> [{'name': 'get_weather', 'args': {'city': 'Rome'}, 'id': 'toolu_01...', 'type': 'tool_call'}]
ToolMessage -> It's always sunny in Rome!
AIMessage -> It's sunny in Rome right now.
```

**Streaming an agent.** The compiled graph supports LangGraph stream modes. The two most useful:

```python
# Node-by-node progress
for update in agent.stream({"messages": [{"role": "user", "content": "Weather in Oslo?"}]}, stream_mode="updates"):
    print(update)

# Token-by-token model output
for token, metadata in agent.stream({"messages": [{"role": "user", "content": "Weather in Oslo?"}]}, stream_mode="messages"):
    print(token.text, end="")
```

```text
{'model': {'messages': [AIMessage(content='', tool_calls=[{'name': 'get_weather', ...}])]}}
{'tools': {'messages': [ToolMessage(content="It's always sunny in Oslo!", ...)]}}
{'model': {'messages': [AIMessage(content="It's sunny in Oslo.")]}}
```

> [!TIP]
> Middleware can emit your own progress events with `runtime.stream_writer({...})`; receive them with `stream_mode="custom"`.

> [!NOTE]
> The agent graph's `recursion_limit` is set to **9,999** by `create_agent`, so long tool loops are not cut off at LangGraph's usual limit. Use `ModelCallLimitMiddleware` or `ToolCallLimitMiddleware` to cap loops deliberately.

### 4.3 Defining tools

**Plain functions** work directly — the name comes from the function, the description from the docstring, the schema from type hints. For more control use the `@tool` decorator:

```python
from typing import Annotated
from langchain.tools import tool


@tool(parse_docstring=True)
def search_orders(customer_id: str, limit: int = 5) -> str:
    """Look up recent orders for a customer.

    Args:
        customer_id: The customer's ID, e.g. "C-1001".
        limit: Maximum number of orders to return.
    """
    return f"{limit} orders for {customer_id}"


print(search_orders.name, search_orders.args)
```

```text
search_orders {'customer_id': {'description': 'The customer\'s ID, e.g. "C-1001".', 'title': 'Customer Id', 'type': 'string'}, 'limit': {'default': 5, 'description': 'Maximum number of orders to return.', 'title': 'Limit', 'type': 'integer'}}
```

`@tool` options:

| Option | Default | Meaning |
|---|---|---|
| first positional arg | function name | Custom tool name: `@tool("lookup")` |
| `description` | docstring | Overrides the description |
| `return_direct` | `False` | End the agent loop and return the tool output directly |
| `args_schema` | inferred | Pydantic model or JSON-schema dict for arguments |
| `infer_schema` | `True` | Infer the schema from the signature |
| `response_format` | `"content"` | `"content_and_artifact"` means the tool returns `(content, artifact)`; the artifact is kept on the `ToolMessage` but not sent to the model |
| `parse_docstring` | `False` | Read argument descriptions from a Google-style `Args:` section |
| `error_on_invalid_docstring` | `True` | Raise if `parse_docstring=True` and the docstring is malformed |
| `extras` | `None` | Provider-specific fields (e.g. Anthropic `cache_control`, `defer_loading`) |

> [!WARNING]
> Do not name tool arguments `config`, `run_manager` or `callbacks`. `config` is shadowed by the injected `RunnableConfig`, and `run_manager`/`callbacks` are silently removed from the schema.

**Injected arguments** are supplied by the framework and hidden from the model:

```python
from typing import Annotated
from langchain.tools import tool, ToolRuntime, InjectedToolCallId


@tool
def whoami(runtime: ToolRuntime) -> str:
    """Report who the current user is."""
    return f"user={runtime.context['user_id']}, messages so far={len(runtime.state['messages'])}"


@tool
def echo_id(text: str, tool_call_id: Annotated[str, InjectedToolCallId]) -> str:
    """Echo text with the calling tool-call ID."""
    return f"{text} ({tool_call_id})"
```

`ToolRuntime` (re-exported by `langchain.tools` from LangGraph) gives a tool access to `state`, `context`, `config`, `store`, `stream_writer` and `tool_call_id`.

**Errors inside tools.** Raise `ToolException` to report a failure the model can react to, and set `handle_tool_error` on the tool:

```python
from langchain.tools import ToolException, tool


@tool
def divide(a: float, b: float) -> float:
    """Divide a by b."""
    if b == 0:
        raise ToolException("Cannot divide by zero; ask the user for a non-zero divisor.")
    return a / b


divide.handle_tool_error = True  # the exception message becomes the ToolMessage content
```

| `handle_tool_error` value | Behavior |
|---|---|
| `False` (default) | The exception propagates and stops the run |
| `True` | The exception message becomes an error `ToolMessage` (or `"Tool execution error"` if empty) |
| a string | That string becomes the error `ToolMessage` |
| a callable | Its return value becomes the error `ToolMessage` |

> [!TIP]
> For agent-wide policies (retry flaky tools, convert only certain exceptions to messages) use `ToolRetryMiddleware` and `ToolErrorMiddleware` instead of per-tool settings — see Section 6.1.

**Provider-native tools** (web search, code execution, etc.) are passed as dicts and executed by the provider, not locally:

```python
agent = create_agent("openai:gpt-5.5", tools=[{"type": "web_search"}, get_weather])
```

### 4.4 Getting structured output

**From a model** — `with_structured_output` returns a runnable that produces your schema:

```python
from pydantic import BaseModel, Field


class Movie(BaseModel):
    """A movie recommendation."""
    title: str
    year: int = Field(description="Release year")


structured = init_chat_model("openai:gpt-5.5").with_structured_output(Movie)
print(structured.invoke("Recommend one classic sci-fi film."))
```

```text
title='Blade Runner' year=1982
```

Pass `include_raw=True` to get `{"raw": AIMessage, "parsed": Movie | None, "parsing_error": Exception | None}` instead of raising on parse failures.

**From an agent** — pass `response_format` to `create_agent`; the parsed object appears in `result["structured_response"]`:

```python
agent = create_agent("openai:gpt-5.5", tools=[get_weather], response_format=Movie)
result = agent.invoke({"messages": [{"role": "user", "content": "Recommend a film set in Paris."}]})
print(result["structured_response"])
```

```text
title='Midnight in Paris' year=2011
```

How the strategy is chosen:

| You pass | What happens |
|---|---|
| A raw schema (Pydantic, dataclass, TypedDict, JSON-schema dict) | **Auto**: provider-native structured output if the model supports it (from its profile, or a built-in list of known models), otherwise the tool-calling strategy |
| `ToolStrategy(schema, handle_errors=True, tool_message_content=None)` | The schema is exposed as a tool and the model is forced to call a tool (`tool_choice="any"`). Parse errors are fed back to the model with `"Error: {error}\n Please fix your mistakes."` and it retries |
| `ProviderStrategy(schema, strict=None)` | Uses the provider's native JSON-schema output. Parse failures raise `StructuredOutputValidationError` |

```python
from langchain.agents.structured_output import ToolStrategy, ProviderStrategy

agent = create_agent("openai:gpt-5.5", tools=[], response_format=ToolStrategy(Movie, handle_errors=True))
```

> [!TIP]
> `ToolStrategy` accepts a **union** of schemas (`ToolStrategy(Movie | Book)`); the model picks one. If it calls two at once you get `MultipleStructuredOutputsError`, which `handle_errors=True` turns into a retry.

### 4.5 Conversation memory

An agent remembers a conversation only when it has a **checkpointer** and you reuse the same `thread_id`:

```python
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent("openai:gpt-5.5", tools=[get_weather], checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "alice"}}

agent.invoke({"messages": [{"role": "user", "content": "My name is Alice."}]}, cfg)
reply = agent.invoke({"messages": [{"role": "user", "content": "What's my name?"}]}, cfg)
print(reply["messages"][-1].text)
```

```text
Your name is Alice.
```

> [!IMPORTANT]
> `InMemorySaver` loses everything when the process exits. For durable memory use a persistent LangGraph checkpointer (for example a Postgres or SQLite saver from the LangGraph ecosystem).

**Keeping the context window under control:**

| Tool | Where | What it does |
|---|---|---|
| `trim_messages(messages, max_tokens=..., token_counter=..., strategy="last", start_on="human", include_system=True)` | `langchain.messages` | Keep only the most recent messages that fit a budget |
| `SummarizationMiddleware(model, trigger=..., keep=...)` | `langchain.agents.middleware` | Replace older history with a model-written summary when a threshold is crossed |
| `ContextEditingMiddleware(edits=[ClearToolUsesEdit(...)])` | `langchain.agents.middleware` | Replace old tool outputs with `[cleared]` once input tokens exceed a trigger (default 100,000) |

```python
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    "openai:gpt-5.5",
    tools=[get_weather],
    checkpointer=InMemorySaver(),
    middleware=[SummarizationMiddleware("openai:gpt-5.4-mini", trigger=("tokens", 4000), keep=("messages", 20))],
)
```

> [!WARNING]
> `SummarizationMiddleware` has **no default trigger**. If you omit `trigger`, it never summarizes. Always set one, e.g. `trigger=("tokens", 4000)` or `trigger=[("fraction", 0.8), ("messages", 100)]`.

### 4.6 Prompts, chains and parsers

For deterministic pipelines that do not need an agent loop, compose components with the `|` operator (LangChain Expression Language):

```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", "You translate English to {language}."),
    ("human", "{text}"),
])
chain = prompt | init_chat_model("openai:gpt-5.5") | StrOutputParser()
print(chain.invoke({"language": "French", "text": "Good morning"}))
```

```text
Bonjour
```

| Building block | Import | Notes |
|---|---|---|
| `ChatPromptTemplate.from_messages([...])` | `langchain_core.prompts` | Messages as `(role, template)` tuples, strings, message objects or `{"role","content"}` dicts |
| `MessagesPlaceholder("history", optional=True)` | `langchain_core.prompts` | Slot for a list of messages; `("placeholder", "{history}")` is shorthand and optional by default |
| `PromptTemplate.from_template("...")` | `langchain_core.prompts` | Single-string prompts; `template_format` is `f-string` (default), `mustache` or `jinja2` |
| `StrOutputParser`, `JsonOutputParser`, `PydanticOutputParser`, `XMLOutputParser`, list parsers | `langchain_core.output_parsers` | Turn model output into Python values |
| `RunnableParallel` / a plain `dict` | `langchain_core.runnables` | Run steps side by side: `{"a": chain1, "b": chain2}` |
| `RunnablePassthrough.assign(key=...)` | `langchain_core.runnables` | Add computed keys to a dict input |
| `RunnableLambda(fn)` / any function | `langchain_core.runnables` | Wrap a function as a step |
| `.with_retry(stop_after_attempt=3)`, `.with_fallbacks([...])` | any runnable | Resilience on a single step |

> [!CAUTION]
> Never accept `jinja2` templates from untrusted users. The docs in the source state that jinja2 sandboxing "should be treated as a best-effort approach rather than a guarantee of security". Prefer `f-string` or `mustache`.

### 4.7 Embeddings, text splitting and retrieval

```python
from langchain.embeddings import init_embeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

embeddings = init_embeddings("openai:text-embedding-3-small")

splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents([Document(page_content=open("handbook.txt").read(), metadata={"source": "handbook"})])

store = InMemoryVectorStore(embeddings)
store.add_documents(chunks)
for doc in store.similarity_search("vacation policy", k=2):
    print(doc.metadata["source"], doc.page_content[:60])
```

```text
handbook Employees accrue 1.5 days of paid vacation per month...
handbook Vacation requests must be submitted two weeks in advance...
```

`init_embeddings` requires the `provider:model` form (or `provider=`) and supports these providers: `openai`, `azure_openai`, `azure_ai`, `bedrock`, `cohere`, `google_genai`, `google_vertexai`, `huggingface`, `mistralai`, `ollama`.

Turn the store into a retriever and then into a tool an agent can call:

```python
from langchain_core.tools import create_retriever_tool

retriever = store.as_retriever(search_type="mmr", search_kwargs={"k": 4, "fetch_k": 20})
handbook_tool = create_retriever_tool(retriever, "search_handbook", "Search the employee handbook.")
agent = create_agent("openai:gpt-5.5", tools=[handbook_tool])
```

| `search_type` | `search_kwargs` |
|---|---|
| `"similarity"` (default) | `k` (default 4), `filter` |
| `"mmr"` | `k`, `fetch_k` (default 20), `lambda_mult` (default 0.5; 1 = least diverse) |
| `"similarity_score_threshold"` | `k`, `score_threshold` — **must be a float** (e.g. `0.5`, not `1`) |

> [!NOTE]
> `InMemoryVectorStore` filters are Python callables (`filter=lambda doc: doc.metadata["source"] == "handbook"`), not metadata dicts. It needs `numpy` for similarity math, and can be saved with `store.dump(path)` and reloaded with `InMemoryVectorStore.load(path, embeddings)`.


---

## 5. Common Workflows

Each recipe is complete and runnable once the named provider package is installed and its API key is set.

### 5.1 A support agent with memory and per-user context

```python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest
from langchain.tools import tool, ToolRuntime
from langgraph.checkpoint.memory import InMemorySaver


@dataclass
class UserContext:
    user_id: str
    plan: str


@tool
def account_status(runtime: ToolRuntime) -> str:
    """Return the caller's account status."""
    ctx = runtime.context
    return f"User {ctx.user_id} is on the {ctx.plan} plan and in good standing."


@dynamic_prompt
def plan_aware_prompt(request: ModelRequest) -> str:
    return f"You are a support agent. The customer is on the {request.runtime.context.plan} plan."


agent = create_agent(
    "openai:gpt-5.5",
    tools=[account_status],
    middleware=[plan_aware_prompt],
    context_schema=UserContext,
    checkpointer=InMemorySaver(),
)

cfg = {"configurable": {"thread_id": "ticket-881"}}
ctx = UserContext(user_id="u-17", plan="Pro")
out = agent.invoke({"messages": [{"role": "user", "content": "Is my account OK?"}]}, cfg, context=ctx)
print(out["messages"][-1].text)
```

```text
Yes — your account (u-17) is on the Pro plan and in good standing.
```

> [!TIP]
> **Context** is per-run, read-only information you supply (`context=`). **State** is the conversation data the graph accumulates and the checkpointer saves. Keep secrets and user identity in context, not in the prompt.

### 5.2 Human approval before risky tool calls

`HumanInTheLoopMiddleware` pauses the run with a LangGraph interrupt whenever the model calls a gated tool. Resuming requires a checkpointer.

```python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command


@tool
def delete_file(path: str) -> str:
    """Delete a file."""
    return f"Deleted {path}"


@tool
def read_file(path: str) -> str:
    """Read a file."""
    return f"Contents of {path}"


agent = create_agent(
    "openai:gpt-5.5",
    tools=[delete_file, read_file],
    middleware=[HumanInTheLoopMiddleware(interrupt_on={
        "delete_file": {"allowed_decisions": ["approve", "edit", "reject"]},
        "read_file": False,  # auto-approved
    })],
    checkpointer=InMemorySaver(),
)
cfg = {"configurable": {"thread_id": "cleanup-1"}}

paused = agent.invoke({"messages": [{"role": "user", "content": "Delete report.md"}]}, cfg)
[interrupt] = paused["__interrupt__"]
print(interrupt.value["action_requests"][0]["description"])

final = agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), cfg)
print(final["messages"][-1].text)
```

```text
Tool execution requires approval

Tool: delete_file
Args: {'path': 'report.md'}
I've deleted report.md.
```

Decision types you can send back, one per interrupted tool call and in the same order:

| Decision | Shape | Effect |
|---|---|---|
| approve | `{"type": "approve"}` | Run the tool as proposed |
| edit | `{"type": "edit", "edited_action": {"name": "delete_file", "args": {"path": "draft.md"}}}` | Run the reviewer's version instead; the result is prefixed with a notice telling the model a human replaced the call |
| reject | `{"type": "reject", "message": "Not allowed"}` | Skip the tool; an error `ToolMessage` tells the model not to retry |
| respond | `{"type": "respond", "message": "The file is already gone."}` | Skip the tool and answer on its behalf with a successful `ToolMessage` |

> [!IMPORTANT]
> The number of decisions must equal the number of interrupted tool calls, or you get `ValueError: Number of human decisions (...) does not match number of hanging tool calls (...)`.

> [!TIP]
> Use `InterruptOnConfig(allowed_decisions=[...], description=callable, when=predicate)` to build custom approval prompts and to interrupt only some calls (for example only when `args["amount"] > 1000`). The `description` callable receives `(tool_call, state, runtime)`.

### 5.3 Giving an agent tools from MCP servers

```python
import asyncio

from langchain.agents import create_agent
from langchain.mcp import MCPAdapter


async def main() -> None:
    async with MCPAdapter("https://example.com/mcp") as adapter:
        tools = await adapter.list_tools()
    agent = create_agent("anthropic:claude-sonnet-4-5-20250929", tools)
    result = await agent.ainvoke({"messages": [{"role": "user", "content": "What can you do?"}]})
    print(result["messages"][-1].text)


asyncio.run(main())
```

Several servers behind one adapter, mixing transports:

```python
import sys

config = {
    "mcpServers": {
        "weather": {"command": sys.executable, "args": ["weather_server.py"]},
        "docs": {"url": "https://docs.example.com/mcp"},
    }
}
async with MCPAdapter(config) as adapter:
    tools = await adapter.list_tools()   # names are prefixed per server
```

| Target you pass | Transport |
|---|---|
| `"https://..."` string | Streamable HTTP. **Only `http`/`https` strings are accepted** |
| `pathlib.Path("server.py")` | Launches the script over stdio |
| `fastmcp.FastMCP` / `MCPServer` instance | In-process (ideal for tests) |
| `MCPConfig` or `{"mcpServers": {...}}` dict | Several servers, tools prefixed with the server key |
| `fastmcp.Client(...)` | Pre-built client (e.g. with `auth="token"`) |

> [!WARNING]
> `langchain.mcp` is **beta**. Importing it emits `LangChainBetaWarning: `langchain.mcp` is in beta...`. Silence it with `warnings.filterwarnings("ignore", category=LangChainBetaWarning)`. MCP tools are **async-only**, so call the agent with `ainvoke`/`astream`.

> [!NOTE]
> MCP tools that fail on the server reach the model as an error `ToolMessage` so it can retry. When a server asks the user a question mid-call (MCP elicitation), the run pauses with an interrupt of type `mcp_elicitation`; resume with `Command(resume={"responses": {request_key: {"action": "accept", "content": {...}}}})`. This also needs a checkpointer.

### 5.4 Retrieval-augmented agent

```python
from langchain.agents import create_agent
from langchain.embeddings import init_embeddings
from langchain_core.documents import Document
from langchain_core.tools import create_retriever_tool
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter

docs = [Document(page_content=open(p).read(), metadata={"source": p}) for p in ["faq.md", "pricing.md"]]
chunks = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100).split_documents(docs)

store = InMemoryVectorStore(init_embeddings("openai:text-embedding-3-small"))
store.add_documents(chunks)

kb = create_retriever_tool(
    store.as_retriever(search_kwargs={"k": 4}),
    name="search_kb",
    description="Search the product FAQ and pricing pages.",
)
agent = create_agent("openai:gpt-5.5", tools=[kb], system_prompt="Answer only from search_kb results; cite the source.")
print(agent.invoke({"messages": [{"role": "user", "content": "Is there a student discount?"}]})["messages"][-1].text)
```

```text
Yes — students get 50% off the Pro plan (source: pricing.md).
```

> [!TIP]
> For a persistent store swap `InMemoryVectorStore` for `langchain_chroma.Chroma(collection_name="kb", embedding_function=..., persist_directory="./chroma")` or `langchain_qdrant.QdrantVectorStore`.

### 5.5 Guardrails: PII redaction and call limits

```python
from langchain.agents import create_agent
from langchain.agents.middleware import (
    ModelCallLimitMiddleware,
    PIIMiddleware,
    ToolCallLimitMiddleware,
)

agent = create_agent(
    "openai:gpt-5.5",
    tools=[search_orders],
    middleware=[
        PIIMiddleware("email", strategy="redact"),                          # user input
        PIIMiddleware("credit_card", strategy="mask", apply_to_output=True),
        PIIMiddleware("api_key", detector=r"sk-[a-zA-Z0-9]{32}", strategy="block"),
        ModelCallLimitMiddleware(run_limit=10, exit_behavior="end"),
        ToolCallLimitMiddleware(tool_name="search_orders", run_limit=3),
    ],
)
```

| PII strategy | Result for `[email removed]` / card `4111111111111111` |
|---|---|
| `redact` | `[REDACTED_EMAIL]` / `[REDACTED_CREDIT_CARD]` |
| `mask` | `jane@****.com` / `************1111` (a card written with dashes keeps them: `****-****-****-1111`) |
| `hash` | `<email_hash:1a2b3c4d>` — first 8 hex characters of a SHA-256 of the value |
| `block` | raises `PIIDetectionError` |

Built-in detectors: `email`, `credit_card` (Luhn-validated), `ip`, `mac_address`, `url`. Anything else needs `detector=` (a regex string or a function returning `PIIMatch` dicts).

> [!NOTE]
> `PIIMiddleware` checks user input by default (`apply_to_input=True`). Set `apply_to_output=True` to also scrub AI messages — this additionally installs a stream transformer so streamed tokens and tool events are redacted in flight — and `apply_to_tool_results=True` to scrub tool outputs.

### 5.6 Resilience: retries and model fallback

```python
from langchain.agents.middleware import ModelFallbackMiddleware, ModelRetryMiddleware, ToolRetryMiddleware

agent = create_agent(
    "openai:gpt-5.5",
    tools=[search_orders],
    middleware=[
        ModelFallbackMiddleware("anthropic:claude-sonnet-4-5-20250929", "ollama:llama3.1"),
        ModelRetryMiddleware(max_retries=3, backoff_factor=2.0, initial_delay=1.0),
        ToolRetryMiddleware(max_retries=2, tools=["search_orders"], on_failure="continue"),
    ],
)
```

Retry delay is `initial_delay * backoff_factor ** retry_number`, capped at `max_delay` (default 60 s), with ±25% jitter by default. After retries are exhausted, `on_failure="continue"` returns an error message to the model (`AIMessage` for model retries, `ToolMessage` for tool retries), `"error"` re-raises, and a callable lets you format the message.

> [!IMPORTANT]
> Middleware order matters: the **first** middleware in the list is the **outermost** wrapper. Above, a failing model call is retried 3 times by `ModelRetryMiddleware` before `ModelFallbackMiddleware` moves on to the next model.

> [!WARNING]
> By default `ToolRetryMiddleware` retries **any** exception (GitHub issue #40688 notes this can repeat non-idempotent tool calls after ambiguous transport errors). For tools that change data, pass `retry_on=(TimeoutError, ConnectionError)` or restrict with `tools=[...]`.

### 5.7 Switching models at runtime

```python
from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-5.5", configurable_fields=("model", "model_provider", "temperature"), temperature=0)

model.invoke("hi")  # uses gpt-5.5
model.invoke("hi", config={"configurable": {"model": "anthropic:claude-sonnet-4-5-20250929", "temperature": 0.7}})
```

With `config_prefix="writer"`, the keys become `writer_model`, `writer_temperature` and so on, which lets two configurable models live in one app.

> [!CAUTION]
> `configurable_fields="any"` lets callers change `api_key` and `base_url` at runtime, potentially redirecting requests to another service. When config comes from untrusted input, list the allowed fields explicitly.

### 5.8 Planning long tasks with a todo list

```python
from langchain.agents.middleware import TodoListMiddleware

agent = create_agent("openai:gpt-5.5", tools=[read_file], middleware=[TodoListMiddleware()])
result = agent.invoke({"messages": [{"role": "user", "content": "Review these five files and summarize risks."}]})
print(result["todos"])
```

```text
[{'content': 'Read file 1', 'status': 'completed'}, {'content': 'Read file 2', 'status': 'in_progress'}, ...]
```

The middleware adds a `write_todos` tool plus a system-prompt section explaining when to use it, stores the list in state under `todos`, and allows at most one `write_todos` call per model turn.

---

## 6. Advanced Usage

### 6.1 Plugins & Extensions: the middleware catalog

Middleware is LangChain's extension mechanism for agents. Everything below is importable from `langchain.agents.middleware`.

| Middleware | What it does | Key arguments (defaults) |
|---|---|---|
| `ModelRetryMiddleware` | Retry failed model calls with backoff | `max_retries=2`, `retry_on=default_retry_on`, `on_failure="continue"`, `backoff_factor=2.0`, `initial_delay=1.0`, `max_delay=60.0`, `jitter=True` |
| `ToolRetryMiddleware` | Retry failed tool calls | same as above plus `tools=None` (all tools) |
| `ModelFallbackMiddleware` | Try alternative models in order on error | `first_model, *additional_models` |
| `ModelCallLimitMiddleware` | Cap model calls per thread/run | `thread_limit=None`, `run_limit=None`, `exit_behavior="end"` (or `"error"`) |
| `ToolCallLimitMiddleware` | Cap tool calls (all or one tool) | `tool_name=None`, `thread_limit`, `run_limit`, `exit_behavior="continue"` (`"error"`, `"end"`) |
| `ToolErrorMiddleware` | Convert selected tool exceptions into error `ToolMessage`s | `on_error`, `aon_error`, `tools=None` |
| `HumanInTheLoopMiddleware` | Pause for human approve/edit/reject/respond | `interrupt_on`, `description_prefix="Tool execution requires approval"`, `edit_notice` |
| `PIIMiddleware` | Detect and redact/mask/hash/block PII | `pii_type`, `strategy="redact"`, `detector=None`, `apply_to_input=True`, `apply_to_output=False`, `apply_to_tool_results=False` |
| `SummarizationMiddleware` | Summarize old history | `model`, `trigger=None`, `keep=("messages", 20)`, `token_counter`, `summary_prompt`, `trim_tokens_to_summarize=4000` |
| `ContextEditingMiddleware` | Clear old tool outputs over a token threshold | `edits=[ClearToolUsesEdit()]`, `token_count_method="approximate"`, `token_counter=None` |
| `TodoListMiddleware` | `write_todos` planning tool | `system_prompt`, `tool_description` |
| `LLMToolSelectorMiddleware` | Let a (smaller) model pick which tools to expose | `model=None`, `max_tools=None`, `always_include=None`, `max_retries=1`, `on_parsing_failure="error"` |
| `ProviderToolSearchMiddleware` | Defer tools behind the provider's server-side tool search (Anthropic, OpenAI) | `searchable_tools=None` |
| `LLMToolEmulator` | Fake tool results with an LLM (testing) | `tools=None` (all), `model=None` (deprecated default `anthropic:claude-sonnet-4-5-20250929`) |
| `ShellToolMiddleware` | Persistent shell tool | `workspace_root`, `execution_policy=HostExecutionPolicy()`, `redaction_rules`, `tool_name="shell"`, `env` |
| `FilesystemFileSearchMiddleware` | Glob and grep tools over a directory | `root_path`, `use_ripgrep=True`, `max_file_size_mb=10` |

Provider packages ship more middleware:

| Package | Middleware |
|---|---|
| `langchain_anthropic.middleware` | `AnthropicPromptCachingMiddleware`, `ClaudeBashToolMiddleware`, `StateClaudeTextEditorMiddleware`, `StateClaudeMemoryMiddleware`, `FilesystemClaudeTextEditorMiddleware`, `FilesystemClaudeMemoryMiddleware`, `StateFileSearchMiddleware` |
| `langchain_openai.middleware` | `OpenAIModerationMiddleware` (with `OpenAIModerationError`) |
| `langchain_fireworks.middleware` | `FireworksPromptCachingMiddleware` |
| `langchain_typesafe.experimental.middleware` | `ModelRouterMiddleware`, `AutoModeMiddleware` (alpha) |

> [!WARNING]
> Some docstring examples inside `langchain_anthropic.middleware` import names such as `StateTextEditorToolMiddleware` from `langchain.agents.middleware`. Those names do not exist — use the `Claude*` classes from `langchain_anthropic.middleware` listed above.

### 6.2 Writing your own middleware

**With decorators** — the quickest way:

```python
from typing import Any

from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import after_model, before_model, wrap_model_call, wrap_tool_call
from langchain.chat_models import init_chat_model
from langgraph.runtime import Runtime


@before_model
def log_size(state: AgentState, runtime: Runtime) -> None:
    print(f"calling model with {len(state['messages'])} messages")


@before_model(can_jump_to=["end"])
def stop_on_bye(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    if state["messages"][-1].text.strip().lower() == "bye":
        return {"jump_to": "end"}
    return None


@wrap_model_call
def cheaper_model_for_short_chats(request, handler):
    if len(request.messages) < 4:
        request = request.override(model=init_chat_model("openai:gpt-5.4-mini"))
    return handler(request)


@wrap_tool_call
def audit_tools(request, handler):
    print("tool:", request.tool_call["name"], request.tool_call["args"])
    return handler(request)


agent = create_agent("openai:gpt-5.5", tools=[get_weather],
                     middleware=[log_size, stop_on_bye, cheaper_model_for_short_chats, audit_tools])
```

**With a class** — when you need configuration, state or several hooks:

```python
from typing import Any
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config


class MaxTurns(AgentMiddleware):
    def __init__(self, limit: int) -> None:
        super().__init__()
        self.limit = limit

    @hook_config(can_jump_to=["end"])
    def after_model(self, state: AgentState, runtime) -> dict[str, Any] | None:
        ai_turns = sum(1 for m in state["messages"] if m.type == "ai")
        return {"jump_to": "end"} if ai_turns >= self.limit else None
```

| Hook | Runs | Return |
|---|---|---|
| `before_agent` / `abefore_agent` | Once, at the start of a run | state update dict, `Command`, or `None` |
| `before_model` / `abefore_model` | Before every model call | same |
| `wrap_model_call` / `awrap_model_call` | Around every model call; call `handler(request)` zero or more times | `ModelResponse`, `AIMessage`, or `ExtendedModelResponse` |
| `after_model` / `aafter_model` | After every model call | state update dict, `Command`, or `None` |
| `wrap_tool_call` / `awrap_tool_call` | Around every tool execution | `ToolMessage` or `Command` |
| `after_agent` / `aafter_agent` | Once, at the end of a run | state update dict, `Command`, or `None` |
| `dynamic_prompt` (decorator only) | Before every model call | `str` or `SystemMessage` used as the system prompt |

> [!IMPORTANT]
> A class that defines only the **sync** `wrap_model_call`/`wrap_tool_call` fails when the agent runs async (`ainvoke`/`astream`), and vice versa, with `NotImplementedError: Asynchronous implementation of awrap_model_call is not available...`. Decorators on a plain function handle both paths for you; for classes, implement both methods or run the agent in the matching mode.

Use `request.override(model=..., system_message=..., messages=..., tools=..., tool_choice=..., response_format=..., model_settings=...)` to change a model request. Assigning attributes directly (`request.model = ...`) still works but emits a `DeprecationWarning`.

> [!TIP]
> To add custom state fields, give the middleware a `state_schema` that subclasses `AgentState`. Fields annotated with `PrivateStateAttr`/`OmitFromInput` are hidden from the agent's input and/or output schema. Each middleware instance must have a unique `name` (the class name by default) — duplicates raise `AssertionError: Please remove duplicate middleware instances.`

### 6.3 Running shell commands safely

```python
from langchain.agents.middleware import DockerExecutionPolicy, RedactionRule, ShellToolMiddleware

shell = ShellToolMiddleware(
    workspace_root="./workspace",
    execution_policy=DockerExecutionPolicy(image="python:3.12-alpine3.19", network_enabled=False, read_only_rootfs=True),
    redaction_rules=[RedactionRule(pii_type="email", strategy="redact")],
)
agent = create_agent("openai:gpt-5.5", middleware=[shell])
```

| Policy | Isolation | Use when |
|---|---|---|
| `HostExecutionPolicy` (default) | None; optional CPU/memory limits on POSIX | Trusted environments that are already sandboxed |
| `CodexSandboxExecutionPolicy` | Codex CLI sandbox (Seatbelt on macOS, Landlock/seccomp on Linux) | The `codex` CLI is installed |
| `DockerExecutionPolicy` | A container per run, `--network none` by default | Untrusted commands or multi-tenant use |

> [!CAUTION]
> With `HostExecutionPolicy` the agent can modify anything the Python process user can reach. Redaction rules are applied after execution and do not prevent exfiltration.

### 6.4 Tracing and observability

```bash
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY="lsv2_..."
export LANGSMITH_PROJECT="my-agent"
```

Every model call, tool call and middleware hook is then traced. To trim sensitive payloads from middleware spans:

```python
from langchain.agents.middleware import TracePolicy, configure_trace_policy, omit_payload

configure_trace_policy(TracePolicy(process_inputs=omit_payload))  # process-wide default
```

For local debugging without LangSmith:

```python
from langchain_core.globals import set_debug, set_verbose

set_debug(True)     # prints every run start/end with inputs and outputs
set_verbose(True)   # lighter console output
```

Token accounting across calls:

```python
from langchain_core.callbacks import get_usage_metadata_callback

with get_usage_metadata_callback() as cb:
    agent.invoke({"messages": [{"role": "user", "content": "Weather in Lima?"}]})
print(cb.usage_metadata)
```

```text
{'gpt-5.5': {'input_tokens': 312, 'output_tokens': 41, 'total_tokens': 353}}
```

### 6.5 Rate limiting and caching

```python
from langchain_core.rate_limiters import InMemoryRateLimiter
from langchain_core.caches import InMemoryCache
from langchain_core.globals import set_llm_cache

limiter = InMemoryRateLimiter(requests_per_second=2, check_every_n_seconds=0.1, max_bucket_size=5)
model = init_chat_model("openai:gpt-5.5", rate_limiter=limiter)

set_llm_cache(InMemoryCache(maxsize=1000))   # identical prompts are answered from memory
```

> [!NOTE]
> `InMemoryRateLimiter` limits requests per process only (not tokens), and its bucket starts empty, so the very first call waits briefly. `InMemoryCache` evicts the oldest inserted entry (not least-recently-used) and has no TTL. Cache hits skip the rate limiter, and caching does not apply to streaming calls.

### 6.6 Streaming events from any runnable

```python
async for event in chain.astream_events({"language": "German", "text": "Thanks"}, version="v2"):
    if event["event"] == "on_chat_model_stream":
        print(event["data"]["chunk"].text, end="")
```

v2 event names follow `on_{chat_model|llm|chain|tool|retriever|prompt}_{start|stream|end}`, plus `on_tool_error` and `on_custom_event`. There are no `on_chain_error` or `on_llm_error` events — errors surface as raised exceptions. `version="v1"` and `astream_log` are deprecated (removal in 2.0.0); `version="v3"` is a beta protocol supported only on chat models and compiled graphs.

### 6.7 Standard content blocks

Set `output_version="v1"` (or `LC_OUTPUT_VERSION=v1`) to store provider-independent content blocks in `message.content`. Regardless of that setting, `message.content_blocks` always gives the standardized view:

```python
msg = init_chat_model("anthropic:claude-sonnet-4-5-20250929", output_version="v1").invoke("Think, then answer: 17*3?")
for block in msg.content_blocks:
    print(block["type"])
```

```text
reasoning
text
```

Block types: `text`, `reasoning`, `image`, `audio`, `video`, `file`, `text-plain`, `tool_call`, `tool_call_chunk`, `invalid_tool_call`, `server_tool_call`, `server_tool_call_chunk`, `server_tool_result`, `non_standard`. Build input blocks with helpers such as `create_image_block(url=...)` from `langchain_core.messages.content`.

---

## 7. How It Works

### 7.1 The package layers

```text
 your application
        │
        ▼
 langchain (1.4.3) ── create_agent · middleware · init_chat_model · init_embeddings · langchain.mcp
        │                     │
        │                     └── builds a LangGraph StateGraph (langgraph 1.2.x)
        ▼
 langchain-core (1.6.6) ── Runnable protocol · messages · tools · prompts · parsers · callbacks
        ▲
        │ implement core interfaces
 langchain-openai · langchain-anthropic · langchain-ollama · ... (provider packages)
```

`langchain-core` defines interfaces and ships no third-party integrations. Provider packages subclass those interfaces (`BaseChatModel`, `Embeddings`, `VectorStore`, …). `langchain` adds the agent runtime on top. Because every chat model has the same interface, swapping providers is a one-string change.

### 7.2 The agent loop

`create_agent` compiles a graph shaped like this:

```text
START → [before_agent hooks] → [before_model hooks] → model → [after_model hooks] ─┐
            (once)                    ▲                                             │
                                      │   tool calls pending?  yes → tools ─────────┤
                                      └──────────────────────────────────────────── │
                                                                  no → [after_agent hooks] → END
```

1. `before_agent` hooks run once.
2. `before_model` hooks run, then the **model node** calls the model with the system prompt, messages and bound tools, wrapped by every `wrap_model_call` middleware.
3. `after_model` hooks run (in reverse registration order).
4. If the last `AIMessage` has tool calls that have no `ToolMessage` yet, each is sent to the **tools node** (in parallel), wrapped by every `wrap_tool_call` middleware. Then the loop returns to step 2.
5. The loop ends when the model answers without tool calls, when every executed tool had `return_direct=True`, when a structured-output tool succeeded, or when middleware sets `jump_to: "end"`.
6. `after_agent` hooks run once.

Malformed tool calls (arguments that could not be parsed) are answered automatically with an error `ToolMessage` ("...arguments were malformed or truncated.") so the conversation stays valid for the provider.

### 7.3 How middleware composes

- **Node hooks** (`before_*`, `after_*`) become graph nodes named `<MiddlewareName>.<hook>`. A hook can return a state update, or `{"jump_to": "model" | "tools" | "end"}` if it declared `can_jump_to`.
- **Wrap hooks** nest like decorators: `middleware=[A, B, C]` executes as `A(B(C(model_call)))`. The outer layer can call the inner `handler` several times (retry), skip it (cache/short-circuit), or change the request and response.
- Tools that middleware registers (via its `tools` attribute) are added to the agent automatically.

### 7.4 Messages and state

Agent state is a dict whose `messages` list uses LangGraph's `add_messages` reducer: returned messages are appended, and a message with an existing `id` replaces the old one. `RemoveMessage(id=...)` deletes a message. `structured_response` holds the parsed structured output, and middleware may add its own keys (such as `todos`).

### 7.5 Where model capabilities come from

Chat models carry an optional `profile` — a dict of capabilities such as `max_input_tokens`, `tool_calling` and `structured_output`, generated from models.dev data by the `langchain-profiles` CLI and shipped inside provider packages (`data/_profiles.py`). `create_agent` uses it to choose a structured-output strategy, and `SummarizationMiddleware` needs it for `("fraction", ...)` thresholds. When a model has no profile, pass `profile={"max_input_tokens": ...}` to the model constructor.


---

## 8. Troubleshooting

Entries quote the exact message raised by the source (placeholders such as `{provider}` are filled in at runtime; long messages are shortened with `…`). They are ordered: installation and model setup → agents → tools → structured output → middleware → memory and prompts → retrieval → MCP → provider-specific. Errors that link to `https://docs.langchain.com/oss/python/langchain/errors/<CODE>` carry one of the codes `INVALID_PROMPT_INPUT`, `MESSAGE_COERCION_FAILURE` or `OUTPUT_PARSING_FAILURE`.

### Error: `Initializing {class_name} requires the {pkg} package. Please install it with `pip install {pkg}``

**Cause:** `init_chat_model` (or `create_agent` with a model string) resolved a provider whose integration package is not installed.
**Solution:** Install the package named in the message, e.g. `pip install langchain-anthropic`, or `pip install "langchain[anthropic]"`.
**Prevention:** Pin provider packages in your requirements next to `langchain`.

### Error: `Unable to infer model provider for model={model!r}. Please specify 'model_provider' directly.`

**Cause:** The model string has no `provider:` prefix and its name does not start with a recognized prefix (for example `o4-mini`, `llama3`, `qwen`).
**Solution:** Use the prefixed form (`"openai:o4-mini"`, `"ollama:llama3.1"`) or pass `model_provider="..."`.
**Prevention:** Always use `provider:model` strings; inference is best-effort.

### Error: `Unsupported provider={provider!r}. Supported model providers are: …`

**Cause:** The `model_provider` (or prefix) is not in `init_chat_model`'s registry. Hyphens are normalized to underscores, so `google-genai` works but `gemini` does not.
**Solution:** Use a supported key from Section 10.1, or construct the provider's chat model class directly and pass the object.
**Prevention:** Copy provider keys from the reference table.

### Error: `` `model` must be a string (e.g., 'openai:gpt-5.5'), got {type}. If you've already constructed a chat model object, use it directly instead of passing it to init_chat_model(). ``

**Cause:** A model object was passed to `init_chat_model`.
**Solution:** Use the object directly — `create_agent(model=my_chat_model)`.
**Prevention:** Only pass strings to `init_chat_model`.

### Error: `Inferred model_provider='google_vertexai' from {model_name!r}. This default will change to 'google_genai' in the next major release.`

**Cause:** A bare `gemini-...` name was inferred as Vertex AI (a `DeprecationWarning`).
**Solution:** Use `google_vertexai:gemini-...` for Vertex AI or `google_genai:gemini-...` for the Gemini API / AI Studio.
**Prevention:** Always prefix Gemini model names.

### Error: `config_prefix={config_prefix!r} has been set but no fields are configurable.`

**Cause:** `init_chat_model(..., config_prefix="x")` was given a model but no `configurable_fields`.
**Solution:** Add `configurable_fields=("model", "temperature", ...)` or remove `config_prefix`.
**Prevention:** Use `config_prefix` only for configurable models.

### Error: `{name} is not a BaseChatModel attribute and is not implemented on the default model.`

**Cause:** An attribute was accessed on a configurable model (from `init_chat_model` with `configurable_fields`) that the underlying default model does not have.
**Solution:** Access provider attributes on a concrete model, or call only Runnable methods and `bind_tools` / `with_structured_output` on the configurable wrapper.
**Prevention:** Treat configurable models as Runnables.

### Error: `Provider '{provider}' is not supported. Supported providers and their required packages: …`

**Cause:** `init_embeddings` was given a provider outside its 10 supported keys.
**Solution:** Use one of `openai`, `azure_openai`, `azure_ai`, `bedrock`, `cohere`, `google_genai`, `google_vertexai`, `huggingface`, `mistralai`, `ollama`, or instantiate the embeddings class directly.
**Prevention:** Check Section 10.4.

### Error: `Invalid model format '{model_name}'. Model name must be in format 'provider:model-name'`

**Cause:** `init_embeddings("text-embedding-3-small")` — unlike chat models, embeddings never infer the provider.
**Solution:** `init_embeddings("openai:text-embedding-3-small")` or `init_embeddings("text-embedding-3-small", provider="openai")`.
**Prevention:** Always include the provider for embeddings.

### Error: `Could not import {pkg} python package. Please install it with `pip install {pkg}``

**Cause:** The embeddings provider's package is missing.
**Solution:** Install it (e.g. `pip install langchain-ollama`).
**Prevention:** Keep provider packages in your lockfile.

### Error: `Model name cannot be empty`

**Cause:** An empty or whitespace-only model string was given to `init_embeddings`.
**Solution:** Provide a real model name.
**Prevention:** Validate config values before calling.

### Error: `Tracing using LangChainTracerV1 is no longer supported. Please set the LANGCHAIN_TRACING_V2 environment variable to enable tracing instead.`

**Cause:** The legacy `LANGCHAIN_TRACING` or `LANGCHAIN_HANDLER` variable is set.
**Solution:** Unset them and set `LANGSMITH_TRACING=true` with `LANGSMITH_API_KEY`.
**Prevention:** Remove old tracing variables from `.env` files.

### Error: `Please remove duplicate middleware instances.`

**Cause:** Two middleware in `create_agent(middleware=[...])` share a `name`. The name defaults to the class name, so two instances of the same class collide — for example two `ModelRetryMiddleware(...)` with different settings.
**Solution:** Override the `name` property in a subclass, or combine the configuration into one instance. Several `PIIMiddleware` instances **are** allowed as long as their PII types differ, because the name is `PIIMiddleware[<pii_type>]`.
**Prevention:** Give custom middleware unique names.

### Error: `Middleware added tools that the agent doesn't know how to execute. Unknown tools: [...] Registered tools: [...]`

**Cause:** A `wrap_model_call` middleware added tools to `request.tools` that were never passed to `create_agent`.
**Solution:** Register the tools up front (`create_agent(tools=[...])` or the middleware's `tools` attribute), or implement `wrap_tool_call` to execute dynamic tools yourself.
**Prevention:** Middleware should filter the tool list, not invent new tools.

### Error: `ToolStrategy specifies tool '{name}' which wasn't declared in the original response format when creating the agent.`

**Cause:** Middleware replaced `response_format` with a `ToolStrategy` containing a schema that was not in the original.
**Solution:** Declare every possible schema in the original `response_format` (e.g. a union); middleware may only narrow it.
**Prevention:** Treat `response_format` overrides as subsets.

### Error: `Command goto is not yet supported in wrap_model_call middleware. Use the jump_to state field with before_model/after_model hooks instead.`

**Cause:** A `wrap_model_call` returned `ExtendedModelResponse(command=Command(goto=...))`.
**Solution:** Use a `before_model`/`after_model` hook with `can_jump_to` and return `{"jump_to": ...}`.
**Prevention:** Only use `Command(update=...)` inside `wrap_model_call`. (`Command.resume` and `Command.graph` are likewise rejected.)

### Error: `Synchronous implementation of wrap_model_call is not available. You are likely encountering this error because you defined only the async version (awrap_model_call) and invoked your agent in a synchronous context …`

**Cause:** Your middleware class implements only `awrap_model_call`, but you called `agent.invoke()`/`stream()`.
**Solution:** Implement the sync method, use the `@wrap_model_call` decorator on a sync function, or call `ainvoke()`/`astream()`.
**Prevention:** Implement both sync and async hooks in reusable middleware.

### Error: `Asynchronous implementation of awrap_model_call is not available. …`

**Cause:** Only the sync `wrap_model_call` exists, and the agent ran with `ainvoke()`/`astream()` (MCP tools force the async path).
**Solution:** Implement `awrap_model_call`, or use the decorator form.
**Prevention:** Same as above.

### Error: `Synchronous implementation of wrap_tool_call is not available. …`

**Cause:** Only `awrap_tool_call` exists and the agent ran synchronously.
**Solution:** Implement `wrap_tool_call` or run async.
**Prevention:** Implement both.

### Error: `Asynchronous implementation of awrap_tool_call is not available. …`

**Cause:** Only `wrap_tool_call` exists and the agent ran asynchronously.
**Solution:** Implement `awrap_tool_call` or run sync.
**Prevention:** Implement both.

### Error: `Direct attribute assignment to ModelRequest.{name} is deprecated. Use request.override({name}=...) instead …`

**Cause:** Middleware mutated a `ModelRequest` (`request.model = ...`).
**Solution:** `request = request.override(model=...)` and pass the new request to `handler`.
**Prevention:** Treat requests as immutable.

### Error: `Cannot specify both system_prompt and system_message`

**Cause:** Both were passed to `ModelRequest(...)` or `request.override(...)`.
**Solution:** Use only `system_message=SystemMessage(...)` (`system_prompt` is the deprecated form).
**Prevention:** Prefer `system_message` everywhere.

### Error: `Recursion limit of 9999 reached without hitting a stop condition.` (LangGraph `GraphRecursionError`)

**Cause:** The agent looped without ending — for example a tool that keeps failing, or middleware that keeps jumping back to the model. GitHub issue #40767 reports this with `PIIMiddleware(apply_to_output=True)` blanking tool-call parameters.
**Solution:** Inspect the trace; add `ModelCallLimitMiddleware(run_limit=...)`; if using PII output redaction on tool arguments, restrict it or use `apply_to_input` only.
**Prevention:** Always cap loops in production agents.

### Error: `Tool call {name} with id {id} could not be executed - arguments were malformed or truncated.`

**Cause:** Not an exception — an automatic error `ToolMessage` inserted when the model produced an invalid tool call (often truncated JSON because `max_tokens` was too low).
**Solution:** Raise the model's `max_tokens`; the model normally retries.
**Prevention:** Give tool-heavy agents generous output limits.

### Error: `Tool definition for {name} must include valid type annotations for argument 'args_schema' to behave as expected.`

**Cause:** A `BaseTool` subclass annotated `args_schema: BaseModel` instead of `args_schema: type[BaseModel]`.
**Solution:** Annotate as `args_schema: type[BaseModel] = MyArgs`.
**Prevention:** Copy the annotation from existing tools.

### Error: `args_schema must be a subclass of pydantic BaseModel or a JSON schema dict. Got: …`

**Cause:** An invalid object was passed as `args_schema`.
**Solution:** Pass a Pydantic model class or a JSON-schema dict.
**Prevention:** Validate tool definitions in unit tests.

### Error: `Arg {docstring_arg} in docstring not found in function signature.`

**Cause:** `@tool(parse_docstring=True)` found an argument in the `Args:` section that the function does not have.
**Solution:** Fix the docstring or the signature, or pass `error_on_invalid_docstring=False`.
**Prevention:** Keep docstrings in sync with signatures.

### Error: `When tool includes an InjectedToolCallId argument, tool must always be invoked with a full model ToolCall of the form: {'args': {...}, 'name': '...', 'type': 'tool_call', 'tool_call_id': '...'}`

**Cause:** A tool with an `InjectedToolCallId` parameter was called directly with plain arguments.
**Solution:** Invoke it with a full tool-call dict: `tool.invoke({"args": {...}, "name": "t", "type": "tool_call", "id": "call_1"})`.
**Prevention:** Test such tools through the agent or with tool-call dicts.

### Error: `String tool inputs are not allowed when using tools with JSON schema args_schema.`

**Cause:** A tool defined with a JSON-schema dict `args_schema` was invoked with a string.
**Solution:** Invoke with a dict of arguments.
**Prevention:** Use dict inputs for structured tools.

### Error: `Too many arguments to single-input tool {name}. Consider using StructuredTool instead.`

**Cause:** A single-input `Tool` received several arguments.
**Solution:** Use `@tool` on a typed function or `StructuredTool.from_function`.
**Prevention:** Avoid the legacy single-string `Tool` for multi-argument functions.

### Error: `Function and/or coroutine must be provided`

**Cause:** `StructuredTool.from_function()` / `Tool.from_function()` was called without `func` or `coroutine`.
**Solution:** Pass at least one of them.
**Prevention:** Prefer the `@tool` decorator.

### Error: `The first argument must be a string or a callable with a __name__ for tool decorator.`

**Cause:** `@tool(...)` was given an unexpected first argument.
**Solution:** Use `@tool`, `@tool("name")` or `@tool(description="...")`.
**Prevention:** Pass options as keywords.

### Error: `Tool input must be str or dict. If dict, dict arguments must be typed. Either annotate types (e.g., with TypedDict) or pass arg_types into `.as_tool` to specify.`

**Cause:** `runnable.as_tool()` could not infer an argument schema from an untyped runnable.
**Solution:** Pass `args_schema=` or `arg_types={"x": int}` to `as_tool`.
**Prevention:** Type your runnable inputs.

### Error: `Failed to generate JSON schema for '{model_name}': … Tool argument schemas must be JSON-serializable.`

**Cause:** A tool argument uses a custom Python class that Pydantic cannot express as JSON Schema.
**Solution:** Replace it with a Pydantic model, basic types, or a string representation.
**Prevention:** Keep tool arguments JSON-friendly.

### Error: `Failed to parse structured output for tool '{tool_name}': {source}.` (`StructuredOutputValidationError`)

**Cause:** The model's structured-output tool call did not validate against your schema, and error handling was off (or it came from `ProviderStrategy`).
**Solution:** Use `ToolStrategy(schema, handle_errors=True)` so the model gets the error and retries; loosen the schema; use a stronger model.
**Prevention:** Keep schemas simple and well described with `Field(description=...)`.

### Error: `Model incorrectly returned multiple structured responses ({names}) when only one is expected.` (`MultipleStructuredOutputsError`)

**Cause:** With a union `ToolStrategy`, the model called more than one schema tool in one turn.
**Solution:** Keep `handle_errors=True` (default) so the agent asks the model to correct itself.
**Prevention:** Describe in the system prompt when each schema applies.

### Error: `Native structured output expected valid JSON for {schema_name}, but parsing failed: {e}.`

**Cause:** With `ProviderStrategy`, the model's text was not valid JSON (often a truncated response).
**Solution:** Increase `max_tokens`; switch to `ToolStrategy`; confirm the model supports native structured output.
**Prevention:** Let the default auto strategy choose.

### Error: `Unsupported schema type: {type}. Supported types: Pydantic models, dataclasses, TypedDicts, and JSON schema dicts.`

**Cause:** `response_format` was given something else (e.g. a plain class or a `str`).
**Solution:** Convert the schema to one of the four supported kinds.
**Prevention:** Use Pydantic models for structured output.

### Error: `with_structured_output is not implemented for this model.`

**Cause:** The chat model class does not implement `bind_tools` (for example the fake test models, or a minimal custom model).
**Solution:** Use a provider model that supports tool calling, or implement `bind_tools`.
**Prevention:** Check `model.profile.get("tool_calling")`.

### Error: `Received unsupported arguments {kwargs}`

**Cause:** Extra keyword arguments were passed to the base `with_structured_output` of a model that does not override it (only `method` and `strict` are accepted — and ignored — there).
**Solution:** Remove the extra arguments or use a provider model whose `with_structured_output` supports them.
**Prevention:** Check the provider's signature.

### Error: `schema must be specified when method is not 'json_mode'. Received None.`

**Cause:** `with_structured_output(None, method=...)` on OpenAI/Ollama without `json_mode`.
**Solution:** Pass a schema, or use `method="json_mode"`.
**Prevention:** Always pass a schema.

### Error: `This model does not support OpenAI's structured output feature, which is the default method for with_structured_output as of langchain-openai==0.3.`

**Cause:** An OpenAI-compatible model rejected `response_format` of type `json_schema` (a `UserWarning` accompanying the API error).
**Solution:** Use `with_structured_output(schema, method="function_calling")`.
**Prevention:** Choose the method explicitly for OpenAI-compatible servers.

### Error: `Invalid schema for OpenAI's structured output feature …`

**Cause:** OpenAI rejected the JSON schema (strict mode forbids some constructs).
**Solution:** Use `method="function_calling"` or adjust the schema (no unsupported keywords, all fields required in strict mode).
**Prevention:** Test schemas against the provider before shipping.

### Error: `Invalid `interrupt_on` config for tool '{tool_name}': `allowed_decisions` must be a non-empty list of decision types …`

**Cause:** A `HumanInTheLoopMiddleware` entry was a dict without a non-empty `allowed_decisions` (often a typo such as `allowed_decision`).
**Solution:** Use `{"allowed_decisions": ["approve", "reject"]}`, or `True` for all decisions.
**Prevention:** Build entries with `InterruptOnConfig(...)` for type checking.

### Error: `Number of human decisions ({n}) does not match number of hanging tool calls ({m}).`

**Cause:** The `Command(resume={"decisions": [...]})` list length differs from `len(interrupt.value["action_requests"])`.
**Solution:** Send exactly one decision per action request, in order.
**Prevention:** Build decisions by iterating over `action_requests`.

### Error: `Unexpected human decision: {decision}. Decision type '{type}' is not allowed for tool '{tool}'. Expected one of {allowed_decisions} based on the tool's configuration.`

**Cause:** The reviewer sent a decision type (e.g. `edit`) not allowed for that tool.
**Solution:** Offer reviewers only the `review_configs[i]["allowed_decisions"]` options.
**Prevention:** Drive your approval UI from `review_configs`.

### Error: `Reviewer edited tool call {id!r} to {name!r}, which is not an available tool. Available tools: {names}.`

**Cause:** An `edit` decision named a tool the agent does not have.
**Solution:** Restrict edits to registered tool names.
**Prevention:** Validate edited actions in your UI.

### Error: `At least one limit must be specified (thread_limit or run_limit)`

**Cause:** `ModelCallLimitMiddleware()` or `ToolCallLimitMiddleware()` with no limits.
**Solution:** Pass `run_limit=` and/or `thread_limit=`.
**Prevention:** —

### Error: `Invalid exit_behavior: {exit_behavior}. Must be 'end' or 'error'`

**Cause:** `ModelCallLimitMiddleware` only supports `"end"` and `"error"` (not `"continue"`).
**Solution:** Use one of those two values.
**Prevention:** Note that `ToolCallLimitMiddleware` additionally supports `"continue"`.

### Error: `run_limit ({run_limit}) cannot exceed thread_limit ({thread_limit}).`

**Cause:** `ToolCallLimitMiddleware` was configured with a run limit above the thread limit.
**Solution:** Make `run_limit <= thread_limit`.
**Prevention:** —

### Error: `Model call limits exceeded: thread limit ({n}/{limit})` (`ModelCallLimitExceededError`)

**Cause:** `ModelCallLimitMiddleware(exit_behavior="error")` hit its limit.
**Solution:** Catch the exception, or use `exit_behavior="end"` to finish gracefully with an explanatory AI message.
**Prevention:** Set limits from observed usage. Thread limits persist across runs of the same `thread_id`.

### Error: `'{tool_name}' tool call limit reached: run limit exceeded ({n}/{limit} calls).` (`ToolCallLimitExceededError`)

**Cause:** `ToolCallLimitMiddleware(exit_behavior="error")` hit its limit.
**Solution:** Catch it or use `"continue"` (blocks the tool, lets the model continue) / `"end"`.
**Prevention:** —

### Error: `max_retries must be >= 0`

**Cause:** Invalid retry configuration (siblings: `initial_delay must be >= 0`, `max_delay must be >= 0`, `backoff_factor must be >= 0`).
**Solution:** Use non-negative values.
**Prevention:** —

### Error: `on_failure='raise' is deprecated and will be removed in a future version. Use on_failure='error' instead.`

**Cause:** Old `ToolRetryMiddleware` option (similarly `'return_message'` → `'continue'`).
**Solution:** Use the new names.
**Prevention:** —

### Error: `ToolErrorMiddleware requires `on_error` and/or `aon_error`.`

**Cause:** `ToolErrorMiddleware()` was created with no handler.
**Solution:** Pass `on_error=lambda exc, request: ...`.
**Prevention:** —

### Error: `ToolErrorMiddleware has no sync `on_error`; run async (ainvoke/astream) or provide `on_error`.`

**Cause:** Only `aon_error` was given but the agent runs synchronously.
**Solution:** Add `on_error` or run async.
**Prevention:** Provide both handlers for dual-mode agents.

### Error: `Model profile information is required to use fractional token limits, and is unavailable for the specified model.`

**Cause:** `SummarizationMiddleware` uses `("fraction", x)` but the model has no `max_input_tokens` profile.
**Solution:** Use `("tokens", N)`, or construct the model with `profile={"max_input_tokens": 128000}`.
**Prevention:** Prefer absolute token triggers for self-hosted models.

### Error: `trigger clause must specify at least one of 'tokens', 'messages', 'fraction'`

**Cause:** An empty dict was given as a trigger clause. Related: `Unsupported trigger metric: {k!r}`, `{k} trigger values must be integers, got {v!r}`, `Fractional {name} values must be between 0 and 1, got {value}.`, `{name} thresholds must be greater than 0, got {value}.`
**Solution:** Use `{"tokens": 4000}`, `("messages", 50)` or `("fraction", 0.8)`.
**Prevention:** Keep trigger values typed correctly (ints for tokens/messages, 0–1 floats for fraction).

### Error: `max_tokens_before_summary is deprecated. Use trigger=('tokens', value) instead.`

**Cause:** Old `SummarizationMiddleware` keyword (similarly `messages_to_keep` → `keep=('messages', value)`).
**Solution:** Use `trigger=` and `keep=`.
**Prevention:** —

### Error: `Detected {count} instance(s) of {pii_type} in text content` (`PIIDetectionError`)

**Cause:** `PIIMiddleware(..., strategy="block")` found PII.
**Solution:** Catch `PIIDetectionError` and ask the user to resubmit, or switch to `redact`/`mask`.
**Prevention:** Use `block` only where refusing is acceptable.

### Error: `Unknown PII type: {pii_type}. Must be one of ['email', 'credit_card', 'ip', 'mac_address', 'url'] or provide a custom detector.`

**Cause:** A custom PII type was named without a `detector`.
**Solution:** Add `detector=r"regex"` or a function.
**Prevention:** —

### Error: `LLMToolEmulator's default model ('anthropic:claude-sonnet-4-5-20250929') is deprecated and will be removed in a future release. Pass `model` explicitly instead.`

**Cause:** `LLMToolEmulator()` without `model=`.
**Solution:** Pass `model="openai:gpt-5.4-mini"` or any model.
**Prevention:** Always pass models explicitly to middleware.

### Error: `LLMToolEmulator's default model requires `langchain-anthropic` to be installed.`

**Cause:** The implicit default model needs the Anthropic package.
**Solution:** Pass `model=` or install `langchain-anthropic`.
**Prevention:** Same as above.

### Error: `Tools in always_include not found in request: {missing}. Available tools: {names}`

**Cause:** `LLMToolSelectorMiddleware(always_include=[...])` named a tool the agent does not have.
**Solution:** Fix the names.
**Prevention:** Derive the list from your tools: `[t.name for t in tools]`.

### Error: `LLMToolSelectorMiddleware: selection model returned a malformed response after {n} retries (expected a dict with a 'tools' list): …`

**Cause:** The selector model did not return structured output.
**Solution:** Use a model that supports structured output, raise `max_retries`, or set `on_parsing_failure="all"` / `"none"` / a fallback list.
**Prevention:** Use a capable selector model.

### Error: `Model selected invalid tools: {names}`

**Cause:** The selector model returned tool names that do not exist.
**Solution:** Improve tool descriptions or use a stronger selector model.
**Prevention:** Keep tool names short and distinct.

### Error: `ProviderToolSearchMiddleware could not determine the provider for model {name!r}; server-side tool search supports: anthropic, openai`

**Cause:** Tools were deferred but the model is not an Anthropic or OpenAI model (GitHub issue #40876 reported this for `ChatAnthropicBedrock`).
**Solution:** Use a supported provider, or remove `ProviderToolSearchMiddleware`.
**Prevention:** Use `LLMToolSelectorMiddleware` for provider-independent tool filtering.

### Error: `ProviderToolSearchMiddleware: searchable_tools references tool(s) not bound to the model: {names}`

**Cause:** `searchable_tools` lists tools not passed to the agent.
**Solution:** Pass those tools to `create_agent`.
**Prevention:** —

### Error: `Path traversal not allowed`

**Cause:** A file-search (or Claude text-editor) tool call used `..` or `~` in a path.
**Solution:** None needed — this is a safety check; the model gets the error.
**Prevention:** Tell the model paths are relative to the workspace root.

### Error: `Shell tool requires either 'command' or 'restart'.`

**Cause:** The model called the shell tool with neither argument (or both: `Specify only one of 'command' or 'restart'.`).
**Solution:** Usually self-corrects; improve `tool_description` if frequent.
**Prevention:** —

### Error: `Startup command '{command}' failed with exit code {code}`

**Cause:** One of `ShellToolMiddleware(startup_commands=...)` failed.
**Solution:** Run the command manually in the same environment and fix it.
**Prevention:** Keep startup commands idempotent.

### Error: `HostExecutionPolicy cpu/memory limits require the Python 'resource' module. Either remove the limits or run on a POSIX platform.`

**Cause:** `cpu_time_seconds` or `memory_bytes` set on Windows.
**Solution:** Remove the limits, or use `DockerExecutionPolicy`.
**Prevention:** Use Docker isolation on Windows hosts.

### Error: `Docker execution policy requires the 'docker' CLI to be installed and available on PATH.`

**Cause:** `DockerExecutionPolicy` could not find the `docker` binary (similarly `Codex sandbox policy requires the 'codex' CLI…`).
**Solution:** Install Docker or pass `binary="/path/to/docker"`.
**Prevention:** —

### Error: `DockerExecutionPolicy does not support cpu_time_seconds; configure CPU limits using Docker run options such as '--cpus'.`

**Cause:** `cpu_time_seconds` given to the Docker policy.
**Solution:** Use `cpus="1.5"` instead.
**Prevention:** —

### Error: `Input to ChatPromptTemplate is missing variables {missing}. Expected: [...] Received: [...]`

**Cause:** A prompt was invoked without all template variables. A common trigger is literal braces in the template (e.g. JSON examples).
**Solution:** Supply the variables, or escape literal braces as `{{` and `}}`.
**Prevention:** Test prompts with `prompt.input_variables`.

### Error: `Cannot have an input variable named 'stop', as it is used internally, please rename.`

**Cause:** `stop` is reserved.
**Solution:** Rename the variable.
**Prevention:** —

### Error: `Found overlapping input and partial variables: {names}`

**Cause:** A variable was both partially filled and declared as input.
**Solution:** Remove it from one of the two.
**Prevention:** —

### Error: `Invalid format specifier in f-string template. Nested replacement fields are not allowed.`

**Cause:** An f-string template contains `{x:{y}}`; attribute or index access (`{a.b}`, `{a[0]}`) is also rejected.
**Solution:** Pre-compute values into plain variables.
**Prevention:** Keep templates to simple `{name}` fields.

### Error: `Message dict must contain 'role' and 'content' keys, got {message}`

**Cause:** A dict message lacked `role` or `content`.
**Solution:** Use `{"role": "user", "content": "..."}`.
**Prevention:** —

### Error: `Unexpected message type: '{type}'. Use one of 'human', 'user', 'ai', 'assistant', 'function', 'tool', 'system', or 'developer'.`

**Cause:** An unknown role such as `"chat"` or `"bot"`.
**Solution:** Use a supported role.
**Prevention:** Normalize roles when importing chat logs.

### Error: `Message as a sequence must be (role string, template)`

**Cause:** A tuple message had the wrong length.
**Solution:** Use two-element tuples: `("human", "hi")`.
**Prevention:** —

### Error: `ToolMessage content should be a string or a list of string/dicts.`

**Cause:** A tool returned an object that could not be coerced to a string.
**Solution:** Return a string, a list of content blocks, or use `response_format="content_and_artifact"` to attach the object as an artifact.
**Prevention:** Serialize tool outputs explicitly.

### Error: `Using fallback GPT-2 tokenizer for token counting. Token counts may be inaccurate for non-GPT-2 models.`

**Cause:** `get_num_tokens` fell back to the default GPT-2 tokenizer (requires `transformers`).
**Solution:** Use a provider model with its own token counting, or `count_tokens_approximately`.
**Prevention:** Use `token_counter="approximate"` in `trim_messages`.

### Error: `Could not import transformers python package. This is needed in order to calculate get_token_ids.`

**Cause:** Default token counting needs `transformers`.
**Solution:** `pip install transformers`, or use a provider-specific counter / `count_tokens_approximately`.
**Prevention:** —

### Error: `Asked to cache, but no cache found at `langchain.cache`.`

**Cause:** A model was created with `cache=True` but no global cache was set.
**Solution:** Call `set_llm_cache(InMemoryCache())` first.
**Prevention:** —

### Error: `Invalid json output: {text}` (`OutputParserException`)

**Cause:** `JsonOutputParser` received text that is not JSON.
**Solution:** Prefer `with_structured_output`; otherwise add format instructions via `parser.get_format_instructions()`.
**Prevention:** Use structured output features over text parsing.

### Error: `defusedxml is not installed. Please install it to use the defusedxml parser.`

**Cause:** `XMLOutputParser` uses `defusedxml` by default.
**Solution:** `pip install defusedxml`.
**Prevention:** Do not switch to `parser="xml"` unless your stdlib XML is known-safe.

### Error: `If 'exception_key' is specified then input must be a dictionary.`

**Cause:** `with_fallbacks(..., exception_key="error")` used with a non-dict input.
**Solution:** Pass dict inputs, or drop `exception_key`.
**Prevention:** Note the key is written into your input dict in place.

### Error: `The input to RunnablePassthrough.assign() must be a dict.`

**Cause:** `.assign()` / `.pick()` received a non-dict.
**Solution:** Feed a dict (e.g. start the chain with a dict of inputs).
**Prevention:** —

### Error: `Expected a Runnable, callable or dict.Instead got an unsupported type: {type}`

**Cause:** Something that is not a Runnable, function or `dict` was put in a `|` chain (for example a string or a non-dict mapping).
**Solution:** Wrap it: `RunnableLambda(fn)` or a real `dict`.
**Prevention:** —

### Error: `Recursion limit reached when invoking {runnable} with input {input}.`

**Cause:** A `RunnableLambda` kept returning runnables past `recursion_limit` (default 25).
**Solution:** Fix the recursion, or raise `config={"recursion_limit": N}`.
**Prevention:** —

### Error: `astream_events(version='v3') is only supported on Runnable subclasses that implement the v3 streaming protocol (BaseChatModel, CompiledGraph).`

**Cause:** v3 events requested on a chain.
**Solution:** Use `version="v2"`.
**Prevention:** —

### Error: `search_type of {search_type} not allowed. Valid values are: ('similarity', 'similarity_score_threshold', 'mmr')`

**Cause:** A typo in `as_retriever(search_type=...)`.
**Solution:** Use one of the three values.
**Prevention:** —

### Error: `Relevance scores must be between 0 and 1, got {...}`

**Cause:** A `UserWarning` from a vector store whose relevance function does not match its distance metric.
**Solution:** Pass `relevance_score_fn` or use a normalized metric.
**Prevention:** —

### Error: `Using SHA-1 for document hashing. SHA-1 is *not* collision-resistant …`

**Cause:** `index()` uses the default `key_encoder="sha1"` (one-time `UserWarning`).
**Solution:** Pass `key_encoder="blake2b"` (or `sha256`/`sha512`) and rebuild the index once.
**Prevention:** Choose the encoder before your first indexing run.

### Error: `Unable to import from langchain_text_splitters. Please specify text_splitter or install langchain_text_splitters`

**Cause:** `loader.load_and_split()` needs the text-splitters package.
**Solution:** `pip install -U langchain-text-splitters`, or split explicitly.
**Prevention:** Call `splitter.split_documents(loader.load())` instead.

### Error: `chunk_size must be > 0, got {chunk_size}`

**Cause:** Invalid splitter settings; `chunk_overlap` must also be ≥ 0 and ≤ `chunk_size`.
**Solution:** Fix the values.
**Prevention:** —

### Error: `Could not import tiktoken python package. … Please install it with `pip install tiktoken`.`

**Cause:** `TokenTextSplitter` / `from_tiktoken_encoder` need `tiktoken` (text-splitters declares no extras).
**Solution:** `pip install tiktoken`.
**Prevention:** Install optional back ends explicitly (NLTK, spaCy, sentence-transformers, bs4, lxml).

### Error: `json_data must be a dict, got {type}. Top-level lists can be split by passing convert_lists=True.`

**Cause:** `RecursiveJsonSplitter.split_json` received a list.
**Solution:** Pass `convert_lists=True`.
**Prevention:** —

### Error: `Please install FastMCP to use `MCPAdapter` — `pip install fastmcp`.`

**Cause:** `langchain.mcp` imported without FastMCP.
**Solution:** `pip install "langchain[mcp]"`.
**Prevention:** —

### Error: `MCP target {target!r} is not a valid URL. To run a local MCP server over stdio, ask for it explicitly with Path('server.py'), a fastmcp transport, or an MCPConfig …`

**Cause:** A string that is not an http(s) URL was passed to `MCPAdapter` — including local script paths.
**Solution:** Wrap local scripts in `pathlib.Path(...)`, or use an `MCPConfig` dict.
**Prevention:** This is deliberate: string targets can never launch subprocesses.

### Error: `MCP target {target!r} has scheme {scheme!r}, but a string target must be an http or https URL.`

**Cause:** `file:`, `ws:` or a Windows path like `C:\server.py` was passed as a string.
**Solution:** Same as above.
**Prevention:** —

### Error: `Resuming the MCP tool {tool!r} needs an answer for every elicitation request, but these had none: {keys}.`

**Cause:** The resume `responses` dict missed some request keys.
**Solution:** Answer every `request["key"]` from the interrupt payload with `accept`, `decline` or `cancel`.
**Prevention:** Iterate over `interrupt.value["requests"]`.

### Error: `Elicitation answer for {key!r} has action {action!r}; expected 'accept', 'decline', or 'cancel'.`

**Cause:** Invalid `action` in an elicitation answer.
**Solution:** Use one of the three actions.
**Prevention:** —

### Error: `The MCP tool {tool!r} asked for input that interrupt-based elicitation cannot answer: {kinds}. Only elicitation requests are supported.`

**Cause:** The server asked for sampling or roots, which `langchain.mcp` refuses.
**Solution:** Use a pre-built `fastmcp.Client` with your own handlers, or a server that does not need them.
**Prevention:** —

### Error: `Converting MCP audio content to a LangChain content block is not yet supported.`

**Cause:** An MCP tool returned audio.
**Solution:** Have the server return a file/resource instead.
**Prevention:** —

### Error: `Unknown MCP content type: {type}. This usually means the installed `mcp` is newer than the version this adapter supports.`

**Cause:** Version skew between `mcp`/`fastmcp` and `langchain`.
**Solution:** Upgrade `langchain`, or pin `fastmcp` to the range in `langchain[mcp]` (`>=4.0.1,<5.0.0`).
**Prevention:** Upgrade them together.

### Error: `Anthropic authentication failed: no API key or authorization credentials were provided. Set the ANTHROPIC_API_KEY environment variable, pass api_key=... to ChatAnthropic, …`

**Cause:** No Anthropic credentials.
**Solution:** `export ANTHROPIC_API_KEY=...` or pass `api_key=`.
**Prevention:** —

### Error: `Received multiple non-consecutive system messages.`

**Cause:** Anthropic accepts system messages only at the start (or in supported mid-conversation positions).
**Solution:** Merge system instructions into one leading `SystemMessage`.
**Prevention:** Use `system_prompt=` on the agent rather than inserting system messages mid-conversation.

### Error: `A non-leading SystemMessage was moved to the top-level system field and now applies to the entire conversation.`

**Cause:** A `UserWarning` from `ChatAnthropic`: your mid-conversation system message was hoisted.
**Solution:** Put instructions in the leading system prompt, or use a model that supports mid-conversation system messages.
**Prevention:** —

### Error: `Malformed url parameter. Must be either an image URL (https://example.com/image.jpg) or base64 encoded string (data:image/png;base64,...)`

**Cause:** An image block for Anthropic had an invalid URL. GitHub issue #40905 notes base64 data URLs with line breaks were rejected.
**Solution:** Use `create_image_block(base64=..., mime_type="image/png")` with a single-line base64 string.
**Prevention:** Strip newlines from base64 data.

### Error: `Sync client is not available. This happens when an async callable was provided for the API key. Use async methods (ainvoke, astream) instead, …`

**Cause:** `ChatOpenAI(api_key=async_fn)` was used synchronously.
**Solution:** Use `ainvoke`/`astream`, or provide a string or sync callable.
**Prevention:** —

### Error: `Cannot specify 'openai_proxy' if one of 'http_client'/'http_async_client' is already specified.`

**Cause:** Both a proxy and a custom HTTP client were given.
**Solution:** Configure the proxy on your own `httpx` client, or drop the custom client.
**Prevention:** —

### Error: `Received response with null value for 'choices'. This can happen when using OpenAI-compatible APIs (e.g., vLLM) that return a response in an unexpected format.`

**Cause:** An OpenAI-compatible server returned a non-standard response.
**Solution:** Check the server's API compatibility; use a dedicated provider package if one exists.
**Prevention:** —

### Error: `As of openai>=1.0.0, Azure endpoints should be specified via the `azure_endpoint` param not `openai_api_base` (or alias `base_url`).`

**Cause:** `AzureChatOpenAI(base_url=...)` without `/openai` in the URL, or combined with `azure_deployment`.
**Solution:** Use `azure_endpoint="https://<resource>.openai.azure.com/"` plus `azure_deployment=`.
**Prevention:** Set `AZURE_OPENAI_ENDPOINT` and `OPENAI_API_VERSION`.

### Error: `n must be 1 when streaming.`

**Cause:** `n > 1` combined with `streaming=True` (OpenAI, Azure, Groq, xAI, Fireworks, OpenRouter).
**Solution:** Use `n=1` when streaming.
**Prevention:** —

### Error: `Model `{model}` not found in Ollama. Please pull the model (using `ollama pull {model}`) or specify a valid model name.`

**Cause:** `ChatOllama(..., validate_model_on_init=True)` and the model is not pulled.
**Solution:** `ollama pull <model>`.
**Prevention:** —

### Error: `Failed to connect to Ollama. Please check that Ollama is downloaded, running and accessible. https://ollama.com/download`

**Cause:** The Ollama server is not reachable at `base_url` (or the SDK default).
**Solution:** Start Ollama; pass `base_url="http://host:11434"` for remote servers (`OLLAMA_HOST` is not read by LangChain).
**Prevention:** —

### Error: `temperature must be in the range [0.0, 1.0]`

**Cause:** `ChatMistralAI` restricts `temperature` and `top_p` to 0–1.
**Solution:** Use a value in range.
**Prevention:** —

### Error: `xAI API key is not set. Please set it in the `xai_api_key` field or in the `XAI_API_KEY` environment variable.`

**Cause:** Missing xAI key. Similar provider messages: `If using default api base, DEEPSEEK_API_KEY must be set.`, `OPENROUTER_API_KEY must be set.`, `You must specify an api key. … FIREWORKS_API_KEY`.
**Solution:** Set the named environment variable or pass `api_key=`.
**Prevention:** —

### Error: `Perplexity API key not provided. Pass `pplx_api_key` (or `api_key`) … or set the `PPLX_API_KEY` or `PERPLEXITY_API_KEY` environment variable.`

**Cause:** Missing Perplexity key. Note `ChatPerplexity` itself reads **only** `PPLX_API_KEY`.
**Solution:** Set `PPLX_API_KEY`.
**Prevention:** —

### Error: `search_parameters (Live Search) is deprecated by xAI and is ignored.`

**Cause:** `ChatXAI(search_parameters=...)`.
**Solution:** `ChatXAI(model="...").bind_tools([{"type": "web_search"}])`.
**Prevention:** —

### Error: `Only one of 'persist_directory', 'host' and 'chroma_cloud_api_key' is allowed, but got …`

**Cause:** Conflicting `Chroma(...)` connection options.
**Solution:** Choose local persistence, a server host, or Chroma Cloud (which also needs `tenant` and `database`).
**Prevention:** —

### Error: `The 'fastembed' package is not installed.`

**Cause:** `FastEmbedSparse` needs `pip install "langchain-qdrant[fastembed]"` (Python < 3.13).
**Solution:** Install the extra.
**Prevention:** —

---

## 9. FAQ

**Q: Which package should I import from — `langchain`, `langchain_core` or `langchain_classic`?**

Use `langchain` for agents, middleware and model initialization; it re-exports the everyday types (`langchain.messages`, `langchain.tools`, `langchain.chat_models`, `langchain.embeddings`, `langchain.rate_limiters`). Use `langchain_core` for prompts, output parsers, runnables, documents, vector store interfaces and callbacks. Use `langchain_classic` only to keep old code running.

**Q: Do I need to learn LangGraph?**

No. `create_agent` builds and compiles the LangGraph graph for you. You will meet LangGraph concepts only for persistence (checkpointers, `thread_id`), interrupts (`Command(resume=...)`) and stream modes.

**Q: Is `AgentExecutor` still supported?**

It lives in `langchain-classic` and is superseded by `create_agent`. Classic memory classes are deprecated with `create_agent` as the named alternative.

**Q: How do I swap OpenAI for Anthropic or a local model?**

Change the model string: `"openai:gpt-5.5"` → `"anthropic:claude-sonnet-4-5-20250929"` or `"ollama:llama3.1"`, and install the provider package. Tools, middleware and structured output keep working.

**Q: Why doesn't my agent remember previous messages?**

You need both a `checkpointer=` on `create_agent` and the same `config={"configurable": {"thread_id": ...}}` on every call.

**Q: How do I stop an agent from looping forever?**

Add `ModelCallLimitMiddleware(run_limit=N)` and/or `ToolCallLimitMiddleware(run_limit=N)`. The graph's own recursion limit is set very high (9,999) by design.

**Q: Can one agent use tools from several MCP servers?**

Yes — pass an `{"mcpServers": {...}}` dict to one `MCPAdapter`; tool names are prefixed with each server's key.

**Q: Does LangChain send my data anywhere besides the model provider?**

Only if you enable tracing (`LANGSMITH_TRACING=true`), which sends run inputs and outputs to LangSmith, or configure the LangSmith gateway. Use `TracePolicy(process_inputs=omit_payload)` to drop payloads from middleware spans.

**Q: What is the difference between `context` and `state`?**

`context` is immutable per-run data you pass to `invoke(..., context=...)` and read via `runtime.context`. `state` (`messages`, `structured_response`, middleware fields) evolves during the run and is saved by the checkpointer.

**Q: Why is `max_tokens` different on my Anthropic model than I expected?**

`ChatAnthropic` fills `max_tokens` from the model's profile (`max_output_tokens`), falling back to 4096 when no profile exists. Set it explicitly for predictable behavior.

**Q: How do I count tokens and cost across an agent run?**

Wrap the call in `with get_usage_metadata_callback() as cb:` and read `cb.usage_metadata`, keyed by model name. Each `AIMessage` also carries `usage_metadata`.

**Q: Can tools return images or files to the model?**

Yes — return a list of content blocks (e.g. `create_image_block(...)`), provided the model supports image tool messages (see the profile's `image_tool_message`).

**Q: How do I test agents without calling a real model?**

Use `GenericFakeChatModel(messages=iter([...]))` or `FakeListChatModel(responses=[...])` from `langchain_core.language_models`, and `LLMToolEmulator` to fake tool results. Note that fake models do not support `with_structured_output`.

**Q: Is the `openwiki/` folder in the repository official documentation?**

It is an automatically generated code wiki. The repository's own `AGENTS.md` says to treat source code and tests as authoritative over it.

**Q: Why am I getting deprecation warnings I did not ask for?**

Importing `langchain_core` turns on display of LangChain's deprecation and beta warnings. Wrap code in `langchain_core._api.suppress_langchain_deprecation_warning()` or `suppress_langchain_beta_warning()` to silence them locally.

**Q: Which Python versions are tested?**

CI runs unit tests for `langchain-core` on 3.10 through 3.14 and for other packages on 3.10 and 3.14.

---

## 10. Reference

### 10.1 `init_chat_model` providers

| `model_provider` | Package | Class |
|---|---|---|
| `openai` | `langchain-openai` | `ChatOpenAI` |
| `azure_openai` | `langchain-openai` | `AzureChatOpenAI` |
| `azure_ai` | `langchain-azure-ai` | `AzureAIOpenAIApiChatModel` |
| `anthropic` | `langchain-anthropic` | `ChatAnthropic` |
| `anthropic_bedrock` | `langchain-aws` | `ChatAnthropicBedrock` |
| `bedrock` | `langchain-aws` | `ChatBedrock` |
| `bedrock_converse` | `langchain-aws` | `ChatBedrockConverse` |
| `bedrock_mantle_anthropic` | `langchain-aws[anthropic]` | `ChatAnthropicMantle` |
| `bedrock_mantle_openai` | `langchain-aws[openai]` | `ChatOpenAIMantle` |
| `google_vertexai` | `langchain-google-vertexai` | `ChatVertexAI` |
| `google_genai` | `langchain-google-genai` | `ChatGoogleGenerativeAI` |
| `google_anthropic_vertex` | `langchain-google-vertexai` | `ChatAnthropicVertex` |
| `cohere` | `langchain-cohere` | `ChatCohere` |
| `mistralai` | `langchain-mistralai` | `ChatMistralAI` |
| `groq` | `langchain-groq` | `ChatGroq` |
| `fireworks` | `langchain-fireworks` | `ChatFireworks` |
| `together` | `langchain-together` | `ChatTogether` |
| `ollama` | `langchain-ollama` (falls back to `langchain-community`) | `ChatOllama` |
| `huggingface` | `langchain-huggingface` | `ChatHuggingFace.from_model_id(model_id=...)` |
| `ibm` | `langchain-ibm` | `ChatWatsonx(model_id=...)` |
| `nvidia` | `langchain-nvidia-ai-endpoints` | `ChatNVIDIA` |
| `deepseek` | `langchain-deepseek` | `ChatDeepSeek` |
| `xai` | `langchain-xai` | `ChatXAI` |
| `perplexity` | `langchain-perplexity` | `ChatPerplexity` |
| `openrouter` | `langchain-openrouter` | `ChatOpenRouter` |
| `upstage` | `langchain-upstage` | `ChatUpstage` |
| `baseten` | `langchain-baseten` | `ChatBaseten` |
| `litellm` | `langchain-litellm` | `ChatLiteLLM` |
| `meta` | `langchain-meta` | `ChatMetaModel` |
| `langsmith` | `langchain-openai` | `ChatOpenAI` via the LangSmith gateway (Responses API) |

### 10.2 Provider inference from bare model names

| Name starts with (case-insensitive) | Inferred provider |
|---|---|
| `gpt-`, `o1`, `o3`, `chatgpt`, `text-davinci` | `openai` |
| `claude` | `anthropic` |
| `command` | `cohere` |
| `accounts/fireworks` | `fireworks` |
| `gemini` | `google_vertexai` (with a `DeprecationWarning`) |
| `amazon.`, `anthropic.`, `meta.` | `bedrock` |
| `mistral`, `mixtral` | `mistralai` |
| `deepseek` | `deepseek` |
| `grok` | `xai` |
| `sonar` | `perplexity` |
| `solar` | `upstage` |

### 10.3 `create_agent` parameters

| Parameter | Type | Default |
|---|---|---|
| `model` | `str \| BaseChatModel` | required |
| `tools` | `Sequence[BaseTool \| Callable \| dict] \| None` | `None` |
| `system_prompt` | `str \| SystemMessage \| None` | `None` |
| `middleware` | `Sequence[AgentMiddleware]` | `()` |
| `response_format` | `ToolStrategy \| ProviderStrategy \| type \| dict \| None` | `None` |
| `state_schema` | `type[AgentState] \| None` | `None` |
| `context_schema` | `type \| None` | `None` |
| `checkpointer` | `Checkpointer \| None` | `None` |
| `store` | `BaseStore \| None` | `None` |
| `interrupt_before`, `interrupt_after` | `list[str] \| None` | `None` |
| `debug` | `bool` | `False` |
| `name` | `str \| None` | `None` |
| `cache` | `BaseCache \| None` (LangGraph cache) | `None` |
| `transformers` | `Sequence[TransformerFactory] \| None` | `None` |

Graph node names: `model`, `tools`, and `<MiddlewareName>.before_agent` / `.before_model` / `.after_model` / `.after_agent` — use these in `interrupt_before` / `interrupt_after`.

### 10.4 `init_embeddings` providers

| Provider | Package | Class |
|---|---|---|
| `openai` | `langchain-openai` | `OpenAIEmbeddings` |
| `azure_openai` | `langchain-openai` | `AzureOpenAIEmbeddings` |
| `azure_ai` | `langchain-azure-ai` | `AzureAIOpenAIApiEmbeddingsModel` |
| `bedrock` | `langchain-aws` | `BedrockEmbeddings(model_id=...)` |
| `cohere` | `langchain-cohere` | `CohereEmbeddings` |
| `google_genai` | `langchain-google-genai` | `GoogleGenerativeAIEmbeddings` |
| `google_vertexai` | `langchain-google-vertexai` | `VertexAIEmbeddings` |
| `huggingface` | `langchain-huggingface` | `HuggingFaceEmbeddings(model_name=...)` |
| `mistralai` | `langchain-mistralai` | `MistralAIEmbeddings` |
| `ollama` | `langchain-ollama` | `OllamaEmbeddings` |

### 10.5 Public import map

| Import path | Names |
|---|---|
| `langchain.agents` | `create_agent`, `AgentState` |
| `langchain.agents.middleware` | all built-in middleware, `AgentMiddleware`, `ModelRequest`, `ModelResponse`, `ExtendedModelResponse`, `ToolCallRequest`, `InterruptOnConfig`, `ClearToolUsesEdit`, execution policies, `RedactionRule`, `PIIMatch`, `PIIDetectionError`, `TriggerClause`, `TracePolicy`, `configure_trace_policy`, `omit_payload`, `Runtime`, decorators `before_agent`, `before_model`, `after_model`, `after_agent`, `wrap_model_call`, `wrap_tool_call`, `dynamic_prompt`, `hook_config` |
| `langchain.agents.structured_output` | `ToolStrategy`, `ProviderStrategy`, `AutoStrategy`, `StructuredOutputError`, `StructuredOutputValidationError`, `MultipleStructuredOutputsError` |
| `langchain.chat_models` | `init_chat_model`, `BaseChatModel` |
| `langchain.embeddings` | `init_embeddings`, `Embeddings` |
| `langchain.messages` | message classes, content-block types, `trim_messages` |
| `langchain.tools` | `tool`, `BaseTool`, `ToolException`, `InjectedToolArg`, `InjectedToolCallId`, `InjectedState`, `InjectedStore`, `ToolRuntime` |
| `langchain.rate_limiters` | `BaseRateLimiter`, `InMemoryRateLimiter` |
| `langchain.mcp` (beta) | `MCPAdapter`, `MCPToolArtifact`, `as_langchain_tool` |

### 10.6 CLI Reference

LangChain has no end-user command-line interface. The only console script in the monorepo is `langchain-profiles`, a maintainer tool from `langchain-model-profiles` that regenerates model capability data for provider packages.

| Command | Flag | Required | Default | Description |
|---|---|---|---|---|
| `langchain-profiles refresh` | `--provider` | yes | — | Provider ID from models.dev (e.g. `anthropic`, `openai`, `google`) |
| | `--data-dir` | yes | — | Directory containing `profile_augmentations.toml`; `_profiles.py` is written there |
| `langchain-profiles summarize` | `--providers` | yes | — | JSON array of `{"provider": ..., "data_dir": ...}` objects (paths relative to the repo root) |
| | `--base-ref` | no | `HEAD` | Git ref to compare the working tree against |
| | `--repo-root` | no | current directory | Repository root |

```bash
cd libs/model-profiles
uv run langchain-profiles refresh --provider openai --data-dir ../partners/openai/langchain_openai/data
```

```text
Provider: openai
Data directory: ../partners/openai/langchain_openai/data

Downloading data from https://models.dev/api.json...
Downloaded <N> providers with <M> models
Extracted <K> models for openai
Loading augmentations...
Writing to ../partners/openai/langchain_openai/data/_profiles.py...
```

> [!NOTE]
> When `--data-dir` is outside the current directory the CLI asks `Continue? (y/N):`; pipe `echo y |` to confirm non-interactively. The `refresh` command fetches `https://models.dev/api.json` with a 30-second timeout.

### 10.7 Useful commands

| Task | Command |
|---|---|
| Install framework + OpenAI | `pip install -U "langchain[openai]"` |
| Install MCP support | `pip install -U "langchain[mcp]"` |
| Show versions for a bug report | `python -m langchain_core.sys_info` |
| Enable tracing | `export LANGSMITH_TRACING=true LANGSMITH_API_KEY=...` |
| Default to standard content blocks | `export LC_OUTPUT_VERSION=v1` |

---

_Generated September 2026 from langchain-ai/langchain at a9780cd. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
