Skip to content

Commit 5fd74d5

Browse files
committed
feat: add gemma3 ollama model support
Adds support for locally-running Gemma3 models exposed via Ollama. This will enable developers to run fully-local agent workflows, using the larger two Gemma3 models. Functionality is achieved by extending LiteLlm models and using a custom prompt template with some request/response pre/post processing. As part of this work, the existing Gemma 3 support (via Gemini API) is refactored to clarify functionality and support broader reuse across both modes of interacting with the LLM. A separate `hello_world_gemma3_ollama` example is provided to highlight local usage. NOTE: Adds an optional dependency on `instructor` for finding and parsing JSON in Gemma3 response blocks. Testing Test Plan - add and run integration and unit tests - manual run of both `hello_world_gemma` and `hellow_world_gemma3_ollama` agents - manual run of `multi_tool_agent` from quickstart using new `Gemma3Ollama` LLM. Automated Tests | Test Command | Results | |--------------|---------| | pytest ./tests/unittests | 2779 passed, 2387 warnings in 63.03s | | pytest ./tests/unittests/models/test_gemma_llm.py | 15 passed in 4.06s | | pytest ./tests/integration/models/test_gemma_llm.py | 1 passed in 33.22s | Manual Tests Log of running `multi_agent_tool` with a locally-built wheel: ``` [user]: what is the weather in new york? 15:12:24 - LiteLLM:INFO: utils.py:3373 - LiteLLM completion() model= gemma3:12b; provider = ollama 15:12:28 - LiteLLM:INFO: utils.py:3373 - LiteLLM completion() model= gemma3:12b; provider = ollama [weather_time_agent]: The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit). [user]: what is the time in new york? 15:12:43 - LiteLLM:INFO: utils.py:3373 - LiteLLM completion() model= gemma3:12b; provider = ollama 15:12:48 - LiteLLM:INFO: utils.py:3373 - LiteLLM completion() model= gemma3:12b; provider = ollama [weather_time_agent]: The current time in New York is 2025-10-08 18:12:48 EDT-0400. ``` `DEBUG` log snippet of an agent run: ``` 2025-10-08 15:32:33,322 - DEBUG - lite_llm.py:810 - LLM Request: ----------------------------------------------------------- System Instruction: You roll dice and answer questions about the outcome of the dice rolls. ... You are an agent. Your internal name is "data_processing_agent". ... ----------------------------------------------------------- Contents: {"parts":[{"text":"Hi, introduce yourself."}],"role":"user"} {"parts":[{"text":"I am data_processing_agent, a hello world agent that can roll a dice of 8 sides and check prime numbers."}],"role":"model"} {"parts":[{"text":"Roll a die with 100 sides and check if it is prime"}],"role":"user"} {"parts":[{"text":"{\"args\":{\"sides\":100},\"name\":\"roll_die\"}"}],"role":"model"} {"parts":[{"text":"Invoking tool `roll_die` produced: `{\"result\": 26}`."}],"role":"user"} {"parts":[{"text":"{\"args\":{\"nums\":[26]},\"name\":\"check_prime\"}"}],"role":"model"} {"parts":[{"text":"Invoking tool `check_prime` produced: `{\"result\": \"No prime numbers found.\"}`."}],"role":"user"} {"parts":[{"text":"Okay, the roll was 26, and it is not a prime number."}],"role":"model"} {"parts":[{"text":"Roll it again."}],"role":"user"} {"parts":[{"text":"{\"args\":{\"sides\":100},\"name\":\"roll_die\"}"}],"role":"model"} {"parts":[{"text":"Invoking tool `roll_die` produced: `{\"result\": 69}`."}],"role":"user"} {"parts":[{"text":"{\"args\":{\"nums\":[69]},\"name\":\"check_prime\"}"}],"role":"model"} {"parts":[{"text":"Invoking tool `check_prime` produced: `{\"result\": \"No prime numbers found.\"}`."}],"role":"user"} {"parts":[{"text":"The roll was 69, and it is not a prime number."}],"role":"model"} {"parts":[{"text":"What numbers did I get?"}],"role":"user"} ----------------------------------------------------------- Functions: ----------------------------------------------------------- ```
1 parent 32f2ec3 commit 5fd74d5

File tree

10 files changed

+433
-252
lines changed

10 files changed

+433
-252
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ venv.bak/
3232
# IDE
3333
.idea/
3434
.vscode/
35+
.zed/
3536
*.swp
3637
*.swo
3738
.DS_Store

contributing/samples/hello_world_gemma/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import random
1717

1818
from google.adk.agents.llm_agent import Agent
19-
from google.adk.models.gemma_llm import Gemma
19+
from google.adk.models.gemma_llm import Gemma3GeminiAPI
2020
from google.genai.types import GenerateContentConfig
2121

2222

@@ -61,7 +61,7 @@ async def check_prime(nums: list[int]) -> str:
6161

6262

6363
root_agent = Agent(
64-
model=Gemma(model="gemma-3-27b-it"),
64+
model=Gemma3GeminiAPI(model="gemma-3-27b-it"),
6565
name="data_processing_agent",
6666
description=(
6767
"hello world agent that can roll many-sided dice and check if numbers"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2025 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+
16+
from . import agent
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright 2025 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+
import logging
16+
import random
17+
18+
from google.adk.agents.llm_agent import Agent
19+
from google.adk.models.gemma_llm import Gemma3Ollama
20+
21+
litellm_logger = logging.getLogger("LiteLLM")
22+
litellm_logger.setLevel(logging.WARNING)
23+
24+
25+
def roll_die(sides: int) -> int:
26+
"""Roll a die and return the rolled result.
27+
28+
Args:
29+
sides: The integer number of sides the die has.
30+
31+
Returns:
32+
An integer of the result of rolling the die.
33+
"""
34+
return random.randint(1, sides)
35+
36+
37+
async def check_prime(nums: list[int]) -> str:
38+
"""Check if a given list of numbers are prime.
39+
40+
Args:
41+
nums: The list of numbers to check.
42+
43+
Returns:
44+
A str indicating which number is prime.
45+
"""
46+
primes = set()
47+
for number in nums:
48+
number = int(number)
49+
if number <= 1:
50+
continue
51+
is_prime = True
52+
for i in range(2, int(number**0.5) + 1):
53+
if number % i == 0:
54+
is_prime = False
55+
break
56+
if is_prime:
57+
primes.add(number)
58+
return (
59+
"No prime numbers found."
60+
if not primes
61+
else f"{', '.join(str(num) for num in primes)} are prime numbers."
62+
)
63+
64+
65+
root_agent = Agent(
66+
model=Gemma3Ollama(model="ollama/gemma3:12b"),
67+
name="data_processing_agent",
68+
description=(
69+
"hello world agent that can roll a dice of 8 sides and check prime"
70+
" numbers."
71+
),
72+
instruction="""
73+
You roll dice and answer questions about the outcome of the dice rolls.
74+
You can roll dice of different sizes.
75+
You can use multiple tools in parallel by calling functions in parallel(in one request and in one round).
76+
It is ok to discuss previous dice roles, and comment on the dice rolls.
77+
When you are asked to roll a die, you must call the roll_die tool with the number of sides. Be sure to pass in an integer. Do not pass in a string.
78+
You should never roll a die on your own.
79+
When checking prime numbers, call the check_prime tool with a list of integers. Be sure to pass in a list of integers. You should never pass in a string.
80+
You should not check prime numbers before calling the tool.
81+
When you are asked to roll a die and check prime numbers, you should always make the following two function calls:
82+
1. You should first call the roll_die tool to get a roll. Wait for the function response before calling the check_prime tool.
83+
2. After you get the function response from roll_die tool, you should call the check_prime tool with the roll_die result.
84+
2.1 If user asks you to check primes based on previous rolls, make sure you include the previous rolls in the list.
85+
3. When you respond, you must include the roll_die result from step 1.
86+
You should always perform the previous 3 steps when asking for a roll and checking prime numbers.
87+
You should not rely on the previous history on prime results.
88+
""",
89+
tools=[
90+
roll_die,
91+
check_prime,
92+
],
93+
)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2025 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+
16+
import asyncio
17+
import time
18+
19+
import agent
20+
from dotenv import load_dotenv
21+
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
22+
from google.adk.cli.utils import logs
23+
from google.adk.runners import Runner
24+
from google.adk.sessions.in_memory_session_service import InMemorySessionService
25+
from google.adk.sessions.session import Session
26+
from google.genai import types
27+
28+
load_dotenv(override=True)
29+
logs.log_to_tmp_folder()
30+
31+
32+
async def main():
33+
34+
app_name = 'my_app'
35+
user_id_1 = 'user1'
36+
session_service = InMemorySessionService()
37+
artifact_service = InMemoryArtifactService()
38+
runner = Runner(
39+
app_name=app_name,
40+
agent=agent.root_agent,
41+
artifact_service=artifact_service,
42+
session_service=session_service,
43+
)
44+
session_11 = await session_service.create_session(
45+
app_name=app_name, user_id=user_id_1
46+
)
47+
48+
async def run_prompt(session: Session, new_message: str):
49+
content = types.Content(
50+
role='user', parts=[types.Part.from_text(text=new_message)]
51+
)
52+
print('** User says:', content.model_dump(exclude_none=True))
53+
async for event in runner.run_async(
54+
user_id=user_id_1,
55+
session_id=session.id,
56+
new_message=content,
57+
):
58+
if event.content.parts and event.content.parts[0].text:
59+
print(f'** {event.author}: {event.content.parts[0].text}')
60+
61+
start_time = time.time()
62+
print('Start time:', start_time)
63+
print('------------------------------------')
64+
await run_prompt(session_11, 'Hi, introduce yourself.')
65+
await run_prompt(
66+
session_11, 'Roll a die with 100 sides and check if it is prime'
67+
)
68+
await run_prompt(session_11, 'Roll it again.')
69+
await run_prompt(session_11, 'What numbers did I get?')
70+
end_time = time.time()
71+
print('------------------------------------')
72+
print('End time:', end_time)
73+
print('Total time:', end_time - start_time)
74+
75+
76+
if __name__ == '__main__':
77+
asyncio.run(main())

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ extensions = [
150150
"llama-index-embeddings-google-genai>=0.3.0",# For files retrieval using LlamaIndex.
151151
"lxml>=5.3.0", # For load_web_page tool.
152152
"toolbox-core>=0.1.0", # For tools.toolbox_toolset.ToolboxToolset
153+
"instructor>=1.11.3", # For Gemma3 LLMs (parsing function responses).
153154
]
154155

155156
otel-gcp = [

src/google/adk/models/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"""Defines the interface to support a model."""
1616

1717
from .base_llm import BaseLlm
18-
from .gemma_llm import Gemma
18+
from .gemma_llm import Gemma3GeminiAPI
19+
from .gemma_llm import Gemma3Ollama
1920
from .google_llm import Gemini
2021
from .llm_request import LlmRequest
2122
from .llm_response import LlmResponse
@@ -24,10 +25,12 @@
2425
__all__ = [
2526
'BaseLlm',
2627
'Gemini',
27-
'Gemma',
28+
'Gemma3GeminiAPI',
29+
'Gemma3Ollama',
2830
'LLMRegistry',
2931
]
3032

3133

3234
LLMRegistry.register(Gemini)
33-
LLMRegistry.register(Gemma)
35+
LLMRegistry.register(Gemma3GeminiAPI)
36+
LLMRegistry.register(Gemma3Ollama)

0 commit comments

Comments
 (0)