chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -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
@@ -0,0 +1,90 @@
# 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.
import random
from google.adk import Agent
from google.adk.models.anthropic_llm import Claude
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=Claude(model="claude-3-5-sonnet-v2@20241022"),
name="hello_world_agent",
description=(
"hello world agent that can roll a dice of 8 sides and check prime"
" numbers."
),
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 roles, 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,
],
)
@@ -0,0 +1,76 @@
# 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.
import asyncio
import time
import agent
from dotenv import load_dotenv
from google.adk import Runner
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
logs.log_to_tmp_folder()
async def main():
app_name = 'my_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_11 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_11, 'Hi, introduce yourself.')
await run_prompt(
session_11,
'Run the following request 10 times: roll a die with 100 sides and check'
' if it is prime',
)
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -0,0 +1,8 @@
# This is a sample .env file.
# Copy this file to .env and replace the placeholder values with your actual credentials.
# Your Google API key for accessing Gemini models.
GOOGLE_API_KEY="your-google-api-key"
# The URL of your Apigee proxy.
APIGEE_PROXY_URL="https://your-apigee-proxy.net/basepath"
@@ -0,0 +1,94 @@
# Hello World with Apigee LLM
This sample demonstrates how to use the Agent Development Kit (ADK) with an LLM fronted by an Apigee proxy. It showcases the flexibility of the `ApigeeLlm` class in configuring the target LLM provider (Gemini or Vertex AI) and API version through the model string.
## Setup
Before running the sample, you need to configure your environment with the necessary credentials.
1. **Create a `.env` file:**
Copy the sample environment file to a new file named `.env` in the same directory.
```bash
cp .env-sample .env
```
1. **Set Environment Variables:**
Open the `.env` file and provide values for the following variables:
- `GOOGLE_API_KEY`: Your API key for the Google AI services (Gemini).
- `APIGEE_PROXY_URL`: The full URL of your Apigee proxy endpoint.
Example `.env` file:
```
GOOGLE_API_KEY="your-google-api-key"
APIGEE_PROXY_URL="https://your-apigee-proxy.net/basepath"
```
The `main.py` script will automatically load these variables when it runs.
## Run the Sample
Once your `.env` file is configured, you can run the sample with the following command:
```bash
python main.py
```
## Configuring the Apigee LLM
The `ApigeeLlm` class is configured using a special model string format in `agent.py`. This string determines which backend provider (Vertex AI or Gemini) and which API version to use.
### Model String Format
The supported format is:
`apigee/[<provider>/][<version>/]<model_id>`
- **`provider`** (optional): Can be `vertex_ai` or `gemini`.
- If specified, it forces the use of that provider.
- If omitted, the provider is determined by the `GOOGLE_GENAI_USE_ENTERPRISE` environment variable. If this variable is set to `true` or `1`, Vertex AI is used; otherwise, `gemini` is used by default.
- **`version`** (optional): The API version to use (e.g., `v1`, `v1beta`).
- If omitted, the default version for the selected provider is used.
- **`model_id`** (required): The identifier for the model you want to use (e.g., `gemini-2.5-flash`).
### Configuration Examples
Here are some examples of how to configure the model string in `agent.py` to achieve different behaviors:
1. **Implicit Provider (determined by environment variable):**
- `model="apigee/gemini-2.5-flash"`
- Uses the default API version.
- Provider is Vertex AI if `GOOGLE_GENAI_USE_ENTERPRISE` is true; otherwise, Gemini.
- `model="apigee/v1/gemini-2.5-flash"`
- Uses API version `v1`.
- Provider is determined by the environment variable.
1. **Explicit Provider (ignores environment variable):**
- `model="apigee/vertex_ai/gemini-2.5-flash"`
- Uses Vertex AI with the default API version.
- `model="apigee/gemini/gemini-2.5-flash"`
- Uses Gemini with the default API version.
- `model="apigee/gemini/v1/gemini-2.5-flash"`
- Uses Gemini with API version `v1`.
- `model="apigee/vertex_ai/v1beta/gemini-2.5-flash"`
- Uses Vertex AI with API version `v1beta`.
By modifying the `model` string in `agent.py`, you can test various configurations without changing the core logic of the agent.
@@ -0,0 +1,108 @@
# 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.
import random
from google.adk import Agent
from google.adk.tools.tool_context import ToolContext
from google.genai import types
def roll_die(sides: int, tool_context: ToolContext) -> 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.
"""
result = random.randint(1, sides)
if "rolls" not in tool_context.state:
tool_context.state["rolls"] = []
tool_context.state["rolls"] = tool_context.state["rolls"] + [result]
return result
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="apigee/gemini-2.5-flash",
name="hello_world_agent",
description=(
"hello world agent that can roll a dice of 8 sides and check prime"
" numbers."
),
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 roles, 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,
],
# planner=BuiltInPlanner(
# thinking_config=types.ThinkingConfig(
# include_thoughts=True,
# ),
# ),
generate_content_config=types.GenerateContentConfig(
safety_settings=[
types.SafetySetting( # avoid false alarm about rolling dice.
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.OFF,
),
]
),
)
@@ -0,0 +1,112 @@
# 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.
import asyncio
import os
import time
import agent
from dotenv import load_dotenv
from google.adk.agents.run_config import RunConfig
from google.adk.cli.utils import logs
from google.adk.runners import InMemoryRunner
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
logs.log_to_tmp_folder()
async def main():
app_name = "my_app"
user_id_1 = "user1"
runner = InMemoryRunner(
agent=agent.root_agent,
app_name=app_name,
)
session_11 = await runner.session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role="user", parts=[types.Part.from_text(text=new_message)]
)
print("** User says:", content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f"** {event.author}: {event.content.parts[0].text}")
async def run_prompt_bytes(session: Session, new_message: str):
content = types.Content(
role="user",
parts=[
types.Part.from_bytes(
data=str.encode(new_message), mime_type="text/plain"
)
],
)
print("** User says:", content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
run_config=RunConfig(save_input_blobs_as_artifacts=True),
):
if event.content.parts and event.content.parts[0].text:
print(f"** {event.author}: {event.content.parts[0].text}")
async def check_rolls_in_state(rolls_size: int):
session = await runner.session_service.get_session(
app_name=app_name, user_id=user_id_1, session_id=session_11.id
)
assert len(session.state["rolls"]) == rolls_size
for roll in session.state["rolls"]:
assert roll > 0 and roll <= 100
start_time = time.time()
print("Start time:", start_time)
print("------------------------------------")
await run_prompt(session_11, "Hi")
await run_prompt(session_11, "Roll a die with 100 sides")
await check_rolls_in_state(1)
await run_prompt(session_11, "Roll a die again with 100 sides.")
await check_rolls_in_state(2)
await run_prompt(session_11, "What numbers did I got?")
await run_prompt_bytes(session_11, "Hi bytes")
print(
await runner.artifact_service.list_artifact_keys(
app_name=app_name, user_id=user_id_1, session_id=session_11.id
)
)
end_time = time.time()
print("------------------------------------")
print("End time:", end_time)
print("Total time:", end_time - start_time)
if __name__ == "__main__":
# The API key can be set in a .env file.
# For example, create a .env file with the following content:
# GOOGLE_API_KEY="your-api-key"
# APIGEE_PROXY_URL="your-proxy-url"
if not os.getenv("GOOGLE_API_KEY"):
raise ValueError("GOOGLE_API_KEY environment variable is not set.")
if not os.getenv("APIGEE_PROXY_URL"):
raise ValueError("APIGEE_PROXY_URL environment variable is not set.")
asyncio.run(main())
@@ -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
@@ -0,0 +1,95 @@
# 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.
import random
from google.adk.agents.llm_agent import Agent
from google.adk.models.gemma_llm import Gemma
from google.genai.types import GenerateContentConfig
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 = 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=Gemma(model="gemma-3-27b-it"),
name="data_processing_agent",
description=(
"hello world agent that can roll many-sided dice and check if numbers"
" are prime."
),
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 roles, 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 the user reports a 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,
],
generate_content_config=GenerateContentConfig(
temperature=1.0,
top_p=0.95,
),
)
@@ -0,0 +1,77 @@
# 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.
import asyncio
import logging
import time
import agent
from dotenv import load_dotenv
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
logs.log_to_tmp_folder(level=logging.INFO)
async def main():
app_name = 'my_gemma_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_11 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_11, 'Hi, introduce yourself.')
await run_prompt(
session_11, 'Roll a die with 100 sides and check if it is prime'
)
await run_prompt(session_11, 'Roll it again.')
await run_prompt(session_11, 'What numbers did I get?')
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -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
@@ -0,0 +1,93 @@
# 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.
import logging
import random
from google.adk.agents.llm_agent import Agent
from google.adk.models import Gemma3Ollama
litellm_logger = logging.getLogger("LiteLLM")
litellm_logger.setLevel(logging.WARNING)
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=Gemma3Ollama(),
name="data_processing_agent",
description=(
"hello world agent that can roll a dice of 8 sides and check prime"
" numbers."
),
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,
],
)
@@ -0,0 +1,77 @@
# 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.
import asyncio
import time
import agent
from dotenv import load_dotenv
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
logs.log_to_tmp_folder()
async def main():
app_name = 'my_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_1 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_1, 'Hi, introduce yourself.')
await run_prompt(
session_1, 'Roll a die with 100 sides and check if it is prime'
)
await run_prompt(session_1, 'Roll it again.')
await run_prompt(session_1, 'What numbers did I get?')
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -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
@@ -0,0 +1,94 @@
# 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.
import random
from google.adk.agents.llm_agent import Agent
from google.adk.models.lite_llm import LiteLlm
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=LiteLlm(model="gemini/gemini-2.5-pro-exp-03-25"),
# model=LiteLlm(model="vertex_ai/gemini-2.5-pro-exp-03-25"),
# model=LiteLlm(model="vertex_ai/claude-3-5-haiku"),
model=LiteLlm(model="openai/gpt-4o"),
# model=LiteLlm(model="anthropic/claude-3-sonnet-20240229"),
name="data_processing_agent",
description=(
"hello world agent that can roll a dice of 8 sides and check prime"
" numbers."
),
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 roles, 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,
],
)
@@ -0,0 +1,76 @@
# 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.
import asyncio
import time
import agent
from dotenv import load_dotenv
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
logs.log_to_tmp_folder()
async def main():
app_name = 'my_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_11 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_11, 'Hi, introduce yourself.')
await run_prompt(
session_11, 'Roll a die with 100 sides and check if it is prime'
)
await run_prompt(session_11, 'Roll it again.')
await run_prompt(session_11, 'What numbers did I got?')
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -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
@@ -0,0 +1,81 @@
# 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.
import random
from google.adk import Agent
from google.adk.models.lite_llm import LiteLlm
# Ensures langchain_core.tools.base is importable; langchain-core 1.x does not
# auto-import it, so convert_to_openai_function fails without this.
import langchain_core.tools
from langchain_core.utils.function_calling import convert_to_openai_function
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)
def check_prime(number: int) -> str:
"""Check if a given number is prime.
Args:
number: The input number to check.
Returns:
A str indicating the number is prime or not.
"""
if number <= 1:
return f"{number} is not prime."
is_prime = True
for i in range(2, int(number**0.5) + 1):
if number % i == 0:
is_prime = False
break
if is_prime:
return f"{number} is prime."
else:
return f"{number} is not prime."
root_agent = Agent(
model=LiteLlm(
model="vertex_ai/meta/llama-4-maverick-17b-128e-instruct-maas",
# If the model is not trained with functions and you would like to
# enable function calling, you can add functions to the models, and the
# functions will be added to the prompts during inferences.
functions=[
convert_to_openai_function(roll_die),
convert_to_openai_function(check_prime),
],
),
name="data_processing_agent",
description="""You are a helpful assistant.""",
instruction="""
You are a helpful assistant, and call tools optionally.
If call tools, the tool format should be in json, and the tool arguments should be parsed from users inputs.
""",
tools=[
roll_die,
check_prime,
],
)
@@ -0,0 +1,81 @@
# 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.
import asyncio
import time
import agent
from dotenv import load_dotenv
from google.adk import Runner
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
logs.log_to_tmp_folder()
async def main():
app_name = 'my_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_11 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts:
part = event.content.parts[0]
if part.text:
print(f'** {event.author}: {part.text}')
if part.function_call:
print(f'** {event.author} calls tool: {part.function_call}')
if part.function_response:
print(
f'** {event.author} gets tool response: {part.function_response}'
)
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_11, 'Hi, introduce yourself.')
await run_prompt(session_11, 'Roll a die with 100 sides.')
await run_prompt(session_11, 'Check if it is prime.')
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -0,0 +1,115 @@
# Using ollama models with ADK
## Model choice
If your agent is relying on tools, please make sure that you select a model with tool support from [ollama website](https://ollama.com/search?c=tools).
For reliable results, we recommend using a decent size model with tool support.
The tool support for the model can be checked with the following command:
```bash
ollama show mistral-small3.1
Model
architecture mistral3
parameters 24.0B
context length 131072
embedding length 5120
quantization Q4_K_M
Capabilities
completion
vision
tools
```
You are supposed to see `tools` listed under capabilities.
You can also look at the model's template and tweak it based on your needs.
```bash
ollama show --modelfile llama3.1 > model_file_to_modify
```
Then you can create a model with the following command:
```bash
ollama create llama3.1-modified -f model_file_to_modify
```
## Using ollama_chat provider
Our LiteLlm wrapper can be used to create agents with ollama models.
```py
root_agent = Agent(
model=LiteLlm(model="ollama_chat/mistral-small3.1"),
name="dice_agent",
description=(
"hello world agent that can roll a dice of 8 sides and check prime"
" numbers."
),
instruction="""
You roll dice and answer questions about the outcome of the dice rolls.
""",
tools=[
roll_die,
check_prime,
],
)
```
**It is important to set the provider `ollama_chat` instead of `ollama`. Using `ollama` will result in unexpected behaviors such as infinite tool call loops and ignoring previous context.**
While `api_base` can be provided inside litellm for generation, litellm library is calling other APIs relying on the env variable instead as of v1.65.5 after completion. So at this time, we recommend setting the env variable `OLLAMA_API_BASE` to point to the ollama server.
```bash
export OLLAMA_API_BASE="http://localhost:11434"
adk web
```
## Using openai provider
Alternatively, `openai` can be used as the provider name. But this will also require setting the `OPENAI_API_BASE=http://localhost:11434/v1` and `OPENAI_API_KEY=anything` env variables instead of `OLLAMA_API_BASE`. **Please notice that api base now has `/v1` at the end.**
```py
root_agent = Agent(
model=LiteLlm(model="openai/mistral-small3.1"),
name="dice_agent",
description=(
"hello world agent that can roll a dice of 8 sides and check prime"
" numbers."
),
instruction="""
You roll dice and answer questions about the outcome of the dice rolls.
""",
tools=[
roll_die,
check_prime,
],
)
```
```bash
export OPENAI_API_BASE=http://localhost:11434/v1
export OPENAI_API_KEY=anything
adk web
```
## Debugging
You can see the request sent to the ollama server by adding the following in your agent code just after imports.
```py
import litellm
litellm._turn_on_debug()
```
Look for a line like the following:
```bash
quest Sent from LiteLLM:
curl -X POST \
http://localhost:11434/api/chat \
-d '{'model': 'mistral-small3.1', 'messages': [{'role': 'system', 'content': ...
```
+15
View File
@@ -0,0 +1,15 @@
# 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
+89
View File
@@ -0,0 +1,89 @@
# 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.
import random
from google.adk.agents.llm_agent import Agent
from google.adk.models.lite_llm import LiteLlm
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)
def check_prime(numbers: list[int]) -> str:
"""Check if a given list of numbers are prime.
Args:
numbers: The list of numbers to check.
Returns:
A str indicating which number is prime.
"""
primes = set()
for number in numbers:
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=LiteLlm(model="ollama_chat/mistral-small3.1"),
name="dice_roll_agent",
description=(
"hello world agent that can roll a dice of any number of sides and"
" check prime numbers."
),
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 roles, 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,
],
)
+77
View File
@@ -0,0 +1,77 @@
# 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.
import asyncio
import time
import warnings
import agent
from dotenv import load_dotenv
from google.adk import Runner
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types
load_dotenv(override=True)
warnings.filterwarnings('ignore', category=UserWarning)
logs.log_to_tmp_folder()
async def main():
app_name = 'my_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_11 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_11, 'Hi, introduce yourself.')
await run_prompt(
session_11, 'Roll a die with 100 sides and check if it is prime'
)
await run_prompt(session_11, 'Roll it again.')
await run_prompt(session_11, 'What numbers did I get?')
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -0,0 +1,155 @@
# Interactions API Sample Agent
This sample agent demonstrates the Interactions API integration in ADK. The
Interactions API provides stateful conversation capabilities, allowing chained
interactions using `previous_interaction_id` instead of sending full
conversation history.
## Features Tested
1. **Basic Text Generation** - Simple conversation without tools
1. **Google Search Tool** - Web search using `GoogleSearchTool` with
`bypass_multi_tools_limit=True`
1. **Multi-Turn Conversations** - Stateful interactions with context retention
via `previous_interaction_id`
1. **Custom Function Tool** - Weather lookup using `get_current_weather`
## Important: Tool Compatibility
The Interactions API does **NOT** support mixing custom function calling tools
with built-in tools (like `google_search`) in the same agent. To work around
this limitation:
```python
# Use bypass_multi_tools_limit=True to convert google_search to a function tool
GoogleSearchTool(bypass_multi_tools_limit=True)
```
This converts the built-in `google_search` to a function calling tool (via
`GoogleSearchAgentTool`), which allows it to work alongside custom function
tools.
## How to Run
### Prerequisites
```bash
# From the adk-python root directory
uv sync --all-extras
source .venv/bin/activate
# Set up authentication (choose one):
# Option 1: Using Google Cloud credentials
export GOOGLE_CLOUD_PROJECT=your-project-id
# Option 2: Using API Key
export GOOGLE_API_KEY=your-api-key
```
### Running Tests
```bash
cd contributing/samples
# Run automated tests with Interactions API
python -m interactions_api.main
```
## Key Differences: Interactions API vs Standard API
### Interactions API (`use_interactions_api=True`)
- Uses stateful interactions via `previous_interaction_id`
- Only sends current turn contents when chaining interactions
- Returns `interaction_id` in responses for chaining
- Ideal for long conversations with many turns
- Context caching is not used (state maintained via interaction chaining)
### Standard API (`use_interactions_api=False`)
- Uses stateless `generate_content` calls
- Sends full conversation history with each request
- No interaction IDs in responses
- Context caching can be used
## Code Structure
```
interactions_api/
├── __init__.py # Package initialization
├── agent.py # Agent definition with Interactions API
├── main.py # Test runner
├── test_interactions_curl.sh # cURL-based API tests
├── test_interactions_direct.py # Direct API tests
└── README.md # This file
```
## Agent Configuration
```python
from google.adk.agents.llm_agent import Agent
from google.adk.models.google_llm import Gemini
from google.adk.tools.google_search_tool import GoogleSearchTool
root_agent = Agent(
model=Gemini(
model="gemini-2.5-flash",
use_interactions_api=True, # Enable Interactions API
),
name="interactions_test_agent",
tools=[
GoogleSearchTool(bypass_multi_tools_limit=True), # Converted to function tool
get_current_weather, # Custom function tool
],
)
```
## Example Output
```
============================================================
TEST 1: Basic Text Generation
============================================================
>> User: Hello! What can you help me with?
<< Agent: Hello! I can help you with: 1) Search the web...
[Interaction ID: v1_abc123...]
PASSED: Basic text generation works
============================================================
TEST 2: Function Calling (Google Search Tool)
============================================================
>> User: Search for the capital of France.
[Tool Call] google_search_agent({'request': 'capital of France'})
[Tool Result] google_search_agent: {'result': 'The capital of France is Paris...'}
<< Agent: The capital of France is Paris.
[Interaction ID: v1_def456...]
PASSED: Google search tool works
============================================================
TEST 3: Multi-Turn Conversation (Stateful)
============================================================
>> User: Remember the number 42.
<< Agent: I'll remember that number - 42.
[Interaction ID: v1_ghi789...]
>> User: What number did I ask you to remember?
<< Agent: You asked me to remember the number 42.
[Interaction ID: v1_jkl012...]
PASSED: Multi-turn conversation works with context retention
============================================================
TEST 5: Custom Function Tool (get_current_weather)
============================================================
>> User: What's the weather like in Tokyo?
[Tool Call] get_current_weather({'city': 'Tokyo'})
[Tool Result] get_current_weather: {'city': 'Tokyo', 'temperature_f': 68, ...}
<< Agent: The weather in Tokyo is 68F and Partly Cloudy.
[Interaction ID: v1_mno345...]
PASSED: Custom function tool works with bypass_multi_tools_limit
ALL TESTS PASSED (Interactions API)
```
@@ -0,0 +1,17 @@
# 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.
"""Sample agent for testing the Interactions API integration."""
from . import agent
@@ -0,0 +1,90 @@
# 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.
"""Agent definition for testing the Interactions API integration."""
from google.adk.agents.llm_agent import Agent
from google.adk.models.google_llm import Gemini
from google.adk.tools.google_search_tool import GoogleSearchTool
def get_current_weather(city: str) -> dict:
"""Get the current weather for a city.
This is a mock implementation for testing purposes.
Args:
city: The name of the city to get weather for.
Returns:
A dictionary containing weather information.
"""
# Mock weather data for testing
weather_data = {
"new york": {"temperature": 72, "condition": "Sunny", "humidity": 45},
"london": {"temperature": 59, "condition": "Cloudy", "humidity": 78},
"tokyo": {
"temperature": 68,
"condition": "Partly Cloudy",
"humidity": 60,
},
"paris": {"temperature": 64, "condition": "Rainy", "humidity": 85},
"sydney": {"temperature": 77, "condition": "Clear", "humidity": 55},
}
city_lower = city.lower()
if city_lower in weather_data:
data = weather_data[city_lower]
return {
"city": city,
"temperature_f": data["temperature"],
"condition": data["condition"],
"humidity": data["humidity"],
}
else:
return {
"city": city,
"temperature_f": 70,
"condition": "Unknown",
"humidity": 50,
"note": "Weather data not available, using defaults",
}
# Main agent with google_search built-in tool and custom function tools
#
# NOTE: code_executor is not compatible with function calling mode because the model
# tries to call a function (e.g., run_code) instead of outputting code in markdown.
root_agent = Agent(
model=Gemini(
model="gemini-3.1-flash-lite",
use_interactions_api=True,
),
name="interactions_test_agent",
description="An agent for testing the Interactions API integration",
instruction="""You are a helpful assistant that can:
1. Search the web for information using google_search
2. Get weather information using get_current_weather
When users ask for information that requires searching, use google_search.
When users ask about weather, use get_current_weather.
Be concise and helpful in your responses. Always confirm what you did.
""",
tools=[
GoogleSearchTool(),
get_current_weather,
],
)
@@ -0,0 +1,452 @@
# 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.
"""Main script for testing the Interactions API integration.
This script tests the following features:
1. Basic text generation
2. Google Search tool
3. Multi-turn conversations with stateful interactions
4. Google Search tool (additional coverage)
5. Custom function tool (get_current_weather)
NOTE: Code execution via UnsafeLocalCodeExecutor is not compatible with function
calling mode because the model tries to call a function instead of outputting
code in markdown.
Run with:
cd contributing/samples
python -m interactions_api_test.main
"""
import argparse
import asyncio
import logging
from pathlib import Path
import time
from dotenv import load_dotenv
from google.adk.agents.run_config import RunConfig
from google.adk.cli.utils import logs
from google.adk.runners import InMemoryRunner
from google.adk.runners import Runner
from google.genai import types
import httpx
from .agent import root_agent
# Load .env from the samples directory (parent of this module's directory)
_env_path = Path(__file__).parent.parent / ".env"
load_dotenv(_env_path)
APP_NAME = "interactions_api_test_app"
USER_ID = "test_user"
async def call_agent_async(
runner: Runner,
user_id: str,
session_id: str,
prompt: str,
agent_name: str = "",
show_interaction_id: bool = True,
additional_parts: list[types.Part] | None = None,
) -> tuple[str, str | None]:
"""Call the agent asynchronously with the user's prompt.
Args:
runner: The agent runner
user_id: The user ID
session_id: The session ID
prompt: The prompt to send
agent_name: The expected agent name for filtering responses
show_interaction_id: Whether to show interaction IDs in output
additional_parts: Optional list of additional content parts (e.g. files)
Returns:
A tuple of (response_text, interaction_id)
"""
parts = [types.Part.from_text(text=prompt)]
if additional_parts:
parts.extend(additional_parts)
content = types.Content(role="user", parts=parts)
final_response_text = ""
last_interaction_id = None
print(f"\n>> User: {prompt}")
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=content,
run_config=RunConfig(save_input_blobs_as_artifacts=False),
):
# Track interaction ID if available
if event.interaction_id:
last_interaction_id = event.interaction_id
# Show function calls
if event.get_function_calls():
for fc in event.get_function_calls():
print(f" [Tool Call] {fc.name}({fc.args})")
# Show function responses
if event.get_function_responses():
for fr in event.get_function_responses():
print(f" [Tool Result] {fr.name}: {fr.response}")
# Collect text responses from the agent (not user, not partial)
if (
event.content
and event.content.parts
and event.author != "user"
and not event.partial
):
for part in event.content.parts:
if part.text:
# Filter by agent name if provided, otherwise accept any non-user
if not agent_name or event.author == agent_name:
final_response_text += part.text
print(f"<< Agent: {final_response_text}")
if show_interaction_id and last_interaction_id:
print(f" [Interaction ID: {last_interaction_id}]")
return final_response_text, last_interaction_id
async def test_basic_text_generation(runner: Runner, session_id: str):
"""Test basic text generation without tools."""
print("\n" + "=" * 60)
print("TEST 1: Basic Text Generation")
print("=" * 60)
response, interaction_id = await call_agent_async(
runner, USER_ID, session_id, "Hello! What can you help me with?"
)
assert response, "Expected a non-empty response"
print("PASSED: Basic text generation works")
return interaction_id
async def test_function_calling(runner: Runner, session_id: str):
"""Test function calling with the google_search tool."""
print("\n" + "=" * 60)
print("TEST 2: Function Calling (Google Search Tool)")
print("=" * 60)
response, interaction_id = await call_agent_async(
runner,
USER_ID,
session_id,
"Search for the capital of France.",
)
assert response, "Expected a non-empty response"
assert "paris" in response.lower(), f"Expected Paris in response: {response}"
print("PASSED: Google search tool works")
return interaction_id
async def test_multi_turn_conversation(runner: Runner, session_id: str):
"""Test multi-turn conversation to verify stateful interactions."""
print("\n" + "=" * 60)
print("TEST 3: Multi-Turn Conversation (Stateful)")
print("=" * 60)
# Turn 1: Tell the agent a fact directly (test conversation memory)
response1, id1 = await call_agent_async(
runner,
USER_ID,
session_id,
"My favorite color is blue. Just acknowledge this, don't use any tools.",
)
assert response1, "Expected a response for turn 1"
print(f" Turn 1 interaction_id: {id1}")
# Turn 2: Ask about something else (use weather tool to add variety)
response2, id2 = await call_agent_async(
runner,
USER_ID,
session_id,
"What's the weather like in London?",
)
assert response2, "Expected a response for turn 2"
assert (
"59" in response2
or "london" in response2.lower()
or "cloudy" in response2.lower()
), f"Expected London weather info in response: {response2}"
print(f" Turn 2 interaction_id: {id2}")
# Turn 3: Ask the agent to recall conversation context
response3, id3 = await call_agent_async(
runner,
USER_ID,
session_id,
"What is my favorite color that I mentioned earlier in our conversation?",
)
assert response3, "Expected a response for turn 3"
assert (
"blue" in response3.lower()
), f"Expected agent to remember the color 'blue': {response3}"
print(f" Turn 3 interaction_id: {id3}")
# Verify interaction IDs are different (new interactions) but chained
if id1 and id2 and id3:
print(f" Interaction chain: {id1} -> {id2} -> {id3}")
print("PASSED: Multi-turn conversation works with context retention")
async def test_google_search_tool(runner: Runner, session_id: str):
"""Test the google_search built-in tool."""
print("\n" + "=" * 60)
print("TEST 4: Google Search Tool (Additional)")
print("=" * 60)
response, interaction_id = await call_agent_async(
runner,
USER_ID,
session_id,
"Use google search to find out who wrote the novel '1984'.",
)
assert response, "Expected a non-empty response"
assert (
"orwell" in response.lower() or "george" in response.lower()
), f"Expected George Orwell in response: {response}"
print("PASSED: Google search built-in tool works")
async def test_custom_function_tool(runner: Runner, session_id: str):
"""Test the custom function tool alongside google_search.
The root_agent has both GoogleSearchTool (with bypass_multi_tools_limit=True)
and get_current_weather. This tests that function calling tools work with
the Interactions API when all tools are function calling types.
"""
print("\n" + "=" * 60)
print("TEST 5: Custom Function Tool (get_current_weather)")
print("=" * 60)
response, interaction_id = await call_agent_async(
runner,
USER_ID,
session_id,
"What's the weather like in Tokyo?",
)
assert response, "Expected a non-empty response"
# The mock weather data for Tokyo has temperature 68, condition "Partly Cloudy"
assert (
"68" in response
or "partly" in response.lower()
or "tokyo" in response.lower()
), f"Expected weather info for Tokyo in response: {response}"
print("PASSED: Custom function tool works with bypass_multi_tools_limit")
return interaction_id
async def test_pdf_summarization(runner: Runner, session_id: str) -> str | None:
"""Test PDF summarization using the Interactions API."""
print("\n" + "=" * 60)
print("TEST 6: PDF Summarization")
print("=" * 60)
url = "https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/2403.05530.pdf"
print(f"Downloading {url}...")
async with httpx.AsyncClient() as client:
response = await client.get(
url, headers={"User-Agent": "Mozilla/5.0"}, follow_redirects=True
)
response.raise_for_status()
pdf_bytes = response.content
pdf_part = types.Part.from_bytes(data=pdf_bytes, mime_type="application/pdf")
response, interaction_id = await call_agent_async(
runner,
USER_ID,
session_id,
"Please summarize the attached PDF document.",
additional_parts=[pdf_part],
)
assert response, "Expected a non-empty response"
assert len(response) > 0, f"Expected summary in response: {response}"
assert (
"gemini" in response.lower() or "multimodal" in response.lower()
), f"Expected summary of PDF in response: {response}"
print("PASSED: PDF Summarization works")
return interaction_id
def check_interactions_api_available() -> bool:
"""Check if the interactions API is available in the SDK."""
try:
from google.genai import Client
client = Client()
# Check if interactions attribute exists
return hasattr(client.aio, "interactions")
except Exception:
return False
async def run_all_tests():
"""Run all tests with the Interactions API."""
print("\n" + "#" * 70)
print("# Running tests with Interactions API")
print("#" * 70)
# Check if interactions API is available
if not check_interactions_api_available():
print("\nERROR: Interactions API is not available in the current SDK.")
print("The interactions API requires a SDK version with this feature.")
print("To use the interactions API, ensure you have the SDK with")
print("interactions support installed (e.g., from private-python-genai).")
return False
test_agent = root_agent
runner = InMemoryRunner(
agent=test_agent,
app_name=APP_NAME,
)
# Create a new session
session = await runner.session_service.create_session(
user_id=USER_ID,
app_name=APP_NAME,
)
print(f"\nSession created: {session.id}")
try:
# Run all tests
await test_basic_text_generation(runner, session.id)
await test_function_calling(runner, session.id)
await test_multi_turn_conversation(runner, session.id)
await test_google_search_tool(runner, session.id)
await test_custom_function_tool(runner, session.id)
await test_pdf_summarization(runner, session.id)
print("\n" + "=" * 60)
print("ALL TESTS PASSED (Interactions API)")
print("=" * 60)
return True
except AssertionError as e:
print(f"\nTEST FAILED: {e}")
return False
except Exception as e:
print(f"\nERROR: {e}")
import traceback
traceback.print_exc()
return False
async def interactive_mode():
"""Run in interactive mode for manual testing."""
# Check if interactions API is available
if not check_interactions_api_available():
print("\nERROR: Interactions API is not available in the current SDK.")
print("To use the interactions API, ensure you have the SDK with")
print("interactions support installed (e.g., from private-python-genai).")
return
print("\nInteractive mode with Interactions API")
print("Type 'quit' to exit, 'new' for a new session\n")
test_agent = agent.root_agent
runner = InMemoryRunner(
agent=test_agent,
app_name=APP_NAME,
)
session = await runner.session_service.create_session(
user_id=USER_ID,
app_name=APP_NAME,
)
print(f"Session created: {session.id}\n")
while True:
try:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
break
if user_input.lower() == "new":
session = await runner.session_service.create_session(
user_id=USER_ID,
app_name=APP_NAME,
)
print(f"New session created: {session.id}\n")
continue
await call_agent_async(runner, USER_ID, session.id, user_input)
except KeyboardInterrupt:
break
print("\nGoodbye!")
def main():
parser = argparse.ArgumentParser(
description="Test the Interactions API integration"
)
parser.add_argument(
"--mode",
choices=["test", "interactive"],
default="test",
help=(
"Run mode: 'test' runs automated tests, 'interactive' for manual"
" testing"
),
)
parser.add_argument(
"--debug",
action="store_true",
help="Enable debug logging",
)
args = parser.parse_args()
if args.debug:
logs.setup_adk_logger(level=logging.DEBUG)
else:
logs.setup_adk_logger(level=logging.INFO)
start_time = time.time()
if args.mode == "test":
success = asyncio.run(run_all_tests())
if not success:
exit(1)
elif args.mode == "interactive":
asyncio.run(interactive_mode())
end_time = time.time()
print(f"\nTotal execution time: {end_time - start_time:.2f} seconds")
if __name__ == "__main__":
main()
@@ -0,0 +1,37 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "Hello! What can you help me with?"
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "Hello! I am \"interactions_test_agent,\" an agent designed for testing the Interactions API integration.\n\nI can help you with two main tasks:\n1. **Searching the web:** If you have questions that require up-to-date information, I can search the web for you.\n2. **Checking the weather:** I can provide the current weather for a specific city.\n\nHow can I assist you today?"
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
}
]
}
@@ -0,0 +1,86 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "What's the weather like in Tokyo?"
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"city": "Tokyo"
},
"id": "fc-1",
"name": "get_current_weather"
}
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"longRunningToolIds": [],
"nodeInfo": {
"path": "interactions_test_agent@1"
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-1",
"name": "get_current_weather",
"response": {
"city": "Tokyo",
"condition": "Partly Cloudy",
"humidity": 60,
"temperature_f": 68
}
}
}
],
"role": "user"
},
"id": "e-3",
"invocationId": "i-1",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "I have retrieved the weather information for Tokyo. It is currently 68\u00b0F and partly cloudy with 60% humidity."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-4",
"invocationId": "i-1",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
}
]
}
@@ -0,0 +1,40 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "Use google search to find out who wrote the novel '1984'."
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "search_suggestions='<style>\\n.container {\\n align-items: center;\\n border-radius: 8px;\\n display: flex;\\n font-family: Google Sans, Roboto, sans-serif;\\n font-size: 14px;\\n line-height: 20px;\\n padding: 8px 12px;\\n}\\n.chip {\\n display: inline-block;\\n border: solid 1px;\\n border-radius: 16px;\\n min-width: 14px;\\n padding: 5px 16px;\\n text-align: center;\\n user-select: none;\\n margin: 0 8px;\\n -webkit-tap-highlight-color: transparent;\\n}\\n.carousel {\\n overflow: auto;\\n scrollbar-width: none;\\n white-space: nowrap;\\n margin-right: -12px;\\n}\\n.headline {\\n display: flex;\\n margin-right: 4px;\\n}\\n.gradient-container {\\n position: relative;\\n}\\n.gradient {\\n position: absolute;\\n transform: translate(3px, -9px);\\n height: 36px;\\n width: 9px;\\n}\\n@media (prefers-color-scheme: light) {\\n .container {\\n background-color: #fafafa;\\n box-shadow: 0 0 0 1px #0000000f;\\n }\\n .headline-label {\\n color: #1f1f1f;\\n }\\n .chip {\\n background-color: #ffffff;\\n border-color: #d2d2d2;\\n color: #5e5e5e;\\n text-decoration: none;\\n }\\n .chip:hover {\\n background-color: #f2f2f2;\\n }\\n .chip:focus {\\n background-color: #f2f2f2;\\n }\\n .chip:active {\\n background-color: #d8d8d8;\\n border-color: #b6b6b6;\\n }\\n .logo-dark {\\n display: none;\\n }\\n .gradient {\\n background: linear-gradient(90deg, #fafafa 15%, #fafafa00 100%);\\n }\\n}\\n@media (prefers-color-scheme: dark) {\\n .container {\\n background-color: #1f1f1f;\\n box-shadow: 0 0 0 1px #ffffff26;\\n }\\n .headline-label {\\n color: #fff;\\n }\\n .chip {\\n background-color: #2c2c2c;\\n border-color: #3c4043;\\n color: #fff;\\n text-decoration: none;\\n }\\n .chip:hover {\\n background-color: #353536;\\n }\\n .chip:focus {\\n background-color: #353536;\\n }\\n .chip:active {\\n background-color: #464849;\\n border-color: #53575b;\\n }\\n .logo-light {\\n display: none;\\n }\\n .gradient {\\n background: linear-gradient(90deg, #1f1f1f 15%, #1f1f1f00 100%);\\n }\\n}\\n</style>\\n<div class=\"container\">\\n <div class=\"headline\">\\n <svg class=\"logo-light\" width=\"18\" height=\"18\" viewBox=\"9 9 35 35\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M42.8622 27.0064C42.8622 25.7839 42.7525 24.6084 42.5487 23.4799H26.3109V30.1568H35.5897C35.1821 32.3041 33.9596 34.1222 32.1258 35.3448V39.6864H37.7213C40.9814 36.677 42.8622 32.2571 42.8622 27.0064V27.0064Z\" fill=\"#4285F4\"/>\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M26.3109 43.8555C30.9659 43.8555 34.8687 42.3195 37.7213 39.6863L32.1258 35.3447C30.5898 36.3792 28.6306 37.0061 26.3109 37.0061C21.8282 37.0061 18.0195 33.9811 16.6559 29.906H10.9194V34.3573C13.7563 39.9841 19.5712 43.8555 26.3109 43.8555V43.8555Z\" fill=\"#34A853\"/>\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M16.6559 29.8904C16.3111 28.8559 16.1074 27.7588 16.1074 26.6146C16.1074 25.4704 16.3111 24.3733 16.6559 23.3388V18.8875H10.9194C9.74388 21.2072 9.06992 23.8247 9.06992 26.6146C9.06992 29.4045 9.74388 32.022 10.9194 34.3417L15.3864 30.8621L16.6559 29.8904V29.8904Z\" fill=\"#FBBC05\"/>\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M26.3109 16.2386C28.85 16.2386 31.107 17.1164 32.9095 18.8091L37.8466 13.8719C34.853 11.082 30.9659 9.3736 26.3109 9.3736C19.5712 9.3736 13.7563 13.245 10.9194 18.8875L16.6559 23.3388C18.0195 19.2636 21.8282 16.2386 26.3109 16.2386V16.2386Z\" fill=\"#EA4335\"/>\\n </svg>\\n <svg class=\"logo-dark\" width=\"18\" height=\"18\" viewBox=\"0 0 48 48\" xmlns=\"http://www.w3.org/2000/svg\">\\n <circle cx=\"24\" cy=\"23\" fill=\"#FFF\" r=\"22\"/>\\n <path d=\"M33.76 34.26c2.75-2.56 4.49-6.37 4.49-11.26 0-.89-.08-1.84-.29-3H24.01v5.99h8.03c-.4 2.02-1.5 3.56-3.07 4.56v.75l3.91 2.97h.88z\" fill=\"#4285F4\"/>\\n <path d=\"M15.58 25.77A8.845 8.845 0 0 0 24 31.86c1.92 0 3.62-.46 4.97-1.31l4.79 3.71C31.14 36.7 27.65 38 24 38c-5.93 0-11.01-3.4-13.45-8.36l.17-1.01 4.06-2.85h.8z\" fill=\"#34A853\"/>\\n <path d=\"M15.59 20.21a8.864 8.864 0 0 0 0 5.58l-5.03 3.86c-.98-2-1.53-4.25-1.53-6.64 0-2.39.55-4.64 1.53-6.64l1-.22 3.81 2.98.22 1.08z\" fill=\"#FBBC05\"/>\\n <path d=\"M24 14.14c2.11 0 4.02.75 5.52 1.98l4.36-4.36C31.22 9.43 27.81 8 24 8c-5.93 0-11.01 3.4-13.45 8.36l5.03 3.85A8.86 8.86 0 0 1 24 14.14z\" fill=\"#EA4335\"/>\\n </svg>\\n <div class=\"gradient-container\"><div class=\"gradient\"></div></div>\\n </div>\\n <div class=\"carousel\">\\n<a class=\"chip\" href=\"https://www.google.com/search?q=who+wrote+the+novel+1984&client=app-vertex-grounding&safesearch=active\">who wrote the novel 1984</a>\\n </div>\\n</div>'"
},
{
"text": "The novel *1984* was written by the English author **George Orwell** (whose real name was Eric Arthur Blair). It was published in 1949."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
}
]
}
@@ -0,0 +1,40 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "Search for the capital of France."
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "search_suggestions='<style>\\n.container {\\n align-items: center;\\n border-radius: 8px;\\n display: flex;\\n font-family: Google Sans, Roboto, sans-serif;\\n font-size: 14px;\\n line-height: 20px;\\n padding: 8px 12px;\\n}\\n.chip {\\n display: inline-block;\\n border: solid 1px;\\n border-radius: 16px;\\n min-width: 14px;\\n padding: 5px 16px;\\n text-align: center;\\n user-select: none;\\n margin: 0 8px;\\n -webkit-tap-highlight-color: transparent;\\n}\\n.carousel {\\n overflow: auto;\\n scrollbar-width: none;\\n white-space: nowrap;\\n margin-right: -12px;\\n}\\n.headline {\\n display: flex;\\n margin-right: 4px;\\n}\\n.gradient-container {\\n position: relative;\\n}\\n.gradient {\\n position: absolute;\\n transform: translate(3px, -9px);\\n height: 36px;\\n width: 9px;\\n}\\n@media (prefers-color-scheme: light) {\\n .container {\\n background-color: #fafafa;\\n box-shadow: 0 0 0 1px #0000000f;\\n }\\n .headline-label {\\n color: #1f1f1f;\\n }\\n .chip {\\n background-color: #ffffff;\\n border-color: #d2d2d2;\\n color: #5e5e5e;\\n text-decoration: none;\\n }\\n .chip:hover {\\n background-color: #f2f2f2;\\n }\\n .chip:focus {\\n background-color: #f2f2f2;\\n }\\n .chip:active {\\n background-color: #d8d8d8;\\n border-color: #b6b6b6;\\n }\\n .logo-dark {\\n display: none;\\n }\\n .gradient {\\n background: linear-gradient(90deg, #fafafa 15%, #fafafa00 100%);\\n }\\n}\\n@media (prefers-color-scheme: dark) {\\n .container {\\n background-color: #1f1f1f;\\n box-shadow: 0 0 0 1px #ffffff26;\\n }\\n .headline-label {\\n color: #fff;\\n }\\n .chip {\\n background-color: #2c2c2c;\\n border-color: #3c4043;\\n color: #fff;\\n text-decoration: none;\\n }\\n .chip:hover {\\n background-color: #353536;\\n }\\n .chip:focus {\\n background-color: #353536;\\n }\\n .chip:active {\\n background-color: #464849;\\n border-color: #53575b;\\n }\\n .logo-light {\\n display: none;\\n }\\n .gradient {\\n background: linear-gradient(90deg, #1f1f1f 15%, #1f1f1f00 100%);\\n }\\n}\\n</style>\\n<div class=\"container\">\\n <div class=\"headline\">\\n <svg class=\"logo-light\" width=\"18\" height=\"18\" viewBox=\"9 9 35 35\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M42.8622 27.0064C42.8622 25.7839 42.7525 24.6084 42.5487 23.4799H26.3109V30.1568H35.5897C35.1821 32.3041 33.9596 34.1222 32.1258 35.3448V39.6864H37.7213C40.9814 36.677 42.8622 32.2571 42.8622 27.0064V27.0064Z\" fill=\"#4285F4\"/>\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M26.3109 43.8555C30.9659 43.8555 34.8687 42.3195 37.7213 39.6863L32.1258 35.3447C30.5898 36.3792 28.6306 37.0061 26.3109 37.0061C21.8282 37.0061 18.0195 33.9811 16.6559 29.906H10.9194V34.3573C13.7563 39.9841 19.5712 43.8555 26.3109 43.8555V43.8555Z\" fill=\"#34A853\"/>\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M16.6559 29.8904C16.3111 28.8559 16.1074 27.7588 16.1074 26.6146C16.1074 25.4704 16.3111 24.3733 16.6559 23.3388V18.8875H10.9194C9.74388 21.2072 9.06992 23.8247 9.06992 26.6146C9.06992 29.4045 9.74388 32.022 10.9194 34.3417L15.3864 30.8621L16.6559 29.8904V29.8904Z\" fill=\"#FBBC05\"/>\\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M26.3109 16.2386C28.85 16.2386 31.107 17.1164 32.9095 18.8091L37.8466 13.8719C34.853 11.082 30.9659 9.3736 26.3109 9.3736C19.5712 9.3736 13.7563 13.245 10.9194 18.8875L16.6559 23.3388C18.0195 19.2636 21.8282 16.2386 26.3109 16.2386V16.2386Z\" fill=\"#EA4335\"/>\\n </svg>\\n <svg class=\"logo-dark\" width=\"18\" height=\"18\" viewBox=\"0 0 48 48\" xmlns=\"http://www.w3.org/2000/svg\">\\n <circle cx=\"24\" cy=\"23\" fill=\"#FFF\" r=\"22\"/>\\n <path d=\"M33.76 34.26c2.75-2.56 4.49-6.37 4.49-11.26 0-.89-.08-1.84-.29-3H24.01v5.99h8.03c-.4 2.02-1.5 3.56-3.07 4.56v.75l3.91 2.97h.88z\" fill=\"#4285F4\"/>\\n <path d=\"M15.58 25.77A8.845 8.845 0 0 0 24 31.86c1.92 0 3.62-.46 4.97-1.31l4.79 3.71C31.14 36.7 27.65 38 24 38c-5.93 0-11.01-3.4-13.45-8.36l.17-1.01 4.06-2.85h.8z\" fill=\"#34A853\"/>\\n <path d=\"M15.59 20.21a8.864 8.864 0 0 0 0 5.58l-5.03 3.86c-.98-2-1.53-4.25-1.53-6.64 0-2.39.55-4.64 1.53-6.64l1-.22 3.81 2.98.22 1.08z\" fill=\"#FBBC05\"/>\\n <path d=\"M24 14.14c2.11 0 4.02.75 5.52 1.98l4.36-4.36C31.22 9.43 27.81 8 24 8c-5.93 0-11.01 3.4-13.45 8.36l5.03 3.85A8.86 8.86 0 0 1 24 14.14z\" fill=\"#EA4335\"/>\\n </svg>\\n <div class=\"gradient-container\"><div class=\"gradient\"></div></div>\\n </div>\\n <div class=\"carousel\">\\n<a class=\"chip\" href=\"https://www.google.com/search?q=capital+of+France&client=app-vertex-grounding&safesearch=active\">capital of France</a>\\n </div>\\n</div>'"
},
{
"text": "I have searched for the capital of France and confirmed that it is Paris."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
}
]
}
@@ -0,0 +1,152 @@
{
"events": [
{
"author": "user",
"content": {
"parts": [
{
"text": "My favorite color is blue. Just acknowledge this, don't use any tools."
}
],
"role": "user"
},
"id": "e-1",
"invocationId": "i-1",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "I acknowledge that your favorite color is blue."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-2",
"invocationId": "i-1",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
},
{
"author": "user",
"content": {
"parts": [
{
"text": "What's the weather like in London?"
}
],
"role": "user"
},
"id": "e-3",
"invocationId": "i-2",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"functionCall": {
"args": {
"city": "London"
},
"id": "fc-1",
"name": "get_current_weather"
}
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-4",
"invocationId": "i-2",
"longRunningToolIds": [],
"nodeInfo": {
"path": "interactions_test_agent@1"
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"functionResponse": {
"id": "fc-1",
"name": "get_current_weather",
"response": {
"city": "London",
"condition": "Cloudy",
"humidity": 78,
"temperature_f": 59
}
}
}
],
"role": "user"
},
"id": "e-5",
"invocationId": "i-2",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "I have checked the weather in London for you. It is currently cloudy with a temperature of 59\u00b0F and 78% humidity."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-6",
"invocationId": "i-2",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
},
{
"author": "user",
"content": {
"parts": [
{
"text": "What is my favorite color that I mentioned earlier in our conversation?"
}
],
"role": "user"
},
"id": "e-7",
"invocationId": "i-3",
"nodeInfo": {
"path": ""
}
},
{
"author": "interactions_test_agent",
"content": {
"parts": [
{
"text": "Your favorite color, which you mentioned earlier, is blue."
}
],
"role": "model"
},
"finishReason": "STOP",
"id": "e-8",
"invocationId": "i-3",
"nodeInfo": {
"path": "interactions_test_agent@1"
}
}
]
}
@@ -0,0 +1,17 @@
# 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 __future__ import annotations
from . import agent
@@ -0,0 +1,174 @@
# 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 __future__ import annotations
import datetime
import json
import re
from typing import Any
from zoneinfo import ZoneInfo
from zoneinfo import ZoneInfoNotFoundError
from google.adk.agents.llm_agent import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.models.lite_llm import LiteLLMClient
class InlineJsonToolClient(LiteLLMClient):
"""LiteLLM client that emits inline JSON tool calls for testing."""
async def acompletion(self, model, messages, tools, **kwargs):
del tools, kwargs # Only needed for API parity.
tool_message = _find_last_role(messages, role="tool")
if tool_message:
tool_summary = _coerce_to_text(tool_message.get("content"))
return {
"id": "mock-inline-tool-final-response",
"model": model,
"choices": [{
"message": {
"role": "assistant",
"content": (
f"The instrumentation tool responded with: {tool_summary}"
),
},
"finish_reason": "stop",
}],
"usage": {
"prompt_tokens": 60,
"completion_tokens": 12,
"total_tokens": 72,
},
}
timezone = _extract_timezone(messages) or "Asia/Taipei"
inline_call = json.dumps(
{
"name": "get_current_time",
"arguments": {"timezone_str": timezone},
},
separators=(",", ":"),
)
return {
"id": "mock-inline-tool-call",
"model": model,
"choices": [{
"message": {
"role": "assistant",
"content": (
f"{inline_call}\nLet me double-check the clock for you."
),
},
"finish_reason": "tool_calls",
}],
"usage": {
"prompt_tokens": 45,
"completion_tokens": 15,
"total_tokens": 60,
},
}
def _find_last_role(
messages: list[dict[str, Any]], role: str
) -> dict[str, Any]:
"""Returns the last message with the given role."""
for message in reversed(messages):
if message.get("role") == role:
return message
return {}
def _coerce_to_text(content: Any) -> str:
"""Best-effort conversion from OpenAI message content to text."""
if isinstance(content, str):
return content
if isinstance(content, dict):
return _coerce_to_text(content.get("text"))
if isinstance(content, list):
texts = []
for part in content:
if isinstance(part, dict):
texts.append(part.get("text") or "")
elif isinstance(part, str):
texts.append(part)
return " ".join(text for text in texts if text)
return ""
_TIMEZONE_PATTERN = re.compile(r"([A-Za-z]+/[A-Za-z_]+)")
def _extract_timezone(messages: list[dict[str, Any]]) -> str | None:
"""Extracts an IANA timezone string from the last user message."""
user_message = _find_last_role(messages, role="user")
text = _coerce_to_text(user_message.get("content"))
if not text:
return None
match = _TIMEZONE_PATTERN.search(text)
if match:
return match.group(1)
lowered = text.lower()
if "taipei" in lowered:
return "Asia/Taipei"
if "new york" in lowered:
return "America/New_York"
if "london" in lowered:
return "Europe/London"
if "tokyo" in lowered:
return "Asia/Tokyo"
return None
def get_current_time(timezone_str: str) -> dict[str, str]:
"""Returns mock current time for the provided timezone."""
try:
tz = ZoneInfo(timezone_str)
except ZoneInfoNotFoundError as exc:
return {
"status": "error",
"report": f"Unable to parse timezone '{timezone_str}': {exc}",
}
now = datetime.datetime.now(tz)
return {
"status": "success",
"report": (
f"The current time in {timezone_str} is"
f" {now.strftime('%Y-%m-%d %H:%M:%S %Z')}."
),
}
_mock_model = LiteLlm(
model="mock/inline-json-tool-calls",
llm_client=InlineJsonToolClient(),
)
root_agent = Agent(
name="litellm_inline_tool_tester",
model=_mock_model,
description=(
"Demonstrates LiteLLM inline JSON tool-call parsing without an external"
" VLLM deployment."
),
instruction=(
"You are a deterministic clock assistant. Always call the"
" get_current_time tool before answering user questions. After the tool"
" responds, summarize what it returned."
),
tools=[get_current_time],
)
@@ -0,0 +1,15 @@
# 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
@@ -0,0 +1,27 @@
# 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.
"""LiteLLM sample agent for SSE text streaming."""
from __future__ import annotations
from google.adk import Agent
from google.adk.models.lite_llm import LiteLlm
root_agent = Agent(
name='litellm_streaming_agent',
model=LiteLlm(model='gemini/gemini-2.5-flash'),
description='A LiteLLM agent used for streaming text responses.',
instruction='You are a verbose assistant',
)
@@ -0,0 +1,107 @@
# 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.
"""Runs the LiteLLM streaming sample with SSE enabled."""
from __future__ import annotations
import asyncio
from dotenv import load_dotenv
from google.adk.agents.run_config import RunConfig
from google.adk.agents.run_config import StreamingMode
from google.adk.cli.utils import logs
from google.adk.runners import InMemoryRunner
from google.genai import types
from . import agent
load_dotenv(override=True)
logs.log_to_tmp_folder()
async def _run_prompt(
*,
runner: InMemoryRunner,
user_id: str,
session_id: str,
prompt: str,
) -> None:
"""Runs one prompt and prints partial chunks in real time."""
content = types.Content(
role='user',
parts=[types.Part.from_text(text=prompt)],
)
print(f'User: {prompt}')
print('Agent: ', end='', flush=True)
saw_text = False
saw_partial_text = False
# For `adk web`, enable the `Streaming` toggle in the UI to get
# partial SSE responses similar to this script.
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=content,
run_config=RunConfig(streaming_mode=StreamingMode.SSE),
):
if not event.content:
continue
text = ''.join(part.text for part in event.content.parts if part.text)
if not text:
continue
if event.partial:
print(text, end='', flush=True)
saw_text = True
saw_partial_text = True
continue
# With SSE mode, ADK emits a final aggregated event after partial chunks.
if not saw_partial_text:
print(text, end='', flush=True)
saw_text = True
if saw_text:
print()
else:
print('(no text response)')
print('------------------------------------')
async def main() -> None:
app_name = 'litellm_streaming_demo'
user_id = 'user_1'
runner = InMemoryRunner(agent=agent.root_agent, app_name=app_name)
session = await runner.session_service.create_session(
app_name=app_name,
user_id=user_id,
)
prompts = [
'Write an essay about the roman empire',
'Now summarize the essay into one sentence.',
]
for prompt in prompts:
await _run_prompt(
runner=runner,
user_id=user_id,
session_id=session.id,
prompt=prompt,
)
if __name__ == '__main__':
asyncio.run(main())
@@ -0,0 +1,15 @@
# 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
@@ -0,0 +1,47 @@
# 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.
"""Sample agent showing LiteLLM structured output support."""
from __future__ import annotations
from google.adk import Agent
from google.adk.models.lite_llm import LiteLlm
from pydantic import BaseModel
from pydantic import Field
class CitySummary(BaseModel):
"""Simple structure used to verify LiteLLM JSON schema handling."""
city: str = Field(description="Name of the city being described.")
highlights: list[str] = Field(
description="Bullet points summarising the city's key highlights.",
)
recommended_visit_length_days: int = Field(
description="Recommended number of days for a typical visit.",
)
root_agent = Agent(
name="litellm_structured_output_agent",
model=LiteLlm(model="gemini-2.5-flash"),
description="Generates structured travel recommendations for a given city.",
instruction="""
Produce a JSON object that follows the CitySummary schema.
Only include fields that appear in the schema and ensure highlights
contains short bullet points.
""".strip(),
output_schema=CitySummary,
)
@@ -0,0 +1,11 @@
# LiteLLM with Fallback Models
This agent is built for resilience using LiteLLM's built-in fallback mechanism. It automatically switches models to guard against common disruptions like token limit errors and connection failures, while ensuring full conversational context is preserved across all model changes.
To run this example, ensure your .env file includes the following variables:
```
GOOGLE_API_KEY=
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
```
@@ -0,0 +1,15 @@
# 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
@@ -0,0 +1,88 @@
# 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.
import random
from google.adk import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools.tool_context import ToolContext
from google.genai import types
def roll_die(sides: int, tool_context: ToolContext) -> int:
"""Roll a die and return the rolled result.
Args:
sides: The integer number of sides the die has.
tool_context: The tool context to use for the die roll.
Returns:
An integer of the result of rolling the die.
The result is also stored in the tool context for future use.
"""
result = random.randint(1, sides)
if 'rolls' not in tool_context.state:
tool_context.state['rolls'] = []
tool_context.state['rolls'] = tool_context.state['rolls'] + [result]
return result
async def before_model_callback(callback_context, llm_request):
print('@before_model_callback')
print(f'Beginning model choice: {llm_request.model}')
callback_context.state['beginning_model_choice'] = llm_request.model
return None
async def after_model_callback(callback_context, llm_response):
print('@after_model_callback')
print(f'Final model choice: {llm_response.model_version}')
callback_context.state['final_model_choice'] = llm_response.model_version
return None
root_agent = Agent(
model=LiteLlm(
model='gemini/gemini-2.5-pro',
fallbacks=[
'anthropic/claude-sonnet-4-5-20250929',
'openai/gpt-4o',
],
),
name='resilient_agent',
description=(
'hello world agent that can roll a dice of given number of sides.'
),
instruction="""
You roll dice and answer questions about the outcome of the dice rolls.
You can roll dice of different sizes.
It is ok to discuss previous dice roles, 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.
""",
tools=[
roll_die,
],
generate_content_config=types.GenerateContentConfig(
safety_settings=[
types.SafetySetting( # avoid false alarm about rolling dice.
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.OFF,
),
]
),
before_model_callback=before_model_callback,
after_model_callback=after_model_callback,
)
@@ -0,0 +1,15 @@
# 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
@@ -0,0 +1,39 @@
# 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 __future__ import annotations
from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.sequential_agent import SequentialAgent
from google.adk.models.lite_llm import LiteLlm
ollama_model = LiteLlm(model="ollama_chat/qwen2.5:7b")
hello_agent = LlmAgent(
name="hello_step",
instruction="Say hello to the user. Be concise.",
model=ollama_model,
)
summarize_agent = LlmAgent(
name="summarize_step",
instruction="Summarize the previous assistant message in 5 words.",
model=ollama_model,
)
root_agent = SequentialAgent(
name="ollama_seq_test",
description="Two-step sanity check for Ollama LiteLLM chat.",
sub_agents=[hello_agent, summarize_agent],
)