fix(core): fix tool_use/tool_result pairing for Anthropic-compatible providers - #6651
Conversation
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
Add mergeConsecutiveAssistantMessages and cleanOrphanedToolCalls to the Anthropic converter, mirroring the same-name functions already present in the OpenAI converter. When the Gemini history has consecutive model turns (e.g. from streaming chunk-level recording, max_tokens recovery, or adaptive thinking splits), processContent emits one Anthropic message per Content. The Anthropic API requires tool_use blocks to be immediately followed by tool_result blocks — consecutive assistant messages break this pairing and cause HTTP 400. The merge pass combines consecutive assistant messages into one (thinking blocks first, then non-thinking). The clean pass removes orphaned tool_use and tool_result blocks. A second merge pass fixes any alternation issues created by dropped messages.
|
Re-run triggered by @wenshao · commit Thanks for the PR! Template looks good ✓ Problem: The Direction: Aligned. Fixing tool_use/tool_result pairing is essential for Anthropic-compatible provider support. The two-layer approach (targeted compaction fix + converter-level safety net) mirrors the proven pattern already in the OpenAI converter ( Size: Core paths touched. Production logic: 247 lines (converter.ts: 220, chatCompressionService.ts: 27). Test lines: 936. Well under the 500-line threshold — no size concerns. Approach: Scope feels right. Layer 1 alone wouldn't cover the streaming/max_tokens/adaptive-thinking paths; Layer 2 alone wouldn't fix the compaction timing window. Both layers are needed and both are minimal. The implementation faithfully mirrors the OpenAI converter's Moving on to code review. 🔍 中文说明由 @wenshao 触发的重新审查 · commit 感谢贡献! 模板完整 ✓ 问题: 方向: 对齐。修复 tool_use/tool_result 配对对于 Anthropic-compatible 提供方支持至关重要。两层方案与 OpenAI 转换器中已验证的模式一致。 规模: 触及核心路径。生产逻辑 247 行。测试 936 行。远低于 500 行阈值,无规模顾虑。 方案: 范围合理,两层缺一不可,实现忠实镜像了 OpenAI 转换器的模式。无无关改动。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
|
Re-run triggered by @wenshao · commit Code ReviewIndependent proposal (before reading diff): Add post-processing to the Anthropic converter — Comparison with the PR: The implementation matches my proposal exactly. The Anthropic-specific adaptations are correct:
Reuse check: The functions mirror the existing No critical blockers or AGENTS.md violations found. The code is clean, well-documented, and follows project conventions. TestingUnit Tests187/187 tests pass. New tests cover: merging consecutive assistant messages, thinking block reordering, orphaned tool_use/tool_result cleanup, tool_use deduplication, pending tool result inclusion in compaction side-query, and proper tool_use/tool_result pairing in existing tests. LintSmoke Test (dev build)Dev build starts and responds correctly. No startup regressions. Real-Scenario E2ECannot run — reproducing the HTTP 400 error requires a live Anthropic-compatible API (Claude 4.6 with adaptive thinking) and specific trigger conditions (streaming chunk-level recording, max_tokens recovery, or compaction during tool loop). No Anthropic API credentials are available in this CI environment. However, the maintainer (@wenshao) ran a deterministic converter-level E2E verification (see comment above) that feeds four realistic Gemini histories — one per trigger path — through the real 中文说明代码审查独立方案(未读 diff 前): 在 Anthropic 转换器中添加后处理—— 与 PR 对比: 实现完全匹配。Anthropic 特有的适配正确:thinking 块重排、tool_use 去重、无条件运行 cleanOrphanedToolCalls、compaction 中正确检查 pending tool result。 复用检查: 函数镜像了 OpenAI 转换器中已有的同名函数,因消息格式不同必须独立实现。无重复问题。 无关键阻塞或 AGENTS.md 违规。 测试单元测试187/187 通过(converter: 60, compression: 90, cli: 37)。 Lintexit 0,无告警。 冒烟测试Dev 构建正常启动并正确响应,无回归。 真实场景 E2ECI 环境中无法运行(需要真实 Anthropic API)。但 maintainer 已进行确定性转换器级别 E2E 验证:main 上 4 个场景中 3 个失败,本分支全部通过。 — Qwen Code · qwen3.7-max |
|
Re-run triggered by @wenshao · commit ReflectionStepping back, this PR does exactly what it should: it ports a battle-tested pattern from the OpenAI converter to the Anthropic converter. The The two-layer approach is well-justified. Layer 1 (compaction side-query) fixes a specific timing window where the pending tool result is missing from the API request. Layer 2 (converter post-processing) is a safety net that catches all paths that create consecutive model Content entries. Neither layer alone is sufficient; together they provide comprehensive coverage. The compaction fix is surgical — ~20 lines that check for a pending tool result and include it in the side-query. No over-engineering. What gives me confidence: 187 unit tests passing, covering the new merge/clean logic thoroughly including edge cases. The maintainer's deterministic converter-level E2E verification reproduces the failure on What I can't verify: live Anthropic API behavior. No E2E run against a real Anthropic-compatible provider was possible in CI. But the converter logic is well-tested, the pattern is proven in the OpenAI converter, and the fix targets a documented API constraint. The double This is a clean, focused bugfix that solves a real problem. Ships it. ✅ 中文说明反思这个 PR 将 OpenAI 转换器中经过实战验证的模式移植到 Anthropic 转换器。两层方案有充分理由:第一层修复压缩时机窗口,第二层是转换器层面的兜底方案。 信心来源:187 个单元测试全部通过,maintainer 的确定性 E2E 验证在 main 上复现了失败(4 个场景中 3 个失败),本分支全部通过。ESLint 干净,dev 构建冒烟测试通过。 无法验证的部分:真实 Anthropic API 行为。但转换器逻辑测试充分,模式已在 OpenAI 转换器中验证。 双重 这是一个干净、聚焦的 bugfix,解决了真实问题。可以合并。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
qqqys
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— GPT-5 via Qwen Code /review
Suggestions — commit
|
✅ Local build & test verification (maintainer)Built and tested this PR locally as a merge reference. Everything passes, and I added an end-to-end converter check that reproduces the exact
Environment — tested at commit 1. Build & typecheck — ✅ clean2. Unit tests — ✅ 187 / 187 passed
3. Lint — ✅ clean4. End-to-end converter verification (before → after)Beyond the unit tests, I fed four realistic Gemini histories — one per trigger path in the PR's table — through the real BEFORE ( AFTER (this PR) — all invariants hold: The Verdict
LGTM from a build/test standpoint — recommend merge. 👍 Caveat, matching the PR's own scope note: this verifies converter output against Anthropic's documented pairing rules deterministically; it does not include a live Anthropic-compatible API E2E round-trip. The converter-level check above is what actually gates the HTTP 400, so I consider it sufficient coverage for the fix. 🇨🇳 中文版本(点击展开)✅ 本地构建与测试验证(维护者)作为合并参考,我在本地完整构建并测试了本 PR。全部通过,并额外补充了一个端到端转换器验证:在
环境 — 测试于 commit 1. 构建与类型检查 — ✅ 通过2. 单元测试 — ✅ 187 / 187 通过
3. Lint — ✅ 干净对两个改动的源文件运行 4. 端到端转换器验证(修复前 → 修复后)除单元测试外,我按 PR 表格中的四条触发路径各构造一个真实 Gemini 历史,喂入真实的 修复前( 修复后(本 PR)— 全部满足: 4/4 场景通过。 结论
从构建/测试角度 LGTM,建议合并。 👍 说明(与 PR 自身范围一致): 此验证以确定性方式校验转换器输出是否符合 Anthropic 文档的配对规则,不包含真实 Anthropic-compatible API 的 E2E 往返请求。由于上述转换器层面的检查正是实际拦截 HTTP 400 的环节,我认为对本修复而言覆盖已足够。 |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Post-release verification: 0.19.9Verified the published The isolated test configuration intentionally omitted
No run emitted Some model-generated parallel Conclusion: |
What this PR does
Fixes the
tool_use ids were found without tool_result blocks immediately afterHTTP 400 error that occurs with Anthropic-compatible providers (notably Claude 4.6 with adaptive thinking). The fix has two complementary layers:Layer 1 — Compaction side-query (first commit): When automatic compaction runs during a tool loop before the tool result is committed to chat history, the compaction side-query now includes the pending tool result so Anthropic-compatible providers see it immediately after the preceding tool_use block.
Layer 2 — Anthropic converter merge/clean (second commit): Adds
mergeConsecutiveAssistantMessagesandcleanOrphanedToolCallsto the Anthropic converter, mirroring the same-name functions already present in the OpenAI converter. When the Gemini history has consecutive model turns (from streaming chunk-level recording, max_tokens recovery, or adaptive thinking splits),processContentemits one Anthropic message per Content. The Anthropic API requires tool_use blocks to be immediately followed by tool_result blocks — consecutive assistant messages break this pairing. The merge pass combines consecutive assistant messages into one (thinking blocks first, then non-thinking). The clean pass removes orphaned tool_use and tool_result blocks. A second merge pass fixes any alternation issues created by dropped messages.Why it is needed
Anthropic-compatible providers require every
tool_useblock to be followed immediately by its correspondingtool_result. Multiple code paths can create consecutive model Content entries that violate this requirement:Layer 1 is a targeted fix for the compaction timing window. Layer 2 is a converter-level fallback that covers all trigger paths. Together they provide comprehensive coverage.
Reviewer Test Plan
How to verify
Trigger a tool-heavy session with an Anthropic-compatible model (e.g. Claude Opus 4.6) and confirm that tool loops, compaction, and subagent spawning all complete without a missing-tool_result 400 error. Specifically: spawn a subagent that makes multiple tool calls in a single response, then verify the continuation request succeeds.
Evidence (Before & After)
N/A. This is a non-UI change.
Tested on
Environment (optional)
macOS.
cd packages/core && npx vitest run src/core/anthropicContentGenerator/converter.test.ts src/services/chatCompressionService.test.ts— 145 tests passed. ESLint clean.Risk & Scope
@lydell/node-ptytypecheck error on clean branch is outside this change.Linked Issues
Closes #6654
中文说明
What this PR does
修复 Anthropic-compatible 提供方(尤其是 Claude 4.6 adaptive thinking)出现的
tool_use ids were found without tool_result blocks immediately after400 错误。包含两层互补修复:第一层 — 压缩 side-query(第一个提交): 自动压缩在工具循环中、工具结果尚未写入历史时触发,side-query 现在会携带待发送的工具结果。
第二层 — Anthropic 转换器合并/清理(第二个提交): 在 Anthropic 转换器中添加
mergeConsecutiveAssistantMessages和cleanOrphanedToolCalls,与 OpenAI 转换器中已有的同名函数对齐。当 Gemini 历史中出现连续 model turn(流式 chunk 级别存储、max_tokens 恢复、adaptive thinking 分裂等),转换器逐 Content 生成 Anthropic 消息导致连续 assistant 消息,违反 API 的 tool_use → tool_result 配对要求。合并步骤将连续 assistant 消息合并为一条(thinking 块在前),清理步骤移除孤立的 tool_use/tool_result 块。Why it is needed
Anthropic-compatible 提供方要求每个
tool_use紧随对应的tool_result。多个代码路径会产生违反此要求的连续 model Content:第一层是压缩时机窗口的针对性修复,第二层是转换器层面的兜底方案,覆盖所有触发路径。
Reviewer Test Plan
How to verify
使用 Anthropic-compatible 模型(如 Claude Opus 4.6)进行工具密集型会话,确认工具循环、压缩、子代理启动均不出现缺少 tool_result 的 400 错误。具体:启动一个在单次响应中发起多个工具调用的子代理,验证后续请求成功。
Evidence (Before & After)
N/A,非 UI 变更。
Tested on
Environment (optional)
macOS;
cd packages/core && npx vitest run src/core/anthropicContentGenerator/converter.test.ts src/services/chatCompressionService.test.ts,145 tests passed;ESLint clean。Risk & Scope
@lydell/node-ptytypecheck 错误不在本次修改范围。Linked Issues
无关联 issue — 此 bug 在 Claude 4.6 测试中发现。如需跟踪可由 maintainer 创建 issue。