Skip to content

第32章 常见问题与故障排查 ​

本章按问题类别组织常见故障,每条问题按【现象】→【原因】→【排查步骤】→【解决方案】的格式呈现。

32.1 启动与导入、状态问题 ​

启动/导入问题

问题一:ImportError: cannot import name '...' from 'langgraph'。 【现象】启动 Python 进程时抛出 ImportError,提示无法从 langgraph 包导入指定名称。 【原因】安装的 langgraph 版本与代码引用的 API 版本不匹配,或 langchain-core 等依赖包版本过低导致传递依赖冲突。 【排查步骤】执行 pip show langgraph 与 pip show langchain-core 确认安装版本;检查报错名称是否在对应版本的 init.py 中导出;确认是否在本地目录存在与 langgraph 同名的模块文件造成遮蔽。 【解决方案】将 langgraph 升级到手册对应版本(1.2.x),同步升级 langchain-core 到兼容版本;检查并移除本地同名模块文件。

问题二:版本冲突(langchain-core 版本不匹配)。 【现象】pip install 时弹出依赖冲突警告,或运行时出现 AttributeError: '...' object has no attribute '...'。 【原因】langgraph 1.x 对 langchain-core 有最低版本要求,项目中其他依赖可能锁定了较低版本的 langchain-core。 【排查步骤】执行 pip check 查看冲突报告;在虚拟环境中执行 pipdeptree -p langgraph 查看依赖树。 【解决方案】在 requirements.txt 或 pyproject.toml 中显式指定 langchain-core>=<兼容版本>;使用 pip install --upgrade langgraph langchain-core 共同升级。

问题三:Python 版本过低。 【现象】安装或运行时报出 SyntaxError 或类型注解相关错误。 【原因】LangGraph 1.x 要求 Python ≥ 3.9,部分类型语法(如 PEP 604 联合类型 X | Y)在 3.8 及以下版本不支持。 【排查步骤】执行 python --version 确认当前 Python 版本。 【解决方案】使用 Python 3.9 或更高版本的虚拟环境;通过 pyenv 或 conda 创建符合版本要求的环境。

状态相关问题

问题一:InvalidUpdateError: Can receive only one value per step。 【现象】并行扇入节点执行时抛出 InvalidUpdateError,提示同一通道在单步内接收到多个值。 【原因】某通道使用了 LastValue 等非合并型 reducer,多个并行节点同时向该通道写入,冲突无法自动解决。 【排查步骤】检查 State 定义中报错通道的 reducer 类型;确认并行分支是否同时更新该通道;检查是否存在 Send 扇出导致多个节点写入同一键。 【解决方案】将该通道的 reducer 替换为支持合并的类型(如 BinaryOperatorAggregate 或自定义合并函数);或调整图结构避免并行写入同一非合并通道。

问题二:messages 被覆盖(未用 add_messages reducer)。 【现象】向 messages 通道追加消息后,列表仅保留最新一条,历史消息丢失。 【原因】State 中 messages 通道未配置 add_messages reducer,默认使用 LastValue 语义导致新值替换旧值。 【排查步骤】检查 State 定义中 messages 字段的注解类型与 reducer 配置;确认是否继承了 MessagesState 或手动配置了 Annotated[list, add_messages]。 【解决方案】使用预定义的 MessagesState 作为 State 基类,或在 TypedDict 中显式声明 messages: Annotated[list[AnyMessage], add_messages]。

问题三:checkpoint 序列化失败(State 中有不可序列化对象)。 【现象】启用 Checkpointer 后执行图时抛出序列化异常(TypeError: cannot pickle '...' object)。 【原因】State 中存储了不可被 pickle/JSON 序列化的对象(如数据库连接、线程锁、打开的文件句柄、生成器对象)。 【排查步骤】检查节点函数向 State 写入的所有值;逐一确认每个值的类型是否可序列化;重点关注模型客户端、数据库会话、线程相关对象。 【解决方案】将不可序列化对象从 State 中移出,改为通过依赖注入或 RunnableConfig 在节点运行时获取;仅在 State 中保存可序列化的原始数据与结构化对象。

问题四:节点内对 state 的 in-place 修改不生效。 【现象】在节点函数内部通过 state["key"].append(...) 或 state.key = ... 直接修改传入的 state 对象,但该修改未在后续节点中体现。 【原因】LangGraph 的 State 在节点间以不可变方式传递,节点函数接收的 state 是只读副本(或浅拷贝),in-place 修改不会触发通道 reducer 更新。 【排查步骤】检查节点函数是否直接修改了传入的 state 参数而非通过返回值返回更新。 【解决方案】所有状态更新必须通过节点函数的返回值(return {"key": new_value} 或 Command(update=...))显式声明,依赖 reducer 进行合并。

32.2 路由与循环、工具调用问题 ​

路由与循环问题

问题一:GraphRecursionError(死循环/recursion_limit 过低)。 【现象】图执行抛出 GraphRecursionError,提示超过最大递归步数。 【原因】存在两种可能:图中形成无出口的循环(死循环);或合法多步任务的步数超过了默认 recursion_limit(默认 25 步)。 【排查步骤】检查条件边的退出条件是否可达;在编译时设置 config.recursion_limit 为更大值进行测试;通过 LangSmith trace 查看实际执行的节点序列。 【解决方案】若为死循环,修正条件边的终止逻辑(如为 ReAct 循环添加最大迭代次数判断或工具调用结果为空时退出);若为合法长流程,在 config.configurable 或 compile 参数中提高 recursion_limit。

问题二:条件边返回未注册节点名 KeyError。 【现象】条件边执行后抛出 KeyError,提示目标节点未找到。 【原因】条件边函数返回了未通过 add_node 注册的节点名字符串,或拼写错误(大小写、下划线不一致)。 【排查步骤】打印条件边返回值;对照 add_node 注册的节点名列表逐一核对;检查是否在子图中返回了主图节点名而未加命名空间前缀。 【解决方案】修正返回字符串使其与已注册节点名一致;子图跳转主图节点需使用带命名空间前缀的完整路径或通过 Command 构造正确的 goto 目标。

问题三:节点永远走不到(孤立节点/路由没连到)。 【现象】某节点函数在执行中从未被调用,LangSmith trace 中无该节点记录。 【原因】该节点未被任何边引用,或所有可达路径均不经过该节点;入口点设置错误导致执行流从错误节点开始。 【排查步骤】通过 graph.get_graph().draw_mermaid() 可视化图结构;检查 set_entry_point 配置;追踪从 START 出发的所有边路径。 【解决方案】添加缺失的边或条件分支;若该节点为并行扇出目标,确认 Send 调用正确指向该节点;修正入口点配置。

工具调用问题

问题一:模型不输出 tool_calls(忘记 bind_tools 或模型不支持)。 【现象】模型在应该调用工具的轮次仅输出自然语言文本,响应中不含 tool_calls 字段。 【原因】模型实例未通过 bind_tools() 绑定工具定义,或所用模型不支持 function/tool calling 能力。 【排查步骤】检查模型初始化后是否调用了 .bind_tools(tools);确认所选模型(及对应 API 版本)支持 tool calling;打印模型响应的 additional_kwargs 与 response_metadata。 【解决方案】在模型初始化后调用 model.bind_tools(tools);切换到支持 tool calling 的模型;若必须使用不支持该能力的模型,改为通过结构化输出(with_structured_output)或提示词工程解析工具调用意图。

问题二:ToolMessage tool_call_id 不匹配。 【现象】工具执行后返回 ToolMessage,但模型在下一轮报错或无法关联工具结果,提示 tool_call_id 缺失或不匹配。 【原因】构造 ToolMessage 时传入的 tool_call_id 与 AIMessage 中对应 tool_call 的 id 不一致,或手动构造时遗漏该字段。 【解决方案】使用 ToolNode 自动处理 tool_call 与 ToolMessage 的关联;若手动构造,确保 ToolMessage(content=..., tool_call_id=ai_msg.tool_calls[i]["id"]) 严格对应。

问题三:工具执行异常但 LLM 看不到(handle_tool_errors 配置错误)。 【现象】工具内部抛出异常,但执行流未将错误信息反馈给 LLM,而是直接中断或静默失败。 【原因】ToolNode 默认的错误处理行为可能将异常转为图级错误抛出,或 handle_tool_errors 未开启导致异常未被包装为 ToolMessage。 【排查步骤】检查 ToolNode 初始化时是否传入 handle_tool_errors=True;查看异常被捕获的层级(是 ToolNode 内部处理还是冒泡到图执行层)。 【解决方案】在 ToolNode(tools, handle_tool_errors=True) 中显式开启错误处理;对于自定义工具节点,使用 try/except 捕获异常并将错误信息作为 ToolMessage content 返回给模型,使模型能够根据错误信息调整策略。

问题四:并行工具调用顺序问题。 【现象】模型一次输出多个 tool_calls 并行执行,但 ToolMessage 返回顺序与 tool_calls 顺序不一致,导致下游处理错乱。 【原因】ToolNode 对并行工具调用的执行可能采用并发方式,返回的 ToolMessage 列表顺序不保证与原始 tool_calls 顺序一致。 【排查步骤】打印 ToolNode 输出的 messages 列表中各 ToolMessage 的 tool_call_id。 【解决方案】下游处理逻辑应以 tool_call_id 作为关联依据而非列表位置;add_messages reducer 本身不依赖顺序,模型在下一轮可通过 id 正确匹配各工具结果。

32.3 流式与持久化问题 ​

流式问题

问题一:流式被 Nginx 缓冲不输出。 【现象】通过 Nginx 反向代理访问 SSE(Server-Sent Events)流式端点时,客户端直到图执行完毕才一次性收到全部数据。 【原因】Nginx 默认启用响应缓冲(proxy_buffering),SSE 流式输出被代理层缓存。 【排查步骤】绕过 Nginx 直接访问后端服务验证流式是否正常;检查 Nginx 配置中 proxy_buffering 与 X-Accel-Buffering 头。 【解决方案】在 Nginx 配置中对流式端点关闭 proxy_buffering;或在后端响应中添加 X-Accel-Buffering: no 头;LangGraph Server 默认已处理该头,但自托管 FastAPI 应用需手动配置。

问题二:values 模式推送过大 JSON。 【现象】使用 stream_mode="values" 时,每步推送的完整 State JSON 体积过大,导致网络带宽占用高、前端渲染卡顿。 【原因】values 模式在每个超步(Superstep)后推送完整 State 快照,当 State 中包含长 messages 列表或大量上下文时负载较大。 【排查步骤】统计单步推送的 JSON 字节大小;确认 messages 列表长度与 State 中其他大字段。 【解决方案】改用 stream_mode="updates" 仅接收增量更新;或使用 stream_mode="messages" 直接消费 token 级流;在 State 中避免冗余存储大对象。

问题三:messages 模式 token 不对齐(多模型并发流)。 【现象】stream_mode="messages" 时,多个模型节点并发执行产生的 token 块交错到达,前端显示时归属混乱。 【原因】messages 模式下所有 LLM 节点的 token 事件共享同一事件流,未按节点区分时无法判断 token 来源。 【排查步骤】检查事件中的 ns(命名空间)字段与 metadata.langgraph_node 字段。 【解决方案】消费端根据事件的 ns 字段或 metadata 中的节点名将 token 路由到对应的会话或 UI 组件;在 v2 格式中 ns 字段明确标识事件来源的子图路径。

问题四:子图流式事件不显示(未传 subgraphs=True)。 【现象】编译子图后,在主流中无法看到子图内部节点的流式事件。 【原因】stream/astream 默认不展开子图事件,仅在子图边界产出聚合事件。 【排查步骤】检查 stream 调用是否传入 subgraphs=True 参数。 【解决方案】在调用 stream/astream/stream_events 时传入 subgraphs=True,以展开子图内部事件;消费端通过 ns 字段区分子图来源。

持久化问题

问题一:重启后状态丢失(用了 MemorySaver)。 【现象】服务重启后,使用相同 thread_id 继续对话时历史状态不存在,图从初始状态开始执行。 【原因】MemorySaver(InMemorySaver)将检查点存储在进程内存中,进程退出后数据丢失。 【排查步骤】确认编译时传入的 checkpointer 类型;检查是否在每次服务启动时重新实例化了 MemorySaver。 【解决方案】生产环境使用持久化 Checkpointer,如 PostgresSaver、AsyncPostgresSaver 或 SqliteSaver;MemorySaver 仅适用于开发调试与单元测试。

问题二:PostgresSaver 连不上。 【现象】实例化 PostgresSaver 或执行 setup() 时抛出连接异常。 【原因】数据库连接 URI 错误、PostgreSQL 服务未启动、网络策略阻止连接、认证凭据错误,或未安装 psycopg 驱动。 【排查步骤】通过 psql 或其他数据库客户端使用相同连接串验证连通性;检查是否安装 langgraph[postgres] 扩展依赖;查看 PostgreSQL 日志。 【解决方案】修正连接 URI 格式(postgresql://user:pass@host:port/dbname);确保 PostgreSQL 服务可达并接受来自应用服务器的连接;pip install langgraph[postgres] 安装驱动。

问题三:thread_id 复用时状态错乱(多用户串线)。 【现象】多个用户使用同一应用时,A 用户看到 B 用户的对话历史或状态。 【原因】不同用户请求使用了相同的 thread_id,导致共享同一条检查点线程;或 thread_id 生成逻辑未按用户会话隔离。 【排查步骤】检查 thread_id 的生成与传递逻辑;在请求入口打印 thread_id 确认隔离性。 【解决方案】thread_id 必须按会话维度唯一生成(如 UUID),与用户身份或会话 ID 绑定;禁止在多用户间复用固定 thread_id。

问题四:checkpoint 表膨胀。 【现象】PostgreSQL 或 SQLite 中 checkpoint 表数据量持续增长,磁盘占用过高,查询性能下降。 【原因】每次超步都会写入 checkpoint 记录,长运行对话或高频调用场景下表持续膨胀。 【排查步骤】查询 checkpoint_blobs、checkpoint_writes 表的行数与字节大小;确认业务场景的调用频率与单对话步数。 【解决方案】定期清理已完成会话的 checkpoint 数据(通过 thread_id 与完成时间标记);配置 TTL(Time To Live,存活时间)策略;对于无需要长期保存的场景,在对话结束后主动删除对应 thread_id 的检查点。

32.4 HITL 与多智能体问题 ​

HITL(Human-in-the-Loop)问题

问题一:interrupt() 报"Checkpointer is required"。 【现象】在节点中调用 interrupt() 时抛出异常,提示 Checkpointer is required。 【原因】interrupt() 依赖 Checkpointer 保存中断时的执行状态,编译图时未传入 checkpointer。 【排查步骤】检查 graph.compile() 是否传入 checkpointer 参数;确认传入的 checkpointer 实例非 None。 【解决方案】在 compile 时传入 MemorySaver(开发)或 PostgresSaver(生产)实例;任何使用 interrupt() 的图必须配置 Checkpointer。

问题二:resume 后节点从头执行导致副作用重复。 【现象】interrupt 恢复后,被中断节点内中断语句之前的副作用操作(如发邮件、写数据库、调用外部 API)被重复执行。 【原因】LangGraph 的恢复机制从中断节点的起始处重新执行该节点,而非从中断点恢复;若副作用操作位于 interrupt() 调用之前,会在 resume 时再次执行。 【排查步骤】审查被中断节点的代码结构,确认 interrupt() 调用位置与副作用操作的相对顺序。 【解决方案】将有副作用的操作放在 interrupt() 调用之后,或通过 State 标记位记录副作用是否已执行(幂等性设计);关键副作用操作应设计为幂等,重复调用不产生额外影响。

问题三:跨进程 resume 失败(checkpointer 不共享)。 【现象】在进程 A 中触发 interrupt 后,从进程 B 调用 invoke/stream 传入 Command(resume=...) 无法恢复,抛出检查点未找到错误。 【原因】两个进程使用了不同的 Checkpointer 实例或不同的后端存储(如进程 A 用 MemorySaver,进程 B 也用独立的 MemorySaver),检查点数据不共享。 【排查步骤】确认两个进程的 Checkpointer 配置是否指向同一持久化后端(同一 PostgreSQL 数据库、同一 SQLite 文件);确认 thread_id 一致。 【解决方案】跨进程 resume 场景必须使用共享持久化后端(PostgresSaver 或网络共享的 SqliteSaver),确保各进程连接同一数据库。

多智能体问题

问题一:Handoffs 无限循环。 【现象】两个或多个 Agent 之间通过 Handoff(交接)反复跳转,形成无终止循环。 【原因】Handoff 触发条件定义不清,Agent A 在某条件下交接给 Agent B,Agent B 在同样条件下又交接回 Agent A,缺乏终止判断。 【排查步骤】通过 LangSmith trace 查看 Handoff 序列;检查各 Agent 的 Handoff 工具描述与触发条件。 【解决方案】在 Handoff 提示词中明确约束交接条件与终止条件;引入全局步数计数或交接次数上限,超过阈值时强制终止或交回 Supervisor。

问题二:Supervisor 决策错误(prompt 不清)。 【现象】Supervisor 模式下,Supervisor Agent 频繁错误选择下一个执行 Agent,或在应结束时继续调度。 【原因】Supervisor 的系统提示词对各 Worker Agent 的能力描述不清晰,或缺少明确的结束指令。 【排查步骤】打印 Supervisor 的系统提示词;检查各 Worker Agent 的名称与能力描述是否存在重叠或歧义;在 LangSmith 中审查 Supervisor 的决策输出。 【解决方案】重写 Supervisor 提示词,以结构化方式列出每个 Worker 的独占能力边界与典型任务类型;明确给出 FINISH/END 的判定条件;必要时为 Supervisor 添加输出结构化约束(如强制从固定枚举中选择下一个 Agent)。

问题三:messages 列表过长上下文爆炸。 【现象】多轮对话或多 Agent 协作后,State 中的 messages 列表过长,超出模型上下文窗口限制或导致 token 费用激增。 【原因】多 Agent 场景下每个 Agent 的工具调用、中间结果与对话记录均追加到共享 messages 列表,缺乏截断或摘要机制。 【排查步骤】统计当前 messages 列表的 token 长度;检查是否有冗余的工具输出(如长文本检索结果)直接进入列表。 【解决方案】在关键节点添加消息裁剪或摘要节点(如保留最近 N 轮,历史轮次通过 LLM 生成摘要替换);对 ToolMessage 中的大段输出做截断或摘要后再放入 messages;使用带 token 计数的 reducer 控制列表长度。

32.5 性能与部署问题 ​

性能问题

问题一:图调用延迟高(模型慢/网络慢/无并行)。 【现象】单次图执行端到端延迟过高,用户等待时间长。 【原因】延迟可能来自三个层面:模型推理本身耗时长;到模型 API 的网络延迟高;图结构中可并行的步骤被串行执行。 【排查步骤】通过 LangSmith trace 查看各节点耗时分布;区分 LLM 调用耗时、工具执行耗时与纯计算耗时;检查是否存在可扇出并行的节点被错误配置为串行。 【解决方案】对于模型延迟,考虑使用更快的模型或开启流式输出以降低首 token 时间(TTFT);将无依赖关系的工具调用或子任务通过 Send 机制并行扇出;在网络层面确保应用服务器与模型 API 之间低延迟连接。

问题二:API 429 频控(并发过高)。 【现象】高并发场景下模型 API 返回 429 Too Many Requests 错误,图执行失败。 【原因】并行扇出或批量调用导致瞬时并发请求数超过模型 API 的速率限制(Rate Limit)。 【排查步骤】查看 LangSmith trace 中失败节点的错误信息;统计并发模型调用数量;检查 batch/abatch 的并发度配置。 【解决方案】在模型层配置 max_concurrency 参数限制并发请求数;引入重试策略(RetryPolicy)并设置指数退避(Exponential Backoff);对工具调用与模型调用添加限流层;必要时向 API 提供商申请更高配额。

问题三:Postgres checkpoint 写入慢(连接池小/数据量大)。 【现象】高吞吐场景下 checkpoint 写入成为瓶颈,节点执行因等待 checkpoint 持久化而变慢。 【原因】PostgresSaver 的连接池过小,高并发下出现连接等待;或 checkpoint 表数据量大、缺少索引导致写入查询慢。 【排查步骤】监控 PostgreSQL 的连接数与慢查询日志;检查 checkpoint 表的索引配置;评估单次 checkpoint 写入的数据量。 【解决方案】配置足够大小的数据库连接池;确保 checkpoint 表在 thread_id、checkpoint_id 等查询字段上有索引;对于超高吞吐场景评估是否需要对 checkpoint 写入做异步批量处理,或使用 Redis 等更快的存储后端作为缓存层。

部署问题

问题一:FastAPI 中 async 调用同步 invoke 阻塞事件循环。 【现象】FastAPI 应用在高并发下吞吐量极低,日志显示请求排队。 【原因】在 async def 路由中调用了同步的 graph.invoke() 方法,该调用会阻塞整个事件循环,导致其他协程无法被调度。 【排查步骤】检查路由函数定义:若路由为 async def 且内部调用 graph.invoke()(非 ainvoke),则存在阻塞问题。 【解决方案】在 async 路由中使用 graph.ainvoke()、graph.astream() 等异步方法;若必须调用同步方法,通过 asyncio.to_thread() 将其放到线程池执行;或将路由定义改为普通 def,FastAPI 会自动将其放到线程池处理。

问题二:Docker 容器内无法访问本地服务。 【现象】Docker 容器中运行的 LangGraph 应用无法访问宿主机上的模型 API 或数据库(连接地址为 localhost/127.0.0.1)。 【原因】Docker 容器内的 localhost 指向容器自身而非宿主机,容器网络与宿主网络隔离。 【排查步骤】在容器内执行网络诊断(如 curl、ping)确认目标地址可达性;确认宿主机服务是否监听 0.0.0.0 而非仅 127.0.0.1。 【解决方案】在 Linux 上使用 --network="host" 模式,或使用 host.docker.internal(Docker Desktop for Mac/Windows)作为宿主机地址;将应用与依赖服务统一放入 docker-compose 网络中通过服务名访问。

问题三:LangSmith trace 丢失(环境变量未生效)。 【现象】配置了 LANGSMITH_API_KEY 与 LANGSMITH_TRACING=true,但执行请求时 LangSmith 控制台无 trace 记录。 【原因】环境变量在进程启动后才设置;或变量名拼写错误(如 LANGCHAIN_TRACING 而非 LANGSMITH_TRACING);或 API Key 无效;或网络策略阻止到 LangSmith 端点的出站请求。 【排查步骤】在应用启动时打印 os.environ 中相关变量确认加载;验证 API Key 有效性;检查网络出站连接;确认 langsmith 包版本与 LangGraph 兼容。 【解决方案】在进程启动前(如通过 .env 文件或系统环境变量)正确设置 LANGSMITH_TRACING=true、LANGSMITH_API_KEY、LANGSMITH_PROJECT;确保应用服务器能访问 https://api.smith.langchain.com;升级 langsmith 包到兼容版本。