fix: 修复 Claude→OpenAI 流式转换中 tool_call index 偏移导致工具调用丢失 - #5229
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThis PR updates Claude-to-OpenAI streaming tool-call index mapping by introducing a per-response ChangesTool Index Mapping for Claude Streaming
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
51fdfc5 to
2b6f1df
Compare
背景 / 现象
通过 OpenAI 兼容端点(
/v1/chat/completions)调用 Claude 渠道(请求转换为OpenAI Compatible → Claude Messages)、流式 + 带 tools 时,客户端会偶发收到:finish_reason = "tool_calls"tool_calls为空(toolCallCount = 0),只收到了模型的前导文本在 claude-opus-4-8 上尤其容易复现,且时好时坏、难以稳定重现。
fix: 移除 fcIdx -1 偏移,修复并发工具调用撞键问题 (#5095)
没有彻底修复, 只是把常量偏移从
-1改成0,两个版本都不正确:常量偏移无法表达真实映射关系,只是各自"刚好"覆盖了一种场景。根因
StreamResponseClaude2OpenAI直接把 Claude 的内容块序号 当作 OpenAI 的tool_calls[].index:index对所有内容块(text / thinking / tool_use)统一连续计数;tool_calls[].index必须只针对工具调用、从 0 起连续。因此只要 tool_use 前面存在任何非工具块(一段前导文本,或 thinking 块),tool_use 就落在块
index ≥ 1。转换后发出的 OpenAI tool_call delta 携带index: 1(或更大),且不存在index: 0。严格按 0 基下标累积 tool_calls 的客户端无法正确归并,直接丢弃该工具调用;而finish_reason=tool_calls(由message_delta的stop_reason=tool_use映射)仍照常下发——于是出现「有 finish_reason、无 tool_calls」的矛盾。-1偏移:刚好修正了「恰好一个前导块」的情况,却在「无前导、多工具从 0 起」时撞键(即 fix: 移除 fcIdx -1 偏移,修复并发工具调用撞键问题 #5095 遇到的问题);claude-opus-4-8 等模型在调用工具前常先输出一句前导文本或先思考,因此高频命中;当模型直接以 tool_use(块 0)起始时则正常——这正是"时好时坏"的原因。
修复方案
不再使用常量偏移,改为在
ClaudeResponseInfo中维护一张 Claude 块序号 → 0 基工具序号 的映射(首次见到该块时分配下一个序号),并仅对 tool_use 块(content_block_start的 tool_use 及其input_json_delta)调用;text / thinking 块不占用序号。效果:
text(0), tool(1)index=1→ 被客户端丢弃index=0✓thinking(0), tool(1), tool(2)1, 20, 1✓tool(0), tool(1)(#5095 的撞键场景)0, 10, 1✓(不撞键)影响范围
RelayFormat=OpenAI(即 OpenAI 格式客户端调用 Claude 模型)。测试
新增回归用例覆盖三种场景并断言 OpenAI
tool_calls[].index为 0 基连续:TestStreamToolCallIndex_LeadingTextDoesNotShiftToolIndex(前导文本)TestStreamToolCallIndex_LeadingThinkingThenTools(前导 thinking + 多工具)TestStreamToolCallIndex_MultipleToolsFromZeroNoCollision(fix: 移除 fcIdx -1 偏移,修复并发工具调用撞键问题 #5095 撞键场景回归)修复前:


修复后:
Summary by CodeRabbit
Release Notes
Bug Fixes
Tests