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
┌───────────────────────────────────────────────────────────────────────────────┐
│ 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_schemas 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
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_GATEWAYis set to a custom URL andLANGSMITH_GATEWAY_API_KEYis absent,LANGSMITH_API_KEYis used as the provider credential and sent to that URL. Only pointLANGSMITH_GATEWAYat 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
configurablevalues (str/int/float/bool, not starting with__) are copied into LangSmith trace metadata. Onlyapi_keyis excluded. Never pass secrets throughconfigurablewhen 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 ModelErrors, 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,langgraphand 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/ToolCallLimitMiddlewareto every production agent - [ ] Gate destructive tools with
HumanInTheLoopMiddleware - [ ] Isolate shell/code tools with
DockerExecutionPolicyor 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.
# 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.
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:
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>" |
| 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_modelmiddleware returningjump_to="tools"can bypass the HITL pending-tool-call gate and re-execute rejected calls inlangchain1.4.0. Avoidjump_to="tools"in custom middleware used alongsideHumanInTheLoopMiddlewareuntil resolved.
6.3 TLS / Certificates
- Provider SDKs (
openai,anthropic,groq, …) andhttpxhandle TLS.langchain-openaidepends oncertifi>=2024.6.2; you can supplyhttp_client/http_async_clientwith customverify=and certificates. - The SSRF-safe transports in
langchain_core._securitypin the TCP connection to the validated IP while preserving the originalHostheader and TLS SNI, so certificate validation still targets the hostname. ChatOpenAIimage-size fetching (for token counting withallow_fetching_images=True) uses the SSRF-safe client withverify=global_ssl_contextandfollow_redirects=False, a 5-second timeout and a 50 MB cap.- Proxies:
OPENAI_PROXY/openai_proxy,ANTHROPIC_PROXY,GROQ_PROXY, or standardHTTP(S)_PROXYvariables consumed by httpx. Combiningopenai_proxywith a custom client raisesValueError.
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 defaultFalseand passsecrets_mapexplicitly.- Trace metadata: every
configurablescalar exceptapi_keyis copied into LangSmith metadata; every traced run recordsplatform.platform(), Python version and package versions. - VCR cassettes in
langchain-testsfilterauthorization,x-api-keyandapi-keyheaders toPLACEHOLDER; 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 anyload/loadsof 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=toShellToolMiddleware - [ ] Gate write/delete/payment tools with
HumanInTheLoopMiddleware - [ ] Set
TracePolicy(process_inputs=omit_payload)if traces must not contain conversation content - [ ] Use
key_encoder="blake2b"/sha256forindex()where collision attacks matter - [ ] Do not set
LANGCHAIN_ENV=local*outside tests - [ ] Point
LANGSMITH_GATEWAYonly 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:
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 Releaseworkflow (_release.yml): build → release notes → pre-release checks (unit tests, minimum-version tests, partner integration tests) → TestPyPI → core-compatibility tests against publishedanthropicandopenaipartners → dependents (deepagents) for core/langchain releases → PyPI (trusted publishing) → GitHub release and tag. - Within a
0.xseries, fixes and additive features take patch bumps. - Deprecations name
sinceandremovalversions; 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_coreturns on display ofLangChainDeprecationWarningandLangChainBetaWarning. 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()'skey_encoderafter 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
RunnableParallelignoringmax_concurrencyonainvoke/astreamwhile 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/MCPServerinstance,MCPConfig/dict,fastmcp.ClientorClientGroup. It clones and "arms" clients without an elicitation handler so elicitation becomes a LangGraphinterrupt(); 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 asdestructive_hint,_meta) andmetadata["mcp"]["server"]; structured content is attached asMCPToolArtifact(structured_content=...)on theToolMessageartifact. - Only elicitation is answered; sampling, roots and continuation rounds raise
NotImplementedError. Interrupt payload type:"mcp_elicitation"withrequestsof modeform(JSON-schema) orurl.
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
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
- Model resolution. A string model goes through
init_chat_model. - Response format.
None→ no structured output.ToolStrategy/ProviderStrategy/AutoStrategyare kept; raw schemas becomeAutoStrategy. ForAutoStrategy, aToolStrategyis built up front so structured-output tools exist at graph-build time; the decision to useProviderStrategyis made per model call in_get_bound_modelvia_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_OUTPUTregexes (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).
- Tools. Dict tools are provider built-ins (bound but not executed locally). Callables and
BaseTools plus middleware tools go into a LangGraphToolNodetogether with the composedwrap_tool_callchain. AToolNodeexists if there are client-side tools or any wrap-tool middleware. - Middleware validation. Duplicate names raise
AssertionError. Hook presence is detected by comparing each class's method with theAgentMiddlewarebase method (sync or async override counts). - Wrap composition.
_chain_model_call_handlerscomposes right-to-left so the first middleware is outermost;Commands fromExtendedModelResponseare accumulated inner-first, cleared on each inner call for retry safety, and converted into additionalCommands after the model's own state update. Commands withgoto,resumeorgraphraiseNotImplementedError. - Binding.
ProviderStrategy→bind_tools(final_tools, **strategy.to_model_kwargs(), **model_settings)plusstrict=Truefor OpenAI-compatible models not using the Responses API.ToolStrategy→bind_tools(..., tool_choice="any")when structured-output tools exist. Otherwisebind_tools(tools, tool_choice=request.tool_choice)or plainbind(**model_settings). - Invalid tool calls. Before each model call,
_patch_invalid_tool_callsappends an errorToolMessagefor every unansweredinvalid_tool_call; the repaired history replaces the state history viaRemoveMessage(REMOVE_ALL_MESSAGES). - 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 aToolMessage, or raise). Whenresponse_formatis set and no structured response was produced this turn,structured_responseis explicitly reset toNoneto avoid a stale checkpointed value. - Edges. Entry is the first
before_agent, else firstbefore_model, elsemodel. Loop entry is the firstbefore_modelormodel.after_modelhooks chain from last to first. Model→tools routing: honorjump_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 arereturn_direct, or a structured-output tool succeeded; otherwise loop. - 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_blocksresolution:output_version == "v1"content returned as-is → registered provider translator byresponse_metadata["model_provider"]→ best-effort parse, then missing tool calls appended and reasoning fromadditional_kwargsprepended.- Message dict coercion uses a hard-coded allowlist for
{"lc": 1, "type": "constructor", ...}shapes and never callsload(). Serializable.is_lc_serializable()defaults toFalse(opt-in).to_json()mergeslc_secretsacross the MRO.dumpsrefuses a customdefault=and wraps user dicts that contain anlckey as{"__lc_escaped__": ...};loadunwraps 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
msgvariable beforeraise; no bareexcept:. - 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 --strictwith the pydantic plugin in most packages;text-splitterstype-checks withty.- 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); typesfeat,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:
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_ids, 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 aliasazure_deployment) is specified then openai_api_base(or aliasbase_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
- Publish a
SECURITY.mdwith a disclosure channel and the threat model already written intoload/load.pyand_security/. - Change
load's defaultallowed_objectsto"messages"(already signaled by the pending deprecation) and makeindex()default to a collision-resistant key encoder. - Make
ToolRetryMiddleware's defaultretry_onconservative (transport-safe exceptions only) and document idempotency. - Add an explicit, typed marker to limit-exceeded AI messages (#40866) so applications can distinguish them.
- Emit a warning when
SummarizationMiddlewareis constructed without atrigger, since it otherwise never runs. - Honor
OLLAMA_HOSTinChatOllamaandPERPLEXITY_API_KEYinChatPerplexityfor parity with SDK conventions. - Fix the stale docstring imports and README drift listed above; add a doc-test that imports every example symbol.
- 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
# 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.