fix(gemini): Add reasoning_content to streaming responses with tools (#16854)

Fixes #16805

When using Gemini models (2.5/3.0) with streaming + tools enabled,
the reasoning_content field was missing from stream chunks, even though
thinking_blocks were present in non-streaming responses.

Changes:
- Convert thinking_blocks to reasoning_content for streaming responses
- Extract "thinking" field from each thinking_block
- Concatenate multiple thinking parts with newlines
- Assign to reasoning_content in chat_completion_message for streaming

Testing:
- Added test_streaming_chunk_with_tool_calls_includes_reasoning_content
- Test verifies reasoning_content appears with tool calls in streaming
- All 39 existing Gemini tests pass
This commit is contained in:
Cesar Garcia
2025-11-19 19:09:37 -08:00
committed by GitHub
parent 7a84469605
commit 7d5cb8ebb2
2 changed files with 65 additions and 0 deletions
@@ -1673,6 +1673,19 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
if thinking_blocks is not None:
chat_completion_message["thinking_blocks"] = thinking_blocks # type: ignore
# Convert thinking_blocks to reasoning_content for streaming
# This ensures reasoning_content is available in streaming responses
if isinstance(model_response, ModelResponseStream) and reasoning_content is None:
reasoning_content_parts = []
for block in thinking_blocks:
thinking_text = block.get("thinking")
if thinking_text:
reasoning_content_parts.append(thinking_text)
if reasoning_content_parts:
reasoning_content = "\n".join(reasoning_content_parts)
chat_completion_message["reasoning_content"] = reasoning_content
if isinstance(model_response, ModelResponseStream):
choice = VertexGeminiConfig._create_streaming_choice(
chat_completion_message=chat_completion_message,
@@ -390,6 +390,58 @@ def test_streaming_chunk_includes_reasoning_content():
)
def test_streaming_chunk_with_tool_calls_includes_reasoning_content():
"""
Test for issue #16805: Ensure that when Gemini returns a streaming chunk with
tool calls AND thoughtSignature, the reasoning_content is included in the delta.
Previously, thinking_blocks were only added to non-streaming responses, causing
reasoning_content to be missing in streaming mode when tools were enabled.
"""
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
ModelResponseIterator,
)
litellm_logging = MagicMock()
chunk = {
"candidates": [
{
"content": {
"parts": [
{
"functionCall": {
"name": "get_current_time",
"args": {"timezone": "America/New_York"},
},
"thoughtSignature": "EsEDCr4DAdHtim...", # Base64 signature
}
]
},
"finishReason": "STOP",
}
],
"usageMetadata": {
"promptTokenCount": 68,
"candidatesTokenCount": 120,
"totalTokenCount": 188,
},
}
iterator = ModelResponseIterator(
streaming_response=[], sync_stream=True, logging_obj=litellm_logging
)
streaming_chunk = iterator.chunk_parser(chunk)
# Verify that reasoning_content is present in the streaming delta
assert streaming_chunk.choices[0].delta.reasoning_content is not None
# Verify tool calls are also present
assert streaming_chunk.choices[0].delta.tool_calls is not None
assert len(streaming_chunk.choices[0].delta.tool_calls) == 1
assert streaming_chunk.choices[0].delta.tool_calls[0].function.name == "get_current_time"
def test_check_finish_reason():
finish_reason_mappings = VertexGeminiConfig.get_finish_reason_mapping()
for k, v in finish_reason_mappings.items():