fix(models): 净化 invalid_tool_call 与孤儿 ToolMessage,规避 DeepSeek 等接口报错 - #1006
Conversation
|
Codex Review: 预 review 基于 发现 1 个需修复的问题。 [P1] 清空解析字段后,原始无效 tool_call 会被重新序列化。 chat.py:42–45 只清空 已用实际 LangChain 消息和安装的 OpenAI 序列化函数复现:原始调用参数为 验证:执行了从该 head 提取净化函数、构造真实 |
- additional_kwargs["tool_calls"] 收敛为合法 id 子集,阻断 langchain_openai 回退重发 - 净化返回新消息对象,不再原地修改共享 graph state/checkpoint 消息 - wire payload 回归测试 6 个(纯无效/混合/不改入参/孤儿/内容块/流式入口) - tracked decision
|
感谢 review,复现准确,已修复(最新 head [P1] 已修复:净化同步收敛 additional_kwargs 原始表示确认根因: 修复:
回归测试(断言最终 wire payload)新增
tracked decision: |
|
Codex Review: 仅供参考:本轮预 review 基于 复查上次 P1:原始无效 tool_call 被重新序列化的问题已修复。 当前实现同步过滤 实际验证:在当前 head 独立快照运行 范围限制:wire payload 验证使用实际 langchain_openai 转换函数;流式包装入口使用 fake model。未调用真实 DeepSeek/Anthropic/Gemini,也未跑真实 provider E2E,因此本结论只确认上次缺陷及现有测试覆盖的行为,不代表所有供应商已验证。 |
|
经过 Codex 质量评审,建议调整实现方案后再合入。这个问题值得修复,但目前通过动态 mixin 包装所有供应商、覆盖四个调用入口并全局删除孤儿 ToolMessage,范围偏大,也可能掩盖消息链路本身的问题。 建议采用更小、更明确的方案:
建议本 PR 先收窄为有明确复现的 Chat Completions 协议兼容修复;孤儿工具消息按根因单独处理。这样实现更简单,行为边界和验证也更清晰。 评审范围:PR diff、现有适配器和容器内依赖源码;已执行本地消息序列化验证,未向真实供应商发请求,未运行完整回归测试。 |
按作者建议收窄:移除动态 _InvalidToolCallFilterMixin/_wrap_model 四入口包装, 移除孤儿 ToolMessage 全局删除,改在 ChatCompletionsAdapter._get_request_payload 的 wire 边界处理。 根因修正(实测 langchain_openai 1.6.0 序列化): - invalid_tool_calls 属性转成 type:"function"(不是 invalid_tool_call 变体), 真正问题是 arguments 截断导致参数解析失败; - content 里的 invalid_tool_call block 已被 _convert_from_v1_to_chat_completions 丢弃。 修复:按 id 移除 wire 里来自 invalid_tool_calls 的截断 function,并在 content 追加 text 失败反馈。只影响 OpenAI 兼容协议(ChatCompletionsAdapter),不改 Anthropic/Gemini, 原始 checkpoint 不动。真实 adapter + mock HTTP 覆盖四条路径。
|
感谢 review,四点都成立,已按此收窄(最新 head 采纳:收窄到 ChatCompletionsAdapter._get_request_payload移除动态 根因修正(实测 langchain_openai 1.6.0 的真实序列化)你说得对,我上一版的根因假设错了。实测
所以上一版「清空属性 + 收敛 additional_kwargs + content block 转文本」里,前两项是在修一个不存在的变体问题,第三项是空转(上游已丢弃)。已全部移除。 修复:截断 function 降级为明确失败反馈
孤儿 验证(真实 adapter + mock HTTP,断言最终请求体)
全量 unit 未调用真实 DeepSeek;wire 载荷用真实 langchain_openai 序列化 + mock HTTP 捕获验证。若需要真实 provider E2E 或把孤儿 ToolMessage 根因一并定位,我再补。 |
Refactor _sanitize_wire_invalid_tool_calls function to handle invalid tool calls and provide feedback in wire messages.
现象
用 DeepSeek 等模型时,偶尔报两类错误导致整轮失败:
unknown variant invalid_tool_call—— 模型生成无效工具调用(JSON 参数截断/格式错)时,LangChain 记录invalid_tool_call,序列化后以该变体发往模型,DeepSeek 不认识。role='tool' must be a response to a preceding message with 'tool_calls'—— 孤儿ToolMessage(tool_call_id已无对应tool_calls)。机理
现有的
normalize_tool_call_chunks只处理了「流式续片空串归一化」这一半,没有「清空invalid_tool_calls属性」「把 content 里的invalid_tool_callblock 转文本」「删除孤儿 ToolMessage」这三步。改进方法
在消息发送前做三层净化:
invalid_tool_calls属性清空;invalid_tool_callblock 转成文本[工具调用失败] name: error;tool_call_id不在任何合法tool_calls里的孤儿ToolMessage。通过
_InvalidToolCallFilterMixin混入各类 ChatModel,重写_generate/_agenerate/_stream/_astream四个入口,统一在发送前净化。效果
DeepSeek 等接口不再因
invalid_tool_call变体或孤儿 ToolMessage 报错;单次工具调用失败降级为文本提示,不再拖垮整轮。