Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions contributing/samples/hello_world_gemma/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Hello World — Gemma 3

This sample demonstrates using **Gemma 3** models with ADK via the `Gemma`
class. The `Gemma` class provides workarounds for Gemma 3's lack of native
function calling and system instruction support.

## When to use this

Use this approach for **Gemma 3 models only**. For Gemma 4 and later, use the
standard `Gemini` class directly — see the
[`hello_world_gemma4/`](../hello_world_gemma4/) sample.

## Running this sample

```bash
# From the repository root
adk run contributing/samples/hello_world_gemma

# Or via the web UI
adk web contributing/samples
```

## Related samples

- [`hello_world_gemma4/`](../hello_world_gemma4/) — Gemma 4 via standard
Gemini class (recommended for Gemma 4+)
- [`hello_world_gemma3_ollama/`](../hello_world_gemma3_ollama/) — Gemma 3 via
Ollama
3 changes: 3 additions & 0 deletions contributing/samples/hello_world_gemma/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# This sample uses the `Gemma` class, which provides workarounds for Gemma 3's
# lack of native function calling. For Gemma 4+, use `Gemini` directly —
# see the hello_world_gemma4/ sample.

import random

Expand Down
4 changes: 4 additions & 0 deletions contributing/samples/hello_world_gemma3_ollama/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# This sample uses `Gemma3Ollama`, which provides workarounds for Gemma 3's
# lack of native function calling on Ollama. For Gemma 4+ on Ollama,
# use `LiteLlm` directly.

import logging
import random

Expand Down
58 changes: 58 additions & 0 deletions contributing/samples/hello_world_gemma4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Hello World — Gemma 4

This sample demonstrates using **Gemma 4** with ADK via the standard `Gemini`
class. Gemma 4 supports native function calling and system instructions, so no
special workaround classes are needed.

### Gemma 4 (this sample)

```python
from google.adk.agents.llm_agent import Agent
from google.adk.models.google_llm import Gemini

root_agent = Agent(
model=Gemini(model="gemma-4-31b-it"), # gemma-4-26b-a4b-it or gemma-4-31b-it
...
)
```

### Gemma 3

```python
from google.adk.agents.llm_agent import Agent
from google.adk.models.gemma_llm import Gemma

root_agent = Agent(
model=Gemma(model="gemma-3-27b-it"),
...
)
```

See the [`hello_world_gemma/`](../hello_world_gemma/) sample for the full
Gemma 3 example.

## Why separate classes?

The `Gemma` and `Gemma3Ollama` classes exist because Gemma 3 lacks native
function calling and system instruction support. They provide workarounds by:

- Injecting tool declarations into text prompts
- Parsing function calls from model text responses
- Converting system instructions to user-role messages

Gemma 4 doesn't need any of this — it works natively with the standard
`Gemini` class (via Gemini API) and `LiteLlm` class (via other providers like
Ollama).

## Running this sample

```bash
# From the repository root
adk run contributing/samples/hello_world_gemma4
```

## Related samples

- [`hello_world_gemma/`](../hello_world_gemma/) — Gemma 3 via Gemini API
- [`hello_world_gemma3_ollama/`](../hello_world_gemma3_ollama/) — Gemma 3 via
Ollama
16 changes: 16 additions & 0 deletions contributing/samples/hello_world_gemma4/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from . import agent
103 changes: 103 additions & 0 deletions contributing/samples/hello_world_gemma4/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Gemma 4 sample — uses the standard `Gemini` class directly.
# Gemma 4 supports native function calling and system instructions,
# so no workaround classes are needed.
# Compare with the hello_world_gemma/ sample (Gemma 3, requires workarounds).

import random

from google.adk.agents.llm_agent import Agent
from google.adk.models.google_llm import Gemini


def roll_die(sides: int) -> int:
"""Roll a die and return the rolled result.

Args:
sides: The integer number of sides the die has.

Returns:
An integer of the result of rolling the die.
"""
return random.randint(1, sides)


async def check_prime(nums: list[int]) -> str:
"""Check if a given list of numbers are prime.

Args:
nums: The list of numbers to check.

Returns:
A str indicating which number is prime.
"""
primes = set()
for number in nums:
number = int(number)
if number <= 1:
continue
is_prime = True
for i in range(2, int(number**0.5) + 1):
if number % i == 0:
is_prime = False
break
if is_prime:
primes.add(number)
return (
"No prime numbers found."
if not primes
else f"{', '.join(str(num) for num in primes)} are prime numbers."
)


root_agent = Agent(
model=Gemini(model="gemma-4-31b-it"),
name="data_processing_agent",
description=(
"Hello world agent using Gemma 4 via the standard Gemini class."
),
instruction="""\
You roll dice and answer questions about the outcome of the dice rolls.
You can roll dice of different sizes.
You can use multiple tools in parallel by calling functions in parallel
(in one request and in one round).
It is ok to discuss previous dice rolls, and comment on the dice rolls.
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.
You should never roll a die on your own.
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.
You should not check prime numbers before calling the tool.
When you are asked to roll a die and check prime numbers, you should
always make the following two function calls:
1. You should first call the roll_die tool to get a roll. Wait for the
function response before calling the check_prime tool.
2. After you get the function response from roll_die tool, you should
call the check_prime tool with the roll_die result.
2.1 If user asks you to check primes based on previous rolls, make
sure you include the previous rolls in the list.
3. When you respond, you must include the roll_die result from step 1.
You should always perform the previous 3 steps when asking for a roll
and checking prime numbers.
You should not rely on the previous history on prime results.
""",
tools=[
roll_die,
check_prime,
],
)
5 changes: 4 additions & 1 deletion src/google/adk/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@


LLMRegistry.register(Gemini)
# Gemma 3 integration (provides function calling workarounds).
# For Gemma 4+, use Gemini or LiteLlm directly.
LLMRegistry.register(Gemma)
LLMRegistry.register(ApigeeLlm)

Expand All @@ -54,7 +56,8 @@
# LiteLLM support requires: pip install google-adk[extensions]
pass

# Optionally register Gemma3Ollama if litellm package is installed
# Gemma 3 on Ollama (provides function calling workarounds).
# For Gemma 4+ on Ollama, use LiteLlm directly.
try:
from .gemma_llm import Gemma3Ollama

Expand Down
65 changes: 37 additions & 28 deletions src/google/adk/models/gemma_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@


class GemmaFunctionCallingMixin:
"""Mixin providing function calling support for Gemma models.
"""Mixin providing function calling support for Gemma 3 models.

Gemma models don't have native function calling support, so this mixin
Gemma 3 models don't have native function calling support, so this mixin
provides the logic to:
1. Convert function declarations to system instruction prompts
2. Convert function call/response parts to text in the conversation
3. Extract function calls from model text responses

This mixin is NOT needed for Gemma 4+, which supports function calling
natively through the standard Gemini/LiteLLM integrations.
"""

def _move_function_calls_into_system_instruction(
Expand Down Expand Up @@ -161,31 +164,29 @@ class GemmaFunctionCallModel(BaseModel):


class Gemma(GemmaFunctionCallingMixin, Gemini):
"""Integration for Gemma models exposed via the Gemini API.
"""Integration for Gemma 3 models exposed via the Gemini API.

This class is for **Gemma 3 only**. It provides workarounds for Gemma 3's
lack of native function calling and system instruction support:
- Tools are injected into text prompts (not passed via the API)
- Function calls are parsed from model text responses
- System instructions are converted to user-role messages

For Gemma 4 and later, use the standard ``Gemini`` class directly::

# Gemma 4 — use Gemini (native function calling & system instructions)
agent = Agent(model=Gemini(model="gemma-4-<size>"), ...)

# Gemma 3 — use this class (workarounds applied automatically)
agent = Agent(model=Gemma(model="gemma-3-27b-it"), ...)

Only Gemma 3 models are supported at this time. For agentic use cases,
use of gemma-3-27b-it and gemma-3-12b-it are strongly recommended.
For agentic use cases with Gemma 3, ``gemma-3-27b-it`` and ``gemma-3-12b-it``
are strongly recommended.

For full documentation, see: https://ai.google.dev/gemma/docs/core/

NOTE: Gemma does **NOT** support system instructions. Any system instructions
will be replaced with an initial *user* prompt in the LLM request. If system
instructions change over the course of agent execution, the initial content
**SHOULD** be replaced. Special care is warranted here.
See:
https://ai.google.dev/gemma/docs/core/prompt-structure#system-instructions

NOTE: Gemma's function calling support is limited. It does not have full
access to the
same built-in tools as Gemini. It also does not have special API support for
tools and
functions. Rather, tools must be passed in via a `user` prompt, and extracted
from model
responses based on approximate shape.

NOTE: Vertex AI API support for Gemma is not currently included. This **ONLY**
supports
usage via the Gemini API.
NOTE: This class only supports the Gemini API (Google AI Studio).
Vertex AI API support is not included.
"""

model: str = (
Expand Down Expand Up @@ -365,12 +366,20 @@ def _get_last_valid_json_substring(text: str) -> tuple[bool, str | None]:
class Gemma3Ollama(GemmaFunctionCallingMixin, LiteLlm):
"""Integration for Gemma 3 models running locally via Ollama.

This enables fully local agent workflows using Gemma 3 models.
Requires Ollama to be running with a Gemma 3 model pulled.
This class is for **Gemma 3 only**. It provides the same function calling
workarounds as the ``Gemma`` class, but routes through Ollama via LiteLLM.

For Gemma 4 and later on Ollama, use the standard ``LiteLlm`` class::

# Gemma 4 on Ollama — use LiteLlm directly
agent = Agent(model=LiteLlm(model="ollama_chat/gemma4:<size>"), ...)

# Gemma 3 on Ollama — use this class
agent = Agent(model=Gemma3Ollama(), ...)

Requires Ollama to be running with a Gemma 3 model pulled::

Example:
ollama pull gemma3:12b
model = Gemma3Ollama(model="ollama/gemma3:12b")
ollama pull gemma3:12b
"""

def __init__(self, model: str = 'ollama/gemma3:12b', **kwargs):
Expand Down
2 changes: 2 additions & 0 deletions src/google/adk/models/google_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ def supported_models(cls) -> list[str]:

return [
r'gemini-.*',
# Gemma 4+ works natively with Gemini (no workarounds needed).
r'gemma-4.*',
# model optimizer pattern
r'model-optimizer-.*',
# fine-tuned vertex endpoint pattern
Expand Down
11 changes: 11 additions & 0 deletions tests/unittests/models/test_gemma_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,17 @@ def test_process_response_last_json_object():
assert part.text is None


# Tests for Gemma 4 registry routing
def test_gemma4_resolves_to_gemini_not_gemma():
"""Gemma 4 models should use the standard Gemini class, not the Gemma
workaround class."""
from google.adk.models.google_llm import Gemini

resolved = models.LLMRegistry.resolve("gemma-4-31b-it")
assert resolved is not Gemma
assert resolved is Gemini


# Tests for Gemma3Ollama (only run when LiteLLM is installed)
try:
from google.adk.models.gemma_llm import Gemma3Ollama
Expand Down
9 changes: 5 additions & 4 deletions tests/unittests/models/test_google_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,13 @@ def llm_request_with_computer_use():

def test_supported_models():
models = Gemini.supported_models()
assert len(models) == 4
assert len(models) == 5
assert models[0] == r"gemini-.*"
assert models[1] == r"model-optimizer-.*"
assert models[2] == r"projects\/.+\/locations\/.+\/endpoints\/.+"
assert models[1] == r"gemma-4.*"
assert models[2] == r"model-optimizer-.*"
assert models[3] == r"projects\/.+\/locations\/.+\/endpoints\/.+"
assert (
models[3]
models[4]
== r"projects\/.+\/locations\/.+\/publishers\/google\/models\/gemini.+"
)

Expand Down
Loading