fix(vendor-channels): 修复 zhipu→anthropic 通道 tool_use/tool_result 配对漏洞#236
Merged
ThreeFish-AI merged 2 commits intoMay 11, 2026
Merged
Conversation
生产日志暴露 Anthropic 400 错误:channel transition 已应用 `rewritten_N_srvtoolu_ids` + `misplaced_tool_result_relocated` + `stripped_M_thinking_blocks` 但**未出现** `orphaned_tool_use_repaired` 标签,Anthropic 仍报 `messages.x: tool_use ids were found without tool_result blocks immediately after`。 根因: `_rewrite_srvtoolu_ids` 单遍循环中 Case A (assistant 端 `server_tool_use` → `tool_use` ID 改写) 与 Case B (任意 `tool_result.tool_use_id` 同步改写) 共用一遍 for 循环,Case B 依赖 `id_map` 已被 Case A 填入。GLM-5 偶发将 inline tool_result 块输出在对应 server_tool_use **之前** (同 assistant content 内乱序) 或在更早的 user 消息中, 单遍遍历到 tool_result 时 id_map 还是空 → 漏改; 后续 enforce 主循环 extracted_tool_results 以旧 ID 作 key、tool_use_ids 是新 ID, 错位致默默丢弃 → 对应 user 槽位实际仍然缺位。 修复: 1. `_rewrite_srvtoolu_ids` 改为两遍扫描: Pass 1 仅扫 assistant 收集 id_map (序号 分配次序与单遍一致, 保持已有断言), Pass 2 全量改写 tool_result.tool_use_id, 消 除时序耦合。 2. 新增独立 helper `_enforce_pairing_sanity_pass` 作为 enforce 主循环末尾的纵深防 御, 仅检测+合成 is_error 占位, 不剥离/不重定位; 命中追加 `pairing_sanity_repaired` adaptation 与 WARNING 日志, 便于运维定位。 3. 新增 11 个测试: 2 个两遍扫描乱序回归 + 8 个 sanity helper 单测覆盖各边界 + 1 个 prepare_zhipu_to_anthropic 端到端复现日志故障形态。 4. `docs/issue.md` 沉淀故障档案与历史教训 (commit 9061cd0 引入该修复曾被 2bac9a7 作为副产物连带回滚, 提醒后续 revert vendor_channels 时不要连带回滚 sanity helper 与两遍扫描)。 测试: tests/ 全量 1438 通过, 零回归。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
…/fix-tool-use-result-pairing # Conflicts: # docs/issue.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
zhipu → anthropic 通道流式请求偶发 400:
同一请求带
rewritten_N_srvtoolu_ids+misplaced_tool_result_relocated但没有orphaned_tool_use_repaired——转换层主观认为已配对、Anthropic 仍判定结构不合规。Failover 至 zhipu 后请求成功,证明上游消息体没坏,问题出在跨供应商转换过程引入了不一致。根因
_rewrite_srvtoolu_ids旧实现是单遍循环同时承担两件事:Case A 改写 assistant 端的server_tool_use+srvtoolu_*ID、Case B 同步改写任意位置tool_result.tool_use_id。Case B 依赖id_map已被 Case A 填入。GLM-5 流式响应偶发将 inline
tool_result输出在对应server_tool_use之前(同 assistant content 内乱序),或将tool_result放在更早的 user 消息而对应tool_use在更晚的 assistant 消息。两种乱序下单遍扫描遍历到tool_result时id_map还是空 →tool_use_id漏改、停留在srvtoolu_*。随后enforce_anthropic_tool_pairing以旧 ID 作extracted_tool_results的 key、用新 ID 去查不命中,misplaced tool_result 被默默丢弃,最终 Anthropic 400。修复
_rewrite_srvtoolu_ids改两遍扫描:Pass 1 仅遍历 assistant 收集id_map(按出现顺序分配,保持序号兼容);Pass 2 全量遍历改写任意tool_result.tool_use_id。以"先建表、后改写"次序消除时序耦合。_enforce_pairing_sanity_pass独立 helper:作为enforce_anthropic_tool_pairing主循环末尾的纵深防御层,仅做检测 + 合成is_error=True占位(不剥离、不重定位),命中追加pairing_sanity_repaired并 WARNING。在主循环未来重构时仍能稳定守住 Anthropic 配对约束。TestEnforcePairingSanityPass独立测试套件确保兜底分支具备正向回归保护。实现要点
9061cd0曾实现两遍扫描+sanity,但2bac9a7因缺乏对兜底路径的独立单测被连带回滚)。pairing_sanity_repaired与主循环orphaned_tool_use_repaired分离,便于运维按层归因。docs/issue.md记录根因、处理方式、后续防范与同类问题回滚红线(grep_enforce_pairing_sanity_pass/Pass 1/Pass 2)。验证
uv run pytest tests/test_vendor_channels.py:84 passed🤖 Generated with Claude Code, CodeX, Gemini
Co-Authored-By: Aurelius Huangthreefish.ai@gmail.com