Fix: Shorten Gemini tool_call_id for Azure compatibility (#12941)

This commit is contained in:
Gaston 2025-07-25 01:52:16 -03:00 committed by GitHub
parent 650ae0ef88
commit 6cfaf674e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 135 additions and 31 deletions

View file

@ -305,9 +305,9 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
return None
for tool in value:
openai_function_object: Optional[ChatCompletionToolParamFunctionChunk] = (
None
)
openai_function_object: Optional[
ChatCompletionToolParamFunctionChunk
] = None
if "function" in tool: # tools list
_openai_function_object = ChatCompletionToolParamFunctionChunk( # type: ignore
**tool["function"]
@ -597,14 +597,14 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
elif param == "seed":
optional_params["seed"] = value
elif param == "reasoning_effort" and isinstance(value, str):
optional_params["thinkingConfig"] = (
VertexGeminiConfig._map_reasoning_effort_to_thinking_budget(value)
)
optional_params[
"thinkingConfig"
] = VertexGeminiConfig._map_reasoning_effort_to_thinking_budget(value)
elif param == "thinking":
optional_params["thinkingConfig"] = (
VertexGeminiConfig._map_thinking_param(
cast(AnthropicThinkingParam, value)
)
optional_params[
"thinkingConfig"
] = VertexGeminiConfig._map_thinking_param(
cast(AnthropicThinkingParam, value)
)
elif param == "modalities" and isinstance(value, list):
response_modalities = self.map_response_modalities(value)
@ -854,7 +854,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
function = _function_chunk
else:
_tool_response_chunk = ChatCompletionToolCallChunk(
id=f"call_{str(uuid.uuid4())}",
id=f"call_{uuid.uuid4().hex[:28]}",
type="function",
function=_function_chunk,
index=cumulative_tool_call_idx,
@ -1077,10 +1077,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
elif (
finish_reason and finish_reason in mapped_finish_reason.keys()
): # vertex ai
return mapped_finish_reason[finish_reason]
else:
return "stop"
@staticmethod
@ -1175,12 +1173,14 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
if reasoning_content is not None:
chat_completion_message["reasoning_content"] = reasoning_content
functions, tools, cumulative_tool_call_index = (
VertexGeminiConfig._transform_parts(
parts=candidate["content"]["parts"],
cumulative_tool_call_idx=cumulative_tool_call_index,
is_function_call=is_function_call(standard_optional_params),
)
(
functions,
tools,
cumulative_tool_call_index,
) = VertexGeminiConfig._transform_parts(
parts=candidate["content"]["parts"],
cumulative_tool_call_idx=cumulative_tool_call_index,
is_function_call=is_function_call(standard_optional_params),
)
if "logprobsResult" in candidate:
@ -1344,28 +1344,28 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
## ADD METADATA TO RESPONSE ##
setattr(model_response, "vertex_ai_grounding_metadata", grounding_metadata)
model_response._hidden_params["vertex_ai_grounding_metadata"] = (
grounding_metadata
)
model_response._hidden_params[
"vertex_ai_grounding_metadata"
] = grounding_metadata
setattr(
model_response, "vertex_ai_url_context_metadata", url_context_metadata
)
model_response._hidden_params["vertex_ai_url_context_metadata"] = (
url_context_metadata
)
model_response._hidden_params[
"vertex_ai_url_context_metadata"
] = url_context_metadata
setattr(model_response, "vertex_ai_safety_results", safety_ratings)
model_response._hidden_params["vertex_ai_safety_results"] = (
safety_ratings # older approach - maintaining to prevent regressions
)
model_response._hidden_params[
"vertex_ai_safety_results"
] = safety_ratings # older approach - maintaining to prevent regressions
## ADD CITATION METADATA ##
setattr(model_response, "vertex_ai_citation_metadata", citation_metadata)
model_response._hidden_params["vertex_ai_citation_metadata"] = (
citation_metadata # older approach - maintaining to prevent regressions
)
model_response._hidden_params[
"vertex_ai_citation_metadata"
] = citation_metadata # older approach - maintaining to prevent regressions
except Exception as e:
raise VertexAIError(

View file

@ -1,5 +1,6 @@
import asyncio
import json
import re
from copy import deepcopy
from typing import List, cast
from unittest.mock import MagicMock, patch
@ -921,3 +922,106 @@ def test_vertex_ai_process_candidates_with_grounding_metadata():
print(result)
assert isinstance(result[0], list)
assert len(result[0]) == 1
def test_vertex_ai_tool_call_id_format():
"""
Test that tool call IDs have the correct format and length.
The ID should be in format 'call_' + 28 hex characters (total 33 characters).
This test verifies the fix for keeping the code line under 40 characters.
"""
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
VertexGeminiConfig,
)
from litellm.types.llms.vertex_ai import HttpxPartType
# Create parts with function calls
parts_with_functions = [
HttpxPartType(
functionCall={
"name": "get_weather",
"args": {"location": "San Francisco", "unit": "celsius"},
}
),
HttpxPartType(
functionCall={
"name": "get_time",
"args": {"timezone": "PST"}
}
),
]
function, tools, updated_idx = VertexGeminiConfig._transform_parts(
parts=parts_with_functions, cumulative_tool_call_idx=0, is_function_call=False
)
# Verify tools were created
assert function is None
assert tools is not None
assert len(tools) == 2
# Test ID format for both tool calls
for tool in tools:
tool_id = tool["id"]
# Should start with 'call_'
assert tool_id.startswith("call_"), f"ID should start with 'call_', got: {tool_id}"
# Should have exactly 33 total characters (call_ + 28 hex chars)
assert len(tool_id) == 33, f"ID should be 33 characters long, got {len(tool_id)}: {tool_id}"
# The part after 'call_' should be 28 hex characters
hex_part = tool_id[5:] # Remove 'call_' prefix
assert len(hex_part) == 28, f"Hex part should be 28 characters, got {len(hex_part)}: {hex_part}"
# Should only contain valid hex characters
assert re.match(r'^[0-9a-f]{28}$', hex_part), f"Should contain only lowercase hex chars, got: {hex_part}"
# Verify IDs are unique
assert tools[0]["id"] != tools[1]["id"], "Tool call IDs should be unique"
# Test with multiple generations to ensure uniqueness
ids_generated = set()
for _ in range(10):
_, test_tools, _ = VertexGeminiConfig._transform_parts(
parts=[HttpxPartType(functionCall={"name": "test", "args": {}})],
cumulative_tool_call_idx=0,
is_function_call=False,
)
if test_tools:
ids_generated.add(test_tools[0]["id"])
# All generated IDs should be unique
assert len(ids_generated) == 10, f"All 10 IDs should be unique, got {len(ids_generated)} unique IDs"
def test_vertex_ai_code_line_length():
"""
Test that the specific code line generating tool call IDs is within character limit.
This is a meta-test to ensure the code change meets the 40-character requirement.
"""
import inspect
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
VertexGeminiConfig,
)
# Get the source code of the _transform_parts method
source_lines = inspect.getsource(VertexGeminiConfig._transform_parts).split('\n')
# Find the line that generates the ID
id_line = None
for line in source_lines:
if 'id=f"call_{uuid.uuid4().hex' in line:
id_line = line.strip() # Remove indentation for length check
break
assert id_line is not None, "Could not find the ID generation line in source code"
# Check that the line is 40 characters or less (excluding indentation)
line_length = len(id_line)
assert line_length <= 40, f"ID generation line is {line_length} characters, should be ≤40: {id_line}"
# Verify it contains the expected UUID format
assert 'uuid.uuid4().hex[:28]' in id_line, f"Line should contain shortened UUID format: {id_line}"