Skip to content

fix(cross-vendor): 修复 Zhipu GLM-5 跨供应商故障转移时 tool_result 角色错位导致级联 400 错误;#128

Merged
ThreeFish-AI merged 2 commits into
feature/1.x.xfrom
vk/a960-fix-zhipu-glm-5
Apr 8, 2026
Merged

fix(cross-vendor): 修复 Zhipu GLM-5 跨供应商故障转移时 tool_result 角色错位导致级联 400 错误;#128
ThreeFish-AI merged 2 commits into
feature/1.x.xfrom
vk/a960-fix-zhipu-glm-5

Conversation

@ThreeFish-AI

@ThreeFish-AI ThreeFish-AI commented Apr 8, 2026

Copy link
Copy Markdown
Owner

问题描述

当 Zhipu GLM-5 遇到 429 限流后回退至 Anthropic Opus-4.6 时,Anthropic API 返回 400:

messages.105: `tool_result` blocks can only be in `user` messages

此错误被 is_semantic_rejection() 归类为"语义拒绝",导致同样的畸形请求级联转发到所有后续 tier(anthropic → copilot),每个 tier 都重复失败,最终整条请求链崩溃。

根因分析

三重问题叠加:

  1. 畸形数据来源:Zhipu GLM-5 通过 Anthropic 兼容端点返回的 assistant 响应中可能同时包含 tool_usetool_result 内容块(模拟工具执行倾向)。Proxy 薄透传不解析 SSE 流,畸形数据直接到达客户端并存入对话历史。
  2. 请求未被修复:故障转移时 body 原样传递给下一个 tier,Anthropic 严格校验拒绝 tool_result 出现在非 user 消息中。
  3. 级联失败is_semantic_rejection() 将所有 invalid_request_error 视为语义拒绝(包含结构性错误),导致同样的畸形请求在每个 tier 重复失败——这是最严重的级联放大效应。

修复方案:三层纵深防御

第一层 — 请求规范化(request_normalizer.py

检测并修复非 user 消息中的 tool_result 内容块:

  • 剥离非 user 消息中的 tool_result 块,记录 misplaced_tool_result_stripped adaptation
  • 空 assistant 消息自动插入占位 text block,保持消息结构合法性
  • 无后续 user 消息时自动创建新 user 消息容纳错位的 tool_result
  • 重复 tool_use_id 检测,避免重复添加

第二层 — 结构性错误分类器(error_classifier.py)⭐ 核心修复

新增 is_structural_validation_error() 函数,解决级联失败的根本原因:

  • 识别结构性验证错误(tool_result 角色错位、消息交替违规、thinking/tool_use 位置不合规等)
  • is_semantic_rejection() 中前置排除结构性错误
  • 核心逻辑:结构性错误指示请求格式不合规,将同样的畸形请求转发到其他供应商无意义

第三层 — Anthropic 供应商纵深防御(anthropic.py

  • 新增 _strip_misplaced_tool_results() 函数
  • _prepare_request 中作为兜底再次剥离非 user 消息中的 tool_result
  • 剥离后 content 为空时插入占位 text block

执行器短路(executor.py

流式故障转移路径中:

  • 检测到结构性验证错误后立即 raise,停止级联故障转移
  • 避免浪费时间将同样的畸形请求转发到下一层供应商
  • 日志标记:Tier {name} structural validation error, stopping failover

变更文件

文件 改动说明
src/coding/proxy/server/request_normalizer.py 检测并剥离非 user 消息中的 tool_result
src/coding/proxy/routing/error_classifier.py 新增 is_structural_validation_error() 结构性错误分类
src/coding/proxy/routing/executor.py 结构性错误短路,停止级联故障转移
src/coding/proxy/vendors/anthropic.py 纵深防御 _strip_misplaced_tool_results()
tests/test_request_normalizer.py 修复策略测试(剥离、占位符、新建 user 消息、长对话 105+)
tests/test_error_classifier.py 结构性错误分类 + 语义拒绝排除测试
tests/test_vendors.py Anthropic 纵深防御测试

测试覆盖

新增测试覆盖以下场景:

  • ✅ tool_result 在 assistant 消息 → 被剥离
  • ✅ tool_result 在 system 等其他角色消息 → 被迁移到 user 消息
  • ✅ assistant 消息仅含 tool_result → 占位 text block + 新建 user 消息
  • ✅ 重复 tool_use_id → 不重复添加
  • ✅ 长对话 (105+ messages) → 正确处理
  • ✅ 结构性错误 → 不触发级联故障转移
  • ✅ 结构性错误 → 不被误判为语义拒绝
  • ✅ 非 400 状态码 → 不被识别为结构性错误

@ThreeFish-AI ThreeFish-AI changed the title /fix 当zhipu 的 glm-5.1 模型因为 429 异常降级到 Anthropic 的 Opus-4.6 时,出现了如下「tool_result blocks can only be (vibe-kanban) fix(failover): 修复 Zhipu GLM-5 跨供应商回退时 tool_result 角色错位导致 Anthropic 400 级联故障; Apr 8, 2026
当 Zhipu GLM-5 遇到 429 错误后回退至 Anthropic Opus-4.6 时,
对话历史中 assistant 消息内可能包含 tool_result 内容块(GLM-5
可能在 assistant 响应中同时返回 tool_use 和 tool_result),
而 Anthropic API 严格要求 tool_result 只能出现在 user 消息中,
导致 400 invalid_request_error 并级联降级至 copilot。

修复策略(双层防御):
1. request_normalizer: 在路由前剥离非 user 消息中的 tool_result
2. AnthropicVendor._prepare_request: 纵深防御,剥离错位 tool_result

变更文件:
- src/coding/proxy/server/request_normalizer.py: 添加 tool_result 位置检测与剥离
- src/coding/proxy/vendors/anthropic.py: 添加 _strip_misplaced_tool_results() 函数
- tests/test_request_normalizer.py: 新增 5 个测试用例
- tests/test_vendors.py: 新增 4 个测试用例

全部 988 测试通过,无回归。

🤖 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>
根因:当 Zhipu GLM-5 返回含 tool_result 的 assistant 响应后,
Claude Code 存入对话历史,后续请求的 assistant 消息中出现 tool_result。
Zhipu 429 后回退到 Anthropic,Anthropic 严格校验返回 400,
is_semantic_rejection 将所有 invalid_request_error 视为语义拒绝,
导致同样的畸形请求继续级联到 Copilot。

修复内容:
1. error_classifier: 新增 is_structural_validation_error(),
   在 is_semantic_rejection() 中前置排除结构性验证错误,
   阻止 tool_result/tool_use 角色错位等结构性错误触发级联故障转移
2. executor: 流式路径中检测结构性错误后立即 raise 停止故障转移,
   避免将同样的畸形请求转发到下一层供应商
3. request_normalizer: 将 tool_result 处理从「剥离丢弃」升级为
   「修复到正确 user 消息」,包括:重复检测、新 user 消息创建、
   空 assistant 消息占位符

🤖 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>
@ThreeFish-AI
ThreeFish-AI force-pushed the vk/a960-fix-zhipu-glm-5 branch from acbcc4b to 7904b7c Compare April 8, 2026 14:51
@ThreeFish-AI ThreeFish-AI changed the title fix(failover): 修复 Zhipu GLM-5 跨供应商回退时 tool_result 角色错位导致 Anthropic 400 级联故障; fix(cross-vendor): 修复 Zhipu GLM-5 跨供应商故障转移时 tool_result 角色错位导致级联 400 错误; Apr 8, 2026
@ThreeFish-AI
ThreeFish-AI merged commit e01c56e into feature/1.x.x Apr 8, 2026
0 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant