LangChain v1 与 LangGraph 生产故障诊断中心:13 个真实报错对照
线上出了问题,最先需要的不是原理,而是一张能对上号的表:我看到的这行报错,最可能是什么原因,怎么确认,怎么改。 本页把 LangChain v1、create_agent 和 LangGraph 项目里最常见的 13 种失败逐个复现,所有报错原文都来自可重复运行的脚本,而不是凭记忆整理。
本页是查表用的参考页。每一行只给出诊断结论,原理和完整实验在链接的专题里。
适用版本与验证信息
| 项目 | 内容 |
|---|---|
| 验证日期 | 2026-10-25 |
| Python | 3.12.12 |
| 包版本 | langchain==1.4.7、langchain-core==1.7.0、langgraph==1.2.17、langgraph-checkpoint==4.3.0 |
| 模型 | 脚本化模型:按预设顺序回放固定的消息,不联网、不需要 API Key |
| 官方来源 | LangChain v1 迁移指南 (opens in a new tab)、LangGraph 常见错误 (opens in a new tab),对照官方文档仓库 langchain-ai/docs (opens in a new tab) 提交 b8d4ced(2026-10-16) |
| 实验代码 | 站点仓库 labs/production-failure-index/,依赖锁定在 labs/requirements/v1.txt |
报错文字会随版本变化。遇到与本页措辞不同的报错时,以你环境里的实际输出为准,用 python3 labs/run.py --lab production-failure-index 可以在自己的版本下重新生成本页的原始输出。
先说结论
13 个失败里,有 3 个根本不会抛异常,这是生产里最难排查的一类:
- 模型漏掉必填参数(G)和调用不存在的工具(H):框架把错误变成
ToolMessage(status="error")交还给模型,运行继续,只有看消息轨迹才发现; - 使用已弃用的
create_react_agent(D):只有一条弃用警告,代码照常运行。
和上面相反,工具内部抛出的异常(F)会让整个运行失败,不会被自动转换成给模型的错误消息。同样是“工具出了问题”,两种路径的表现完全不同,监控要分别覆盖。
处理顺序建议:先看异常类和第一行报错,在下面的对照表里定位;表里没有的,再用“消息轨迹 + 检查点历史”还原现场。
复现脚本
下面的脚本一次性触发全部 13 种情况,并打印异常类和第一行报错。它依赖的脚本化模型、工具定义在另一个实验的共享模块里,脚本开头已加入对应路径:
capture_errors.py
"""Trigger the failures an LCEL/LangGraph v1 project meets most often and print what they really look like.
Every case uses a scripted model, so the messages are identical on every run.
"""
import operator
import re
import sys
import warnings
from pathlib import Path
from typing import Annotated, TypedDict
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "agent-vs-langgraph"))
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command
from shared import REFUNDS, ScriptedChatModel, lookup_order, refund
def first_line(text: str) -> str:
line = (text.strip().splitlines() or [""])[0]
return re.sub(r" \(/[^)]*\)", " (<path>)", line)[:230] # drop machine-specific paths
def case(label: str, fn) -> None:
print(label)
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
try:
outcome = fn()
print(f" no exception{': ' + outcome if outcome else ''}")
except BaseException as exc: # noqa: BLE001 - the point is to show any failure
print(f" {type(exc).__name__}: {first_line(str(exc))}")
for w in caught:
print(f" warning {w.category.__name__}: {first_line(str(w.message))}")
def model(*messages: AIMessage) -> ScriptedChatModel:
return ScriptedChatModel(script=list(messages))
def tool_call(name: str, args: dict, call_id: str = "c1") -> AIMessage:
return AIMessage(content="", tool_calls=[{"name": name, "args": args, "id": call_id}])
# --- migration errors -------------------------------------------------------
case("A. create_agent(prompt=...)", lambda: create_agent(model(AIMessage(content="x")), tools=[lookup_order], prompt="hi"))
case("B. from langchain.agents import AgentExecutor", lambda: exec("from langchain.agents import AgentExecutor"))
case("C. from langchain.chains import LLMChain", lambda: exec("from langchain.chains import LLMChain"))
case(
"D. from langgraph.prebuilt import create_react_agent",
lambda: exec("from langgraph.prebuilt import create_react_agent; create_react_agent(model(AIMessage(content='x')), [lookup_order])"),
)
# --- agent loop and tools ---------------------------------------------------
def recursion_limit():
looping = model(*[tool_call("lookup_order", {"order_id": "A1"}, f"c{i}") for i in range(10)])
create_agent(looping, tools=[lookup_order]).invoke({"messages": [("user", "go")]}, {"recursion_limit": 3})
@tool
def db_lookup(key: str) -> str:
"""Look up a key (always fails)."""
raise RuntimeError("database unavailable")
def tool_raises():
scripted = model(tool_call("db_lookup", {"key": "a"}), AIMessage(content="done"))
create_agent(scripted, tools=[db_lookup]).invoke({"messages": [("user", "go")]})
def tool_message(scripted: ScriptedChatModel) -> str:
result = create_agent(scripted, tools=[lookup_order]).invoke({"messages": [("user", "go")]})
tool_msg = next(m for m in result["messages"] if m.type == "tool")
return f"ToolMessage(status={tool_msg.status}): {' '.join(tool_msg.content.split())}"
case("E. recursion_limit too small for the loop", recursion_limit)
case("F. a tool raises RuntimeError", tool_raises)
case("G. the model omits a required tool argument", lambda: tool_message(model(tool_call("lookup_order", {}), AIMessage(content="done"))))
case("H. the model calls a tool that does not exist", lambda: tool_message(model(tool_call("no_such_tool", {}), AIMessage(content="done"))))
# --- graph and checkpoint errors --------------------------------------------
class Shared(TypedDict, total=False):
facts: list # no reducer on purpose
def parallel_write():
builder = StateGraph(Shared)
builder.add_node("a", lambda state: {"facts": ["a"]})
builder.add_node("b", lambda state: {"facts": ["b"]})
builder.add_edge(START, "a")
builder.add_edge(START, "b")
builder.add_edge("a", END)
builder.add_edge("b", END)
builder.compile().invoke({})
def simple_graph(checkpointer=None):
builder = StateGraph(Shared)
builder.add_node("a", lambda state: {"facts": ["a"]})
builder.add_edge(START, "a")
builder.add_edge("a", END)
return builder.compile(checkpointer=checkpointer)
def hitl_agent(checkpointer=None):
scripted = model(tool_call("refund", {"order_id": "A1", "amount": 9.0}), AIMessage(content="done"))
middleware = [HumanInTheLoopMiddleware(interrupt_on={"refund": True})]
return create_agent(scripted, tools=[refund], middleware=middleware, checkpointer=checkpointer)
def resume_without_checkpointer():
agent = hitl_agent()
config = {"configurable": {"thread_id": "t"}}
agent.invoke({"messages": [("user", "refund")]}, config)
agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config)
case("I. two parallel nodes write one key without a reducer", parallel_write)
case("J. get_state on a graph compiled without a checkpointer", lambda: simple_graph().get_state({"configurable": {"thread_id": "t"}}))
case("K. checkpointer present but no thread_id", lambda: simple_graph(InMemorySaver()).invoke({}))
case("L. resume a human-in-the-loop run without a checkpointer", resume_without_checkpointer)
case("M. resume on a thread that has no saved run", lambda: simple_graph(InMemorySaver()).invoke(None, {"configurable": {"thread_id": "new"}}))脚本的实际输出,也是下面对照表里所有“报错原文”的来源:
A. create_agent(prompt=...)
TypeError: create_agent() got an unexpected keyword argument 'prompt'
B. from langchain.agents import AgentExecutor
ImportError: cannot import name 'AgentExecutor' from 'langchain.agents' (<path>)
C. from langchain.chains import LLMChain
ModuleNotFoundError: No module named 'langchain.chains'
D. from langgraph.prebuilt import create_react_agent
no exception
warning LangGraphDeprecatedSinceV10: create_react_agent has been moved to `langchain.agents`. Please update your import to `from langchain.agents import create_agent`. Deprecated in LangGraph V1.0 to be removed in V2.0.
E. recursion_limit too small for the loop
GraphRecursionError: Recursion limit of 3 reached without hitting a stop condition. You can increase the limit by setting the `recursion_limit` config key.
F. a tool raises RuntimeError
RuntimeError: database unavailable
G. the model omits a required tool argument
no exception: ToolMessage(status=error): Error invoking tool 'lookup_order' with kwargs {} with error: order_id: Field required Please fix the error and try again.
H. the model calls a tool that does not exist
no exception: ToolMessage(status=error): Error: no_such_tool is not a valid tool, try one of [lookup_order].
I. two parallel nodes write one key without a reducer
InvalidUpdateError: At key 'facts': Can receive only one value per step. Use an Annotated key to handle multiple values.
J. get_state on a graph compiled without a checkpointer
ValueError: No checkpointer set
K. checkpointer present but no thread_id
ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id
L. resume a human-in-the-loop run without a checkpointer
RuntimeError: Cannot use Command(resume=...) without checkpointer
M. resume on a thread that has no saved run
EmptyInputError: Received no input for __start__排障对照表
迁移相关(A–D)
| 编号 | 报错原文 | 原因 | 验证 | 处理 |
|---|---|---|---|---|
| A | TypeError: create_agent() got an unexpected keyword argument 'prompt' | 从 create_react_agent 迁移时,prompt= 在 create_agent 里改名为 system_prompt= | 对照官方迁移指南的参数表 | 改参数名,详见迁移 Runbook |
| B | ImportError: cannot import name 'AgentExecutor' from 'langchain.agents' | v1 精简了 langchain 命名空间,旧的 AgentExecutor 不在这里 | 查导入兼容矩阵 | 安装 langchain-classic 并改用 langchain_classic.agents.AgentExecutor,或迁到 create_agent |
| C | ModuleNotFoundError: No module named 'langchain.chains' | chains 整个模块已不在 langchain 里 | 同上 | 同上:新位置是 langchain_classic.chains,旧路径不会恢复;旧链改写成 LCEL 见RetrievalQA 与 LLMChain 迁移到 LCEL |
| D | 无异常;LangGraphDeprecatedSinceV10: create_react_agent has been moved to langchain.agents ... | create_react_agent 已弃用,计划在 LangGraph V2.0 移除 | 运行时开启警告显示(python -W default) | 改用 from langchain.agents import create_agent,参数差异见迁移 Runbook |
Agent 循环与工具(E–H)
| 编号 | 报错原文 | 原因 | 验证 | 处理 |
|---|---|---|---|---|
| E | GraphRecursionError: Recursion limit of 3 reached without hitting a stop condition ... | 循环步数超过 recursion_limit | 看消息轨迹里工具调用了几轮 | 按“N 轮工具调用需要至少 2N+2”估算上限,详见迁移 Runbook;加了中间件后每一轮还会多占步数,见Agent 中间件与安全护栏 |
| F | RuntimeError: database unavailable(工具内部抛出的原始异常) | 工具执行时抛出异常,没有被转换成给模型的消息,整个运行失败 | 在工具内部加日志,或捕获 invoke 的异常 | 工具内部自己处理可恢复的错误,返回描述性字符串;不可恢复的才抛出;想把异常交还给模型,见工具调用 schema 排障 |
| G | 无异常;ToolMessage(status=error):Error invoking tool 'lookup_order' with kwargs {} with error: order_id: Field required ... | 模型生成的参数不满足工具的参数模式,框架把校验错误反馈给了模型 | 打印消息轨迹,找 status="error" 的 ToolMessage | 检查工具的描述和参数说明是否清晰(参数说明的来源见工具调用 schema 排障);对重要工具统计这类消息的比例并告警 |
| H | 无异常;ToolMessage(status=error):Error: no_such_tool is not a valid tool, try one of [lookup_order]. | 模型调用了没有注册的工具名 | 同上 | 检查工具列表是否和提示里描述的一致;统计并告警 |
图与状态(I)
| 编号 | 报错原文 | 原因 | 验证 | 处理 |
|---|---|---|---|---|
| I | InvalidUpdateError: At key 'facts': Can receive only one value per step. Use an Annotated key to handle multiple values. | 两个并行节点写同一个状态键,该键没有 reducer | 看报错里的键名,检查状态定义 | 给该键声明带 reducer 的类型,详见 create_agent 还是 LangGraph? |
checkpoint 与恢复(J–M)
| 编号 | 报错原文 | 原因 | 验证 | 处理 |
|---|---|---|---|---|
| J | ValueError: No checkpointer set | 编译图时没有传 checkpointer,却调用了 get_state 等需要它的接口 | graph.checkpointer 是否为 None | compile(checkpointer=...),详见 checkpoint 与故障恢复 |
| K | ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id | 有 checkpointer,但调用时没有传 thread_id | 检查 config["configurable"] | 每次调用都带 thread_id,详见 checkpoint 与故障恢复;从旧对话记忆迁移时的这个坑见ConversationBufferMemory 迁移到 v1 |
| L | RuntimeError: Cannot use Command(resume=...) without checkpointer | 人工审批中断后要恢复,但 Agent 没有配 checkpointer;第一次调用不会报错,恢复时才暴露 | 先看第一次调用的结果里是否有 __interrupt__ | 创建 Agent 时传入 checkpointer,详见 create_agent 还是 LangGraph? |
| M | EmptyInputError: Received no input for __start__ | 用 invoke(None, config) 恢复,但这个 thread_id 下没有已保存的运行 | 检查恢复用的 thread_id 是否与崩溃时一致 | 沿用原来的 thread_id,详见 checkpoint 与故障恢复 |
模型服务相关
下面这些错误来自本站对本地模拟的 OpenAI 兼容服务的验收实验,错误类名是 langchain-openai 的真实类名,错误内容是模拟服务返回的文本,不代表任何真实服务商:
| 现象 | 原因 | 处理 |
|---|---|---|
| Agent 返回空内容且没有调用任何工具,不抛异常 | 模型返回的工具参数不是合法 JSON,被放进 invalid_tool_calls | 验收时断言 invalid_tool_calls 为空,线上监控空回复,详见OpenAI 兼容模型接入验收 |
OpenAIInvalidRequestError(继承 openai.BadRequestError) | 服务端不支持你传的参数,例如 tools 或 response_format | 同上 |
OpenAITimeoutError / OpenAIRateLimitError | 超时或限流;默认会自动重试,请求数会被放大 | 同上 |
失败与回滚
- 找不到对应的报错:从
invoke的结果里取出完整消息轨迹,以及(有 checkpointer 时)get_state_history的检查点历史,逐步对照预期。多数问题在轨迹里能直接看出是哪一步偏离; - 把线上问题变成回归测试:对每个修复过的问题,写一个与本页
capture_errors.py同样形态的脚本,用脚本化模型复现,并把期望输出纳入版本控制。下次升级依赖时,输出变化就是一次明确的提示; - 升级前后对比:升级
langchain或langgraph之前,先在隔离环境跑一遍本页脚本,把输出差异当作变更清单。
相关阅读
- 旧版代码迁到 v1 的完整步骤:LangChain v1 迁移 Runbook;
- 旧符号在新版本里的位置:导入兼容矩阵;
- 多步骤流程的崩溃恢复与幂等:LangGraph checkpoint 与故障恢复;
- 模型服务接入的验收:OpenAI 兼容模型接入验收;
- 给 Agent 加限流、脱敏、重试和降级:Agent 中间件与安全护栏;
- 工具参数与工具异常:工具调用 schema 排障;
- 旧链与旧记忆的迁移:RetrievalQA 与 LLMChain 迁移到 LCEL、ConversationBufferMemory 迁移到 v1。
来源
- LangChain v1 迁移指南 (opens in a new tab):命名空间精简、
langchain-classic、create_agent参数变化; - GRAPH_RECURSION_LIMIT (opens in a new tab):
GraphRecursionError的官方说明。
以上来源均于 2026-10-25 核对。表中“报错原文”全部来自站点仓库 labs/production-failure-index/ 在上表版本下的实际运行。