Skip to content

Commit 56047c7

Browse files
committed
Support provider-prefixed Gemini model IDs
1 parent 2b04996 commit 56047c7

6 files changed

Lines changed: 175 additions & 1 deletion

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# OpenRouter with LiteLLM
2+
3+
This sample shows how to use an OpenRouter-hosted model with ADK through the
4+
existing LiteLLM model connector.
5+
6+
## Setup
7+
8+
Install ADK with optional integrations so that `LiteLlm` is available:
9+
10+
```bash
11+
pip install "google-adk[extensions]"
12+
```
13+
14+
Set your OpenRouter API key:
15+
16+
```bash
17+
export OPENROUTER_API_KEY="..."
18+
```
19+
20+
Optionally choose a model:
21+
22+
```bash
23+
export OPENROUTER_MODEL="openrouter/openai/gpt-5.2"
24+
```
25+
26+
Run the sample:
27+
28+
```bash
29+
adk run contributing/samples/hello_world_openrouter
30+
```
31+
32+
## Notes
33+
34+
OpenRouter is used here through LiteLLM's OpenAI-compatible routing path:
35+
36+
```python
37+
LiteLlm(
38+
model="openrouter/openai/gpt-5.2",
39+
api_key=os.getenv("OPENROUTER_API_KEY"),
40+
api_base="https://openrouter.ai/api/v1",
41+
)
42+
```
43+
44+
For Gemini models routed through OpenRouter, use OpenRouter model IDs such as
45+
`openrouter/google/gemini-2.5-pro:online`. ADK's built-in Google tools are
46+
optimized for native Gemini model connections, so verify tool compatibility for
47+
the routed model and provider you select.
48+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import os
18+
19+
from google.adk.agents.llm_agent import Agent
20+
from google.adk.models.lite_llm import LiteLlm
21+
22+
OPENROUTER_API_BASE = 'https://openrouter.ai/api/v1'
23+
OPENROUTER_MODEL = os.getenv('OPENROUTER_MODEL', 'openrouter/openai/gpt-5.2')
24+
25+
26+
root_agent = Agent(
27+
name='openrouter_agent',
28+
model=LiteLlm(
29+
model=OPENROUTER_MODEL,
30+
api_key=os.getenv('OPENROUTER_API_KEY'),
31+
api_base=OPENROUTER_API_BASE,
32+
),
33+
description='A simple ADK agent that uses OpenRouter through LiteLLM.',
34+
instruction=(
35+
'You are a concise assistant running through OpenRouter. Answer the'
36+
' user directly and mention when a question needs current or external'
37+
' information.'
38+
),
39+
)

src/google/adk/utils/model_name_utils.py

Lines changed: 13 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,17 @@ def extract_model_name(model_string: str) -> str:
6364
if model_string.startswith('models/'):
6465
return model_string[len('models/') :]
6566

67+
if model_string.startswith('projects/'):
68+
return model_string
69+
70+
# Handle provider-prefixed LiteLLM-compatible names like
71+
# "gemini/gemini-2.5-flash" or
72+
# "openrouter/google/gemini-2.5-pro:online".
73+
if '/' in model_string:
74+
model_name = model_string.rsplit('/', 1)[1]
75+
if model_name.startswith('gemini-'):
76+
return model_name
77+
6678
# If it's not a path-based model, return as-is (simple model name)
6779
return model_string
6880

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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ def test_extract_model_name_with_models_prefix(self):
6666
assert extract_model_name('models/gemini-2.5-pro') == 'gemini-2.5-pro'
6767
assert extract_model_name('models/gemini-2.5-flash') == 'gemini-2.5-flash'
6868

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

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

194+
def test_is_gemini_1_model_provider_prefixed_names(self):
195+
"""Test Gemini 1.x detection with provider-prefixed model names."""
196+
assert is_gemini_1_model('gemini/gemini-1.5-flash') is True
197+
assert is_gemini_1_model('vertex_ai/gemini-1.5-flash') is True
198+
assert is_gemini_1_model('openrouter/google/gemini-1.5-pro:online') is True
199+
assert is_gemini_1_model('openrouter/google/gemini-2.5-pro') is False
200+
173201
def test_is_gemini_1_model_edge_cases(self):
174202
"""Test edge cases for Gemini 1.x model detection."""
175203
# Test with None
@@ -217,6 +245,17 @@ def test_is_gemini_2_or_above_path_based_names(self):
217245
gemini_3_path = 'projects/12345/locations/us-east1/publishers/google/models/gemini-3.0-pro'
218246
assert is_gemini_2_or_above(gemini_3_path) is True
219247

248+
def test_is_gemini_2_or_above_provider_prefixed_names(self):
249+
"""Test Gemini 2.0+ detection with provider-prefixed model names."""
250+
assert is_gemini_2_or_above('gemini/gemini-2.5-flash') is True
251+
assert is_gemini_2_or_above('vertex_ai/gemini-2.5-flash') is True
252+
assert (
253+
is_gemini_2_or_above('openrouter/google/gemini-2.5-pro:online') is True
254+
)
255+
assert (
256+
is_gemini_2_or_above('openrouter/google/gemini-1.5-pro:online') is False
257+
)
258+
220259
def test_is_gemini_2_or_above_edge_cases(self):
221260
"""Test edge cases for Gemini 2.0+ model detection."""
222261
# Test with None
@@ -245,6 +284,8 @@ def test_model_classification_consistency(self):
245284
'gemini-2.5-flash',
246285
'gemini-2.5-pro',
247286
'gemini-3.0-pro',
287+
'gemini/gemini-2.5-flash',
288+
'openrouter/google/gemini-2.5-pro:online',
248289
'projects/123/locations/us-central1/publishers/google/models/gemini-1.5-pro',
249290
'projects/123/locations/us-central1/publishers/google/models/gemini-2.5-flash',
250291
'projects/123/locations/us-central1/publishers/google/models/gemini-3.0-pro',

0 commit comments

Comments
 (0)