Skip to content

Commit af6042a

Browse files
vietnamesekidclaude
andcommitted
fix: use Gemini 3+ check for native output_schema + tools support
can_use_output_schema_with_tools() was using is_gemini_2_or_above(), which incorrectly enabled native response_schema + tools for all Gemini 2.x models on Vertex AI. Gemini 2.0/2.5 can't actually handle both constraints at the same time — the model just keeps firing tool calls and never produces the structured response, resulting in an infinite loop. Per Google's docs, structured output combined with tools is only supported on Gemini 3 series models. This change adds is_gemini_3_or_above() and uses it so that Gemini 2.x falls back to the SetModelResponseTool workaround (which works fine). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f973673 commit af6042a

4 files changed

Lines changed: 74 additions & 5 deletions

File tree

src/google/adk/utils/model_name_utils.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,31 @@ def is_gemini_2_or_above(model_string: Optional[str]) -> bool:
125125
return False
126126

127127
return parsed_version.major >= 2
128+
129+
130+
def is_gemini_3_or_above(model_string: Optional[str]) -> bool:
131+
"""Check if the model is a Gemini 3.0 or newer model using semantic versions.
132+
133+
Args:
134+
model_string: Either a simple model name or path-based model name
135+
136+
Returns:
137+
True if it's a Gemini 3.0+ model, False otherwise
138+
"""
139+
if not model_string:
140+
return False
141+
142+
model_name = extract_model_name(model_string)
143+
if not model_name.startswith('gemini-'):
144+
return False
145+
146+
version_string = model_name[len('gemini-'):].split('-', 1)[0]
147+
if not version_string:
148+
return False
149+
150+
try:
151+
parsed_version = Version(version_string)
152+
except InvalidVersion:
153+
return False
154+
155+
return parsed_version.major >= 3

src/google/adk/utils/output_schema_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from typing import Union
2424

2525
from ..models.base_llm import BaseLlm
26-
from .model_name_utils import is_gemini_2_or_above
26+
from .model_name_utils import is_gemini_3_or_above
2727
from .variant_utils import get_google_llm_variant
2828
from .variant_utils import GoogleLLMVariant
2929

@@ -49,5 +49,5 @@ def can_use_output_schema_with_tools(model: Union[str, BaseLlm]) -> bool:
4949

5050
return (
5151
get_google_llm_variant() == GoogleLLMVariant.VERTEX_AI
52-
and is_gemini_2_or_above(model_string)
52+
and is_gemini_3_or_above(model_string)
5353
)

tests/unittests/utils/test_model_name_utils.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from google.adk.utils.model_name_utils import extract_model_name
1818
from google.adk.utils.model_name_utils import is_gemini_1_model
1919
from google.adk.utils.model_name_utils import is_gemini_2_or_above
20+
from google.adk.utils.model_name_utils import is_gemini_3_or_above
2021
from google.adk.utils.model_name_utils import is_gemini_model
2122
from google.adk.utils.model_name_utils import is_gemini_model_id_check_disabled
2223

@@ -236,6 +237,40 @@ def test_is_gemini_2_or_above_edge_cases(self):
236237
assert is_gemini_2_or_above('gemini-one') is False
237238

238239

240+
class TestIsGemini3OrAbove:
241+
"""Test the is_gemini_3_or_above function."""
242+
243+
def test_is_gemini_3_or_above_simple_names(self):
244+
"""Test Gemini 3.0+ model detection with simple model names."""
245+
assert is_gemini_3_or_above('gemini-3.0-pro') is True
246+
assert is_gemini_3_or_above('gemini-3.1-pro-preview') is True
247+
assert is_gemini_3_or_above('gemini-3-flash-preview') is True
248+
assert is_gemini_3_or_above('gemini-4.0-pro') is True
249+
assert is_gemini_3_or_above('gemini-2.5-pro') is False
250+
assert is_gemini_3_or_above('gemini-2.0-flash') is False
251+
assert is_gemini_3_or_above('gemini-1.5-flash') is False
252+
assert is_gemini_3_or_above('claude-3-sonnet') is False
253+
254+
def test_is_gemini_3_or_above_path_based_names(self):
255+
"""Test Gemini 3.0+ model detection with path-based model names."""
256+
gemini_3_path = 'projects/12345/locations/us-east1/publishers/google/models/gemini-3.0-pro'
257+
assert is_gemini_3_or_above(gemini_3_path) is True
258+
259+
gemini_3_path_2 = 'projects/12345/locations/us-east1/publishers/google/models/gemini-3.1-pro-preview'
260+
assert is_gemini_3_or_above(gemini_3_path_2) is True
261+
262+
gemini_2_path = 'projects/265104255505/locations/us-central1/publishers/google/models/gemini-2.5-pro'
263+
assert is_gemini_3_or_above(gemini_2_path) is False
264+
265+
def test_is_gemini_3_or_above_edge_cases(self):
266+
"""Test edge cases for Gemini 3.0+ model detection."""
267+
assert is_gemini_3_or_above(None) is False
268+
assert is_gemini_3_or_above('') is False
269+
assert is_gemini_3_or_above('gemini-3.') is False
270+
assert is_gemini_3_or_above('gemini-one') is False
271+
assert is_gemini_3_or_above('my-gemini-3.0-model') is False
272+
273+
239274
class TestModelNameUtilsIntegration:
240275
"""Integration tests for model name utilities."""
241276

tests/unittests/utils/test_output_schema_utils.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@
2929
@pytest.mark.parametrize(
3030
"model, env_value, expected",
3131
[
32-
("gemini-2.5-pro", "1", True),
32+
("gemini-3.1-pro", "1", True),
33+
("gemini-3.1-pro", "0", False),
34+
("gemini-3.1-pro", None, False),
35+
(Gemini(model="gemini-3.0-flash"), "1", True),
36+
(Gemini(model="gemini-3.0-flash"), "0", False),
37+
(Gemini(model="gemini-3.0-flash"), None, False),
38+
("gemini-2.5-pro", "1", False),
3339
("gemini-2.5-pro", "0", False),
3440
("gemini-2.5-pro", None, False),
35-
(Gemini(model="gemini-2.5-pro"), "1", True),
41+
(Gemini(model="gemini-2.5-pro"), "1", False),
3642
(Gemini(model="gemini-2.5-pro"), "0", False),
3743
(Gemini(model="gemini-2.5-pro"), None, False),
38-
("gemini-2.0-flash", "1", True),
44+
("gemini-2.0-flash", "1", False),
3945
("gemini-2.0-flash", "0", False),
4046
("gemini-2.0-flash", None, False),
4147
("gemini-1.5-pro", "1", False),

0 commit comments

Comments
 (0)