Skip to content

fix: Gemini tool_call_id 跨 content 配对 + Ollama 请求侧 assistant 推理回放 - #6604

Open
HandSonic wants to merge 2 commits into
QuantumNous:mainfrom
HandSonic:fix/gemini-ollama-request-fixes
Open

fix: Gemini tool_call_id 跨 content 配对 + Ollama 请求侧 assistant 推理回放#6604
HandSonic wants to merge 2 commits into
QuantumNous:mainfrom
HandSonic:fix/gemini-ollama-request-fixes

Conversation

@HandSonic

@HandSonic HandSonic commented Aug 2, 2026

Copy link
Copy Markdown

📝 变更描述 / Description

修复协议适配层两个独立缺陷(均可在单元测试级别 100% 复现,详见 #6602#6603):

1. Gemini→Chat 转换 tool_call_id 配对失败relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req.go

原实现中 functionCall 的 id 从当前 content 的 toolCalls 切片长度推导(call_1 起),而 functionResponse 属于下一个 content 迭代、切片恒为空,导致 tool 消息的 tool_call_id 恒为 call_0 —— tool_calls 与 tool 消息任何情况下都配对失败,严格校验配对的 OpenAI 兼容上游会直接报错;该错误值已被 golden 快照固化。

修复方式:id 改为请求级递增序号分配;functionResponse 按 Gemini 协议的配对键(函数名)与先前的 functionCall 做 FIFO 配对;无法匹配的 response 分配不冲突的新 id。新增 4 组单测(单调用跨 content 配对、并行调用按名配对、同名重复调用 FIFO、未匹配回退),并重生 3 个 gemini_to_* golden 快照——diff 仅为响应位置 call_0call_1,证明无其他行为漂移。

2. Ollama 渠道请求侧不回放 assistant 推理relay/channel/ollama/relay-ollama.go

openAIChatToOllamaChat 构建 OllamaChatMessage 时映射了 role/content/images/tool_calls,但从不读取 GetReasoningContent()、从不设置 Thinking 字段(响应侧已有上游 thinking → reasoning_content 的反向映射,即"拿得到、传不回")。

修复方式:assistant 消息的 GetReasoningContent() 映射到 OllamaChatMessage.Thinking(仅 assistant 角色),与响应侧形成对称往返。新增单测覆盖 assistant 映射与非 assistant 角色不映射。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

声明:实现、测试与本描述由 AI 辅助完成,已经人工核对。

📸 运行证明 / Proof of Work

新增测试

relaykit/relayconvert/internal/gemini_chat: TestGeminiGenerateContentRequestToOpenAIChatPairsToolCallIDs
  (单调用跨 content 配对 / 并行调用按名配对(乱序响应)/ 同名重复调用 FIFO / 未匹配回退不冲突)
relay/channel/ollama: TestOpenAIChatToOllamaChatPreservesAssistantReasoning
  (assistant 映射 / user 角色不映射)

构建与测试

cd relaykit && GOWORK=off go build ./...          # 通过(模块独立性验证)
cd relaykit && GOWORK=off go test -count=1 ./...  # 全部通过
go build ./relay/... ./service/... ./controller/... # 根模块通过
go test ./relay/channel/... ./service/...          # 全部通过(service 包
  # TestObserveChannelAffinityUsageCacheByRelayFormat 在 origin/main 基线上同样失败,
  # 为上游既有问题,与本 PR 无关)

Golden 快照:3 个 gemini_to_* 请求快照更新,diff 仅为 tool 结果/响应位置的 call_0call_1(与 assistant 侧既有的 call_1 配对),证明行为变更严格收敛在 id 配对上。

对抗审查:3 视角独立审查(配对算法逻辑攻击、无工具/无推理流量零回归验证、协议形状核对),全部通过。

Summary by CodeRabbit

  • Bug Fixes

    • Preserved assistant reasoning across multi-turn Ollama conversations involving tool calls.
    • Improved Gemini-to-OpenAI conversion by reliably matching tool responses, including repeated or parallel calls.
    • Preserved the correct ordering of assistant content and tool responses.
    • Assigned unique identifiers to unmatched tool responses to prevent conflicts.
  • Tests

    • Added coverage for reasoning preservation, tool-call matching, fallback identifiers, and message ordering.

GeminiGenerateContentRequestToOpenAIChat assigned functionCall ids from a
toolCalls slice scoped to the current content, so functionResponse parts in
the following user content always computed len(toolCalls)==0 and received
call_0, while the assistant tool calls started at call_1 — tool calls and
tool messages never paired, and strict OpenAI-family upstreams reject the
misaligned history.

Assign ids from a request-wide sequence and pair functionResponse parts
with earlier functionCall parts by function name (FIFO per name); unmatched
responses get a fresh non-colliding id. Also buffer tool messages per
content and emit them after the content-level message, so a content mixing
functionCall and functionResponse parts no longer produces a tool message
ahead of its assistant message. Refreshes the three gemini_to_* request
golden snapshots to the correctly paired ids.

AI-assisted: implementation and tests were produced with AI assistance.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dd55ec8f-9816-4c2d-b4fe-ccbbf84bec9e

📥 Commits

Reviewing files that changed from the base of the PR and between 09ff415 and 7abe706.

📒 Files selected for processing (3)
  • relay/channel/ollama/relay-ollama.go
  • relay/channel/ollama/relay_ollama_req_test.go
  • relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • relay/channel/ollama/relay_ollama_req_test.go
  • relay/channel/ollama/relay-ollama.go
  • relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req_test.go

Walkthrough

The request conversion changes preserve assistant reasoning for Ollama and coordinate Gemini tool-call identifiers across calls and responses. Tests and golden fixtures validate pairing, ordering, fallback identifiers, and reasoning-field mapping.

Changes

Ollama reasoning replay

Layer / File(s) Summary
Assistant reasoning mapping
relay/channel/ollama/relay-ollama.go, relay/channel/ollama/relay_ollama_req_test.go
Assistant reasoning is serialized into Ollama Thinking. User reasoning is ignored. Serialization failures leave Thinking unset. Tests verify the mapping.

Gemini tool-call pairing

Layer / File(s) Summary
Tool-call sequencing and ordering
relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req.go, relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req_test.go, relaykit/relayconvert/testdata/golden/request/*
Tool calls receive request-wide sequential IDs. Responses consume matching IDs by function name in FIFO order. Unmatched responses receive unique fallback IDs. Buffered tool messages follow their assistant content. Tests and golden fixtures validate pairing and ordering.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit checks each call in line,
And keeps the reasoning aligned.
Ollama stores the thoughts once more,
Gemini pairs each tool-call score.
The tests confirm the IDs agree.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both primary fixes: Gemini tool-call pairing and Ollama assistant reasoning replay.
Linked Issues check ✅ Passed The changes satisfy #6602 and #6603, including FIFO tool-call pairing, fallback IDs, assistant-only reasoning mapping, and focused tests.
Out of Scope Changes check ✅ Passed All code, tests, and golden fixture updates directly support the two linked issue objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req_test.go`:
- Around line 125-131: Add require.Len(t, toolIDs, 2) immediately after
obtaining toolIDs in the test, before the indexed assertions, so toolIDs[0] and
toolIDs[1] are accessed only after confirming both elements exist.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fc6d85a7-d705-459e-bd2b-711f6ddd54cc

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab0202 and 09ff415.

📒 Files selected for processing (7)
  • relay/channel/ollama/relay-ollama.go
  • relay/channel/ollama/relay_ollama_req_test.go
  • relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req.go
  • relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req_test.go
  • relaykit/relayconvert/testdata/golden/request/gemini_to_claude.golden.json
  • relaykit/relayconvert/testdata/golden/request/gemini_to_openai.golden.json
  • relaykit/relayconvert/testdata/golden/request/gemini_to_openai_responses.golden.json

Comment thread relaykit/relayconvert/internal/gemini_chat/to_oai_chat_req_test.go
openAIChatToOllamaChat mapped role/content/images/tool_calls but never
carried reasoning_content into OllamaChatMessage.Thinking (the response
side already maps upstream thinking back to reasoning_content), so
thinking-capable models served over the Ollama channel lost the previous
assistant turn's reasoning on multi-turn tool-call continuations.

Map GetReasoningContent() to Thinking for assistant messages.

AI-assisted: implementation and tests were produced with AI assistance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant