fix(gemini): keep streamed tool_call index/id stable across chunks#1159
fix(gemini): keep streamed tool_call index/id stable across chunks#1159tbille wants to merge 2 commits into
Conversation
WalkthroughA mutable ChangesGemini streaming tool-call indexing
Related Issues: Sequence Diagram(s)sequenceDiagram
participant _acompletion as _acompletion
participant _convert_completion_chunk_response as _convert_completion_chunk_response
participant _create_openai_chunk_from_google_chunk as _create_openai_chunk_from_google_chunk
_acompletion->>_acompletion: initialise tool_call_counter
loop each streamed chunk
_acompletion->>_convert_completion_chunk_response: convert chunk with counter
_convert_completion_chunk_response->>_create_openai_chunk_from_google_chunk: forward tool_call_counter
_create_openai_chunk_from_google_chunk->>_create_openai_chunk_from_google_chunk: assign index/id from counter
_create_openai_chunk_from_google_chunk->>_create_openai_chunk_from_google_chunk: increment counter
end
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 32 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 `@tests/unit/providers/test_gemini_provider.py`:
- Around line 940-1012: Add an async provider-level streaming test that
exercises GoogleProvider._acompletion end-to-end, not just
_create_openai_chunk_from_google_chunk(). Mock generate_content_stream() and
verify multiple yielded chunks keep tool_call_counter state across the stream so
tool-call indices increment correctly and ids remain unique. Use the existing
GoogleProvider._acompletion and generate_content_stream symbols to catch any
counter reset or kwargs-forwarding regression in the new plumbing.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 74f959ea-cc5e-4b9e-9221-4c031a11b4e6
📒 Files selected for processing (3)
src/any_llm/providers/gemini/base.pysrc/any_llm/providers/gemini/utils.pytests/unit/providers/test_gemini_provider.py
Each chunk in a Gemini stream was previously converted independently in _create_openai_chunk_from_google_chunk, so tool_calls_list was a fresh list per chunk. This reset ChoiceDeltaToolCall.index to 0 for every chunk instead of tracking position across the whole assistant turn, and also caused repeated calls to the same function across different chunks to receive an identical id (hash(name) + position within the chunk). Thread a mutable tool_call_counter through the stream (mirroring the tool_index_map pattern already used by the bedrock provider) so index and id stay unique and stable for the life of the stream. Fixes #1148
Address CodeRabbit review comment and Codecov patch-coverage gap on src/any_llm/providers/gemini/base.py by adding a test that exercises GoogleProvider._acompletion with stream=True end-to-end (mocking generate_content_stream), rather than only calling the pure _create_openai_chunk_from_google_chunk helper directly. This verifies tool_call_counter is correctly threaded through _convert_completion_chunk_response's **kwargs across streamed chunks.
ae4ecfb to
f99c297
Compare
There was a problem hiding this comment.
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 `@tests/unit/providers/test_gemini_provider.py`:
- Around line 1014-1016: The async helper `_async_iter_chunks` is annotated too
loosely with `Any`, which triggers Ruff ANN401 and weakens type safety. Update
its return type to a concrete async iterator type like `AsyncIterator[Mock]`,
and make sure the annotation matches the yielded `Mock` items in the helper.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 273fd1b1-8c73-4d7f-bc7c-ca595789a97b
📒 Files selected for processing (3)
src/any_llm/providers/gemini/base.pysrc/any_llm/providers/gemini/utils.pytests/unit/providers/test_gemini_provider.py
| async def _async_iter_chunks(items: list[Mock]) -> Any: | ||
| for item in items: | ||
| yield item |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Tighten the async helper return type.
Line 1014 returns Any, which trips Ruff ANN401 and weakens type checking in this helper. Use a concrete async iterator type such as AsyncIterator[Mock] instead.
🧰 Tools
🪛 Ruff (0.15.20)
[warning] 1014-1014: Dynamically typed expressions (typing.Any) are disallowed in _async_iter_chunks
(ANN401)
🤖 Prompt for 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.
In `@tests/unit/providers/test_gemini_provider.py` around lines 1014 - 1016, The
async helper `_async_iter_chunks` is annotated too loosely with `Any`, which
triggers Ruff ANN401 and weakens type safety. Update its return type to a
concrete async iterator type like `AsyncIterator[Mock]`, and make sure the
annotation matches the yielded `Mock` items in the helper.
Source: Linters/SAST tools
Description
Fixes streamed tool call
index/idfor the Gemini provider._create_openai_chunk_from_google_chunkconverts one GoogleGenerateContentResponsechunk at a time, but it was buildingChoiceDeltaToolCall.index(and the generatedid) from a list scoped to that single chunk. In a real stream, each chunk is converted independently, so:indexreset to0/1/... for every chunk instead of tracking position across the whole assistant turn.read_filecalls, as in the issue's example) got an identicalid(hash(name)+ position-within-chunk), since the per-chunk counter always restarted at 0.Downstream consumers that merge streamed tool-call deltas by
index/idcan therefore overwrite or misassociate arguments between tool calls.Fix
Thread a mutable
tool_call_counter(a single-element list) through the stream, created once per_stream()call and passed into_convert_completion_chunk_response(..., tool_call_counter=...)on every chunk. This mirrors thetool_index_mappattern already used by the Bedrock provider (src/any_llm/providers/bedrock/utils.py/bedrock.py) to keep tool-call indices stable across a stream. The counter is optional (defaults to a fresh[0]) so existing single-chunk call sites/tests are unaffected.PR Type
Relevant issues
Fixes #1148
Checklist
AI Usage Information
AI Model used: Claude Sonnet 5
AI Developer Tool used: OpenCode
Any other info you'd like to share: Investigated the issue, designed and implemented the fix (following the existing
tool_index_mappattern from the Bedrock provider), and added regression tests reproducing the reported scenario.I am an AI Agent filling out this form (check box if true)
Summary by CodeRabbit
Bug Fixes
Tests