👁 33 次阅读
LangChain 人机协作编辑后为何重试原始工具调用?
来源:GitHub Issue #33787现象描述
在 LangChain 的HumanInTheLoopMiddleware 流程中,当代理(Agent)发起的工具调用被人工介入中断,且操作者选择 edit(编辑)决策时,中间件能够成功执行编辑后的工具调用。但在随后的步骤中,代理会重新评估自身状态,将其与原始的 HumanMessage(用户的初始请求)及编辑后工具调用的结果进行比对,并得出原始请求仍未得到满足的结论。
其结果是,代理会生成一条新的 AiMessage,试图执行原始且未经编辑的工具调用。从用户视角观察,这一行为属于缺陷。edit 决策的本意是“替换”——用户的意图是取消原始工具调用,并以新的编辑版本取而代之。当前行为未能“清除”代理的原始意图,导致人机交互陷入令人沮丧的循环:操作者不得不反复先执行 edit,再立即对同一概念性动作执行 reject(拒绝)。
背景原因
该缺陷的核心症结在于,中间件直接修改了AIMessage 对象本身,但 LangGraph 未能正确持久化这些变更。据 issue 讨论中社区贡献者 zhangzhefang-github 的分析,修复方向是创建一条带有编辑后工具调用的新 AIMessage,而非原地修改。
另一个被提及的关联因素是 工具输出层的幂等性保护。在并发或重试场景下,工具本身缺少防止重复执行(side-effect prevention)的机制,导致同一操作被多次触发。尽管这一机制对重复执行防护有效,但对于 HumanInTheLoopMiddleware 编辑决策后的状态同步问题,两者存在微妙差别:原始调用被中断并编辑后,代理的内部状态(additional_kwargs 中的 tool_calls)未与编辑后的动作保持同步。
排查与量化方法
复现代码如下(摘自 issue 讨论):from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool
from langchain.messages import HumanMessage
from langgraph.types import Command
@tool
def read_email_tool(email_id: str) -> str:
"""Read an email by its ID.
Args:
email_id: The ID of the email to read
Returns:
The content of the email
"""
# Simulated email content
emails = {
"email_001": "Subject: Meeting Tomorrow\n\nHi, let's meet tomorrow at 2pm.",
"email_002": "Subject: Project Update\n\nThe project is progressing well.",
}
return emails.get(email_id, f"Email {email_id} not found")
@tool
def send_email_tool(to: str, subject: str, bod
> 注:上述代码在 issue 中为截断状态,完整复现需参考原 issue 的补充内容。
量化确认该缺陷的关键诊断问题是:人工介入后,代理状态是否已被更新,还是下一步骤仍从编辑前的工具决策处继续执行。若存在包含原始工具调用、人工编辑以及重试调用的脱敏追踪记录,即可通过分析工具(如 issue 讨论中提到的 Rootlyze)进行验证并生成证明包。
完整解决方案(含代码)
截至 issue 关闭时,官方提交的修复方案有两个来源:方案一:创建新 AIMessage(PR #33789)
社区贡献者zhangzhefang-github 在 2025-11-04 提出,已在 PR #33789 中完成修复。核心思路是:
- 问题根源:中间件直接修改
AIMessage对象,但 LangGraph 无法正确持久化这些变更。 - 修复方式:创建一条带有编辑后工具调用的新
AIMessage,替换原始消息。 - 附带测试:新增测试用例以确保编辑决策后的行为符合预期。
方案二:同步 additional_kwargs(PR #39848)
社区贡献者SparshM8 于 2026-08-22 提交了 PR #39848,声称通过确保编辑决策期间同步 additional_kwargs(尤其是 tool_calls)来修复该问题。该方法通过保持代理内部状态与编辑后动作的一致性,防止其重试原始工具调用。
该 PR 提交时同样在等待维护者分配。
对比说明
| 方案 | 核心机制 | 提交来源 | 状态(据 issue 讨论) |
| PR #33789 | 创建新 AIMessage,避免原地修改 | zhangzhefang-github(2025-11-04) | 等待审查 |
| PR #39848 | 同步 additional_kwargs 中的 tool_calls | SparshM8(2026-08-22) | 等待维护者分配 |
completed 状态关闭,但关闭原因与维护者判定无关。
常见错误排查
1. 错误:编辑决策后代理仍重试原始调用 排查重点:检查HumanInTheLoopMiddleware 是否在编辑决策后正确更新了代理的 additional_kwargs 中的 tool_calls。若未同步,需按 PR #39848 的思路进行修复。
2. 错误:中间件修改 AIMessage 后变更丢失
排查重点:确认是否直接修改了消息对象本身。据 PR #33789 的分析,LangGraph 无法持久化此类原地变更,应改为创建新消息对象。
3. 错误:人工介入后出现重复执行
排查重点:检查工具输出层是否具备幂等性保护。社区讨论中指出,HumanInTheLoopMiddleware 的编辑决策与工具输出层的幂等性防护存在区分,前者是状态同步问题,后者是防止副作用问题。
4. 错误:编辑后立即拒绝仍无法终止循环
排查重点:确认代理是否已将原始调用的意图“清除”。若未清除,即使执行了拒绝操作,代理仍可能重新发起原始调用。
5. 错误:使用最新稳定版本仍未解决
排查重点:核实所用版本是否包含上述两个 PR 的修复。若未包含,需等待版本发布,或自行应用上述两种修复方案中的任意一种。
实践建议
- 优先采用 PR #39848 的方案:同步
additional_kwargs中的tool_calls相比创建新消息对象,对代理内部状态的一致性保护更为直接。该方法已在 2026-08-22 提交,据 issue 讨论仍等待维护者审查。 - 对于生产环境:在官方修复合入前,建议在应用层增加防护——在
edit决策执行后,显式清理代理状态中残留的原始工具调用意图,避免其重新评估时产生重试。 - 对于社区贡献者:提交 PR 时需附带完整的回归测试,覆盖“编辑 → 后续步骤不再重试原始调用”这一行为路径,确保修复不会引入新的状态不一致。
- 注意区分两种防护:工具输出层的幂等性保护(如 CrewAI PR #6492 中实现的机制)是防止重复执行的必要手段,但无法解决
HumanInTheLoopMiddleware编辑决策后的状态同步问题。两者需要分别处理。
常见问题
Q1:编辑决策的本意是什么? A1:edit 决策的本意是“替换”——用户的意图是取消原始工具调用,并以编辑后的新调用取而代之。当前实现未能“清除”代理的原始意图,导致代理继续尝试执行原始调用。
Q2:PR #33789 的修复思路是什么?
A2:据 issue 讨论,该 PR 的修复方式是创建一条带有编辑后工具调用的新 AIMessage,而非直接修改原始消息对象。原因在于 LangGraph 无法正确持久化直接修改后的消息变更。
Q3:PR #39848 的修复思路是什么?
A3:据 issue 讨论,该 PR 通过确保编辑决策期间同步 additional_kwargs(尤其是 tool_calls)来修复。该方法使代理的内部状态与编辑后的动作保持一致,从而防止重试原始工具调用。
Q4:官方是否已确认修复并随版本发布?
A4:官方尚未明确确认相关修复已合并到主干。两个社区 PR(#33789 和 #39848)均在等待审查,issue 以 completed 状态关闭,但关闭原因与维护者判定无关。需通过 GitHub API 查询 PR 状态以确认是否已合并。
Q5:该缺陷的严重程度如何?
A5:从用户视角看,该缺陷会导致人机交互循环:操作者不得不先执行 edit,再立即对同一概念性动作执行 reject。这严重影响了代理在人工介入场景下的可用性,属于功能性缺陷。
🔗 相关阅读(你可能还想看)
📄 Outlook 节点创建草稿添加附件为何报 UnableToDeserializePostBody?
📄 Flowise 点击市场或聊天页为何整页白屏?
📄 如何解决 Azure AI Search 向量存储的 $filter 递归深度超限错误?
📄 如何解决 shadcn/ui Sidebar 与 Header 重叠的问题?
📄 Next.js 开发服务器为何会 JavaScript heap out of memory?
📄 Nuxt 偶发 Cannot read properties of undefined 报错是怎么回事
📄 如何解决「Visual Studio Code for ipad」相关的问题?
📄 如何在AutoGen中实现企业级代理治理的加密行动收据(AAR)?
📄 记忆内容未清洗直接注入系统提示词,如何防范间接提示注入?
crewAI LiteAgent 将记忆内容直接拼接到系统提示词,存在间接提示注入风险。社区提出分层防御方案,但 iss