Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

langchain — User Guide

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.

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:

pip install -U langchain langchain-openai
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:

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:

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)
Hello there, nice to meet!

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

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)
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)

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

pip

pip install -U langchain
pip install -U langchain-openai

pip with extras

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

MCP support

# 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

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:

python -m langchain_core.sys_info
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)
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:

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
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:

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:

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"))
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:

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)
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:

answers = model.batch(["Capital of France?", "Capital of Japan?"], config={"max_concurrency": 4})
print([a.text for a in answers])
['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.

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:

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)
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:

# 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="")
{'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:

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)
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:

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:

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:

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:

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."))
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"]:

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"])
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
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:

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)
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)
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):

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"}))
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

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])
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:

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

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)
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.

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)
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

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:

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

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)
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

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

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

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

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"])
[{'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 ToolMessages 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:

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:

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

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

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:

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:

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:

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)
{'gpt-5.5': {'input_tokens': 312, 'output_tokens': 41, 'total_tokens': 353}}

6.5 Rate limiting and caching

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

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:

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"])
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

 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:

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_onconfig 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_errorand/oraon_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_endpointparam notopenai_api_base(or aliasbase_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 (usingollama 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_keyfield or in theXAI_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(orapi_key) … or set the PPLX_API_KEYorPERPLEXITY_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
cd libs/model-profiles
uv run langchain-profiles refresh --provider openai --data-dir ../partners/openai/langchain_openai/data
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