# langchain — Admin Guide

This guide is for architects, platform engineers, security reviewers and contributors who run LangChain-based agents in production or work on the framework itself. It documents how the `langchain-ai/langchain` Python monorepo (commit `a9780cd`) is built, every configuration surface, the security model, operations, performance levers, the extension contracts, and the internals of the agent runtime. The companion **User Guide** covers task-oriented usage.

---

## 1. Executive Summary

| Aspect | Summary |
|---|---|
| **What it is** | A Python framework (MIT licensed, © LangChain, Inc.) for building LLM agents and applications. It is an importable library — no server, daemon, database or config file of its own. |
| **Shape of the repo** | A `uv` monorepo of independently versioned packages under `libs/`: `core` (`langchain-core` 1.6.6), `langchain_v1` (`langchain` 1.4.3), `langchain` (`langchain-classic` 1.0.8, legacy), `text-splitters` (1.1.2), `standard-tests` (`langchain-tests` 1.1.9), `model-profiles` (0.0.6) and 16 provider packages under `partners/`. There is no root `pyproject.toml`. |
| **Built with** | Python ≥ 3.10, Pydantic v2, LangGraph 1.2.x (agent runtime), LangSmith SDK (tracing), httpx, tenacity; `hatchling` builds; `uv`, `ruff`, `mypy`/`ty`, `pytest` for development. |
| **Core abstraction** | The **Runnable** protocol (`invoke`/`stream`/`batch` + async) implemented by every model, prompt, parser, tool and retriever, composable with `|`. |
| **Agent runtime** | `create_agent` compiles a LangGraph `StateGraph` with a model node, a tool node and one node per middleware hook; `wrap_model_call`/`wrap_tool_call` middleware nest around execution. |
| **Deployment shape** | Embedded in your application process. Durable state (conversation memory, interrupts) lives in a LangGraph checkpointer you supply; vector data lives in the vector store you choose. |
| **Operational burden** | Low for the library itself; the real work is provider credentials, rate limits, persistence back ends, tracing/observability, and securing tool execution (shell, filesystem, HTTP, deserialization). |
| **Security posture** | Defense-in-depth features exist (SSRF-safe HTTP client, sandboxed jinja2, deserialization allowlists, path-traversal checks, Docker/Codex shell policies, HITL approval, PII middleware), but LangChain executes whatever tools you give it. The repository ships **no `SECURITY.md`**. |

> [!IMPORTANT]
> Treat an agent as untrusted code execution driven by model output. Every tool is a capability you grant the model; the framework does not sandbox tools unless you use a sandboxing middleware or policy.

---

## 2. Architecture

### 2.1 Layered view

```text
┌───────────────────────────────────────────────────────────────────────────────┐
│ Application code                                                              │
├───────────────────────────────────────────────────────────────────────────────┤
│ langchain (libs/langchain_v1)                                                 │
│   agents/factory.py ── create_agent → LangGraph StateGraph                    │
│   agents/middleware/ ── 16 built-in middleware + hook types + decorators      │
│   agents/structured_output.py ── ToolStrategy / ProviderStrategy / Auto       │
│   chat_models/base.py ── init_chat_model + _ConfigurableModel                 │
│   embeddings/base.py ── init_embeddings                                       │
│   mcp/ ── MCPAdapter (FastMCP) + elicitation-as-interrupt (beta)              │
├───────────────────────────────────────────────────────────────────────────────┤
│ langgraph 1.2.x ── StateGraph, ToolNode, checkpointers, stores, interrupts    │
├───────────────────────────────────────────────────────────────────────────────┤
│ langchain-core (libs/core)                                                    │
│   runnables/ · language_models/ · messages/ · tools/ · prompts/               │
│   output_parsers/ · callbacks/ · tracers/ · load/ · vectorstores/ · indexing/ │
│   _security/ (SSRF policy + transports, internal)                             │
├───────────────────────────────────────────────────────────────────────────────┤
│ Provider packages (libs/partners/*) ── subclass BaseChatModel, Embeddings,    │
│   VectorStore, BaseTool; ship model profiles and provider middleware          │
└───────────────────────────────────────────────────────────────────────────────┘
       ▲ standard-tests (conformance suites)   ▲ model-profiles (profile generator CLI)
```

### 2.2 Component inventory

| Component | Location | Responsibility |
|---|---|---|
| `create_agent` | `libs/langchain_v1/langchain/agents/factory.py` (2,164 lines) | Resolves model/tools/response format, validates middleware, builds nodes and conditional edges, compiles the graph with `recursion_limit=9_999` and metadata `ls_integration="langchain_create_agent"` |
| Middleware types | `agents/middleware/types.py` (2,201 lines) | `AgentMiddleware`, `AgentState`, `ModelRequest`, `ModelResponse`, `ExtendedModelResponse`, `OmitFromSchema`, decorators, `hook_config` |
| Built-in middleware | `agents/middleware/*.py` | Retry, fallback, limits, HITL, PII, summarization, context editing, todos, tool selection, provider tool search, tool emulation, tool errors, shell, file search |
| Structured output | `agents/structured_output.py` | Schema specs, strategy classes, parsing bindings, error types |
| Subagent streaming | `agents/_subagent_transformer.py` | Promotes nested named agents to typed handles on `run.subagents` |
| `init_chat_model` | `chat_models/base.py` | 30-entry provider registry, name-based inference, configurable model proxy |
| `init_embeddings` | `embeddings/base.py` | 10-entry provider registry |
| MCP adapter | `mcp/adapter.py`, `mcp/tools.py`, `mcp/elicitation.py` | FastMCP client wrapping, MCP→LangChain tool conversion, elicitation loop |
| Runnable protocol | `libs/core/langchain_core/runnables/` (14,312 lines) | Composition, config propagation, batching, retries, fallbacks, configurable fields, event streaming |
| Chat model base | `language_models/chat_models.py` | Invoke/stream plumbing, caching, rate limiting, streaming fallback, v3 event protocol, profiles |
| Messages | `messages/` (9,479 lines) | Message classes, standard content blocks, provider block translators, utilities |
| Tools | `tools/` | `BaseTool`, `StructuredTool`, `@tool`, injected args, retriever tool |
| Serialization | `load/` | `dumpd`/`dumps`/`load`/`loads` with allowlists, secret handling, injection escaping |
| Callbacks & tracers | `callbacks/`, `tracers/` | Handler hooks, managers, LangSmith tracer, event-stream tracer |
| SSRF protection | `_security/` | Policy, URL/IP validation, pinned-IP httpx transports (internal API) |

### 2.3 Data Layer

LangChain owns no database. Data flows through four stores that you choose:

| Data | Where it lives | Owned by |
|---|---|---|
| Conversation state (`messages`, `structured_response`, middleware fields such as `todos`, `thread_model_call_count`) | LangGraph **checkpointer** passed to `create_agent(checkpointer=...)`, keyed by `configurable.thread_id` | LangGraph |
| Cross-thread memory | LangGraph **store** (`create_agent(store=...)`), reachable in tools via `ToolRuntime.store` | LangGraph |
| Embeddings + documents | A `VectorStore` implementation (`InMemoryVectorStore`, `Chroma`, `QdrantVectorStore`, …) | Provider package / your database |
| Indexing bookkeeping | A `RecordManager` (`InMemoryRecordManager` in core; `SQLRecordManager` in `langchain-classic`) | Your database |

Agent state schema (`AgentState`): `messages` (reducer `add_messages`), `jump_to` (ephemeral, private), `structured_response` (omitted from input). Middleware `state_schema`s are merged in registration order with the caller's `state_schema` last, so the caller wins field conflicts. Fields annotated `PrivateStateAttr` are hidden from both the input and output schemas; `OmitFromInput`/`OmitFromOutput` hide one side. Middleware counters that must not persist across runs (`run_model_call_count`, `run_tool_call_count`, shell session resources) are annotated `UntrackedValue`.

### 2.4 Caching

| Cache | Scope | Configuration | Notes |
|---|---|---|---|
| LLM response cache | Process or back end you supply | `set_llm_cache(BaseCache)` globally, or `cache=` per model | Lookup keys strip message IDs; cache hits bypass the rate limiter; not used for streaming. `InMemoryCache(maxsize=None)` evicts FIFO, no TTL |
| LangGraph node cache | Graph | `create_agent(cache=BaseCache)` (LangGraph's cache) | Passed straight to `graph.compile` |
| Provider prompt caching | Provider side | `AnthropicPromptCachingMiddleware(ttl="5m"\|"1h")`, `FireworksPromptCachingMiddleware`, OpenAI `prompt_cache_options` field | Anthropic marks the last system block and last tool with `cache_control`; Fireworks pins session affinity to SHA-256(`thread_id`) |
| Model-provider registry | Process | `functools.lru_cache` on `_get_chat_model_creator` / `_get_embeddings_class_creator` | Avoids repeated imports |
| Schema type hints | Process | `lru_cache(maxsize=100)` in `factory._get_schema_type_hints` | Speeds repeated `create_agent` calls |
| Tool call schema | Per tool instance | memoized `tool_call_schema` | Invalidated when `name`, `description` or `args_schema` is reassigned |

### 2.5 How the pieces connect at runtime

```text
agent.invoke({"messages": [...]}, config, context=ctx)
  │
  ├─ LangGraph Pregel loop (checkpoint load if thread_id + checkpointer)
  │
  ├─ node "<Mw>.before_agent"  (once)          ── returns state update / jump_to
  ├─ node "<Mw>.before_model"  (each turn)
  ├─ node "model"
  │     ModelRequest(model, messages, system_message, tools, response_format, state, runtime)
  │     └─ wrap_model_call chain: Mw1(Mw2(...(_execute_model)))
  │           _get_bound_model → bind_tools(... tool_choice, strict) or bind(**model_settings)
  │           model.invoke([system_message, *messages])
  │           _handle_model_output → structured_response / retry ToolMessages
  │     → list[Command(update=...)] (+ middleware commands)
  ├─ node "<Mw>.after_model"   (reverse order)
  ├─ edge model→tools: Send("tools", [tool_call]) per pending call (parallel)
  ├─ node "tools" (ToolNode) ── wrap_tool_call chain → tool.invoke → ToolMessage | Command
  ├─ edge tools→model | exit (return_direct / structured output)
  └─ node "<Mw>.after_agent"   (once) → END (checkpoint saved)
```

---

## 3. System Requirements

| Item | Requirement |
|---|---|
| Python | `>=3.10.0,<4.0.0` for every package. CI covers 3.10–3.14 for `langchain-core`, 3.10 and 3.14 for other packages; releases build on 3.11 |
| Operating systems | Any CPython platform. `HostExecutionPolicy` CPU/memory limits need the POSIX `resource` module (Linux uses `prlimit` after spawn; macOS uses `preexec_fn`); on Windows those limits raise `RuntimeError`. OpenAI TCP keepalive tuning sets only `SO_KEEPALIVE` on Windows |
| Memory / CPU | Library overhead is small; size for your checkpointer, vector store and any local models (Hugging Face pipelines, Ollama) |
| Network | Outbound HTTPS to model providers; optional LangSmith (`api.smith.langchain.com` via the SDK) and gateway (`https://gateway.smith.langchain.com`); `https://models.dev/api.json` only for the profile CLI |
| Key dependency ranges | `pydantic>=2.7.4,<3`, `langgraph>=1.2.11,<1.3.0` (for `langchain`), `langsmith>=0.3.45,<1` (core), `openai>=2.45.0,<4` (langchain-openai), `anthropic>=0.120.0,<2` (langchain-anthropic), `fastmcp>=4.0.1,<5` (MCP extra) |
| Optional tools | `docker` CLI (`DockerExecutionPolicy`), `codex` CLI (`CodexSandboxExecutionPolicy`), `rg` ripgrep (`FilesystemFileSearchMiddleware`, falls back to Python), `numpy` (`InMemoryVectorStore`, `simsimd` used if present), `transformers` (default GPT-2 token counting), `grandalf`/`pygraphviz`/`pyppeteer` (graph drawing) |
| Development | `uv` (CI pins `0.12.8`), `make`, Docker (for `libs/langchain_v1` tests that start Postgres/Redis via compose) |

---

## 4. Configuration Mastery

There is no global config file. The configuration surfaces are, in order of precedence for a single call: **per-call `config`** → **constructor arguments** → **environment variables** → **library defaults**.

### 4.1 Framework environment variables

| Variable | Read by | Default | Effect |
|---|---|---|---|
| `LC_OUTPUT_VERSION` | `BaseChatModel.output_version`, `ChatOpenAI.output_version` | unset (`None`) | `v0` provider-native content; `v1` standard content blocks. OpenAI also accepts `responses/v1` |
| `LANGSMITH_TRACING` / `LANGCHAIN_TRACING_V2` | `langsmith` SDK (core delegates to `langsmith.utils.tracing_is_enabled()`) | off | Enables `LangChainTracer` on every run |
| `LANGSMITH_API_KEY`, `LANGSMITH_PROJECT`, `LANGSMITH_ENDPOINT` | `langsmith` SDK | — | Tracing credentials, project and endpoint |
| `LANGCHAIN_TRACING`, `LANGCHAIN_HANDLER` | `callbacks/manager.py` | — | **Setting either raises `RuntimeError`** (tracer v1 removed) |
| `LANGSMITH_GATEWAY` | `langchain_core/utils/_gateway.py` | disabled | `true`/`1`/`yes` → `https://gateway.smith.langchain.com/{provider_path}`; `false`/`0`/`no`/unset → off; any other value → custom gateway base URL. Honored by `ChatOpenAI`, `ChatAnthropic`, `ChatFireworks` only |
| `LANGSMITH_GATEWAY_API_KEY` | `_gateway.py` | — | Gateway key (only when the gateway is enabled) |
| `LANGCHAIN_ENV` | `_security/_policy.py`, `_security/_ssrf_protection.py` | — | Values starting `local` allow `localhost`/`testserver` through the SSRF policy; `local_test` also bypasses legacy checks for `test*server*` hosts |
| `LANGCHAIN_OPENAI_STREAM_CHUNK_TIMEOUT_S` | `ChatOpenAI.stream_chunk_timeout` | `120.0` | Max gap between parsed chunks on async streams before `StreamChunkTimeoutError` |
| `LANGCHAIN_OPENAI_TCP_KEEPALIVE` | `langchain_openai` transport | `1` | `0` disables socket keepalive tuning |
| `LANGCHAIN_OPENAI_TCP_KEEPIDLE` / `_KEEPINTVL` / `_KEEPCNT` / `_USER_TIMEOUT_MS` | `langchain_openai` transport | `60` / `10` / `3` / `120000` | Linux-style TCP keepalive tuning; invalid values fall back with a warning |
| `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY` (any case) | `langchain_openai` transport | — | Detected to alter transport injection |
| `LANGCHAIN_CODEX_ORIGINATOR` | `_ChatOpenAICodex` (experimental, private) | `langchain` | `originator` header |
| `GITHUB_ACTIONS`, `LANGSMITH_TAGS`, `LANGSMITH_METADATA` | `langchain_tests._langsmith_plugin` | — | CI-only trace tagging |

> [!CAUTION]
> Gateway key precedence: when `LANGSMITH_GATEWAY` is set to a **custom URL** and `LANGSMITH_GATEWAY_API_KEY` is absent, `LANGSMITH_API_KEY` is used as the provider credential and sent to that URL. Only point `LANGSMITH_GATEWAY` at hosts you trust with that key.

### 4.2 Provider credential and endpoint variables

| Package / class | Variable | Notes |
|---|---|---|
| `ChatOpenAI` | `OPENAI_API_KEY`, `OPENAI_API_BASE`, `OPENAI_BASE_URL` (SDK), `OPENAI_ORG_ID` → `OPENAI_ORGANIZATION`, `OPENAI_PROXY` | Base URL precedence: `base_url` kwarg → `OPENAI_API_BASE` → gateway → SDK (`OPENAI_BASE_URL`) |
| `OpenAIEmbeddings` / `OpenAI` | `OPENAI_API_KEY`, `OPENAI_API_BASE`, `OPENAI_ORG_ID`/`OPENAI_ORGANIZATION`, `OPENAI_PROXY`, `OPENAI_API_VERSION`, `OPENAI_API_TYPE` | |
| `AzureChatOpenAI` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY` → `OPENAI_API_KEY`, `OPENAI_API_VERSION` (**no default**), `AZURE_OPENAI_AD_TOKEN`, `OPENAI_API_TYPE` (default `azure`) | Workload identity when `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_FEDERATED_TOKEN_FILE` are all set and `azure-identity` is installed |
| `AzureOpenAIEmbeddings` | same as chat; `OPENAI_API_VERSION` defaults to `2023-05-15` | |
| `ChatAnthropic` | `ANTHROPIC_API_KEY`, `ANTHROPIC_API_URL` → `ANTHROPIC_BASE_URL`, `ANTHROPIC_PROXY` | Default URL `https://api.anthropic.com` |
| `ChatFireworks` & co. | `FIREWORKS_API_KEY`, `FIREWORKS_API_BASE` | |
| `ChatGroq` | `GROQ_API_KEY`, `GROQ_API_BASE`, `GROQ_PROXY` | |
| `ChatMistralAI` | `MISTRAL_API_KEY`, `MISTRAL_BASE_URL` | Embeddings ignore `MISTRAL_BASE_URL`; tokenizer download may need `HF_TOKEN` |
| `ChatXAI` | `XAI_API_KEY`, `XAI_API_BASE` (default `https://api.x.ai/v1/`) | |
| `ChatDeepSeek` | `DEEPSEEK_API_KEY`, `DEEPSEEK_API_BASE` (default `https://api.deepseek.com/v1`) | `strict=True` switches to `https://api.deepseek.com/beta` |
| `ChatOpenRouter` | `OPENROUTER_API_KEY`, `OPENROUTER_API_BASE`, `OPENROUTER_APP_URL` (default `https://docs.langchain.com`), `OPENROUTER_APP_TITLE` (default `LangChain`), `OPENROUTER_SESSION_ID` | App URL/title sent as `HTTP-Referer` / `X-Title` |
| `ChatPerplexity` | `PPLX_API_KEY` only | Embeddings/retriever/tool also accept `PERPLEXITY_API_KEY` |
| Hugging Face | `HUGGINGFACEHUB_API_TOKEN` → `HF_TOKEN`, `HF_INFERENCE_ENDPOINT` | Token only sent to `huggingface.co` / `hf.space` hosts |
| `NomicEmbeddings` | `NOMIC_API_KEY` | Triggers `nomic.login()` |
| Exa | `EXA_API_KEY` | |
| Legacy `Qdrant` | `QDRANT_API_KEY` | `QdrantVectorStore` does not read it |
| `TypeSafeClassifier` | `TYPESAFE_API_KEY`, `TYPESAFE_BASE_URL` (default `https://api.typesafe.ai`) | |
| `ChatOllama`, `Chroma` | none | Pass `base_url` / connection args explicitly |

### 4.3 `RunnableConfig` (per call)

| Key | Type | Default | Behavior |
|---|---|---|---|
| `tags` | `list[str]` | `[]` | Merged across nested calls (union, sorted) |
| `metadata` | `dict` | `{}` | Merged; `configurable["model"]` and `checkpoint_ns` strings are copied into metadata |
| `callbacks` | handlers or manager | `None` | Inherited by child runs |
| `run_name` | `str` | class name | Trace name |
| `max_concurrency` | `int \| None` | executor default | Only the **first** config's value is used in `batch` |
| `recursion_limit` | `int` | `25` | Only enforced for `RunnableLambda` returning runnables; agents set 9,999 |
| `configurable` | `dict` | `{}` | Runtime params (`thread_id`, configurable fields). **Unknown top-level keys are moved here silently** |
| `run_id` | `UUID` | generated | For `batch`, only element 0 keeps it (with a `RuntimeWarning`) |

> [!WARNING]
> Scalar `configurable` values (str/int/float/bool, not starting with `__`) are copied into LangSmith trace metadata. Only `api_key` is excluded. Never pass secrets through `configurable` when tracing is on.

### 4.4 `BaseChatModel` / `BaseLanguageModel` fields (all chat models)

| Field | Default | Meaning |
|---|---|---|
| `cache` | `None` | `True` global cache, `False` none, `None` global if set, or a `BaseCache` |
| `verbose` | `get_verbose()` | Adds `StdOutCallbackHandler` |
| `callbacks`, `tags`, `metadata` | `None` | Attached to every call |
| `custom_get_token_ids` | `None` | Custom tokenizer for `get_num_tokens` |
| `rate_limiter` | `None` | `BaseRateLimiter`; `acquire(blocking=True)` before each non-cached call |
| `disable_streaming` | `False` | `True` always invoke; `"tool_calling"` disable only when tools are bound |
| `output_version` | `LC_OUTPUT_VERSION` or `None` | Content representation |
| `profile` | provider-resolved or `None` | `ModelProfile` capability dict (beta) |

Every model's `metadata` also gets `lc_versions` (package versions) at construction.

### 4.5 Key provider constructor fields

**`ChatOpenAI`**

| Field (alias) | Default | Notes |
|---|---|---|
| `model_name` (`model`) | `"gpt-3.5-turbo"` | Set explicitly — the default is legacy |
| `openai_api_key` (`api_key`) | env | `SecretStr` or sync/async callable |
| `openai_api_base` (`base_url`) | `None` | |
| `request_timeout` (`timeout`) | `None` | |
| `max_retries` | `None` (SDK default) | |
| `max_tokens` (`max_completion_tokens`) | `None` | |
| `stream_usage` | auto | Auto-enabled only for the default OpenAI endpoint with no proxy or custom client |
| `reasoning_effort`, `reasoning`, `verbosity` | `None` | Reasoning controls |
| `use_responses_api` | `None` (auto) | Auto when built-in tools, `reasoning`, `include`, `truncation`, `context_management`, `previous_response_id`, `use_previous_response_id`, `output_version="responses/v1"`, or models `gpt-5-pro`, `gpt-5.2-pro`, `gpt-5.4-pro`, `gpt-5.5-pro`, `gpt-5.6-sol`, `*codex*` |
| `service_tier` | `None` | `auto`/`default`/`flex` |
| `store`, `truncation`, `include`, `context_management`, `prompt_cache_options` | `None` | Responses API options |
| `stream_chunk_timeout` | env or `120.0` | Async streams only |
| `include_response_headers` | `False` | |
| `disabled_params` | `None` | Strip unsupported params |
| `http_client`, `http_async_client`, `http_socket_options`, `default_headers`, `default_query`, `extra_body` | `None` | Transport customization |

**`AzureChatOpenAI`** adds `azure_endpoint`, `deployment_name` (`azure_deployment`), `openai_api_version` (`api_version`), `azure_ad_token`, `azure_ad_token_provider`, `azure_ad_async_token_provider`, `model_version`, `validate_base_url=True`; `model_name` is for tracing/token counting only and does **not** select the deployment.

**`ChatAnthropic`**

| Field (alias) | Default | Notes |
|---|---|---|
| `model` (`model_name`) | required | |
| `max_tokens` (`max_tokens_to_sample`) | profile `max_output_tokens`, else `4096` | |
| `max_retries` | `2` | |
| `default_request_timeout` (`timeout`) | `None` | |
| `stream_usage` | `True` | |
| `thinking` | `None` | `{"type": "enabled", "budget_tokens": N}` or `{"type": "adaptive"}` depending on model |
| `reasoning_effort` (`effort`) | `None` | Sets `output_config.effort`; defaults thinking to adaptive+summarized |
| `output_config`, `betas`, `mcp_servers`, `context_management`, `container`, `inference_geo`, `user_profile_id` | `None` | Advanced API features; tool types auto-add required beta headers |

**`ChatOllama`**: `model` required; `base_url=None` (SDK default; supports `http://user:pass@host` converted to auth headers); `reasoning`; `validate_model_on_init=False`; `num_ctx`, `num_predict`, `temperature`, `top_k`, `top_p`, `seed`, `format`, `keep_alive`, `client_kwargs`/`sync_client_kwargs`/`async_client_kwargs`.

**Defaults that differ by provider:**

| Class | Default model | Default temperature | Default retries |
|---|---|---|---|
| `ChatOpenAI` | `gpt-3.5-turbo` | `None` | SDK |
| `ChatAnthropic` | required | `None` | 2 |
| `ChatGroq` | required | `0.7` | 2 |
| `ChatMistralAI` | `mistral-small` | `0.7` | 5 (timeout 120 s) |
| `ChatXAI` | `grok-4` | `None` | inherited |
| `ChatPerplexity` | `sonar` | `0.7` | 6 |
| `ChatDeepSeek`, `ChatFireworks`, `ChatOpenRouter`, `ChatOllama` | required | — | Fireworks/OpenRouter 2 |

### 4.6 `init_chat_model` configuration

| Parameter | Default | Behavior |
|---|---|---|
| `model` | `None` | `"provider:model"` or bare name; `None` makes a configurable model |
| `model_provider` | `None` | Explicit provider (normalized: `-`→`_`, lowercase) |
| `configurable_fields` | `None`, or `("model", "model_provider")` when `model` is `None` | `"any"` or a list/tuple of field names |
| `config_prefix` | `""` | Namespaces runtime keys as `{prefix}_{field}` |
| `**kwargs` | — | Passed to the provider class constructor |

The configurable model queues `bind_tools` and `with_structured_output` calls and replays them on the concrete model built per call. Batch calls with more than one config fall back to the generic executor so each input may use a different model.

### 4.7 Middleware configuration reference

| Middleware | Constructor | Validation / notes |
|---|---|---|
| `ModelRetryMiddleware` | `(*, 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)` | `default_retry_on`: `ModelError.is_retryable` for core `ModelError`s, `True` for any other exception. Rejects negative values |
| `ToolRetryMiddleware` | same + `tools=None` | `on_failure` legacy values `raise`/`return_message` warn |
| `ModelFallbackMiddleware` | `(first_model, *additional_models)` | Strings resolved with `init_chat_model` |
| `ModelCallLimitMiddleware` | `(*, thread_limit=None, run_limit=None, exit_behavior="end")` | One limit required; `exit_behavior` ∈ {`end`, `error`} |
| `ToolCallLimitMiddleware` | `(*, tool_name=None, thread_limit=None, run_limit=None, exit_behavior="continue")` | `run_limit ≤ thread_limit`; `exit_behavior` ∈ {`continue`, `error`, `end`} |
| `ToolErrorMiddleware` | `(on_error=None, *, aon_error=None, tools=None)` | At least one handler |
| `HumanInTheLoopMiddleware` | `(interrupt_on, *, description_prefix="Tool execution requires approval", edit_notice=<default>)` | Entries: `True` (all four decisions), `False` (auto-approve) or `InterruptOnConfig(allowed_decisions, description, args_schema, when)` |
| `PIIMiddleware` | `(pii_type, *, strategy="redact", detector=None, apply_to_input=True, apply_to_output=False, apply_to_tool_results=False)` | Name is `PIIMiddleware[<type>]`; streaming redaction lookback 128 chars |
| `SummarizationMiddleware` | `(model, *, trigger=None, keep=("messages", 20), token_counter=count_tokens_approximately, summary_prompt=DEFAULT_SUMMARY_PROMPT, trim_tokens_to_summarize=4000)` | No trigger → never runs; `fraction` needs a profile; summary calls retried up to 3 attempts |
| `ContextEditingMiddleware` | `(*, edits=None, token_count_method="approximate", token_counter=None)` | Default edit `ClearToolUsesEdit(trigger=100000, clear_at_least=0, keep=3, clear_tool_inputs=False, exclude_tools=(), placeholder="[cleared]")` |
| `TodoListMiddleware` | `(*, system_prompt=..., tool_description=...)` | Adds `write_todos`, state key `todos` |
| `LLMToolSelectorMiddleware` | `(*, model=None, system_prompt=..., max_tools=None, always_include=None, max_retries=1, on_parsing_failure="error")` | `on_parsing_failure` ∈ `error`/`none`/`all`/list/callable |
| `ProviderToolSearchMiddleware` | `(*, searchable_tools=None)` | Anthropic `tool_search_tool_bm25_20251119`, OpenAI `tool_search` |
| `LLMToolEmulator` | `(*, tools=None, model=None)` | Implicit default model deprecated |
| `ShellToolMiddleware` | `(workspace_root=None, *, startup_commands=None, shutdown_commands=None, execution_policy=None, redaction_rules=None, tool_description=None, tool_name="shell", shell_command=None, env=None)` | Temp workspace when `workspace_root` omitted; `env` omitted → inherits process env |
| `FilesystemFileSearchMiddleware` | `(*, root_path, use_ripgrep=True, max_file_size_mb=10)` | Rejects `..` and `~` |

Execution policy fields:

| Policy | Fields (defaults) |
|---|---|
| all (`BaseExecutionPolicy`) | `command_timeout=30.0`, `startup_timeout=30.0`, `termination_timeout=10.0`, `max_output_lines=100`, `max_output_bytes=None` |
| `HostExecutionPolicy` | `cpu_time_seconds=None`, `memory_bytes=None`, `create_process_group=True` |
| `CodexSandboxExecutionPolicy` | `binary="codex"`, `platform="auto"` (`macos`/`linux`), `config_overrides={}` |
| `DockerExecutionPolicy` | `binary="docker"`, `image="python:3.12-alpine3.19"`, `remove_container_on_exit=True`, `network_enabled=False`, `extra_run_args=None`, `memory_bytes=None`, `cpus=None`, `read_only_rootfs=False`, `user=None` (`cpu_time_seconds` rejected) |

Process-wide middleware tracing default: `configure_trace_policy(TracePolicy | None)` — read at span creation, so it applies to agents built before or after the call. A middleware's own `trace_policy` attribute overrides it wholesale.

### 4.8 Serialization (`load`/`loads`) parameters

| Parameter | Default | Guidance |
|---|---|---|
| `allowed_objects` | `None` → behaves as `"core"` with a pending-deprecation warning | Use `"messages"` or an explicit class list for untrusted data |
| `secrets_map` | `None` | Supply secrets explicitly |
| `secrets_from_env` | `False` | Keep `False`: a payload can name any environment variable |
| `valid_namespaces` | `None` | Adds to the default trusted namespaces |
| `additional_import_mappings` | `None` | Extends/overrides class mappings |
| `ignore_unserializable_fields` | `False` | |
| `init_validator` | `default_init_validator` | Blocks `template_format="jinja2"` before instantiation |

---

## 5. Deployment

LangChain is deployed **inside** your application. The deployment decisions are about process model, persistence and isolation.

### 5.1 Deployment patterns

| Pattern | How | Considerations |
|---|---|---|
| Library in a web service | Build agents once at startup (`create_agent` is relatively expensive), call `ainvoke`/`astream` per request | Use async paths under ASGI servers; MCP tools are async-only |
| Stateful assistant | Supply a persistent LangGraph checkpointer; map each conversation to a `thread_id` | `InMemorySaver` is per-process and non-durable |
| Human-approval workflows | `HumanInTheLoopMiddleware` + checkpointer; persist the `thread_id` and resume with `Command(resume=...)` from any worker | Every resume must reach the same checkpointer |
| Batch / offline jobs | `batch`/`abatch` with `max_concurrency`, `InMemoryRateLimiter` | Rate limiter is per-process only |
| LangGraph platforms | `create_agent` returns a compiled graph; the repo's MCP examples include a `langgraph.json` graph-factory pattern (`examples/mcp/graph_factory.py`) | One long-lived `MCPAdapter` shared by every run |

- [ ] Pin exact versions of `langchain`, `langchain-core`, `langgraph` and each provider package
- [ ] Choose and configure a durable checkpointer
- [ ] Put provider keys in a secret manager and inject as environment variables
- [ ] Decide tracing (`LANGSMITH_TRACING`) and payload policy (`TracePolicy`)
- [ ] Add `ModelCallLimitMiddleware`/`ToolCallLimitMiddleware` to every production agent
- [ ] Gate destructive tools with `HumanInTheLoopMiddleware`
- [ ] Isolate shell/code tools with `DockerExecutionPolicy` or an external sandbox

### 5.2 Docker

The repository uses Docker in two places.

**Development container** (`.devcontainer/`): `devcontainer.json` names the container `langchain`, uses a Compose service built from `libs/langchain/dev.Dockerfile` (`FROM python:3.14-slim-bookworm`, installs `uv`, runs `uv sync --dev`, user `vscode` uid 1000), mounts the workspace at `/workspaces/langchain` on volume `langchain-workspaces`, sets `UV_LINK_MODE=copy`, adds the `git` and `github-cli` features, and runs `cd libs/langchain_v1 && uv sync` after creation.

```bash
# VS Code: "Dev Containers: Reopen in Container", or with the CLI:
devcontainer up --workspace-folder .
```

**Shell-tool isolation** (`DockerExecutionPolicy`): `ShellToolMiddleware` can run the agent's shell in a dedicated container per run, with `--network none` by default, optional read-only root filesystem, user remapping, memory and CPU limits. The workspace is bind-mounted only when it is an existing non-temporary directory.

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

policy = DockerExecutionPolicy(
    image="python:3.12-alpine3.19",
    network_enabled=False,
    read_only_rootfs=True,
    user="1000:1000",
    memory_bytes=512 * 1024 * 1024,
    cpus="1.0",
)
shell = ShellToolMiddleware(workspace_root="/srv/agent-workspace", execution_policy=policy)
```

> [!WARNING]
> Container isolation is only as strong as the Docker daemon configuration. Run agents on hosts with rootless Docker or AppArmor/SELinux, and review anything passed through `extra_run_args`.

> [!NOTE]
> The repository does not ship a Dockerfile or image for running applications — containerize your own application as usual and install the LangChain packages into it.

### 5.3 Test infrastructure containers

`libs/langchain_v1` unit tests start Postgres and Redis for checkpointer tests:

```bash
cd libs/langchain_v1
make start_services   # docker compose -f tests/unit_tests/agents/compose-postgres.yml -f .../compose-redis.yml up -V --force-recreate --wait --remove-orphans
make test             # starts services, runs pytest, always stops services
make test_fast        # LANGGRAPH_TEST_FAST=1, no Docker services
```


---

## 6. Security

LangChain's threat surface is the union of (a) model output driving tool calls, (b) untrusted data entering prompts, templates, serialized objects and URLs, and (c) credentials held in process memory and environment. The repository has **no `SECURITY.md`** and no documented vulnerability-disclosure process; the bug-report issue template refers reporters to "the security option", which `ISSUE_TEMPLATE/config.yml` does not define. The contributor rules in `AGENTS.md` require no `eval()`, `exec()` or `pickle` on user-controlled input and no bare `except:`.

### 6.1 Authentication

| Surface | Mechanism |
|---|---|
| Model providers | API keys from constructor (`api_key=`) or environment (Section 4.2). Keys are stored as Pydantic `SecretStr`, and `Serializable.lc_secrets` replaces them with secret placeholders when serializing (`{"lc": 1, "type": "secret", "id": ["OPENAI_API_KEY"]}`) |
| Dynamic OpenAI keys | `ChatOpenAI(api_key=callable)` accepts a sync or async callable (token refresh); async callables disable the sync client |
| Azure | API key, AAD token (`AZURE_OPENAI_AD_TOKEN`), token providers (`azure_ad_token_provider`), or automatic **workload identity** (`WorkloadIdentityCredential`, scope `https://cognitiveservices.azure.com/.default`) when `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_FEDERATED_TOKEN_FILE` are set and the `azure-identity` extra is installed |
| LangSmith gateway | `LANGSMITH_GATEWAY_API_KEY`, falling back to `LANGSMITH_API_KEY` only when the base URL came from the gateway |
| Hugging Face | Token is sent only to `huggingface.co`, `hf.space` and their subdomains; custom `endpoint_url` hosts receive no token |
| Ollama | `base_url` may embed `user:pass@`, converted to auth headers (the docstring warns this is insecure outside trusted networks) |
| MCP servers | Delegated to FastMCP: pre-built `fastmcp.Client(url, auth="<bearer>" | "oauth" | httpx2.Auth)`; examples cover static bearer tokens and full OAuth 2.1 with dynamic client registration |
| ChatGPT OAuth (experimental, private) | `langchain_openai.chatgpt_oauth` stores tokens at `~/.langchain/chatgpt-auth.json` with mode `0600` in a `0700` directory |

### 6.2 Authorization & Access Control

LangChain has no user model; authorization is about **what the model may do**.

| Control | How it restricts the model |
|---|---|
| Tool selection | Only tools passed to `create_agent` (or registered by middleware) can execute; unknown tools added by middleware raise `ValueError` unless a `wrap_tool_call` handles them |
| `HumanInTheLoopMiddleware` | Per-tool approval with `approve`/`edit`/`reject`/`respond`; `when` predicates gate conditionally; edits to a tool the agent lacks are rejected |
| `ToolCallLimitMiddleware` / `ModelCallLimitMiddleware` | Budget caps per run and per thread |
| `LLMToolSelectorMiddleware`, `ProviderToolSearchMiddleware` | Reduce the exposed tool set per turn |
| File tools | `FilesystemFileSearchMiddleware` and the Anthropic filesystem middleware resolve paths under `root_path` and reject `..`/`~`; the state-backed Claude tools enforce `allowed_path_prefixes` (memory defaults to `/memories`) |
| `init_chat_model(configurable_fields=...)` | With `"any"`, callers can change `api_key`/`base_url` at runtime — enumerate fields when config is untrusted |
| `ToolErrorMiddleware` | Opt-in exception surfacing so internal exception text is not leaked to the model unless you choose to |
| `OpenAIModerationMiddleware` | Checks input/output/tool results with OpenAI moderation; `exit_behavior` `error`/`end`/`replace` |
| TypeSafe `AutoModeMiddleware` (alpha) | Classifies risky tool calls (probability ≥ 0.5 blocked) |

> [!WARNING]
> GitHub issue #40492 (open) reports that an `after_model` middleware returning `jump_to="tools"` can bypass the HITL pending-tool-call gate and re-execute rejected calls in `langchain` 1.4.0. Avoid `jump_to="tools"` in custom middleware used alongside `HumanInTheLoopMiddleware` until resolved.

### 6.3 TLS / Certificates

- Provider SDKs (`openai`, `anthropic`, `groq`, …) and `httpx` handle TLS. `langchain-openai` depends on `certifi>=2024.6.2`; you can supply `http_client`/`http_async_client` with custom `verify=` and certificates.
- The SSRF-safe transports in `langchain_core._security` pin the TCP connection to the validated IP **while preserving the original `Host` header and TLS SNI**, so certificate validation still targets the hostname.
- `ChatOpenAI` image-size fetching (for token counting with `allow_fetching_images=True`) uses the SSRF-safe client with `verify=global_ssl_context` and `follow_redirects=False`, a 5-second timeout and a 50 MB cap.
- Proxies: `OPENAI_PROXY`/`openai_proxy`, `ANTHROPIC_PROXY`, `GROQ_PROXY`, or standard `HTTP(S)_PROXY` variables consumed by httpx. Combining `openai_proxy` with a custom client raises `ValueError`.

### 6.4 SSRF protection

`langchain_core._security` (explicitly internal, not public API) provides:

| Element | Default |
|---|---|
| Allowed schemes | `http`, `https` |
| Blocked | RFC1918 private ranges, loopback, link-local, CGNAT `100.64/10`, TEST-NETs, multicast, reserved; IPv6 ULA/link-local/multicast/mapped/NAT64 (embedded IPv4 extracted and checked) |
| Always blocked | Cloud metadata IPs (`169.254.169.254`, `169.254.170.2`, `169.254.170.23`, `100.100.100.200`, `fd00:ec2::254`, …) and hostnames (`metadata.google.internal`, `metadata.amazonaws.com`, `metadata`, `instance-data`) |
| Localhost names | `localhost`, `localhost.localdomain`, `host.docker.internal` |
| Kubernetes | `*.svc.cluster.local` |
| Redirects | Re-validated on every hop (`follow_redirects=True`, `max_redirects=10` in the client factories) |
| DNS rebinding | All resolved IPs validated, connection pinned to the first |

Users in this repo: `langchain-openai` image fetching and the deprecated `HTMLHeaderTextSplitter.split_text_from_url`. `LANGCHAIN_ENV` values starting with `local` relax the policy for `localhost`/`testserver` — never set it in production.

### 6.5 Secrets handling

- Keys are `SecretStr`; serialization emits placeholders, never values.
- `load(..., secrets_from_env=True)` lets a payload pull **any** environment variable — keep the default `False` and pass `secrets_map` explicitly.
- Trace metadata: every `configurable` scalar except `api_key` is copied into LangSmith metadata; every traced run records `platform.platform()`, Python version and package versions.
- VCR cassettes in `langchain-tests` filter `authorization`, `x-api-key` and `api-key` headers to `PLACEHOLDER`; review cassettes for secrets in bodies before committing.
- Shell sessions inherit the parent environment unless `ShellToolMiddleware(env=...)` is given — pass a minimal environment so the agent cannot read provider keys.

### 6.6 Input validation and injection surfaces

| Surface | Protection in source | Residual risk |
|---|---|---|
| Deserialization (`load`/`loads`) | Namespace allowlist, `allowed_objects` modes, `__lc_escaped__` wrapping of user dicts containing `lc`, jinja2 blocked by `init_validator`, `DISALLOW_LOAD_FROM_PATH` | Default `"core"` mode is **unsafe for untrusted manifests**; `"all"` can set a chat model's `base_url` to an attacker host (SSRF) — documented as expected behavior |
| Prompt templates | f-string validator rejects attribute/index access; jinja2 uses `SandboxedEnvironment`; mustache traverses only dict/list/tuple | Jinja2 sandbox is "best-effort"; never accept jinja2 templates from users |
| `load_prompt` (deprecated since 1.2.21) | Rejects absolute paths and `..` unless `allow_dangerous_paths=True`; template files must be `.txt`; YAML via `safe_load`; jinja2 refused; `lc://` hub paths refused | — |
| `ImagePromptTemplate` | `path` loading removed (0.3.15) | — |
| `utils.image.encode_image` / `image_to_data_url` | Removed for security (raise `ValueError`) | — |
| XML parsing | `XMLOutputParser` defaults to `defusedxml` | Switching to `parser="xml"` reintroduces stdlib XML risks |
| HTML splitting | `HTMLSectionSplitter` lxml parser with `no_network`, `resolve_entities=False`, `load_dtd=False`, XSLT `DENY_ALL` | — |
| MCP targets | String targets must be http(s) URLs — a string can never launch a subprocess (FastMCP would otherwise treat an existing `.py`/`.js` path as a stdio command) | Local servers via `Path`/`MCPConfig` run arbitrary commands by design |
| Shell tool | Execution policies, output limits, redaction rules | `HostExecutionPolicy` offers no filesystem/network sandboxing |
| Hugging Face | `HuggingFacePipeline` strips `trust_remote_code` from model kwargs | — |
| Legacy (`langchain-classic`) | Python REPL tools and `LLMBashChain` removed (raise errors pointing to `langchain-experimental`); `APIChain` requires `limit_to_domains`; SQL chain carries a security note; `LLMMathChain` deprecated (`numexpr`) | Classic packages proxy to `langchain-community` utilities with their own risks |

### 6.7 Network exposure

LangChain opens no listening sockets. Outbound connections go to model providers, LangSmith (if tracing), MCP servers you configure, vector databases, `models.dev` (profile CLI only) and `mermaid.ink` (only if you render graphs with the API method).

### 6.8 Hardening checklist

- [ ] Use `allowed_objects="messages"` or an explicit class list for any `load`/`loads` of data you did not produce
- [ ] Keep `secrets_from_env=False`
- [ ] Never accept jinja2 templates or `configurable_fields="any"` from untrusted input
- [ ] Run shell/code tools under `DockerExecutionPolicy` (network off, read-only rootfs, non-root user) or an external sandbox
- [ ] Pass a minimal `env=` to `ShellToolMiddleware`
- [ ] Gate write/delete/payment tools with `HumanInTheLoopMiddleware`
- [ ] Set `TracePolicy(process_inputs=omit_payload)` if traces must not contain conversation content
- [ ] Use `key_encoder="blake2b"`/`sha256` for `index()` where collision attacks matter
- [ ] Do not set `LANGCHAIN_ENV=local*` outside tests
- [ ] Point `LANGSMITH_GATEWAY` only at trusted hosts

---

## 7. Operations

### 7.1 Starting, stopping and upgrading

There is nothing to start or stop — agents live in your process. Upgrades are package upgrades:

```bash
pip install -U "langchain==1.4.3" "langchain-core==1.6.6" "langchain-openai==1.6.6"
python -m langchain_core.sys_info
```

Versioning and release facts from the repository:

- Each package is released independently through the `🚀 Package Release` workflow (`_release.yml`): build → release notes → pre-release checks (unit tests, minimum-version tests, partner integration tests) → TestPyPI → core-compatibility tests against published `anthropic` and `openai` partners → dependents (`deepagents`) for core/langchain releases → PyPI (trusted publishing) → GitHub release and tag.
- Within a `0.x` series, fixes and additive features take patch bumps.
- Deprecations name `since` and `removal` versions; several removals are scheduled for **2.0.0** (`astream_log`, `astream_events(version="v1")`, `RunnableWithMessageHistory`, `load_prompt`, `.text()` as a method, classic memory, `hub.push/pull`, `BaseChatModel.dict()`).

> [!TIP]
> Importing `langchain_core` turns on display of `LangChainDeprecationWarning` and `LangChainBetaWarning`. Run your test suite with warnings as errors for those classes before upgrading to catch scheduled removals early.

### 7.2 Health and capacity

| Concern | Mechanism |
|---|---|
| Runaway loops | `ModelCallLimitMiddleware`, `ToolCallLimitMiddleware`; the graph recursion limit is 9,999 |
| Provider throttling | `InMemoryRateLimiter` (per process, request-count token bucket starting empty), `ModelRetryMiddleware` with backoff and jitter; core `ModelRateLimitError`, `ModelAPIError`, `ModelConnectionError`, `ModelTimeoutError` are `is_retryable=True` |
| Provider outages | `ModelFallbackMiddleware`, or `Runnable.with_fallbacks` (fallbacks apply only before the first streamed chunk) |
| Context overflow | Core `ContextOverflowError` (provider subclasses: `OpenAIContextOverflowError`, `AnthropicContextOverflowError`, `GroqContextOverflowError`); mitigate with `SummarizationMiddleware`, `ContextEditingMiddleware`, `trim_messages` |
| Stalled streams | `ChatOpenAI.stream_chunk_timeout` (async) raises `StreamChunkTimeoutError` |

### 7.3 Logging

| Tool | Configure | Output |
|---|---|---|
| Debug mode | `langchain_core.globals.set_debug(True)` | `ConsoleCallbackHandler` prints every run start/end with inputs/outputs |
| Verbose mode | `set_verbose(True)` or `verbose=True` on a component | `StdOutCallbackHandler` (chains, agents, tools; not tokens) |
| Agent graph debug | `create_agent(debug=True)` | LangGraph prints node executions and state updates |
| File log | `with FileCallbackHandler("run.log"): ...` | Chain/tool events to a file (use as a context manager) |
| LangSmith tracing | `LANGSMITH_TRACING=true` | Full trace tree; middleware hook spans named `<Mw>.wrap_model_call` etc.; model calls tagged with `ls_integration="langchain_create_agent"` and `lc_agent_name` |
| Payload policy | `configure_trace_policy(TracePolicy(process_inputs=omit_payload))` or per-middleware `trace_policy` | Keeps spans and timing, drops payloads; messages still appear on the inner model span |
| Usage accounting | `get_usage_metadata_callback()` / `UsageMetadataCallbackHandler` | Aggregated `UsageMetadata` per model name |
| Python logging | Standard `logging` loggers per module (e.g. `langchain.agents.middleware.shell_tool`) | Configure with `logging.getLogger("langchain").setLevel(...)` |

### 7.4 Backup & Restore

LangChain holds no durable state itself. Back up what your deployment configures:

| Asset | Backup | Restore |
|---|---|---|
| Conversation state | Back up the checkpointer's database (Postgres/SQLite/Redis per the LangGraph saver you use) | Restore the database; resume threads by `thread_id` |
| Cross-thread memory | Back up the LangGraph store's database | Restore database |
| `InMemoryVectorStore` | `store.dump("vectors.json")` (JSON via `dumpd`, parent dirs created) | `InMemoryVectorStore.load("vectors.json", embeddings)` — loads with `allowed_objects=[Document]` |
| External vector stores | Use the database's own tools (Chroma persist directory, Qdrant snapshots) | Database tools |
| Indexing record manager | Back up with the vector store — they must stay in sync | Restore both together, or re-run `index(..., cleanup="full")` |
| Serialized chains/prompts | `dumps(obj, pretty=True)` | `loads(text, allowed_objects=[...])` |

> [!CAUTION]
> Changing `index()`'s `key_encoder` after data exists produces duplicates. Rebuild the index when switching encoders.

---

## 8. Performance Tuning

| Lever | Setting | Effect / caveat |
|---|---|---|
| Parallel batching | `batch(..., config={"max_concurrency": N})` / `abatch` | Thread pool (sync) or semaphore-bounded `gather` (async); only the first config's `max_concurrency` counts |
| Parallel tools | Automatic | Each pending tool call is dispatched with its own LangGraph `Send`; the tool node hydrates state from channels instead of copying full state into each `Send` (removing an O(N²) write pattern) |
| Streaming | `stream`/`astream`, `stream_mode="messages"` | Lower time-to-first-token; `disable_streaming="tool_calling"` forces non-streaming only when tools are bound |
| Response caching | `set_llm_cache(...)` | Skips provider calls for identical prompts; not for streaming |
| Prompt caching | `AnthropicPromptCachingMiddleware`, `FireworksPromptCachingMiddleware`, OpenAI `prompt_cache_options` | Cuts input-token cost/latency on repeated prefixes |
| Fewer tool schemas | `LLMToolSelectorMiddleware(model="<small model>", max_tools=N)` or `ProviderToolSearchMiddleware` | Smaller requests with large tool catalogs |
| Context size | `ContextEditingMiddleware`, `SummarizationMiddleware`, `trim_messages` | Keep requests under the window; summarization adds a model call |
| Token counting | `token_count_method="approximate"` (default) vs `"model"`; custom `token_counter` | Approximate is faster; exact may call provider APIs |
| Rate limiting | `InMemoryRateLimiter(requests_per_second, max_bucket_size)` | Smooths bursts per process |
| OpenAI transport | `LANGCHAIN_OPENAI_TCP_KEEPALIVE*`, `http_socket_options`, `stream_chunk_timeout` | Detects dead connections faster |
| Agent construction | Build agents once and reuse | Schema resolution and graph compilation happen in `create_agent` |
| Structured output | Let `AutoStrategy` choose provider-native output where supported | Avoids an extra tool round-trip |

> [!NOTE]
> GitHub issue #40826 (open) reports that streaming a large tool call re-parses its arguments in pure Python on every chunk, and #40877 reports `RunnableParallel` ignoring `max_concurrency` on `ainvoke`/`astream` while honoring it on sync calls. Benchmark streaming of very large tool arguments and async parallel fan-out in your workload.

The repository measures performance with `pytest-benchmark` and CodSpeed (`make benchmark` in `libs/core` runs `pytest tests/benchmarks --codspeed`; `libs/langchain_v1` has `tests/benchmarks/test_create_agent.py`).

---

## 9. Integrations

### 9.1 Plugin / Extension System

LangChain has three extension contracts.

**1. Provider packages (subclass core interfaces).**

| To add… | Subclass | Must implement | Should implement |
|---|---|---|---|
| Chat model | `langchain_core.language_models.BaseChatModel` | `_generate`, `_llm_type` | `_stream`/`_astream`, `bind_tools`, `with_structured_output`, `_resolve_model_profile`, error translation to core `ModelError` subclasses |
| Embeddings | `langchain_core.embeddings.Embeddings` | `embed_documents`, `embed_query` | async variants |
| Vector store | `langchain_core.vectorstores.VectorStore` | `similarity_search`, `from_texts` | `add_texts`/`add_documents`, `delete`, `get_by_ids`, MMR, async variants |
| Retriever | `langchain_core.retrievers.BaseRetriever` | `_get_relevant_documents` | `_aget_relevant_documents` |
| Tool | `langchain_core.tools.BaseTool` (or `@tool`) | `_run` | `_arun`, `args_schema` |
| Document loader | `langchain_core.document_loaders.BaseLoader` | `lazy_load` | `alazy_load` |
| Cache / store / chat history | `BaseCache`, `BaseStore`, `BaseChatMessageHistory` | abstract methods | async variants |

Conformance is verified with `langchain-tests` (Section 10.6). Model capability data is shipped as `langchain_<pkg>/data/_profiles.py`, generated by `langchain-profiles refresh` and refreshed daily in CI by `refresh_model_profiles.yml`. Provider-specific content formats are translated to standard content blocks by registering a translator with `langchain_core.messages.block_translators.register_translator(provider, translate_content, translate_content_chunk)`; registered translators in core: `anthropic`, `bedrock`, `bedrock_converse`, `google_genai`, `google_vertexai`, `groq`, `openai`.

To make `init_chat_model` / `init_embeddings` infer your provider, it must be added to `_BUILTIN_PROVIDERS` in `libs/langchain_v1/langchain/chat_models/base.py` or `embeddings/base.py`; otherwise users construct your class directly.

**2. Agent middleware.** Subclass `AgentMiddleware[StateT, ContextT, ResponseT]` or use the decorators. Contract:

| Attribute / hook | Contract |
|---|---|
| `name` | Unique per agent (default class name) |
| `state_schema` | `AgentState` subclass merged into the graph state |
| `tools` | Extra tools registered with the agent |
| `trace_policy` | Optional `TracePolicy` for this middleware's spans |
| `transformers` | Stream transformer factories (e.g. `InternalCallTransformer` to hide internal model calls from `run.messages`) |
| `before_agent`, `before_model`, `after_model`, `after_agent` (+ async) | Return a state update, `Command`, or `None`; declare jumps with `@hook_config(can_jump_to=[...])` |
| `wrap_model_call` / `awrap_model_call` | `(request, handler) -> ModelResponse \| AIMessage \| ExtendedModelResponse`; `ExtendedModelResponse.command` supports `update` only |
| `wrap_tool_call` / `awrap_tool_call` | `(request, handler) -> ToolMessage \| Command` |

**3. Runnables.** Any object implementing `Runnable.invoke` (or a plain function, generator or dict, via coercion) composes with `|`. `RunnableSerializable.configurable_fields(...)` and `configurable_alternatives(...)` expose runtime-switchable parameters.

### 9.2 LangGraph

`create_agent` returns a `CompiledStateGraph` and accepts LangGraph primitives directly: `checkpointer`, `store`, `cache`, `interrupt_before`/`interrupt_after`, `Command`, `Send`, `Runtime`, `ToolRuntime`, `InjectedState`, `InjectedStore`. The compiled agent can be embedded as a subgraph node (the `name` parameter labels it), and `SubagentTransformer` exposes nested named agents as typed handles on `run.subagents` when streaming.

### 9.3 Model Context Protocol

`langchain.mcp` (beta) wraps FastMCP:

- `MCPAdapter(target)` accepts a URL string (http/https only), `Path`, FastMCP transport, `FastMCP`/`MCPServer` instance, `MCPConfig`/dict, `fastmcp.Client` or `ClientGroup`. It clones and "arms" clients without an elicitation handler so elicitation becomes a LangGraph `interrupt()`; clients with their own handler are left untouched.
- `list_tools(cache_mode="use"|"refresh"|"bypass")` honors the client-side response cache (SEP-2549).
- Tools are async. Tool metadata is grouped under `metadata["mcp"]["tool"]` (annotations such as `destructive_hint`, `_meta`) and `metadata["mcp"]["server"]`; structured content is attached as `MCPToolArtifact(structured_content=...)` on the `ToolMessage` artifact.
- Only elicitation is answered; sampling, roots and continuation rounds raise `NotImplementedError`. Interrupt payload type: `"mcp_elicitation"` with `requests` of mode `form` (JSON-schema) or `url`.

### 9.4 LangSmith

Tracing, evaluation and the gateway are provided by the `langsmith` SDK. The repo's CI sends integration-test traces to LangSmith (`LANGSMITH_PROJECT` defaults to `scheduled-testing-py`), bridged by the `langchain_tests._langsmith_plugin` pytest plugin that only activates when `GITHUB_ACTIONS=true`.

---

## 10. Developer Internals

### 10.1 Repository structure

```text
langchain/
├── AGENTS.md                 contributor & agent guidelines (authoritative dev rules)
├── README.md, LICENSE (MIT), CITATION.cff
├── .github/                  workflows, actions (uv_setup pins uv 0.12.8), scripts/check_diff.py,
│                             ISSUE_TEMPLATE, CODEOWNERS, dependabot.yml, pr-labeler config
├── .devcontainer/            VS Code dev container (compose + libs/langchain/dev.Dockerfile)
├── .pre-commit-config.yaml   per-package format/lint + version-check hooks
├── .mcp.json                 docs-langchain and reference-langchain MCP servers (for coding agents)
├── openwiki/                 auto-generated code wiki (25 pages; refreshed daily; not authoritative)
└── libs/
    ├── Makefile              lock / check-lock across core, text-splitters, langchain, langchain_v1, model-profiles
    ├── core/                 langchain-core
    ├── langchain_v1/         langchain (import package "langchain")
    ├── langchain/            langchain-classic (import package "langchain_classic")
    ├── text-splitters/       langchain-text-splitters
    ├── standard-tests/       langchain-tests
    ├── model-profiles/       langchain-model-profiles (CLI langchain-profiles)
    └── partners/             16 provider packages, each with pyproject.toml, uv.lock, Makefile, tests/
```

Each package has its own `pyproject.toml`, `uv.lock` and `Makefile`; local development uses editable path installs declared in `[tool.uv.sources]` so sibling packages resolve to the working tree. Always `cd` into a package before running commands.

### 10.2 Agent factory internals

1. **Model resolution.** A string model goes through `init_chat_model`.
2. **Response format.** `None` → no structured output. `ToolStrategy`/`ProviderStrategy`/`AutoStrategy` are kept; raw schemas become `AutoStrategy`. For `AutoStrategy`, a `ToolStrategy` is built up front so structured-output tools exist at graph-build time; the decision to use `ProviderStrategy` is made per model call in `_get_bound_model` via `_supports_provider_strategy(model, tools)`:
   - `model.profile["structured_output"]` truthy → provider strategy, except Gemini models before the 3-series when tools are bound;
   - otherwise the model name is matched against `FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT` regexes (GPT-4.1, GPT-4o, GPT-5.x, GPT-6 astra/luna/sol, Claude Opus 4.5–4.8, Sonnet 4.5–4.6, Haiku 4.5, Claude fable/mythos 5, Grok-4, grok-build).
3. **Tools.** Dict tools are provider built-ins (bound but not executed locally). Callables and `BaseTool`s plus middleware tools go into a LangGraph `ToolNode` together with the composed `wrap_tool_call` chain. A `ToolNode` exists if there are client-side tools **or** any wrap-tool middleware.
4. **Middleware validation.** Duplicate names raise `AssertionError`. Hook presence is detected by comparing each class's method with the `AgentMiddleware` base method (sync **or** async override counts).
5. **Wrap composition.** `_chain_model_call_handlers` composes right-to-left so the first middleware is outermost; `Command`s from `ExtendedModelResponse` are accumulated inner-first, cleared on each inner call for retry safety, and converted into additional `Command`s after the model's own state update. Commands with `goto`, `resume` or `graph` raise `NotImplementedError`.
6. **Binding.** `ProviderStrategy` → `bind_tools(final_tools, **strategy.to_model_kwargs(), **model_settings)` plus `strict=True` for OpenAI-compatible models not using the Responses API. `ToolStrategy` → `bind_tools(..., tool_choice="any")` when structured-output tools exist. Otherwise `bind_tools(tools, tool_choice=request.tool_choice)` or plain `bind(**model_settings)`.
7. **Invalid tool calls.** Before each model call, `_patch_invalid_tool_calls` appends an error `ToolMessage` for every unanswered `invalid_tool_call`; the repaired history replaces the state history via `RemoveMessage(REMOVE_ALL_MESSAGES)`.
8. **Structured output handling.** Provider strategy parses the final text when there are no tool calls. Tool strategy parses the schema-tool call; multiple schema calls or validation failures follow `handle_errors` (retry with a `ToolMessage`, or raise). When `response_format` is set and no structured response was produced this turn, `structured_response` is explicitly reset to `None` to avoid a stale checkpointed value.
9. **Edges.** Entry is the first `before_agent`, else first `before_model`, else `model`. Loop entry is the first `before_model` or `model`. `after_model` hooks chain from last to first. Model→tools routing: honor `jump_to`; no AI message or no tool calls → exit; pending calls → `Send("tools", [call])` each; a fresh structured response → exit; otherwise back to the model (synthetic tool messages were injected). Tools→model routing: exit if all executed client tools are `return_direct`, or a structured-output tool succeeded; otherwise loop.
10. **Compile.** `graph.compile(checkpointer, store, interrupt_before, interrupt_after, debug, name, cache, transformers=[ToolCallTransformer, SubagentTransformer, *middleware transformers, *user transformers])` de-duplicated by identity, then `.with_config({"recursion_limit": 9999, "metadata": {"ls_integration": "langchain_create_agent", "lc_agent_name": name}})`.

### 10.3 Runnable internals

| Mechanism | Detail |
|---|---|
| Composition | `__or__`/`__ror__` build `RunnableSequence` (flattening nested sequences, ≥ 2 steps). Coercion order: Runnable → generator function (`RunnableGenerator`) → callable (`RunnableLambda`) → `dict` (`RunnableParallel`) → `TypeError`. `__or__` never returns `NotImplemented` |
| Config propagation | `ensure_config` merges the context-var parent config; child runs get tags like `seq:step:N`, `map:key:K`, `retry:attempt:N` |
| Retry | `RunnableRetry` on tenacity with `reraise=True`; `batch` retries only failed items; streams are not retried |
| Fallbacks | `RunnableWithFallbacks` raises the first error if all fail; streaming falls back only before the first chunk; `__getattr__` forwards Runnable-returning methods (e.g. `bind_tools`) to primary and fallbacks |
| Configurables | `ConfigurableField`, `ConfigurableFieldSingleOption/MultiOption`, alternatives namespaced `which==key/field` with `prefix_keys` |
| Events | `astream_events` v2 uses an event-stream tracer; v3 is implemented by `BaseChatModel` and LangGraph only |
| Chat-model streaming fallback | If `_stream` is not overridden, `stream()` yields one chunk from `invoke()` |

### 10.4 Message and serialization internals

- `AIMessage.content_blocks` resolution: `output_version == "v1"` content returned as-is → registered provider translator by `response_metadata["model_provider"]` → best-effort parse, then missing tool calls appended and reasoning from `additional_kwargs` prepended.
- Message dict coercion uses a hard-coded allowlist for `{"lc": 1, "type": "constructor", ...}` shapes and never calls `load()`.
- `Serializable.is_lc_serializable()` defaults to `False` (opt-in). `to_json()` merges `lc_secrets` across the MRO.
- `dumps` refuses a custom `default=` and wraps user dicts that contain an `lc` key as `{"__lc_escaped__": ...}`; `load` unwraps them without instantiation.

### 10.5 Code patterns and conventions

- Google-style docstrings with `Args:`/`Returns:`/`Raises:`; types in signatures, not docstrings; single-backtick code in docs; American English.
- Error messages are built in a `msg` variable before `raise`; no bare `except:`.
- New public parameters are keyword-only; public signatures are treated as stable (“Would this change break someone's code if they used it last week?”).
- `ruff` (format + lint, line length 88), `mypy --strict` with the pydantic plugin in most packages; `text-splitters` type-checks with `ty`.
- Conventional Commit PR titles with a **required** scope from the allowed list (`core`, `langchain`, `langchain-classic`, `model-profiles`, `standard-tests`, `text-splitters`, `docs`, each partner, `infra`, `deps`, `partners`); types `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, `release`, `hotfix`. Branches: `<github-username>/<scope>/<short-description>`.
- GitHub Actions must be pinned to full commit SHAs.

### 10.6 Testing Strategy

| Layer | Location | Rules |
|---|---|---|
| Unit tests | `libs/<pkg>/tests/unit_tests/` | No network: `pytest-socket --disable-socket --allow-unix-socket`; parallel with `pytest -n auto` (xdist) in core, langchain_v1 and text-splitters; `blockbuster` detects blocking calls in async code |
| Integration tests | `tests/integration_tests/` | Live APIs; run daily at 13:00 UTC (`integration_tests.yml`) on Python 3.10 and 3.14 for OpenAI, Anthropic, Fireworks, Groq, Mistral, xAI and external Google/AWS packages |
| VCR tests | `langchain-openai` | Cassettes in `tests/cassettes`, replayed with `--record-mode=none -m vcr` in CI (`_test_vcr.yml`) |
| Snapshot tests | `syrupy` | `--snapshot-update` in watch targets |
| Minimum-version tests | `_test.yml` | Re-runs unit tests with minimum dependency versions |
| Pydantic matrix | `_test_pydantic.yml` | Every Pydantic `2.x.0` minor within the supported range, Python 3.12 |
| Benchmarks | `tests/benchmarks`, CodSpeed | Python 3.13.11 pinned in `codspeed.yml` |
| Standard conformance | `langchain-tests` | Subclass `ChatModelUnitTests`/`ChatModelIntegrationTests`, `EmbeddingsUnitTests`/`EmbeddingsIntegrationTests`, `ToolsUnitTests`/`ToolsIntegrationTests`, `VectorStoreIntegrationTests`, `RetrieversIntegrationTests`, `BaseStoreSyncTests`/`BaseStoreAsyncTests`, `SyncCacheTestSuite`/`AsyncCacheTestSuite` |

A standard chat-model test class looks like:

```python
from langchain_tests.unit_tests import ChatModelUnitTests
from my_pkg import ChatMyProvider


class TestChatMyProvider(ChatModelUnitTests):
    @property
    def chat_model_class(self):
        return ChatMyProvider

    @property
    def chat_model_params(self):
        return {"model": "my-model-1"}

    @property
    def init_from_env_params(self):
        return ({"MY_PROVIDER_API_KEY": "key"}, {}, {"api_key": "key"})
```

Capability flags you override include `has_tool_calling`, `has_tool_choice`, `has_structured_output`, `supports_json_mode`, `supports_image_inputs`, `supports_image_urls`, `supports_pdf_inputs`, `supports_audio_inputs`, `supports_video_inputs`, `supports_image_tool_message`, `supports_pdf_tool_message`, `returns_usage_metadata`, `supported_usage_metadata_details`, `enable_vcr_tests`. `test_no_overrides_DO_NOT_OVERRIDE` fails if you delete or override a standard test without `@pytest.mark.xfail(reason=...)`.

Per-package Makefile targets:

| Package | `make test` runs | Other notable targets |
|---|---|---|
| `libs/core` | Unsets `LANGCHAIN_TRACING_V2`, `LANGCHAIN_API_KEY`, `LANGSMITH_API_KEY`, `LANGSMITH_TRACING`, `LANGCHAIN_PROJECT`, then `uv run --group test pytest -n auto --benchmark-disable --disable-socket --allow-unix-socket` | `extended_tests`, `lint` (+ `lint_imports.sh`), `type`, `format`, `benchmark`, `check_imports`, `check_version` |
| `libs/langchain_v1` | Starts Postgres/Redis, `LANGGRAPH_TEST_FAST=0 … pytest -n auto … --snapshot-update`, stops services | `test_fast`, `coverage`, `coverage_agents`, `integration_tests`, `benchmark` |
| `libs/partners/openai` | Downloads `cl100k_base`/`o200k_base` into `tiktoken_cache/`, then `TIKTOKEN_CACHE_DIR=tiktoken_cache … pytest --disable-socket --allow-unix-socket` | `integration_tests` (SDK smoke tests against openai 2.x and 3.x, then full suite), `test_vcr` |
| `libs/text-splitters` | `pytest -n auto --disable-socket --allow-unix-socket` | `lint` uses `ty check` |
| `libs/standard-tests` | `pytest` (no socket flags) | `integration_tests` |
| `libs/model-profiles` | `pytest --disable-socket --allow-unix-socket` | `refresh-profiles` for all 10 providers with profiles |
| `libs/` | — | `make lock`, `make check-lock` |

CI change detection (`.github/scripts/check_diff.py`): changes in `core` → test `core` and every later package in the order core, text-splitters, langchain, langchain_v1, model-profiles; changes in `standard-tests` → also test mistralai, openai, anthropic, fireworks, groq; workflow changes → test all; ≥ 300 changed files → everything; an unknown `libs/<x>` directory fails CI with `Unknown lib: … check_diff.py likely needs an update for this new library!`.


---

## 11. Troubleshooting

Operator-grade entries: deployment, configuration, security controls, serialization, providers, persistence and the development toolchain. Messages are quoted from source; `{...}` marks runtime values and `…` marks shortened text. Everyday agent/tool/prompt errors are covered in the User Guide.

### Error: `Deserialization of {mapping_key!r} is not allowed. The default (allowed_objects='core') only permits core langchain-core classes. To allow trusted partner integrations, use allowed_objects='all'. …`

**Cause:** `load`/`loads` met a class (for example `ChatOpenAI`) outside the active allowlist.
**Solution:** For trusted payloads pass an explicit list: `loads(text, allowed_objects=[ChatOpenAI, ChatPromptTemplate])`. Use `"all"` only for payloads you produced.
**Prevention:** Decide the allowlist per data source; never use `"all"` on user uploads.

### Error: `Jinja2 templates are not allowed during deserialization for security reasons. Use 'f-string' template format instead, or explicitly allow jinja2 by providing a custom init_validator.`

**Cause:** A serialized prompt uses `template_format="jinja2"`.
**Solution:** Convert the prompt to f-string or mustache and re-serialize; only supply a permissive `init_validator` for fully trusted data.
**Prevention:** Standardize on f-string templates.

### Error: `Trying to deserialize something that cannot be deserialized in current version of langchain-core: {mapping_key}.`

**Cause:** The payload references a `langchain_community` or `langchain` path that is only loadable through the mapping table, or a class removed in this version.
**Solution:** Re-serialize with current packages, or add a mapping via `additional_import_mappings`.
**Prevention:** Version serialized artifacts alongside package versions.

### Error: `Trying to load an object that doesn't implement serialization: {value}`

**Cause:** The object was dumped as `{"type": "not_implemented"}` because its class is not `Serializable` (`is_lc_serializable()` is `False` by default).
**Solution:** Persist configuration yourself, or make the class opt in with `is_lc_serializable()` returning `True`.
**Prevention:** Test `loads(dumps(obj))` round-trips in CI.

### Error: `allowed_objects must contain Serializable subclasses.`

**Cause:** A non-class or non-`Serializable` entry in `allowed_objects`.
**Solution:** Pass classes such as `AIMessage`, not instances or strings (strings are only `"all"`, `"core"`, `"messages"`).
**Prevention:** —

### Error: `` `default` should not be passed to dumps ``

**Cause:** `dumps(obj, default=...)`.
**Solution:** Remove `default`; `dumps` supplies its own encoder. Use `pretty=True` or `indent=` for formatting.
**Prevention:** —

### Error: `Class {cls} has a deprecated attribute {attr}. Please use the corresponding classmethod instead.`

**Cause:** A custom `Serializable` still defines a legacy attribute (such as `lc_namespace`) instead of the classmethod.
**Solution:** Implement `get_lc_namespace()` / `is_lc_serializable()` classmethods.
**Prevention:** Follow the current `Serializable` API in integrations.

### Error: `SSRF blocked: {reason}` (`SSRFBlockedError`)

**Cause:** A URL fetched through the SSRF-safe client resolved to a private, loopback, link-local, cloud-metadata or cluster-internal address, or used a non-http(s) scheme (reasons include `localhost address`, `invalid IP address`, `scheme '{scheme}' not allowed`).
**Solution:** Use a publicly routable URL. For intentional internal access, fetch the data yourself and pass base64 content instead of a URL.
**Prevention:** Do not set `LANGCHAIN_ENV=local*` in production to work around this.

### Error: `Loading from the deprecated github-based Hub is no longer supported. Please use the new LangChain Hub at https://smith.langchain.com/hub instead.`

**Cause:** `load_prompt("lc://...")`.
**Solution:** Pull prompts with the LangSmith SDK.
**Prevention:** Remove `lc://` references from configuration.

### Error: `Path '{path}' is absolute. Absolute paths are not allowed when loading prompt configurations to prevent path traversal attacks. …`

**Cause:** A prompt config references an absolute `template_path`, `examples` or `example_prompt_path` (sibling error for `..` components).
**Solution:** Use relative paths, or pass `allow_dangerous_paths=True` only for trusted configs.
**Prevention:** `load_prompt` is deprecated since 1.2.21 — migrate to `dumps`/`loads`.

### Error: `Loading templates with '{template_format}' format is no longer supported since it can lead to arbitrary code execution.`

**Cause:** `load_prompt` on a jinja2 template file.
**Solution:** Convert the template to f-string.
**Prevention:** —

### Error: `jinja2 is unsafe and is not supported for templates expressed as dicts. Please use 'f-string' or 'mustache' format.`

**Cause:** A dict-shaped message template with `template_format="jinja2"`.
**Solution:** Use f-string or mustache.
**Prevention:** —

### Error: `Cannot traverse into {type}. … Mustache templates only support dict, list, and tuple.` (`TypeError`)

**Cause:** A mustache variable path tried to read attributes of a non-container object (deliberately blocked).
**Solution:** Convert objects to dicts before formatting.
**Prevention:** Pass plain data into prompts.

### Error: `Batch size must be a positive integer, got {size}.`

**Cause:** `index(..., batch_size=0)` or a negative `cleanup_batch_size`.
**Solution:** Use positive values (defaults 100 and 1,000).
**Prevention:** —

### Error: `time_at_least must be in the past`

**Cause:** `RecordManager.update(time_at_least=...)` with a future timestamp, usually clock skew between hosts writing the same record manager.
**Solution:** Synchronize clocks (NTP) across indexing workers.
**Prevention:** Use the database time source of a SQL record manager for multi-host indexing.

### Error: `IDs must be provided for deletion`

**Cause:** `InMemoryDocumentIndex.delete()` without `ids`.
**Solution:** Pass the IDs to delete.
**Prevention:** —

### Error: `_HashedDocument is an internal abstraction that was deprecated in langchain-core 0.3.63. …`

**Cause:** Code imported the private `_HashedDocument`.
**Solution:** Remove the dependency; use `index()`'s public API and `key_encoder`.
**Prevention:** Avoid underscore-prefixed imports.

### Error: `delete method must be implemented by subclass.`

**Cause:** `index()` with a cleanup mode on a vector store that does not implement `delete`.
**Solution:** Use a vector store with `delete` support, or `cleanup=None`.
**Prevention:** Check integration support before enabling cleanup.

### Error: `Source id key is required when cleanup mode is incremental or scoped_full.`

**Cause:** `incremental` and `scoped_full` cleanup need source IDs to scope deletions.
**Solution:** Pass `source_id_key="source"` (or a callable) and ensure every document has it in metadata.
**Prevention:** Stamp `metadata["source"]` in loaders.

### Error: `config must be a list of the same length as inputs, but got {n} configs for {m} inputs`

**Cause:** `batch(inputs, config=[...])` length mismatch.
**Solution:** Pass one config per input, or a single dict for all.
**Prevention:** —

### Error: `Provided run_id be used only for the first element of the batch.` (`RuntimeWarning`)

**Cause:** A single config with `run_id` used for a multi-input batch.
**Solution:** Pass per-input configs with distinct `run_id`s, or drop `run_id`.
**Prevention:** —

### Error: `Missing keys {keys} in config['configurable'] Expected keys are {expected}.`

**Cause:** `RunnableWithMessageHistory` invoked without `session_id` (or custom history keys).
**Solution:** Pass `config={"configurable": {"session_id": "..."}}`. Better: migrate to `create_agent` + checkpointer — this class is deprecated (removal 2.0.0).
**Prevention:** —

### Error: `RunnableWithMessageHistory is deprecated. Use LangGraph's built-in persistence instead.`

**Cause:** Deprecated since core 1.3.3.
**Solution:** Use a LangGraph checkpointer and `thread_id`.
**Prevention:** Plan removal before 2.0.0.

### Error: `astream_log is deprecated. Use astream instead.`

**Cause:** Deprecated since 1.3.3 (removal 2.0.0), as is `astream_events(version="v1")`.
**Solution:** Use `astream`, `astream_events(version="v2")`, or LangGraph stream modes.
**Prevention:** —

### Error: `The dispatcher API does not accept additional keyword arguments.Please do not pass any additional keyword arguments, instead include them in the data field.`

**Cause:** `dispatch_custom_event(name, data, extra=...)`.
**Solution:** Put extra values inside `data`.
**Prevention:** —

### Error: `RuntimeError` from `dispatch_custom_event` with no parent run (Python 3.10 async)

**Cause:** On Python 3.10 async code, the run context is not propagated automatically.
**Solution:** Pass `config=` explicitly to `adispatch_custom_event(name, data, config=config)`.
**Prevention:** Upgrade to Python 3.11+ for context propagation.

### Error: `File is not open. Use FileCallbackHandler as a context manager.`

**Cause:** `FileCallbackHandler` used after its file was closed or outside a `with` block.
**Solution:** `with FileCallbackHandler("run.log") as handler: ...`.
**Prevention:** —

### Error: `If env_var is set, handle_class must also be set to a non-None value.`

**Cause:** `register_configure_hook(..., env_var=...)` without `handle_class`.
**Solution:** Provide the handler class that the env var should activate.
**Prevention:** —

### Error: `No indexed run ID {run_id}.` (`TracerException`)

**Cause:** A custom tracer received an end/error event for a run it never saw start (often mixed sync/async callbacks or a handler added mid-run).
**Solution:** Attach tracers at the root call via `config={"callbacks": [...]}`.
**Prevention:** Avoid adding tracers inside tools mid-run.

### Error: `Install grandalf to draw graphs: `pip install grandalf`.`

**Cause:** `agent.get_graph().draw_ascii()` without `grandalf` (siblings: `pygraphviz` for PNG, `pyppeteer` for local Mermaid rendering, `requests` for the Mermaid.INK API).
**Solution:** Install the named package, or use `draw_mermaid()` for text output.
**Prevention:** —

### Error: `Failed to reach {base_url} API while trying to render your graph. Status code: {code}.`

**Cause:** `draw_mermaid_png()` uses the external Mermaid.INK service by default.
**Solution:** Allow outbound access, or render locally with `draw_method=MermaidDrawMethod.PYPPETEER`.
**Prevention:** Avoid external rendering in restricted networks.

### Error: `Found duplicate subgraph '{subgraph}' -- this likely means that you're reusing a subgraph node with the same name.`

**Cause:** Two nested agents/subgraphs share a name when drawing a Mermaid graph.
**Solution:** Give each `create_agent(..., name=...)` a unique name.
**Prevention:** —

### Error: `Unable to merge left={left!r} and right={right!r}. Both must be of type str, dict, or list, or else be two equal objects.`

**Cause:** Merging streamed chunks whose `additional_kwargs`/`response_metadata` values have incompatible types (usually a nonstandard OpenAI-compatible server).
**Solution:** Disable streaming for that provider (`disable_streaming=True`) or use a dedicated integration.
**Prevention:** —

### Error: `Cannot concatenate ChatMessageChunks with different roles.`

**Cause:** Aggregating chunks from different messages.
**Solution:** Aggregate per message ID.
**Prevention:** —

### Error: `WARNING! {field_name} is not default parameter. {field_name} was transferred to model_kwargs.`

**Cause:** An unknown constructor argument was passed to a provider class (typo or a new API parameter).
**Solution:** Fix the typo, or pass provider parameters via `model_kwargs` / `extra_body` intentionally.
**Prevention:** Review warnings at startup.

### Error: `Found {field_name} supplied twice.`

**Cause:** The same parameter was given both directly and in `model_kwargs`.
**Solution:** Remove one.
**Prevention:** —

### Error: `As of openai>=1.0.0, if `deployment` (or alias `azure_deployment`) is specified then `openai_api_base` (or alias `base_url`) should not be.`

**Cause:** Legacy Azure configuration mixing `base_url` and `azure_deployment`.
**Solution:** Use `azure_endpoint` + `azure_deployment` + `api_version` (`OPENAI_API_VERSION`).
**Prevention:** Keep `validate_base_url=True`.

### Error: Azure `AzureChatOpenAI` fails with an API-version error on first call

**Cause:** `AzureChatOpenAI` has no default API version (unlike `AzureOpenAIEmbeddings`, which defaults to `2023-05-15`); `OPENAI_API_VERSION` is unset. There is no `AZURE_OPENAI_API_VERSION` variable.
**Solution:** Set `OPENAI_API_VERSION` or pass `api_version=`.
**Prevention:** Include the API version in deployment configuration.

### Error: Azure workload identity not used (authentication fails in Kubernetes)

**Cause:** Workload identity auto-auth runs only when no key/token/provider is configured, all of `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_FEDERATED_TOKEN_FILE` are set, **and** `azure.identity` imports; a missing `azure-identity` package is silently ignored.
**Solution:** `pip install "langchain-openai[azure-identity]"` and verify the three variables inside the pod.
**Prevention:** Add an import check for `azure.identity` in your readiness probe.

### Error: `StreamChunkTimeoutError` during async OpenAI streaming

**Cause:** No parsed chunk arrived within `stream_chunk_timeout` (default 120 s, env `LANGCHAIN_OPENAI_STREAM_CHUNK_TIMEOUT_S`); SSE keepalives do not reset the timer. Long reasoning pauses can trigger it.
**Solution:** Raise the timeout for reasoning-heavy models, or catch and retry.
**Prevention:** Tune per model class.

### Error: `OpenAITimeoutError` / `OpenAIConnectionError`

**Cause:** Provider timeouts or dropped connections; both subclass core `ModelTimeoutError`/`ModelConnectionError` (retryable).
**Solution:** `ModelRetryMiddleware`, `timeout=`, TCP keepalive variables.
**Prevention:** —

### Error: `Response missing 'choices' key: {keys}`

**Cause:** An OpenAI-compatible server returned an error body or an unexpected shape.
**Solution:** Inspect the raw response; set `include_response_headers=True` to see request IDs.
**Prevention:** Use a native integration where available.

### Error: `Got different types for token usage: {type} and {type}`

**Cause:** An OpenAI-compatible server reports usage fields with inconsistent types.
**Solution:** Set `stream_usage=False` or use a native integration.
**Prevention:** —

### Error: `Cannot currently include response headers when response_format is specified.`

**Cause:** `include_response_headers=True` together with streamed structured output (a warning).
**Solution:** Drop one of the two for that call.
**Prevention:** —

### Error: `` `access_token` must be a non-empty string. ``

**Cause:** Experimental ChatGPT OAuth tokens file is incomplete (sibling: `refresh_token`).
**Solution:** Re-run the login flow; the file is `~/.langchain/chatgpt-auth.json`.
**Prevention:** This path is private and experimental — do not depend on it in production.

### Error: `_ChatOpenAICodex is experimental and unofficial. …`

**Cause:** The private Codex chat class warns on use.
**Solution:** Use only where permitted by your OpenAI account and workspace terms.
**Prevention:** Prefer public classes.

### Error: `Could not import transformers python package. This is needed for OpenAIEmbeddings to work without `tiktoken`.`

**Cause:** `OpenAIEmbeddings(tiktoken_enabled=False)` needs `transformers` for length-safe chunking.
**Solution:** Install `transformers`, or re-enable tiktoken.
**Prevention:** —

### Error: tiktoken download failures in air-gapped environments

**Cause:** `tiktoken` fetches encodings on first use (the OpenAI package's own Makefile pre-downloads `cl100k_base` and `o200k_base` into `TIKTOKEN_CACHE_DIR` for this reason).
**Solution:** Pre-populate a cache directory and set `TIKTOKEN_CACHE_DIR`.
**Prevention:** Bake the cache into your image.

### Error: `Anthropic's legacy count_tokens method was removed in anthropic 0.39.0 and langchain-anthropic 0.3.0.`

**Cause:** `AnthropicLLM.get_num_tokens()`.
**Solution:** Use `ChatAnthropic(...).get_num_tokens_from_messages(...)`.
**Prevention:** Migrate from `AnthropicLLM` to `ChatAnthropic`.

### Error: `The 'output_format' parameter is deprecated and will be removed in langchain-anthropic 2.0.0. Use 'output_config={"format": ...}' instead.`

**Cause:** Old structured-output parameter.
**Solution:** Use `output_config`.
**Prevention:** —

### Error: `Tool-change block {type!r} was dropped: it is only valid on a SystemMessage sent in place …`

**Cause:** `tool_addition`/`tool_removal` system blocks hoisted into the top-level system prompt on a model without mid-conversation system support.
**Solution:** Use a model that supports mid-conversation system messages.
**Prevention:** —

### Error: `AnthropicPromptCachingMiddleware caching middleware only supports Anthropic models, not instances of {type}`

**Cause:** The middleware ran with a non-`ChatAnthropic` model (for example after `ModelFallbackMiddleware` switched providers). Default behavior is a warning; `"raise"` makes it an error.
**Solution:** Set `unsupported_model_behavior="ignore"` for multi-provider agents.
**Prevention:** Same applies to `FireworksPromptCachingMiddleware` (`only supports ChatFireworks`).

### Error: `AnthropicPromptCachingMiddleware requires 'langchain' to be installed.`

**Cause:** Provider middleware imported without the `langchain` agents package (similarly for Fireworks).
**Solution:** `pip install langchain`.
**Prevention:** —

### Error: `Path must start with one of {allowed_prefixes}: {path}`

**Cause:** A Claude text-editor/memory tool call outside the allowed prefixes (memory defaults to `/memories`).
**Solution:** Adjust `allowed_path_prefixes` if the access is legitimate.
**Prevention:** Keep prefixes narrow.

### Error: `File not found: {path}`

**Cause:** State-backed Claude text-editor/memory tool viewed a file absent from state.
**Solution:** None — the model receives the error. For persistence across threads use the filesystem variants.
**Prevention:** —

### Error: `Received an error from the Ollama API. Please check your Ollama server logs.`

**Cause:** Ollama rejected `list()` during `validate_model_on_init`.
**Solution:** Check `ollama serve` logs and the server version.
**Prevention:** —

### Error: `Image data only supported through in-line base64 format.`

**Cause:** `ChatOllama` received an image URL block.
**Solution:** Download and pass base64 data.
**Prevention:** —

### Error: `` `stop` found in both the input and default params. ``

**Cause:** `ChatOllama(stop=[...])` plus a per-call `stop=`.
**Solution:** Set stop sequences in one place.
**Prevention:** —

### Error: `` `top_logprobs` must be a positive integer. ``

**Cause:** Invalid `ChatOllama(top_logprobs=0)`. Setting `top_logprobs` without `logprobs` auto-enables `logprobs` with a warning; with `logprobs=False` it raises.
**Solution:** Use a value ≥ 1 and leave `logprobs` unset or `True`.
**Prevention:** —

### Error: `Could not download mistral tokenizer from Huggingface for calculating batch sizes. Set a Huggingface token via the HF_TOKEN environment variable …`

**Cause:** `MistralAIEmbeddings` tried to download the gated `mistralai/Mixtral-8x7B-v0.1` tokenizer.
**Solution:** Set `HF_TOKEN` with access, or accept the `len()` fallback batching.
**Prevention:** Pre-cache the tokenizer in images.

### Error: `` `repo_id` must be a HuggingFace repo ID, not a URL. Use `endpoint_url` for direct endpoints. ``

**Cause:** A URL was passed as `repo_id` to `HuggingFaceEndpoint` (or embeddings `model`/`repo_id`).
**Solution:** Use `endpoint_url=` for URLs.
**Prevention:** —

### Error: `Backend: {backend} requires optimum-intel>={version}.`

**Cause:** `HuggingFacePipeline.from_model_id(backend="openvino"/"ipex")` without the required `optimum-intel`.
**Solution:** `pip install --upgrade --upgrade-strategy eager "optimum[{backend}]"`.
**Prevention:** —

### Error: `` `backend='ipex'` is no longer supported; the default `torch` backend will be used instead. ``

**Cause:** `HuggingFaceEmbeddings(model_kwargs={"backend": "ipex"})`.
**Solution:** Remove the setting; PyTorch 2.5+ supports Intel GPUs natively.
**Prevention:** —

### Error: `At least one HumanMessage must be provided!` / `Last message must be a HumanMessage!`

**Cause:** `ChatHuggingFace` builds a chat-template prompt that must end with a human turn.
**Solution:** Ensure the final message is a `HumanMessage`.
**Prevention:** —

### Error: `Fireworks received an invalid payload: {text}` / `Fireworks Server: Error {status}`

**Cause:** The legacy `Fireworks` completion LLM got a 4xx/5xx response.
**Solution:** Check the model name and parameters; retry 5xx.
**Prevention:** Prefer `ChatFireworks`.

### Error: `FIREWORKS_API_KEY is required unless both client and async_client are supplied.`

**Cause:** `FireworksRerank` without credentials.
**Solution:** Set the variable or pass clients.
**Prevention:** —

### Error: `DeepSeek API returned an invalid response. Please check the API status and try again.`

**Cause:** Non-JSON streaming data from DeepSeek.
**Solution:** Retry; check provider status.
**Prevention:** `ModelRetryMiddleware`.

### Error: `` `extra_body` must be a dict to forward Perplexity-specific parameters to the Responses API … ``

**Cause:** A non-dict `extra_body` on `ChatPerplexity` routed to the Responses API.
**Solution:** Pass a dict.
**Prevention:** Note that on that path `temperature`, `top_p`, `top_k`, `stop` and `metadata` are dropped with a warning, and `tool_choice` raises.

### Error: `Parameters {params} should be specified explicitly. Instead they were passed in as part of `model_kwargs` parameter.`

**Cause:** Known fields were placed in `model_kwargs` (Perplexity and others).
**Solution:** Pass them as constructor arguments.
**Prevention:** —

### Error: `Could not import the `openrouter` Python SDK. Please install it with: pip install openrouter`

**Cause:** Broken install of `langchain-openrouter` (it uses the official OpenRouter SDK, not `openai`).
**Solution:** Reinstall the package.
**Prevention:** —

### Error: `Must provide tenant and database values to connect to Chroma Cloud`

**Cause:** `Chroma(chroma_cloud_api_key=...)` without `tenant`/`database`.
**Solution:** Provide both.
**Prevention:** —

### Error: `Chroma collection not initialized. Use `reset_collection` to re-create and initialize the collection.`

**Cause:** The collection was deleted (`delete_collection()`) and then used.
**Solution:** Call `reset_collection()`.
**Prevention:** —

### Error: `{message}\n\nTry filtering complex metadata using langchain_community.vectorstores.utils.filter_complex_metadata.`

**Cause:** Chroma rejects nested/complex metadata values.
**Solution:** Flatten metadata or apply `filter_complex_metadata`.
**Prevention:** Keep metadata scalar.

### Error: `client should be an instance of qdrant_client.QdrantClient, got {type}`

**Cause:** The legacy `Qdrant` class received a wrong client object.
**Solution:** Migrate to `QdrantVectorStore(client=QdrantClient(...), collection_name=..., embedding=...)` — `Qdrant` is deprecated.
**Prevention:** —

### Error: `Sparse embeddings are `None`. Please set using the `sparse_embedding` parameter.`

**Cause:** `QdrantVectorStore(retrieval_mode=RetrievalMode.SPARSE/HYBRID)` without `sparse_embedding`.
**Solution:** Pass `sparse_embedding=FastEmbedSparse()`.
**Prevention:** —

### Error: `Embeddings are required for {operation}`

**Cause:** Dense operations on a `QdrantVectorStore` created without `embedding`.
**Solution:** Provide `embedding=`.
**Prevention:** —

### Error: `Using dict as a `filter` is deprecated. Please use qdrant-client filters directly`

**Cause:** Legacy dict filters on Qdrant.
**Solution:** Use `qdrant_client.models.Filter`.
**Prevention:** —

### Error: `Unknown distance strategy, must be COSINE, DOT, or EUCLID.`

**Cause:** Unsupported distance for relevance scoring.
**Solution:** Use a supported distance or pass `relevance_score_fn`.
**Prevention:** —

### Error: `Language {language} is not implemented yet!`

**Cause:** `RecursiveCharacterTextSplitter.from_language()` with a `Language` enum member that has no separator set.
**Solution:** Pass custom `separators=`.
**Prevention:** —

### Error: `Unable to import lxml, please install with `pip install lxml`.`

**Cause:** `HTMLSectionSplitter` needs `lxml` (and `bs4`).
**Solution:** Install both.
**Prevention:** Text-splitters declares no extras — list optional deps explicitly.

### Error: `❌ Provider '{provider}' not found in models.dev data` / `Continue? (y/N):` prompt in CI (`langchain-profiles`)

**Cause:** The provider ID is not a models.dev ID (e.g. `mistral` not `mistralai`, `fireworks-ai` not `fireworks`), or `--data-dir` is outside the current directory in a non-interactive job.
**Solution:** Use models.dev IDs (see `libs/model-profiles/Makefile`), and pipe `echo y |` for out-of-tree data dirs.
**Prevention:** Use `make refresh-profiles`.

### Error: `Profile keys not declared in langchain_core ModelProfile: {extra}. Add these fields to langchain_core.language_models.model_profile.ModelProfile and release langchain-core before publishing partner packages …`

**Cause:** models.dev or augmentations introduced a new capability key.
**Solution:** Add the field to `ModelProfile` and release core first.
**Prevention:** Release core before partners that depend on new profile keys.

### Error: `Could not parse profile source as Python: {e}` / `` `_PROFILES` is not a literal expression ``

**Cause:** `langchain-profiles summarize` found a hand-edited or corrupted `_profiles.py`.
**Solution:** Regenerate with `refresh`; never edit `_profiles.py` by hand.
**Prevention:** —

### Error: `Unknown lib: … check_diff.py likely needs an update for this new library!`

**Cause:** A PR added a new directory under `libs/` that CI change detection does not know.
**Solution:** Update `.github/scripts/check_diff.py` (and the other files listed for new partners in `AGENTS.md`).
**Prevention:** Follow the "Adding a new partner to CI" checklist.

### Error: PR title check fails in `pr_lint.yml`

**Cause:** Title not in Conventional Commit form with an allowed scope, uppercase scope, or `release` used as a scope.
**Solution:** Use `type(scope): lowercase description`, e.g. `fix(openai): handle null choices`.
**Prevention:** Check the allowed scopes in Section 10.5.

### Error: External PR closed with label `missing-issue-link`

**Cause:** `require_issue_link.yml` closes external PRs that do not link an approved issue assigned to the author.
**Solution:** Link the issue and get assigned; `reopen_on_assignment.yml` reopens the PR.
**Prevention:** Open an issue first.

### Error: `pytest_socket.SocketBlockedError` in unit tests

**Cause:** Unit tests run with `--disable-socket --allow-unix-socket`; a test made a network call.
**Solution:** Mock the network or move the test to `tests/integration_tests/`.
**Prevention:** Keep unit tests hermetic.

### Error: `blockbuster.BlockingError` in async unit tests

**Cause:** `blockbuster` detected a blocking call (file I/O, `time.sleep`) inside async code.
**Solution:** Use async equivalents or `run_in_executor`.
**Prevention:** —

### Error: `CassetteNotFoundError` in VCR tests

**Cause:** `test_vcr` runs with `--record-mode=none` and the cassette was never recorded.
**Solution:** Record locally against the live API with credentials, commit the gzip-compressed YAML cassette under `tests/cassettes`.
**Prevention:** Review cassettes for secrets before committing.

### Error: `test_no_overrides_DO_NOT_OVERRIDE` fails in an integration package

**Cause:** A standard test was removed or overridden without `@pytest.mark.xfail(reason="...")`.
**Solution:** Restore it, or mark the override with xfail and a reason.
**Prevention:** —

### Error: `uv lock --check` fails (`make check-lock`)

**Cause:** `pyproject.toml` changed without re-locking.
**Solution:** `cd libs/<pkg> && uv lock`; revert unrelated environment-marker churn from different local Python versions.
**Prevention:** `UV_FROZEN=true` in Makefiles surfaces this early.

### Error: Version-check pre-commit hook fails (`make check_version`)

**Cause:** `pyproject.toml` version and `_version.py`/`version.py` differ.
**Solution:** Bump both (and `uv.lock`) in the same release PR.
**Prevention:** Use the `release(<pkg>): <version>` PR pattern.

### Error: `This module has been moved to langchain-experimental. … pip install langchain-experimental`

**Cause:** `LLMBashChain`, Python REPL tools and similar dangerous components were removed from `langchain-classic`.
**Solution:** Use `langchain-experimental` inside a sandbox, or a `ShellToolMiddleware` with a Docker policy.
**Prevention:** —

### Error: `Importing from {module} is not allowed. Allowed top-level packages are: …`

**Cause:** A `langchain_classic` compatibility shim tried to import from an unexpected package.
**Solution:** Import the target class from its real package directly.
**Prevention:** Migrate off `langchain_classic` re-exports.

### Error: `Cannot specify both callback_manager and callbacks.`

**Cause:** Classic chains given the deprecated `callback_manager` and `callbacks`.
**Solution:** Use `callbacks` only.
**Prevention:** —

### Error: `A single string input was passed in, but this chain expects multiple inputs ({keys}).`

**Cause:** A classic multi-input chain called with a string.
**Solution:** Pass a dict with all input keys.
**Prevention:** Migrate classic chains to LCEL or agents.

### Error: `The same variables {overlap} are found in multiplememory object, which is not allowed by CombinedMemory.`

**Cause:** Two classic memory classes expose the same variable.
**Solution:** Rename memory keys — or migrate to a checkpointer (classic memory is deprecated).
**Prevention:** —

### Error: `Could not import upstash_redis python package.`

**Cause:** Classic entity memory with the Upstash back end.
**Solution:** `pip install upstash_redis`.
**Prevention:** —

### Error: `HostExecutionPolicy` commands run as the service user with full access

**Cause:** Default shell policy is `HostExecutionPolicy` (no sandbox).
**Solution:** Switch to `DockerExecutionPolicy` or `CodexSandboxExecutionPolicy`.
**Prevention:** Make an explicit `execution_policy` mandatory in code review.

### Error: `Codex sandbox policy could not determine a supported platform; set 'platform' explicitly.`

**Cause:** Neither macOS nor Linux detected.
**Solution:** Set `platform="linux"` or `"macos"`, or use Docker.
**Prevention:** —

### Error: `Failed to apply resource limits via prlimit.`

**Cause:** The container or kernel forbids `prlimit` on the child process.
**Solution:** Remove `cpu_time_seconds`/`memory_bytes`, or enforce limits via cgroups/container settings.
**Prevention:** —

### Error: `This version of FastMCP does not expose `Client._await_with_session_monitoring`, so an MCP elicitation round cannot be raced against the session task.` (`RuntimeWarning`)

**Cause:** Version skew between `fastmcp` and `langchain`; a transport failure during elicitation could hang.
**Solution:** Install `fastmcp` within the range pinned by `langchain[mcp]` (`>=4.0.1,<5.0.0`).
**Prevention:** Upgrade `langchain` and `fastmcp` together.

### Error: `This MCP server asked for input over the legacy server-initiated elicitation path, which interrupt-based elicitation cannot answer.`

**Cause:** The server uses the legacy elicitation path rather than returning `InputRequiredResult`.
**Solution:** Upgrade the server, or supply your own FastMCP client with an elicitation callback.
**Prevention:** —

---

## 12. Potential Improvements

Findings from reading the source at `a9780cd` plus open GitHub issues captured in the briefing.

### 12.1 Defects and inconsistencies found in source

| Area | Finding | Location |
|---|---|---|
| Error message | `RunnablePick` reports non-dict input with the message for `RunnablePassthrough.assign()` | `libs/core/langchain_core/runnables/passthrough.py:754` |
| Docstrings | Anthropic middleware examples import `StateTextEditorToolMiddleware`, `StateMemoryToolMiddleware`, `FilesystemTextEditorToolMiddleware`, `FilesystemMemoryToolMiddleware` from `langchain.agents.middleware`; none exist | `libs/partners/anthropic/langchain_anthropic/middleware/anthropic_tools.py`, `file_search.py` |
| Docstrings | Core tool docstrings show `from langchain_core.tools import tool, ToolRuntime`; `ToolRuntime` is exported from `langchain.tools` (LangGraph), not core | `libs/core/langchain_core/tools/base.py` |
| Docs drift | `langchain-profiles` README and `refresh` docstring say it writes `profiles.py`; code writes `_profiles.py` | `libs/model-profiles` |
| Docs drift | `langchain-typesafe` README imports `TypeSafeRateLimitError` from the package root; it lives in `langchain_typesafe.client` | `libs/partners/typesafe` |
| Validation gap | `InMemoryRateLimiter` docstring requires `max_bucket_size >= 1`, but nothing enforces it | `libs/core/langchain_core/rate_limiters.py` |
| Defaults | `ChatOpenAI.model_name` still defaults to `gpt-3.5-turbo`; `OpenAIEmbeddings` to `text-embedding-ada-002` | `libs/partners/openai` |
| Defaults | `index()` defaults to SHA-1 keys and only warns | `libs/core/langchain_core/indexing/api.py` |
| Deprecation hygiene | Legacy `Qdrant` is marked `removal="0.5.0"` but still exported at 1.1.0 | `libs/partners/qdrant` |
| Consistency | `ChatPerplexity` reads only `PPLX_API_KEY` while sibling classes accept `PERPLEXITY_API_KEY`; `MistralAIEmbeddings` ignores `MISTRAL_BASE_URL`; `ChatOllama` ignores `OLLAMA_HOST` | partner packages |
| Consistency | `@tool` defaults `error_on_invalid_docstring=True`; `StructuredTool.from_function` defaults `False` | `libs/core/langchain_core/tools/` |
| Silent failure | Profile resolution errors in `BaseChatModel` are suppressed; Azure workload identity silently disables itself without `azure-identity` | core, `langchain-openai` |
| Governance | No `SECURITY.md` / disclosure policy; bug template references an undefined "security option" | repo root, `.github/ISSUE_TEMPLATE` |

### 12.2 Open issues worth tracking (from the briefing's GitHub snapshot)

| Issue | Summary |
|---|---|
| #40492 | `after_model` middleware using `jump_to="tools"` bypasses the HITL gate and can re-execute rejected tool calls |
| #40688 | `ToolRetryMiddleware` defaults retry non-idempotent tools on ambiguous transport errors |
| #40767 | `PIIMiddleware(apply_to_output=True)` blanks tool-call parameters, leading to graph recursion errors |
| #40826 | Streaming a large tool call re-parses arguments in pure Python on every chunk |
| #40877 | `RunnableParallel` ignores `max_concurrency` on `ainvoke`/`astream` |
| #40878 | `with_fallbacks(exception_key=...)` writes the exception into the caller's input mapping |
| #40879 | `InjectedToolCallId` overridden by a value in `args` when a custom `args_schema` omits the field |
| #40880 | Pydantic-aliased injected args not filtered from `on_tool_start` inputs |
| #40881 | `on_tool_error` does not receive caller `**kwargs` |
| #39900 | `MarkdownListOutputParser` drops `+` bullet items (its pattern only matches `-` and `*`) |
| #40871 | OpenAI Responses stream IDs switch from `resp_…` to `lc_run--…` |
| #40892 | `ChatAnthropic` drops message-level `output_config` and `clear_at` from `SystemMessage` |
| #40896 / #40895 / #40897 | `ChatOpenAI` forwards unsupported `stop`/sampling parameters to some reasoning models; Qwen named `tool_choice` rejected in thinking mode |
| #40909 / #40915 / #40914 | Stream crashes on null tool-call deltas (OpenAI) and `TypeError` in `ChatPerplexity.stream()` |
| #40866 | `ModelCallLimitMiddleware` limit message indistinguishable from a model answer |
| #40158 | Proposed opt-in tool-call verification hook |
| #40825 | Support for MCP Apps (SEP-1865) |

### 12.3 Suggested improvements

1. Publish a `SECURITY.md` with a disclosure channel and the threat model already written into `load/load.py` and `_security/`.
2. Change `load`'s default `allowed_objects` to `"messages"` (already signaled by the pending deprecation) and make `index()` default to a collision-resistant key encoder.
3. Make `ToolRetryMiddleware`'s default `retry_on` conservative (transport-safe exceptions only) and document idempotency.
4. Add an explicit, typed marker to limit-exceeded AI messages (#40866) so applications can distinguish them.
5. Emit a warning when `SummarizationMiddleware` is constructed without a `trigger`, since it otherwise never runs.
6. Honor `OLLAMA_HOST` in `ChatOllama` and `PERPLEXITY_API_KEY` in `ChatPerplexity` for parity with SDK conventions.
7. Fix the stale docstring imports and README drift listed above; add a doc-test that imports every example symbol.
8. Validate `InMemoryRateLimiter(max_bucket_size>=1)`.

---

## 13. Quick Reference

### 13.1 Key paths

| Path | Contents |
|---|---|
| `libs/langchain_v1/langchain/agents/factory.py` | `create_agent` |
| `libs/langchain_v1/langchain/agents/middleware/` | Middleware types and implementations |
| `libs/langchain_v1/langchain/chat_models/base.py` | `init_chat_model` and provider registry |
| `libs/langchain_v1/langchain/mcp/` | MCP adapter |
| `libs/core/langchain_core/runnables/base.py` | Runnable protocol |
| `libs/core/langchain_core/language_models/chat_models.py` | `BaseChatModel` |
| `libs/core/langchain_core/load/load.py` | Deserialization and its threat model |
| `libs/core/langchain_core/_security/` | SSRF policy and transports |
| `libs/partners/<p>/langchain_<p>/data/_profiles.py` | Generated model profiles |
| `libs/standard-tests/langchain_tests/` | Conformance suites |
| `.github/workflows/_release.yml` | Release pipeline |
| `.github/scripts/check_diff.py` | CI change detection |
| `AGENTS.md` | Contributor rules |

### 13.2 Commands

```bash
# Development (run inside a package directory)
cd libs/langchain_v1 && uv sync --all-groups
make test            # unit tests (no network)
make lint && make format
uv run --group lint mypy .
make integration_tests

# Monorepo-wide lockfiles
make -C libs lock
make -C libs check-lock

# Model profiles
cd libs/model-profiles && make refresh-profiles

# Release (maintainers)
gh workflow run 63880841 --repo langchain-ai/langchain -f working-directory=<partner> -f release-version=<version>

# Diagnostics
python -m langchain_core.sys_info
```

### 13.3 Critical settings

| Setting | Value to remember |
|---|---|
| Agent graph recursion limit | 9,999 (set by `create_agent`) |
| Runnable `recursion_limit` default | 25 |
| `ModelRetryMiddleware` / `ToolRetryMiddleware` | 2 retries, ×2 backoff from 1 s, max 60 s, ±25% jitter |
| `SummarizationMiddleware` | no default trigger; keeps 20 messages; trims 4,000 tokens for the summary call |
| `ClearToolUsesEdit` | trigger 100,000 tokens, keep 3, placeholder `[cleared]` |
| Shell policy timeouts | command 30 s, startup 30 s, termination 10 s, 100 output lines |
| Docker policy image | `python:3.12-alpine3.19`, network off |
| `FilesystemFileSearchMiddleware` | max file 10 MB, ripgrep preferred |
| OpenAI stream chunk timeout | 120 s (async) |
| `load()` default | `allowed_objects="core"` (unsafe for untrusted data), `secrets_from_env=False` |
| `index()` default | `key_encoder="sha1"`, `batch_size=100`, `cleanup_batch_size=1000` |
| Python | ≥ 3.10 |

### 13.4 Ports and endpoints

LangChain listens on no ports. Endpoints referenced by default configuration: `https://api.anthropic.com`, `https://api.x.ai/v1/`, `https://api.deepseek.com/v1` (`/beta` for strict mode), `https://api.mistral.ai/v1`, `https://api.fireworks.ai/inference/v1`, `https://api.typesafe.ai`, `https://gateway.smith.langchain.com` (gateway), `https://models.dev/api.json` (profile CLI), Ollama SDK default host, Chroma `HttpClient` port 8000.

---

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