diff --git a/contributing/samples/hello_world_gemma/README.md b/contributing/samples/hello_world_gemma/README.md new file mode 100644 index 0000000000..627aabb247 --- /dev/null +++ b/contributing/samples/hello_world_gemma/README.md @@ -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 diff --git a/contributing/samples/hello_world_gemma/agent.py b/contributing/samples/hello_world_gemma/agent.py index c6e5640242..09a51efbd0 100644 --- a/contributing/samples/hello_world_gemma/agent.py +++ b/contributing/samples/hello_world_gemma/agent.py @@ -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 diff --git a/contributing/samples/hello_world_gemma3_ollama/agent.py b/contributing/samples/hello_world_gemma3_ollama/agent.py index ae89a45153..e48425a55b 100644 --- a/contributing/samples/hello_world_gemma3_ollama/agent.py +++ b/contributing/samples/hello_world_gemma3_ollama/agent.py @@ -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 diff --git a/contributing/samples/hello_world_gemma4/README.md b/contributing/samples/hello_world_gemma4/README.md new file mode 100644 index 0000000000..44bbd6634c --- /dev/null +++ b/contributing/samples/hello_world_gemma4/README.md @@ -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 diff --git a/contributing/samples/hello_world_gemma4/__init__.py b/contributing/samples/hello_world_gemma4/__init__.py new file mode 100644 index 0000000000..044e24d388 --- /dev/null +++ b/contributing/samples/hello_world_gemma4/__init__.py @@ -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 diff --git a/contributing/samples/hello_world_gemma4/agent.py b/contributing/samples/hello_world_gemma4/agent.py new file mode 100644 index 0000000000..849c16bd03 --- /dev/null +++ b/contributing/samples/hello_world_gemma4/agent.py @@ -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, + ], +) diff --git a/src/google/adk/models/__init__.py b/src/google/adk/models/__init__.py index 5dd3487943..7ec3fbe910 100644 --- a/src/google/adk/models/__init__.py +++ b/src/google/adk/models/__init__.py @@ -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) @@ -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 diff --git a/src/google/adk/models/gemma_llm.py b/src/google/adk/models/gemma_llm.py index f333bab241..06511d91e2 100644 --- a/src/google/adk/models/gemma_llm.py +++ b/src/google/adk/models/gemma_llm.py @@ -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( @@ -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-"), ...) + + # 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 = ( @@ -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:"), ...) + + # 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): diff --git a/src/google/adk/models/google_llm.py b/src/google/adk/models/google_llm.py index 609de3d3d3..4fcf25a737 100644 --- a/src/google/adk/models/google_llm.py +++ b/src/google/adk/models/google_llm.py @@ -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 diff --git a/tests/unittests/models/test_gemma_llm.py b/tests/unittests/models/test_gemma_llm.py index 07b4b092d3..5264ac860a 100644 --- a/tests/unittests/models/test_gemma_llm.py +++ b/tests/unittests/models/test_gemma_llm.py @@ -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 diff --git a/tests/unittests/models/test_google_llm.py b/tests/unittests/models/test_google_llm.py index 70aa01b69d..525455230a 100644 --- a/tests/unittests/models/test_google_llm.py +++ b/tests/unittests/models/test_google_llm.py @@ -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.+" )