Skip to content

Commit 816a87f

Browse files
kenrogersGWeale
authored andcommitted
feat: Support provider-prefixed Gemini model IDs
Merge #5555 ## Summary - Extract Gemini model names from LiteLLM-compatible provider-prefixed IDs such as `gemini/gemini-2.5-flash`, `vertex_ai/gemini-2.5-flash`, and `openrouter/google/gemini-2.5-pro:online` - Keep malformed Vertex `projects/...` paths from being treated as valid Gemini IDs - Add an OpenRouter-through-LiteLLM sample showing `OPENROUTER_API_KEY`, `api_base`, and `openrouter/...` model usage ## Why ADK's Gemini tool checks already normalize native model IDs and Vertex/Apigee paths, but provider-prefixed LiteLLM model IDs were left as-is. That made Google Search reject routed Gemini IDs even when the underlying model name was Gemini. This updates the shared model-name utility so existing Gemini classification paths work for provider-prefixed IDs without special-casing one provider. Refs #2709. ## Tests - `uv run --extra test pytest tests/unittests/utils/test_model_name_utils.py tests/unittests/tools/test_google_search_tool.py` - `uv run --extra dev pyink --check --diff --config pyproject.toml src/google/adk/utils/model_name_utils.py tests/unittests/utils/test_model_name_utils.py tests/unittests/tools/test_google_search_tool.py contributing/samples/hello_world_openrouter/agent.py contributing/samples/hello_world_openrouter/__init__.py` - `uv run --extra test python -m py_compile contributing/samples/hello_world_openrouter/agent.py` Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=#5555 from kenrogers:codex/provider-prefixed-gemini-models 60234b4 PiperOrigin-RevId: 936889403
1 parent b3f278a commit 816a87f

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/google/adk/utils/model_name_utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def extract_model_name(model_string: str) -> str:
4141
4242
Args:
4343
model_string: Either a simple model name like "gemini-2.5-pro" or a
44-
path-based model name like "projects/.../models/gemini-2.5-flash"
44+
path-based model name like "projects/.../models/gemini-2.5-flash",
45+
or a provider-prefixed model name like "gemini/gemini-2.5-flash".
4546
4647
Returns:
4748
The extracted model name (e.g., "gemini-2.5-pro")
@@ -63,6 +64,19 @@ def extract_model_name(model_string: str) -> str:
6364
if model_string.startswith('models/'):
6465
return model_string[len('models/') :]
6566

67+
# Malformed 'projects/' path (didn't match the Vertex pattern above); return
68+
# as-is so the provider-prefix block below doesn't misread it as a Gemini id.
69+
if model_string.startswith('projects/'):
70+
return model_string
71+
72+
# Handle provider-prefixed LiteLLM-compatible names like
73+
# "gemini/gemini-2.5-flash" or "openrouter/google/gemini-2.5-pro:online".
74+
# Only Gemini names are extracted; other providers fall through unchanged.
75+
if '/' in model_string:
76+
model_name = model_string.rsplit('/', 1)[1]
77+
if model_name.startswith('gemini-'):
78+
return model_name
79+
6680
# If it's not a path-based model, return as-is (simple model name)
6781
return model_string
6882

tests/unittests/tools/test_google_search_tool.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,27 @@ async def test_process_llm_request_with_gemini_2_model_and_existing_tools_succee
250250
assert llm_request.config.tools[0] == existing_tool
251251
assert llm_request.config.tools[1].google_search is not None
252252

253+
@pytest.mark.asyncio
254+
async def test_process_llm_request_with_provider_prefixed_gemini_model(
255+
self,
256+
):
257+
"""Test processing LLM request with provider-prefixed Gemini model."""
258+
tool = GoogleSearchTool()
259+
tool_context = await _create_tool_context()
260+
261+
llm_request = LlmRequest(
262+
model='openrouter/google/gemini-2.5-pro:online',
263+
config=types.GenerateContentConfig(),
264+
)
265+
266+
await tool.process_llm_request(
267+
tool_context=tool_context, llm_request=llm_request
268+
)
269+
270+
assert llm_request.config.tools is not None
271+
assert len(llm_request.config.tools) == 1
272+
assert llm_request.config.tools[0].google_search is not None
273+
253274
@pytest.mark.asyncio
254275
async def test_process_llm_request_with_non_gemini_model_raises_error(self):
255276
"""Test that non-Gemini model raises ValueError."""

tests/unittests/utils/test_model_name_utils.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ def test_extract_model_name_with_models_prefix(self):
6868
assert extract_model_name('models/gemini-2.5-pro') == 'gemini-2.5-pro'
6969
assert extract_model_name('models/gemini-2.5-flash') == 'gemini-2.5-flash'
7070

71+
def test_extract_model_name_provider_prefixed_model(self):
72+
"""Test extraction of provider-prefixed Gemini model names."""
73+
assert extract_model_name('gemini/gemini-2.5-flash') == 'gemini-2.5-flash'
74+
assert extract_model_name('vertex_ai/gemini-2.5-flash') == (
75+
'gemini-2.5-flash'
76+
)
77+
assert (
78+
extract_model_name('openrouter/google/gemini-2.5-pro:online')
79+
== 'gemini-2.5-pro:online'
80+
)
81+
assert extract_model_name('openrouter/anthropic/claude-sonnet-4') == (
82+
'openrouter/anthropic/claude-sonnet-4'
83+
)
84+
7185
def test_extract_model_name_invalid_path(self):
7286
"""Test that invalid path formats return the original string."""
7387
invalid_paths = [
@@ -120,6 +134,13 @@ def test_is_gemini_model_path_based_names(self):
120134
non_gemini_path = 'projects/265104255505/locations/us-central1/publishers/google/models/claude-3-sonnet'
121135
assert is_gemini_model(non_gemini_path) is False
122136

137+
def test_is_gemini_model_provider_prefixed_names(self):
138+
"""Test Gemini model detection with provider-prefixed model names."""
139+
assert is_gemini_model('gemini/gemini-2.5-flash') is True
140+
assert is_gemini_model('vertex_ai/gemini-2.5-flash') is True
141+
assert is_gemini_model('openrouter/google/gemini-2.5-pro:online') is True
142+
assert is_gemini_model('openrouter/anthropic/claude-sonnet-4') is False
143+
123144
def test_is_gemini_model_edge_cases(self):
124145
"""Test edge cases for Gemini model detection."""
125146
# Test with None
@@ -172,6 +193,13 @@ def test_is_gemini_1_model_path_based_names(self):
172193
gemini_2_path = 'projects/265104255505/locations/us-central1/publishers/google/models/gemini-2.5-flash'
173194
assert is_gemini_1_model(gemini_2_path) is False
174195

196+
def test_is_gemini_1_model_provider_prefixed_names(self):
197+
"""Test Gemini 1.x detection with provider-prefixed model names."""
198+
assert is_gemini_1_model('gemini/gemini-1.5-flash') is True
199+
assert is_gemini_1_model('vertex_ai/gemini-1.5-flash') is True
200+
assert is_gemini_1_model('openrouter/google/gemini-1.5-pro:online') is True
201+
assert is_gemini_1_model('openrouter/google/gemini-2.5-pro') is False
202+
175203
def test_is_gemini_1_model_edge_cases(self):
176204
"""Test edge cases for Gemini 1.x model detection."""
177205
# Test with None
@@ -223,6 +251,19 @@ def test_is_gemini_eap_or_2_or_above_path_based_names(self):
223251
gemini_3_path = 'projects/12345/locations/us-east1/publishers/google/models/gemini-3.0-pro'
224252
assert is_gemini_eap_or_2_or_above(gemini_3_path) is True
225253

254+
def test_is_gemini_eap_or_2_or_above_provider_prefixed_names(self):
255+
"""Test Gemini 2.0+ detection with provider-prefixed model names."""
256+
assert is_gemini_eap_or_2_or_above('gemini/gemini-2.5-flash') is True
257+
assert is_gemini_eap_or_2_or_above('vertex_ai/gemini-2.5-flash') is True
258+
assert (
259+
is_gemini_eap_or_2_or_above('openrouter/google/gemini-2.5-pro:online')
260+
is True
261+
)
262+
assert (
263+
is_gemini_eap_or_2_or_above('openrouter/google/gemini-1.5-pro:online')
264+
is False
265+
)
266+
226267
def test_is_gemini_eap_or_2_or_above_edge_cases(self):
227268
"""Test edge cases for Gemini 2.0+ model detection."""
228269
# Test with None
@@ -253,6 +294,8 @@ def test_model_classification_consistency(self):
253294
'gemini-2.5-flash',
254295
'gemini-2.5-pro',
255296
'gemini-3.0-pro',
297+
'gemini/gemini-2.5-flash',
298+
'openrouter/google/gemini-2.5-pro:online',
256299
'projects/123/locations/us-central1/publishers/google/models/gemini-1.5-pro',
257300
'projects/123/locations/us-central1/publishers/google/models/gemini-2.5-flash',
258301
'projects/123/locations/us-central1/publishers/google/models/gemini-3.0-pro',

0 commit comments

Comments
 (0)