Skip to content

Commit 4930155

Browse files
abhiramArisecopybara-github
authored andcommitted
fix: match HF-style gemma-4 model names for tool_responses role
Merge #6340 The gemma4 role fix (#5650) only matched Ollama-style names like gemma4:e2b. HF/vLLM/llama.cpp use gemma-4 with a hyphen, e.g. google/gemma-4-26B-A4B, which the substring check missed, causing the wrong tool role on non-Ollama backends. Fixes #6334 PiperOrigin-RevId: 948012210
1 parent 64a7448 commit 4930155

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

src/google/adk/models/lite_llm.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,26 @@ def _extract_reasoning_value(message: Message | Delta | None) -> Any:
626626
return message.get("reasoning")
627627

628628

629+
_GEMMA4_MODEL_PATTERN = re.compile(r"gemma-?4")
630+
631+
632+
def _is_gemma4_model(model: str) -> bool:
633+
"""Detects Gemma 4 models across naming conventions.
634+
635+
Ollama uses "gemma4" (e.g. "ollama/gemma4:e2b"), while Hugging Face,
636+
vLLM, and llama.cpp use the hyphenated "gemma-4" (e.g.
637+
"google/gemma-4-26B-A4B"). Both need role='tool_responses' for tool
638+
results.
639+
640+
Args:
641+
model: The model name to check.
642+
643+
Returns:
644+
True if the model is a Gemma 4 model, False otherwise.
645+
"""
646+
return bool(_GEMMA4_MODEL_PATTERN.search(model.lower()))
647+
648+
629649
class ChatCompletionFileUrlObject(TypedDict, total=False):
630650
file_data: str
631651
file_id: str
@@ -1006,7 +1026,7 @@ async def _content_to_message_param(
10061026
# from the tool call, instead of OpenAI-compatible 'tool' role used by other models.
10071027
# Earlier Gemma versions before version 4 do not support tool use,
10081028
# so this check is intentionally scoped to only look for "gemma4" in the model name.
1009-
tool_role = "tool_responses" if "gemma4" in model.lower() else "tool"
1029+
tool_role = "tool_responses" if _is_gemma4_model(model) else "tool"
10101030
tool_messages.append(
10111031
ChatCompletionToolMessage(
10121032
role=tool_role,
@@ -1174,7 +1194,7 @@ def _ensure_tool_results(messages: List[Message], model: str) -> List[Message]:
11741194

11751195
healed_messages: List[Message] = []
11761196
pending_tool_call_ids: List[str] = []
1177-
expected_tool_role = "tool_responses" if "gemma4" in model.lower() else "tool"
1197+
expected_tool_role = "tool_responses" if _is_gemma4_model(model) else "tool"
11781198

11791199
for message in messages:
11801200
role = message.get("role")

tests/unittests/models/test_lite_llm_gemma_tool_role.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ async def test_gemma4_model_uses_tool_responses_role(self):
9191
"template; role='tool' causes infinite tool-calling loops."
9292
)
9393

94+
@pytest.mark.asyncio
95+
async def test_gemma4_hf_style_naming_uses_tool_responses_role(self):
96+
"""Hyphenated 'gemma-4' naming should also get role='tool_responses'."""
97+
content = _make_function_response_content()
98+
99+
result = await _content_to_message_param(
100+
content, model="google/gemma-4-26B-A4B"
101+
)
102+
103+
assert _extract_role(result) == "tool_responses", (
104+
"Gemma models require role='tool_responses' to match their chat "
105+
"template; role='tool' causes infinite tool-calling loops."
106+
)
107+
94108
@pytest.mark.asyncio
95109
async def test_gemma4_uppercase_model_name(self):
96110
"""Model name matching should be case-insensitive."""

0 commit comments

Comments
 (0)