chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# Abort Agent Sample
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates a standalone ADK agent designed specifically to showcase cooperative task cancellation on client disconnections.
|
||||
|
||||
The agent leverages a custom tool that counts from 1 to a target number requested by the user, pausing 1 second between each count and printing the progress directly to the server's stdout terminal. This delay allows you to easily trigger connection drops (by cancelling client requests mid-execution) and visually observe that background agent execution halts immediately in the server terminal, resolving resource leaks.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `Count to 10 seconds`
|
||||
|
||||
The agent will call the `count_seconds` tool with a target count of 10, pausing 1 second between each increment.
|
||||
|
||||
- `Please count up to 20`
|
||||
|
||||
The agent will invoke the tool to count to 20. If you close the client connection (such as pressing `Ctrl+C` in a cURL window or closing your browser tab) while the loop is running, counting halts immediately.
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
START --> AbortAgent
|
||||
AbortAgent --> |"Invoke Tool"| count_seconds
|
||||
count_seconds --> |"Abort if Disconnected / Done"| AbortAgent
|
||||
AbortAgent --> ANSWER
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
### 1. Running Locally via the CLI
|
||||
|
||||
To interact with the agent in your local shell terminal:
|
||||
|
||||
```bash
|
||||
adk run contributing/samples/core/abort/
|
||||
```
|
||||
|
||||
Type `count to 10` at the `[user]:` prompt. Pressing `Ctrl+C` mid-run will abort counting and exit.
|
||||
|
||||
### 2. Running via HTTP Server and cURL
|
||||
|
||||
To verify connection-drop abortion over network protocols (e.g. simple HTTP REST request):
|
||||
|
||||
1. Start the local development server to watch the sample workspace:
|
||||
```bash
|
||||
adk web --allow_origins=http://localhost:4200 contributing/samples/
|
||||
```
|
||||
1. In a separate terminal, register the test session (local CLI development servers run with `--auto_create_session` set to `False` by default):
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/apps/abort_agent/users/user/sessions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"session_id": "8b24e6ed-1fff-4f0c-a06a-e065692a446e"}'
|
||||
```
|
||||
1. Initiate the count request (this blocks waiting for a response):
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/run \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"app_name": "abort_agent",
|
||||
"user_id": "user",
|
||||
"session_id": "8b24e6ed-1fff-4f0c-a06a-e065692a446e",
|
||||
"new_message": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "count to 100"}]
|
||||
}
|
||||
}'
|
||||
```
|
||||
1. Observe your `adk web` terminal window. You will see the counting progress logging.
|
||||
1. **Trigger the Abort**: Press `Ctrl+C` in your cURL terminal window to close the client connection.
|
||||
1. Observe the server console window. Counting halts instantly and prints:
|
||||
```text
|
||||
[Counting Tool] Count was ABORTED mid-run at progress: X/100 (Client Disconnected)!
|
||||
```
|
||||
|
||||
### 3. Running and Testing via ADK Web (Dev UI)
|
||||
|
||||
To observe cooperative aborts interactively in the web-based developer interface:
|
||||
|
||||
1. Start the local development server:
|
||||
```bash
|
||||
adk web --allow_origins=http://localhost:4200 contributing/samples/
|
||||
```
|
||||
1. Open the ADK Web interface (`http://localhost:4200`) in your web browser.
|
||||
1. Select the **`abort_agent`** app from the left sidebar panel.
|
||||
1. Type `count to 100` in the message input box and click submit.
|
||||
1. **Trigger the Abort**: Simply close your browser tab, refresh the page, or navigate away from the chat panel.
|
||||
1. Observe the server's stdout terminal console. You will see that counting halts immediately and logs:
|
||||
```text
|
||||
[Counting Tool] Count was ABORTED mid-run (Client Disconnected)!
|
||||
```
|
||||
@@ -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,70 @@
|
||||
# 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
|
||||
|
||||
from google.adk import Agent
|
||||
|
||||
logger = logging.getLogger("google_adk." + __name__)
|
||||
|
||||
|
||||
async def count_seconds(target: int) -> str:
|
||||
"""Counts from 1 to the target number, pausing 1 second between counts, and prints each count.
|
||||
|
||||
Args:
|
||||
target: The target number to count to.
|
||||
"""
|
||||
logger.info("Starting count from 1 to %d...", target)
|
||||
print(
|
||||
f"\n[Counting Tool] Starting counting up to {target} in console...",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
i = 0
|
||||
try:
|
||||
for i in range(1, target + 1):
|
||||
await asyncio.sleep(1)
|
||||
# Print to standard stdout so it shows directly in the server terminal
|
||||
print(f"[Counting Tool] Progress: {i}/{target}", flush=True)
|
||||
logger.info("Counted: %d/%d", i, target)
|
||||
|
||||
print(
|
||||
f"[Counting Tool] Finished counting up to {target}.\n",
|
||||
flush=True,
|
||||
)
|
||||
return f"Successfully counted from 1 to {target} in the console."
|
||||
except asyncio.CancelledError:
|
||||
print(
|
||||
f"\n[Counting Tool] Count was ABORTED mid-run at progress: {i}/{target}"
|
||||
" (Client Disconnected)!\n",
|
||||
flush=True,
|
||||
)
|
||||
logger.warning("Counting tool was cancelled mid-run.")
|
||||
raise
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
name="abort_agent",
|
||||
description=(
|
||||
"An agent designed to demonstrate how ADK handles client disconnects"
|
||||
" and aborts agent executions mid-run using a counting loop with a"
|
||||
" 1-second delay."
|
||||
),
|
||||
instruction="""You are an abort coordinator.
|
||||
Your goal is to demonstrate cooperative task abortion.
|
||||
When asked to count to a number (or count for a number of seconds), invoke the `count_seconds` tool with the target number.
|
||||
Do not try to count by yourself; always delegate counting to the `count_seconds` tool so that progress is accurately printed and logs can show task cancellation when a disconnect happens.""",
|
||||
tools=[count_seconds],
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
# Application Configuration (App)
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates how to configure an `App` in ADK. An `App` serves as the top-level container for an agentic system, wrapping a root agent or workflow and providing application-wide configurations such as plugins, event compaction, and context caching.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `Hello, who are you?`
|
||||
|
||||
*The application executes `greeter_agent`. The `CountInvocationPlugin` logs the agent and LLM run counts to the console.*
|
||||
|
||||
- `Can you help me plan a trip?`
|
||||
|
||||
*As the conversation continues, event compaction automatically summarizes past turns every 2 invocations, while context caching optimizes token usage.*
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
User[User Input] --> AppContainer[App: app]
|
||||
subgraph AppContainer [App Container]
|
||||
Plugins[Plugins: CountInvocation, SaveFilesAsArtifacts] --> GreeterAgent[root_agent: greeter_agent]
|
||||
GreeterAgent --> Configs[Configs: EventCompaction, ContextCache]
|
||||
end
|
||||
AppContainer --> Response[User Response]
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
### Wrapping an Agent in an App
|
||||
|
||||
To configure application-level behaviors, instantiate an `App` object and pass your root agent to the `root_agent` parameter:
|
||||
|
||||
```python
|
||||
from google.adk import Agent
|
||||
from google.adk.apps.app import App
|
||||
|
||||
root_agent = Agent(
|
||||
name="greeter_agent",
|
||||
instruction="You are a friendly assistant.",
|
||||
)
|
||||
|
||||
app = App(
|
||||
name="app",
|
||||
root_agent=root_agent,
|
||||
)
|
||||
```
|
||||
|
||||
### Configuring Application Plugins
|
||||
|
||||
Plugins provide cross-cutting capabilities (such as telemetry, custom logging, or artifact saving) across the entire application. Pass a list of plugin instances to `plugins`:
|
||||
|
||||
```python
|
||||
from google.adk.plugins.save_files_as_artifacts_plugin import SaveFilesAsArtifactsPlugin
|
||||
|
||||
app = App(
|
||||
name="app",
|
||||
root_agent=root_agent,
|
||||
plugins=[
|
||||
CountInvocationPlugin(),
|
||||
SaveFilesAsArtifactsPlugin(),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Configuring Event Compaction and Caching
|
||||
|
||||
The `App` container is also where you define long-term session behavior and optimization strategies:
|
||||
|
||||
- **`events_compaction_config`**: Manages token usage by periodically summarizing older turns in a session.
|
||||
- **`context_cache_config`**: Enables prompt caching across invocations to reduce latency and cost.
|
||||
|
||||
```python
|
||||
from google.adk.apps.app import EventsCompactionConfig
|
||||
from google.adk.agents.context_cache_config import ContextCacheConfig
|
||||
|
||||
app = App(
|
||||
name="app",
|
||||
root_agent=root_agent,
|
||||
events_compaction_config=EventsCompactionConfig(
|
||||
compaction_interval=2,
|
||||
overlap_size=1,
|
||||
),
|
||||
context_cache_config=ContextCacheConfig(
|
||||
cache_intervals=10,
|
||||
ttl_seconds=1800,
|
||||
min_tokens=1000,
|
||||
),
|
||||
)
|
||||
```
|
||||
@@ -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.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.agents.base_agent import BaseAgent
|
||||
from google.adk.agents.callback_context import CallbackContext
|
||||
from google.adk.agents.context_cache_config import ContextCacheConfig
|
||||
from google.adk.apps.app import App
|
||||
from google.adk.apps.app import EventsCompactionConfig
|
||||
from google.adk.models.llm_request import LlmRequest
|
||||
from google.adk.plugins.base_plugin import BasePlugin
|
||||
from google.adk.plugins.save_files_as_artifacts_plugin import SaveFilesAsArtifactsPlugin
|
||||
|
||||
|
||||
class CountInvocationPlugin(BasePlugin):
|
||||
"""A custom plugin that counts agent and LLM invocations."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the plugin with counters."""
|
||||
super().__init__(name="count_invocation")
|
||||
self.agent_count: int = 0
|
||||
self.llm_request_count: int = 0
|
||||
|
||||
async def before_agent_callback(
|
||||
self, *, agent: BaseAgent, callback_context: CallbackContext
|
||||
) -> None:
|
||||
"""Count agent runs."""
|
||||
self.agent_count += 1
|
||||
print(f"[Plugin] Agent run count: {self.agent_count}")
|
||||
|
||||
async def before_model_callback(
|
||||
self, *, callback_context: CallbackContext, llm_request: LlmRequest
|
||||
) -> None:
|
||||
"""Count LLM requests."""
|
||||
self.llm_request_count += 1
|
||||
print(f"[Plugin] LLM request count: {self.llm_request_count}")
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
name="greeter_agent",
|
||||
instruction="""\
|
||||
You are a friendly and helpful concierge assistant. Greet the user and answer their questions.
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
app = App(
|
||||
name="app",
|
||||
root_agent=root_agent,
|
||||
plugins=[
|
||||
CountInvocationPlugin(),
|
||||
SaveFilesAsArtifactsPlugin(),
|
||||
],
|
||||
events_compaction_config=EventsCompactionConfig(
|
||||
compaction_interval=2,
|
||||
overlap_size=1,
|
||||
),
|
||||
context_cache_config=ContextCacheConfig(
|
||||
cache_intervals=10,
|
||||
ttl_seconds=1800,
|
||||
min_tokens=1000,
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello, who are you?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "greeter_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello there! I'm a friendly and helpful concierge assistant, ready to answer your questions. My internal name is `greeter_agent`. How can I assist you today?"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "greeter_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Can you help me plan a trip?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "greeter_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello there! I'd be absolutely delighted to help you plan your trip. To get started, I'll need a little more information.\n\nCould you tell me:\n\n1. **Where are you hoping to go?** (e.g., a specific city, country, or region)\n2. **When are you planning to travel?** (e.g., specific dates, a season, or a general time of year)\n3. **What kind of trip are you envisioning?** (e.g., a relaxing beach vacation, an adventurous hiking trip, a cultural city break, a family holiday, etc.)\n4. **Who are you traveling with?** (e.g., solo, with a partner, family with kids, friends)\n5. **Do you have any specific interests or preferences?** (e.g., food, history, art, nature, nightlife, budget considerations)\n\nThe more details you can provide, the better I can assist you in creating an amazing itinerary!"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "greeter_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Who are you?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "greeter_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello there! I'm a friendly and helpful concierge assistant. How can I assist you today?"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "greeter_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Artifacts Sample
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates how to use the Artifacts feature in ADK to handle different media types (image, audio, video), formats (text, HTML), and artifact versions. Artifacts allow agents to save large pieces of data or binary files outside the main conversation history to avoid cluttering the LLM context window. The agent can then load these artifacts when needed.
|
||||
|
||||
This sample showcases:
|
||||
|
||||
- Generating and saving a valid **Image** (BMP).
|
||||
- Generating and saving a valid **Audio** file (WAV).
|
||||
- Generating and saving a valid **Video** file (MP4) using OpenCV.
|
||||
- Generating reports in both **Text** and **HTML** formats.
|
||||
- Automatic handling of **Artifact Versions**.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `Generate a text report about AI agents`
|
||||
|
||||
- `Generate an HTML report about AI agents`
|
||||
|
||||
- `Generate a dummy image artifact`
|
||||
|
||||
- `Generate a dummy audio artifact`
|
||||
|
||||
- `Generate a dummy video artifact`
|
||||
|
||||
- `Load the latest version of the image artifact`
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
START --> artifacts_agent
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
1. **Text and HTML Reports**: The `generate_report` tool allows generating reports in either `text` or `html` format. It saves the content with the appropriate MIME type (`text/plain` or `text/html`) and file extension (`.txt` or `.html`).
|
||||
1. **Media Artifacts**: The `generate_media_artifact` tool demonstrates how to save binary data (image, audio, video) as artifacts using `types.Part.from_bytes` and `ctx.save_artifact`.
|
||||
- **Image**: Generated manually as a valid 100x100 red BMP file using standard libraries.
|
||||
- **Audio**: Generated manually as a valid 1-second sine wave WAV file using standard libraries.
|
||||
- **Video**: Generated as a valid 3-second MP4 file with a moving square using OpenCV (codec `avc1` for better compatibility).
|
||||
1. **Versioning**: The `generate_report` tool creates a new version of the artifact every time it is called with the same format (`text` or `html`), as it uses fixed filenames (`report.txt` or `report.html`). The version number is returned to the user.
|
||||
1. **Loading Latest Version**: The standard `LoadArtifactsTool` (available as `load_artifacts` to the model) can be used to load the latest version of any artifact, including media files.
|
||||
|
||||
## Dependencies
|
||||
|
||||
To generate the video artifact, the sample requires `opencv-python` and `numpy`. You can install them with:
|
||||
|
||||
```bash
|
||||
pip install opencv-python numpy
|
||||
```
|
||||
|
||||
If these libraries are not installed, the tool will return a helpful error message explaining how to install them, while image and audio generation will still work.
|
||||
@@ -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,217 @@
|
||||
# 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 math
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import wave
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk import Context
|
||||
from google.adk.tools.load_artifacts_tool import LoadArtifactsTool
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def generate_wav(filename: str):
|
||||
"""Generates a simple valid WAV file (a 440Hz sine wave)."""
|
||||
sample_rate = 44100.0
|
||||
duration = 1.0 # seconds
|
||||
frequency = 440.0 # sine wave frequency (A4)
|
||||
num_samples = int(sample_rate * duration)
|
||||
|
||||
with wave.open(filename, "w") as wav_file:
|
||||
wav_file.setnchannels(1)
|
||||
wav_file.setsampwidth(2)
|
||||
wav_file.setframerate(int(sample_rate))
|
||||
for i in range(num_samples):
|
||||
value = int(
|
||||
32767.0 * math.sin(2.0 * math.pi * frequency * i / sample_rate)
|
||||
)
|
||||
wav_file.writeframes(struct.pack("<h", value))
|
||||
|
||||
|
||||
def generate_bmp(filename: str):
|
||||
"""Generates a simple valid BMP file (a red square)."""
|
||||
width, height = 100, 100
|
||||
file_size = 54 + 3 * width * height
|
||||
bmp_header = b"BM" + struct.pack(
|
||||
"<III IiiHHIIiiII",
|
||||
file_size,
|
||||
0,
|
||||
54,
|
||||
40,
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
24,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
pixel_data = b"\x00\x00\xFF" * (width * height) # Red pixels in BGR
|
||||
with open(filename, "wb") as f:
|
||||
f.write(bmp_header + pixel_data)
|
||||
|
||||
|
||||
def generate_video(filename: str):
|
||||
"""Generates a simple valid MP4 video using OpenCV.
|
||||
|
||||
Requires opencv-python.
|
||||
"""
|
||||
try:
|
||||
import cv2
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"opencv-python and numpy are required to generate video. Install"
|
||||
" them with: pip install opencv-python numpy"
|
||||
)
|
||||
|
||||
width, height = 320, 240
|
||||
fourcc = cv2.VideoWriter_fourcc(*"avc1")
|
||||
out = cv2.VideoWriter(filename, fourcc, 20.0, (width, height))
|
||||
if not out.isOpened():
|
||||
raise RuntimeError(
|
||||
"Failed to open VideoWriter. The 'avc1' codec might not be supported on"
|
||||
" this system."
|
||||
)
|
||||
|
||||
for i in range(60): # 3 seconds at 20fps
|
||||
frame = np.zeros((height, width, 3), dtype=np.uint8)
|
||||
# Draw a moving white square
|
||||
x = (i * 5) % width
|
||||
cv2.rectangle(frame, (x, 100), (x + 50, 150), (255, 255, 255), -1)
|
||||
out.write(frame)
|
||||
|
||||
out.release()
|
||||
|
||||
|
||||
async def generate_report(
|
||||
topic: str, ctx: Context, format: str = "text"
|
||||
) -> dict:
|
||||
"""Generates a report on a topic and saves it as an artifact.
|
||||
|
||||
Args:
|
||||
topic: The topic of the report.
|
||||
ctx: The tool context for saving artifacts.
|
||||
format: The format of the report ('text' or 'html').
|
||||
"""
|
||||
if format.lower() == "html":
|
||||
mime_type = "text/html"
|
||||
filename = "report.html"
|
||||
content = f"""<html>
|
||||
<head><title>Report on {topic}</title></head>
|
||||
<body>
|
||||
<h1>REPORT: {topic}</h1>
|
||||
<hr>
|
||||
<p>This is a detailed report about {topic}.</p>
|
||||
<p>It contains a lot of useful information that would clutter the conversation history.</p>
|
||||
<ul>
|
||||
<li>Key point 1</li>
|
||||
<li>Key point 2</li>
|
||||
<li>Key point 3</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>"""
|
||||
else:
|
||||
mime_type = "text/plain"
|
||||
filename = "report.txt"
|
||||
content = f"""REPORT: {topic}
|
||||
=========================================
|
||||
This is a detailed report about {topic}.
|
||||
It contains a lot of useful information that would clutter the conversation history.
|
||||
- Key point 1
|
||||
- Key point 2
|
||||
- Key point 3
|
||||
"""
|
||||
|
||||
version = await ctx.save_artifact(
|
||||
filename,
|
||||
types.Part.from_bytes(data=content.encode("utf-8"), mime_type=mime_type),
|
||||
)
|
||||
return {
|
||||
"message": (
|
||||
f"Report on {topic} saved as artifact '{filename}' (version"
|
||||
f" {version})."
|
||||
),
|
||||
"filename": filename,
|
||||
"version": version,
|
||||
}
|
||||
|
||||
|
||||
async def generate_media_artifact(media_type: str, ctx: Context) -> dict:
|
||||
"""Generates a valid media artifact of specified type.
|
||||
|
||||
Args:
|
||||
media_type: One of 'image', 'audio', 'video'.
|
||||
ctx: The tool context for saving artifacts.
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
if media_type == "image":
|
||||
mime_type = "image/bmp"
|
||||
file_path = os.path.join(tmpdir, "sample.bmp")
|
||||
generate_bmp(file_path)
|
||||
filename = "sample_image.bmp"
|
||||
elif media_type == "audio":
|
||||
mime_type = "audio/wav"
|
||||
file_path = os.path.join(tmpdir, "sample.wav")
|
||||
generate_wav(file_path)
|
||||
filename = "sample_audio.wav"
|
||||
elif media_type == "video":
|
||||
mime_type = "video/mp4"
|
||||
file_path = os.path.join(tmpdir, "sample.mp4")
|
||||
try:
|
||||
generate_video(file_path)
|
||||
except (ImportError, RuntimeError) as e:
|
||||
return {"error": str(e)}
|
||||
filename = "sample_video.mp4"
|
||||
else:
|
||||
return {"error": f"Unsupported media type: {media_type}"}
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
version = await ctx.save_artifact(
|
||||
filename,
|
||||
types.Part.from_bytes(data=data, mime_type=mime_type),
|
||||
)
|
||||
|
||||
return {
|
||||
"message": (
|
||||
f"Media artifact '{filename}' generated and saved (version"
|
||||
f" {version})."
|
||||
),
|
||||
"filename": filename,
|
||||
"version": version,
|
||||
}
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
name="artifacts_agent",
|
||||
tools=[generate_report, generate_media_artifact, LoadArtifactsTool()],
|
||||
instruction="""You are an agent that can manage artifacts, including different media types.
|
||||
|
||||
- To generate a text report, use `generate_report`.
|
||||
- To generate image, audio, or video artifacts, use `generate_media_artifact`.
|
||||
|
||||
When the user asks about an artifact or to load it, use `load_artifacts`.
|
||||
""",
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
# Callback Sample
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates how to use callbacks in ADK to intercept and handle events. Specifically, it shows:
|
||||
|
||||
1. **`before_tool_callback`**: Intercepts tool calls and conditionally short-circuits them.
|
||||
1. **`before_model_callback`**: Intercepts requests to the LLM and conditionally short-circuits them.
|
||||
1. **`after_model_callback`**: Runs after the model completes, allowing you to inspect or modify the response (e.g., appending token usage).
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `What is the weather in Paris?`
|
||||
|
||||
*Calls the tool normally*
|
||||
|
||||
- `What is the weather in London?`
|
||||
|
||||
*Intercepted by the before_tool_callback and returns a mock response*
|
||||
|
||||
- `Hi`
|
||||
|
||||
*Intercepted by the before_model_callback and returns a direct response*
|
||||
|
||||
## How To
|
||||
|
||||
### Tool Callback
|
||||
|
||||
The sample defines a `before_tool_callback` function:
|
||||
|
||||
```python
|
||||
def before_tool_callback(
|
||||
tool: BaseTool,
|
||||
args: dict[str, Any],
|
||||
tool_context: ToolContext,
|
||||
) -> dict[str, Any] | None:
|
||||
# Intercept tool calls for London and return a mocked response
|
||||
if args.get("city") == "London":
|
||||
return {
|
||||
"result": "Weather in London is always rainy (intercepted by callback)."
|
||||
}
|
||||
|
||||
return None
|
||||
```
|
||||
|
||||
If the function returns a dictionary with a `result` key (or any other response data), ADK uses that as the tool output and skips calling the actual tool.
|
||||
|
||||
### Model Callback
|
||||
|
||||
The sample also defines a `before_model_callback` function:
|
||||
|
||||
```python
|
||||
def before_model_callback(
|
||||
callback_context: CallbackContext,
|
||||
llm_request: LlmRequest,
|
||||
) -> LlmResponse | None:
|
||||
# Short-circuit if the user simply says "Hi"
|
||||
if llm_request.contents:
|
||||
last_content = llm_request.contents[-1]
|
||||
if last_content.parts:
|
||||
last_part = last_content.parts[-1]
|
||||
if last_part.text and last_part.text.strip().lower() == "hi":
|
||||
return LlmResponse(
|
||||
content=types.Content(
|
||||
role="model",
|
||||
parts=[
|
||||
types.Part.from_text(
|
||||
text="Hello from before_model callback!"
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
return None
|
||||
```
|
||||
|
||||
If this function returns an `LlmResponse`, ADK skips calling the LLM and returns this response to the user.
|
||||
|
||||
### After Model Callback
|
||||
|
||||
The sample also defines an `after_model_callback` function:
|
||||
|
||||
```python
|
||||
def after_model_callback(
|
||||
callback_context: CallbackContext,
|
||||
llm_response: LlmResponse,
|
||||
) -> LlmResponse:
|
||||
# Append token usage to the response text if available
|
||||
if llm_response.usage_metadata:
|
||||
usage = llm_response.usage_metadata
|
||||
usage_text = f"\n\nafter_model_callback: [Token Usage: Input={usage.prompt_token_count}, Output={usage.candidates_token_count}]"
|
||||
|
||||
if not llm_response.content:
|
||||
llm_response.content = types.Content(role="model", parts=[])
|
||||
|
||||
llm_response.content.parts.append(types.Part.from_text(text=usage_text))
|
||||
|
||||
return llm_response
|
||||
|
||||
```
|
||||
|
||||
This callback runs after the LLM returns a response. It checks if `usage_metadata` is available in the `llm_response`, constructs a string with input and output token counts, and appends it as a new part to the content.
|
||||
|
||||
All callbacks are registered in the `Agent` constructor:
|
||||
|
||||
```python
|
||||
root_agent = Agent(
|
||||
name="callback_demo_agent",
|
||||
tools=[get_weather],
|
||||
before_tool_callback=before_tool_callback,
|
||||
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,118 @@
|
||||
# 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 typing import Any
|
||||
|
||||
from google.adk.agents import Agent
|
||||
from google.adk.agents.callback_context import CallbackContext
|
||||
from google.adk.models.llm_request import LlmRequest
|
||||
from google.adk.models.llm_response import LlmResponse
|
||||
from google.adk.tools import BaseTool
|
||||
from google.adk.tools import ToolContext
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
return f"The weather in {city} is sunny."
|
||||
|
||||
|
||||
def before_tool_callback(
|
||||
tool: BaseTool,
|
||||
args: dict[str, Any],
|
||||
tool_context: ToolContext,
|
||||
) -> dict[str, Any] | None:
|
||||
"""A callback that runs before a tool is called.
|
||||
|
||||
Args:
|
||||
tool: The tool instance being called.
|
||||
args: The arguments passed to the tool.
|
||||
tool_context: The context for the tool execution.
|
||||
|
||||
Returns:
|
||||
A dict containing the mock response if the call should be short-circuited,
|
||||
or None to proceed with the actual tool call.
|
||||
"""
|
||||
# Intercept tool calls for London and return a mocked response
|
||||
if args.get("city") == "London":
|
||||
return {
|
||||
"result": "Weather in London is always rainy (intercepted by callback)."
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def before_model_callback(
|
||||
callback_context: CallbackContext,
|
||||
llm_request: LlmRequest,
|
||||
) -> LlmResponse | None:
|
||||
"""A callback that runs before the model is called.
|
||||
|
||||
Args:
|
||||
callback_context: The context for the callback.
|
||||
llm_request: The request that is about to be sent to the model.
|
||||
|
||||
Returns:
|
||||
An LlmResponse to short-circuit the model call, or None to proceed.
|
||||
"""
|
||||
# Short-circuit if the user simply says "Hi"
|
||||
if llm_request.contents:
|
||||
last_content = llm_request.contents[-1]
|
||||
if last_content.parts:
|
||||
last_part = last_content.parts[-1]
|
||||
if last_part.text and last_part.text.strip().lower() == "hi":
|
||||
return LlmResponse(
|
||||
content=types.Content(
|
||||
role="model",
|
||||
parts=[
|
||||
types.Part.from_text(
|
||||
text="Hello from before_model callback!"
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def after_model_callback(
|
||||
callback_context: CallbackContext,
|
||||
llm_response: LlmResponse,
|
||||
) -> LlmResponse:
|
||||
"""A callback that runs after the model is called."""
|
||||
if llm_response.usage_metadata:
|
||||
usage = llm_response.usage_metadata
|
||||
usage_text = (
|
||||
"\n\nafter_model_callback: [Token Usage:"
|
||||
f" Input={usage.prompt_token_count},"
|
||||
f" Output={usage.candidates_token_count}]"
|
||||
)
|
||||
|
||||
if not llm_response.content:
|
||||
llm_response.content = types.Content(role="model", parts=[])
|
||||
|
||||
llm_response.content.parts.append(types.Part.from_text(text=usage_text))
|
||||
print(llm_response.content)
|
||||
|
||||
return llm_response
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
name="callback_demo_agent",
|
||||
tools=[get_weather],
|
||||
before_tool_callback=before_tool_callback,
|
||||
before_model_callback=before_model_callback,
|
||||
after_model_callback=after_model_callback,
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hi"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello from before_model callback!"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "weather in London"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "London"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_weather"
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "\n\nafter_model_callback: [Token Usage: Input=22, Output=5]"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_weather",
|
||||
"response": {
|
||||
"result": "Weather in London is always rainy (intercepted by callback)."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in London is always rainy (intercepted by callback)."
|
||||
},
|
||||
{
|
||||
"text": "\n\nafter_model_callback: [Token Usage: Input=133, Output=13]"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "weather in Sunnyvale"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "Sunnyvale"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_weather"
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "\n\nafter_model_callback: [Token Usage: Input=23, Output=6]"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_weather",
|
||||
"response": {
|
||||
"result": "The weather in Sunnyvale is sunny."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "callback_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in Sunnyvale is sunny."
|
||||
},
|
||||
{
|
||||
"text": "\n\nafter_model_callback: [Token Usage: Input=108, Output=8]"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "callback_demo_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# ADK Agent Empty Sample
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates how to create a minimal, empty agent using the **ADK** framework.
|
||||
|
||||
It defines a simple `Agent` that doesn't have any specific tools or complex instructions attached to it. This is useful as a basic template or starting point for defining more complex agentic behaviors over time, ensuring it follows the core directory requirements of `adk`.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `go`
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
empty_agent[Agent: empty_agent]
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
1. Define a basic agent using the `Agent` class, specifying a unique name:
|
||||
|
||||
```python
|
||||
from google.adk.agents import Agent
|
||||
|
||||
root_agent = Agent(
|
||||
name="empty_agent",
|
||||
)
|
||||
```
|
||||
@@ -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,21 @@
|
||||
# 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 import Agent
|
||||
|
||||
root_agent = Agent(
|
||||
name="empty_agent",
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "go"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "empty_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Okay, I'm ready! What would you like to do or talk about?"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "empty_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "empty_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello there! How can I help you today?"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "empty_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "How are you?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "empty_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "As an AI, I don't have feelings or a physical state, so I can't be \"fine\" in the human sense. However, I'm ready and operational to help you!\n\nHow can I assist you today?"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "empty_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
# Hello World Assistant
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates a foundational ADK standalone agent that interacts with a user, manages session state via `ToolContext`, and uses multiple tools. Specifically, it features a `hello_world_agent` that can roll an N-sided die (storing roll history in the session state) and check whether numbers in a list are prime.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `Hi`
|
||||
|
||||
*General greeting that does not trigger tool calls.*
|
||||
|
||||
- `Roll a dice with 100 sides`
|
||||
|
||||
*The agent invokes the `roll_die` tool with `sides=100`. The rolled result is appended to the session's `ToolContext` state under the `'rolls'` key.*
|
||||
|
||||
- `Roll a dice again with 100 sides.`
|
||||
|
||||
*The agent invokes `roll_die` again, appending a second roll to the session state.*
|
||||
|
||||
- `What numbers did I got?`
|
||||
|
||||
*The agent references the conversation history and previous tool outcomes to summarize the rolled numbers.*
|
||||
|
||||
- `Roll a die with 8 sides and check if the result is prime.`
|
||||
|
||||
*Demonstrates multi-step tool orchestration. The agent first calls `roll_die(sides=8)`, waits for the response, and then calls `check_prime(nums=[...])` with the rolled result before formulating its final response.*
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Agent[Agent: hello_world_agent] --> Tool1[Tool: roll_die]
|
||||
Agent --> Tool2[Tool: check_prime]
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
### 1. Defining Tools with ToolContext
|
||||
|
||||
Demonstrates how tools can access and modify persistent session state by including `tool_context: ToolContext` as a parameter.
|
||||
|
||||
```python
|
||||
def roll_die(sides: int, tool_context: ToolContext) -> int:
|
||||
result = random.randint(1, sides)
|
||||
if not 'rolls' in tool_context.state:
|
||||
tool_context.state['rolls'] = []
|
||||
tool_context.state['rolls'] = tool_context.state['rolls'] + [result]
|
||||
return result
|
||||
```
|
||||
|
||||
### 2. Configuring Safety Settings
|
||||
|
||||
Demonstrates adjusting `GenerateContentConfig` safety settings to prevent false alarms (e.g., avoiding harm category triggers when discussing rolling dice).
|
||||
|
||||
```python
|
||||
root_agent = Agent(
|
||||
model='gemini-3-flash-preview',
|
||||
name='hello_world_agent',
|
||||
...
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
safety_settings=[
|
||||
types.SafetySetting(
|
||||
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
||||
threshold=types.HarmBlockThreshold.OFF,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Running and Inspecting the Agent Programmatically
|
||||
|
||||
You can execute the agent and inspect its session state programmatically by initializing an `InMemoryRunner`, creating a session, and executing prompts asynchronously:
|
||||
|
||||
```python
|
||||
runner = InMemoryRunner(agent=agent.root_agent, app_name='my_app')
|
||||
session = await runner.session_service.create_session('my_app', 'user1')
|
||||
|
||||
async for event in runner.run_async(
|
||||
user_id='user1',
|
||||
session_id=session.id,
|
||||
new_message=types.Content(...),
|
||||
):
|
||||
# Process execution events
|
||||
pass
|
||||
|
||||
# Inspect modified session state
|
||||
session = await runner.session_service.get_session('my_app', 'user1', session.id)
|
||||
print(session.state['rolls'])
|
||||
```
|
||||
@@ -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,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.
|
||||
|
||||
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 not 'rolls' 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(
|
||||
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,86 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Check if 7 and 10 are prime numbers"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"nums": [
|
||||
7,
|
||||
10
|
||||
]
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "check_prime"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "check_prime",
|
||||
"response": {
|
||||
"result": "7 are prime numbers."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "7 are prime numbers."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Roll a 10-sided die and check if the result is prime"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"sides": 10
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "roll_die"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"actions": {
|
||||
"stateDelta": {
|
||||
"rolls": [
|
||||
2
|
||||
]
|
||||
}
|
||||
},
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "roll_die",
|
||||
"response": {
|
||||
"result": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"nums": [
|
||||
2
|
||||
]
|
||||
},
|
||||
"id": "fc-2",
|
||||
"name": "check_prime"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-2",
|
||||
"name": "check_prime",
|
||||
"response": {
|
||||
"result": "2 are prime numbers."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-5",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "I rolled a 10-sided die and got a 2.\n2 are prime numbers."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-6",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Roll a 6-sided die"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"sides": 6
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "roll_die"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"actions": {
|
||||
"stateDelta": {
|
||||
"rolls": [
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "roll_die",
|
||||
"response": {
|
||||
"result": 6
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "hello_world_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "You rolled a 6-sided die and got a 6."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "hello_world_agent@1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mocks": {
|
||||
"random.randint": [
|
||||
6
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Input and Output Schema
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates how to configure structured `input_schema` and `output_schema` on an ADK agent. When configured with these schemas and `mode='single_turn'`, the agent can be seamlessly used as a structured tool by a parent agent.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `What is the weather in San Jose?`
|
||||
|
||||
*The parent agent calls `weather_agent` with `{"city": "San Jose"}`. The sub-agent returns `{"temperature": "26 C", "conditions": "Sunny"}`. The parent agent then formulates a friendly response.*
|
||||
|
||||
- `Can you check the weather for Cupertino?`
|
||||
|
||||
*The parent agent calls `weather_agent` with `{"city": "Cupertino"}`. The sub-agent returns `{"temperature": "16 C", "conditions": "Foggy"}`.*
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
User[User Input] --> RootAgent[root_agent]
|
||||
RootAgent -- Tool Call: CityQuery --> WeatherAgent[weather_agent]
|
||||
WeatherAgent -- Structured Output: WeatherInfo --> RootAgent
|
||||
RootAgent --> Response[User Response]
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
### Configuring Input and Output Schemas
|
||||
|
||||
To define structured input and output contracts for an agent, pass Pydantic models to the `input_schema` and `output_schema` parameters of the `Agent` constructor:
|
||||
|
||||
```python
|
||||
class CityQuery(BaseModel):
|
||||
city: str = Field(description="The name of the city")
|
||||
|
||||
class WeatherInfo(BaseModel):
|
||||
temperature: str = Field(description="The temperature in Celsius")
|
||||
conditions: str = Field(description="The weather condition")
|
||||
|
||||
weather_agent = Agent(
|
||||
name="weather_agent",
|
||||
mode="single_turn",
|
||||
input_schema=CityQuery,
|
||||
output_schema=WeatherInfo,
|
||||
instruction="Provide weather information for the requested city.",
|
||||
)
|
||||
```
|
||||
|
||||
### Using the Agent as a Tool
|
||||
|
||||
When `weather_agent` is included in the `sub_agents` list of `root_agent`, the ADK framework automatically wraps it in an `AgentTool`. The parent agent sees a tool that accepts `CityQuery` parameters and returns `WeatherInfo`.
|
||||
|
||||
When the tool is invoked, the framework executes `weather_agent` in an isolated context, validates its input against `input_schema`, and validates its generated response against `output_schema`.
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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 import Agent
|
||||
from pydantic import BaseModel
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class CityQuery(BaseModel):
|
||||
city: str = Field(
|
||||
description='The name of the city to query weather for, e.g. San Jose'
|
||||
)
|
||||
|
||||
|
||||
class WeatherInfo(BaseModel):
|
||||
temperature: str = Field(description='The temperature in Celsius')
|
||||
conditions: str = Field(description='The weather condition, e.g. Sunny')
|
||||
|
||||
|
||||
weather_agent = Agent(
|
||||
name='weather_agent',
|
||||
mode='single_turn',
|
||||
input_schema=CityQuery,
|
||||
output_schema=WeatherInfo,
|
||||
instruction="""\
|
||||
Provide weather information for the requested city.
|
||||
|
||||
For San Jose, return temperature: 26 C, conditions: Sunny.
|
||||
For Cupertino, return temperature: 16 C, conditions: Foggy.
|
||||
For any other city, return temperature: unknown, conditions: unknown.
|
||||
""",
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
name='root_agent',
|
||||
instruction="""\
|
||||
You are a helpful weather concierge assistant. Use the weather_agent tool to get weather information for the user's city, and then answer the user in a friendly manner.
|
||||
""",
|
||||
sub_agents=[weather_agent],
|
||||
)
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is the weather in Cupertino?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "Cupertino"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "weather_agent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_agent",
|
||||
"branch": "weather_agent@fc-1",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "{\"temperature\": \"16 C\", \"conditions\": \"Foggy\"}"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"messageAsOutput": true,
|
||||
"outputFor": [
|
||||
"root_agent@1/weather_agent@1"
|
||||
],
|
||||
"path": "root_agent@1/weather_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "weather_agent",
|
||||
"response": {
|
||||
"conditions": "Foggy",
|
||||
"temperature": "16 C"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in Cupertino is Foggy with a temperature of 16 C.\n"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-5",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is the weather in San Jose?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "San Jose"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "weather_agent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_agent",
|
||||
"branch": "weather_agent@fc-1",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "{\"temperature\": \"26 C\", \"conditions\": \"Sunny\"}"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"messageAsOutput": true,
|
||||
"outputFor": [
|
||||
"root_agent@1/weather_agent@1"
|
||||
],
|
||||
"path": "root_agent@1/weather_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "weather_agent",
|
||||
"response": {
|
||||
"conditions": "Sunny",
|
||||
"temperature": "26 C"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in San Jose is sunny with a temperature of 26 degrees Celsius. Enjoy your day!"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-5",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is the weather in Tokyo?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "weather_agent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_agent",
|
||||
"branch": "weather_agent@fc-1",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "{\"temperature\": \"unknown\", \"conditions\": \"unknown\"}"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"messageAsOutput": true,
|
||||
"outputFor": [
|
||||
"root_agent@1/weather_agent@1"
|
||||
],
|
||||
"path": "root_agent@1/weather_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "weather_agent",
|
||||
"response": {
|
||||
"conditions": "unknown",
|
||||
"temperature": "unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "I'm sorry, I don't have the current weather information for Tokyo."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-5",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
# Log Probabilities Demo Agent
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates how to access and display log probabilities from language model responses using the `avg_logprobs` and `logprobs_result` fields in `LlmResponse`. It shows how to configure an ADK agent to request log probabilities and how to use an `after_model_callback` to analyze and append confidence metrics to the response.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `What is the capital of France?`
|
||||
|
||||
*A factual, straightforward question. The agent will answer confidently (e.g., "Paris"), resulting in a high average log probability and confidence score near 100%.*
|
||||
|
||||
- `What are the philosophical implications of artificial consciousness?`
|
||||
|
||||
*A complex, open-ended question. The agent will provide a nuanced answer with varied vocabulary, resulting in a lower average log probability and medium/low confidence score.*
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
User[User Input] --> RootAgent[root_agent: logprobs_demo_agent]
|
||||
RootAgent --> Callback[after_model_callback: append_logprobs_to_response]
|
||||
Callback -- Appended Logprobs Analysis --> Response[User Response]
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
### 1. Enabling Log Probabilities
|
||||
|
||||
To enable log probability collection, configure `generate_content_config` on the `Agent` using `types.GenerateContentConfig`:
|
||||
|
||||
```python
|
||||
from google.genai import types
|
||||
|
||||
root_agent = Agent(
|
||||
name="logprobs_demo_agent",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
response_logprobs=True, # Enable log probability collection
|
||||
logprobs=5, # Collect top 5 alternatives for analysis
|
||||
temperature=0.7,
|
||||
),
|
||||
after_model_callback=append_logprobs_to_response,
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Extracting Log Probabilities in a Callback
|
||||
|
||||
The `after_model_callback` receives the `LlmResponse` object, which contains the `avg_logprobs` and `logprobs_result` fields. You can use this data for confidence analysis, quality filtering, or appending information to the response content:
|
||||
|
||||
```python
|
||||
async def append_logprobs_to_response(
|
||||
callback_context: CallbackContext, llm_response: LlmResponse
|
||||
) -> LlmResponse:
|
||||
if llm_response.avg_logprobs is not None:
|
||||
print(f"📊 Average log probability: {llm_response.avg_logprobs:.4f}")
|
||||
|
||||
# Analyze confidence
|
||||
confidence_level = (
|
||||
"High" if llm_response.avg_logprobs >= -0.5
|
||||
else "Medium" if llm_response.avg_logprobs >= -1.0
|
||||
else "Low"
|
||||
)
|
||||
|
||||
# Access detailed candidates
|
||||
if llm_response.logprobs_result and llm_response.logprobs_result.top_candidates:
|
||||
num_candidates = len(llm_response.logprobs_result.top_candidates)
|
||||
|
||||
return llm_response
|
||||
```
|
||||
|
||||
### 3. Understanding Log Probabilities
|
||||
|
||||
- **Range**: -∞ to 0 (0 = 100% confident, -1 ≈ 37% confident, -2 ≈ 14% confident)
|
||||
- **Confidence Levels**:
|
||||
- **High**: `>= -0.5` (typically factual, straightforward responses)
|
||||
- **Medium**: `-1.0` to `-0.5` (reasonably confident responses)
|
||||
- **Low**: `< -1.0` (uncertain or complex responses)
|
||||
- **Use Cases**: Quality control, uncertainty detection, and response filtering.
|
||||
@@ -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,116 @@
|
||||
# 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 demonstrating log probability usage.
|
||||
|
||||
This agent shows how to access log probabilities from language model responses.
|
||||
The after_model_callback appends confidence information to demonstrate how
|
||||
logprobs can be extracted and used.
|
||||
"""
|
||||
|
||||
from google.adk.agents.callback_context import CallbackContext
|
||||
from google.adk.agents.llm_agent import Agent
|
||||
from google.adk.models.llm_response import LlmResponse
|
||||
from google.genai import types
|
||||
|
||||
|
||||
async def append_logprobs_to_response(
|
||||
callback_context: CallbackContext, llm_response: LlmResponse
|
||||
) -> LlmResponse:
|
||||
"""After-model callback that appends log probability information to response.
|
||||
|
||||
This callback demonstrates how to access avg_logprobs and logprobs_result
|
||||
from the LlmResponse and append the information to the response content.
|
||||
|
||||
Args:
|
||||
callback_context: The current callback context
|
||||
llm_response: The LlmResponse containing logprobs data
|
||||
|
||||
Returns:
|
||||
Modified LlmResponse with logprobs information appended
|
||||
"""
|
||||
# Build log probability analysis
|
||||
if llm_response.avg_logprobs is None:
|
||||
print("⚠️ No log probability data available")
|
||||
logprobs_info = """
|
||||
|
||||
---
|
||||
|
||||
### 📊 Log Probability Analysis
|
||||
|
||||
⚠️ *No log probability data available*"""
|
||||
else:
|
||||
print(f"📊 Average log probability: {llm_response.avg_logprobs:.4f}")
|
||||
|
||||
# Build confidence analysis
|
||||
confidence_level = (
|
||||
"High"
|
||||
if llm_response.avg_logprobs >= -0.5
|
||||
else "Medium"
|
||||
if llm_response.avg_logprobs >= -1.0
|
||||
else "Low"
|
||||
)
|
||||
|
||||
logprobs_info = f"""
|
||||
|
||||
---
|
||||
|
||||
### 📊 Log Probability Analysis
|
||||
|
||||
* **Average Log Probability**: {llm_response.avg_logprobs:.4f}
|
||||
* **Confidence Level**: {confidence_level}
|
||||
* **Confidence Score**: {100 * (2 ** llm_response.avg_logprobs):.1f}%"""
|
||||
|
||||
# Optionally include detailed logprobs_result information
|
||||
if (
|
||||
llm_response.logprobs_result
|
||||
and llm_response.logprobs_result.top_candidates
|
||||
):
|
||||
logprobs_info += (
|
||||
"\n* **Top Alternatives Analyzed**:"
|
||||
f" {len(llm_response.logprobs_result.top_candidates)}"
|
||||
)
|
||||
|
||||
# Append logprobs analysis to the response
|
||||
if llm_response.content and llm_response.content.parts:
|
||||
if not any(
|
||||
"### 📊 Log Probability Analysis" in p.text
|
||||
for p in llm_response.content.parts
|
||||
if p.text
|
||||
):
|
||||
llm_response.content.parts.append(types.Part(text=logprobs_info))
|
||||
|
||||
return llm_response
|
||||
|
||||
|
||||
# Create a simple agent that demonstrates logprobs usage
|
||||
root_agent = Agent(
|
||||
name="logprobs_demo_agent",
|
||||
description=(
|
||||
"A simple agent that demonstrates log probability extraction and"
|
||||
" display."
|
||||
),
|
||||
instruction="""
|
||||
You are a helpful AI assistant. Answer user questions normally and naturally.
|
||||
|
||||
After you respond, you'll see log probability analysis appended to your response.
|
||||
You don't need to include the log probability analysis in your response yourself.
|
||||
""",
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
response_logprobs=True, # Enable log probability collection
|
||||
logprobs=5, # Collect top 5 alternatives for analysis
|
||||
temperature=0.7, # Moderate temperature for varied responses
|
||||
),
|
||||
after_model_callback=append_logprobs_to_response,
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "logprobs_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello there! How can I help you today?"
|
||||
},
|
||||
{
|
||||
"text": "\n\n---\n\n### \ud83d\udcca Log Probability Analysis\n\n* **Average Log Probability**: -0.1564\n* **Confidence Level**: High\n* **Confidence Score**: 89.7%\n* **Top Alternatives Analyzed**: 10"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "logprobs_demo_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Tell me a joke"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "logprobs_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Why don't scientists trust atoms?\n\nBecause they make up everything!"
|
||||
},
|
||||
{
|
||||
"text": "\n\n---\n\n### \ud83d\udcca Log Probability Analysis\n\n* **Average Log Probability**: -0.4411\n* **Confidence Level**: High\n* **Confidence Score**: 73.7%\n* **Top Alternatives Analyzed**: 15"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "logprobs_demo_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is AI?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "logprobs_demo_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Artificial Intelligence (AI) refers to the simulation of human intelligence processes by machines, especially computer systems. These processes include learning (the acquisition of information and rules for using the information), reasoning (using rules to reach approximate or definite conclusions), and self-correction.\n\nThe ultimate goal of AI is to enable machines to perform tasks that typically require human intelligence, such as understanding natural language, recognizing patterns, solving problems, making decisions, and even learning from experience."
|
||||
},
|
||||
{
|
||||
"text": "\n\n---\n\n### \ud83d\udcca Log Probability Analysis\n\n* **Average Log Probability**: -0.1382\n* **Confidence Level**: High\n* **Confidence Score**: 90.9%\n* **Top Alternatives Analyzed**: 92"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "logprobs_demo_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
# Weather & Time Quickstart Agent
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates a fundamental standalone ADK `Agent` configured with multiple tools. It illustrates how an agent can autonomously select and execute Python functions (`get_weather` and `get_current_time`) to gather real-world information and answer user inquiries.
|
||||
|
||||
## Sample Inputs
|
||||
|
||||
- `What is the weather in New York?`
|
||||
|
||||
*The agent will invoke the `get_weather` tool with `city="New York"` and return the current weather report.*
|
||||
|
||||
- `What time is it in New York?`
|
||||
|
||||
*The agent will invoke the `get_current_time` tool with `city="New York"` and return the current timestamp.*
|
||||
|
||||
- `Can you tell me the weather in Tokyo?`
|
||||
|
||||
*The agent will attempt to invoke `get_weather`, which returns an error status for cities other than New York, and gracefully explain that the information is unavailable.*
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
User[User Input] --> RootAgent[root_agent: weather_time_agent]
|
||||
RootAgent -.->|Tool Call: get_weather| WeatherTool[get_weather]
|
||||
WeatherTool -.->|Tool Result| RootAgent
|
||||
RootAgent -.->|Tool Call: get_current_time| TimeTool[get_current_time]
|
||||
TimeTool -.->|Tool Result| RootAgent
|
||||
RootAgent --> Response[User Response]
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
### 1. Defining Tools
|
||||
|
||||
In ADK, standard Python functions with type hints and docstrings can be used directly as tools. The docstring and parameter type hints inform the language model when and how to invoke the function:
|
||||
|
||||
```python
|
||||
def get_weather(city: str) -> dict:
|
||||
"""Retrieves the current weather report for a specified city.
|
||||
|
||||
Args:
|
||||
city (str): The name of the city for which to retrieve the weather report.
|
||||
|
||||
Returns:
|
||||
dict: status and result or error msg.
|
||||
"""
|
||||
if city.lower() == "new york":
|
||||
return {
|
||||
"status": "success",
|
||||
"report": (
|
||||
"The weather in New York is sunny with a temperature of 25 degrees"
|
||||
" Celsius (77 degrees Fahrenheit)."
|
||||
),
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_message": f"Weather information for '{city}' is not available.",
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Configuring the Agent
|
||||
|
||||
To equip an agent with tools, instantiate an `Agent` and pass the functions in the `tools` parameter list, along with clear instructions and description:
|
||||
|
||||
```python
|
||||
from google.adk.agents.llm_agent import Agent
|
||||
|
||||
root_agent = Agent(
|
||||
name="weather_time_agent",
|
||||
description=(
|
||||
"Agent to answer questions about the time and weather in a city."
|
||||
),
|
||||
instruction=(
|
||||
"I can answer your questions about the time and weather in a city."
|
||||
),
|
||||
tools=[get_weather, 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,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.
|
||||
|
||||
from google.adk.agents.llm_agent import Agent
|
||||
|
||||
|
||||
def get_weather(city: str) -> dict:
|
||||
"""Retrieves the current weather report for a specified city.
|
||||
|
||||
Args:
|
||||
city (str): The name of the city for which to retrieve the weather report.
|
||||
|
||||
Returns:
|
||||
dict: status and result or error msg.
|
||||
"""
|
||||
if city.lower() == "new york":
|
||||
return {
|
||||
"status": "success",
|
||||
"report": (
|
||||
"The weather in New York is sunny with a temperature of 25 degrees"
|
||||
" Celsius (77 degrees Fahrenheit)."
|
||||
),
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_message": f"Weather information for '{city}' is not available.",
|
||||
}
|
||||
|
||||
|
||||
def get_current_time(city: str) -> dict:
|
||||
"""Returns the current time in a specified city.
|
||||
|
||||
Args:
|
||||
city (str): The name of the city for which to retrieve the current time.
|
||||
|
||||
Returns:
|
||||
dict: status and result or error msg.
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
if "PYTEST_CURRENT_TEST" in os.environ:
|
||||
return {
|
||||
"status": "success",
|
||||
"report": (
|
||||
"The current time in New York is 2026-05-15 12:00:00 EDT-0400"
|
||||
),
|
||||
}
|
||||
|
||||
if city.lower() == "new york":
|
||||
tz_identifier = "America/New_York"
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_message": (
|
||||
f"Sorry, I don't have timezone information for {city}."
|
||||
),
|
||||
}
|
||||
|
||||
tz = ZoneInfo(tz_identifier)
|
||||
now = datetime.datetime.now(tz)
|
||||
report = (
|
||||
f'The current time in {city} is {now.strftime("%Y-%m-%d %H:%M:%S %Z%z")}'
|
||||
)
|
||||
return {"status": "success", "report": report}
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
name="weather_time_agent",
|
||||
description=(
|
||||
"Agent to answer questions about the time and weather in a city."
|
||||
),
|
||||
instruction=(
|
||||
"I can answer your questions about the time and weather in a city."
|
||||
),
|
||||
tools=[get_weather, get_current_time],
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What time is it in New York?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "New York"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_current_time"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_current_time",
|
||||
"response": {
|
||||
"report": "The current time in New York is 2026-05-15 12:00:00 EDT-0400",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The current time in New York is 2026-05-15 12:00:00 EDT-0400."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is the weather in New York?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "New York"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_weather"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_weather",
|
||||
"response": {
|
||||
"report": "The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit).",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit)."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is the weather and time in New York?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "New York"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_weather"
|
||||
}
|
||||
},
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "New York"
|
||||
},
|
||||
"id": "fc-2",
|
||||
"name": "get_current_time"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_weather",
|
||||
"response": {
|
||||
"report": "The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit).",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-2",
|
||||
"name": "get_current_time",
|
||||
"response": {
|
||||
"report": "The current time in New York is 2026-05-15 12:00:00 EDT-0400",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "weather_time_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit). The current time in New York is 2026-05-15 12:00:00 EDT-0400."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "weather_time_agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
# Runner Debug Helper Example
|
||||
|
||||
This example demonstrates the `run_debug()` helper method that simplifies agent interaction for debugging and experimentation in ADK.
|
||||
|
||||
## Overview
|
||||
|
||||
The `run_debug()` method reduces agent interaction boilerplate from 7-8 lines to just 2 lines, making it ideal for:
|
||||
|
||||
- Quick debugging sessions
|
||||
- Jupyter notebooks
|
||||
- REPL experimentation
|
||||
- Writing examples
|
||||
- Initial agent development
|
||||
|
||||
## Files Included
|
||||
|
||||
- `agent.py` - Agent with 2 tools: weather and stock price
|
||||
- `main.py` - 8 examples demonstrating all features
|
||||
- `README.md` - This documentation
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Set your Google API key:
|
||||
|
||||
```bash
|
||||
export GOOGLE_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
### Running the Example
|
||||
|
||||
```bash
|
||||
python -m contributing.samples.runner_debug_example.main
|
||||
```
|
||||
|
||||
## Features Demonstrated
|
||||
|
||||
1. **Minimal Usage**: Simple 2-line agent interaction
|
||||
1. **Multiple Messages**: Processing multiple messages in sequence
|
||||
1. **Session Persistence**: Maintaining conversation context
|
||||
1. **Separate Sessions**: Managing multiple user sessions
|
||||
1. **Tool Calls**: Displaying tool invocations and results
|
||||
1. **Event Capture**: Collecting events for programmatic inspection
|
||||
1. **Advanced Configuration**: Using RunConfig for custom settings
|
||||
1. **Comparison**: Before/after boilerplate reduction
|
||||
|
||||
## Part Types Supported
|
||||
|
||||
The `run_debug()` method properly displays all ADK part types:
|
||||
|
||||
| Part Type | Display Format | Use Case |
|
||||
| ----------------------- | ---------------------------------------- | ---------------------- |
|
||||
| `text` | `agent > {text}` | Regular text responses |
|
||||
| `function_call` | `agent > [Calling tool: {name}({args})]` | Tool invocations |
|
||||
| `function_response` | `agent > [Tool result: {response}]` | Tool results |
|
||||
| `executable_code` | `agent > [Executing {language} code...]` | Code blocks |
|
||||
| `code_execution_result` | `agent > [Code output: {output}]` | Code execution results |
|
||||
| `inline_data` | `agent > [Inline data: {mime_type}]` | Images, files, etc. |
|
||||
| `file_data` | `agent > [File: {uri}]` | File references |
|
||||
|
||||
## Tools Available in Example
|
||||
|
||||
The example agent includes 2 tools to demonstrate tool handling:
|
||||
|
||||
1. **`get_weather(city)`** - Returns mock weather data for major cities
|
||||
1. **`get_stock_price(ticker)`** - Returns mock stock prices for major tech companies
|
||||
|
||||
## Key Benefits
|
||||
|
||||
### Before (7-8 lines)
|
||||
|
||||
```python
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.genai import types
|
||||
|
||||
APP_NAME = "default"
|
||||
USER_ID = "default"
|
||||
session_service = InMemorySessionService()
|
||||
runner = Runner(agent=agent, app_name=APP_NAME, session_service=session_service)
|
||||
session = await session_service.create_session(
|
||||
app_name=APP_NAME, user_id=USER_ID, session_id="default"
|
||||
)
|
||||
content = types.Content(role="user", parts=[types.Part.from_text("Hi")])
|
||||
async for event in runner.run_async(
|
||||
user_id=USER_ID, session_id=session.id, new_message=content
|
||||
):
|
||||
if event.content and event.content.parts:
|
||||
print(event.content.parts[0].text)
|
||||
```
|
||||
|
||||
### After (2 lines)
|
||||
|
||||
```python
|
||||
runner = InMemoryRunner(agent=agent)
|
||||
await runner.run_debug("Hi")
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
```python
|
||||
async def run_debug(
|
||||
self,
|
||||
user_messages: str | list[str],
|
||||
*,
|
||||
user_id: str = 'debug_user_id',
|
||||
session_id: str = 'debug_session_id',
|
||||
run_config: Optional[RunConfig] = None,
|
||||
quiet: bool = False,
|
||||
verbose: bool = False,
|
||||
) -> List[Event]:
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
- `user_messages`: Single message string or list of messages (required)
|
||||
- `user_id`: User identifier for session tracking (default: 'debug_user_id')
|
||||
- `session_id`: Session identifier for conversation continuity (default: 'debug_session_id')
|
||||
- `run_config`: Optional advanced configuration
|
||||
- `quiet`: Whether to suppress output to console (default: False)
|
||||
- `verbose`: Whether to show detailed tool calls and responses (default: False)
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```python
|
||||
# Minimal usage
|
||||
runner = InMemoryRunner(agent=agent)
|
||||
await runner.run_debug("What's the weather?")
|
||||
|
||||
# Multiple queries
|
||||
await runner.run_debug(["Query 1", "Query 2", "Query 3"])
|
||||
|
||||
# Custom session
|
||||
await runner.run_debug(
|
||||
"Hello",
|
||||
user_id="alice",
|
||||
session_id="debug_session"
|
||||
)
|
||||
|
||||
# Capture events without printing
|
||||
events = await runner.run_debug(
|
||||
"Process this",
|
||||
quiet=True
|
||||
)
|
||||
|
||||
# Show tool calls with verbose mode
|
||||
await runner.run_debug(
|
||||
"What's the weather?",
|
||||
verbose=True # Shows [Calling tool: ...] and [Tool result: ...]
|
||||
)
|
||||
|
||||
# With custom configuration
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
config = RunConfig(support_cfc=False)
|
||||
await runner.run_debug("Query", run_config=config)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues and Solutions
|
||||
|
||||
1. **Tool calls not showing in output**
|
||||
|
||||
- **Issue**: Tool invocations and responses are not displayed
|
||||
|
||||
- **Solution**: Set `verbose=True` to see detailed tool interactions:
|
||||
|
||||
```python
|
||||
await runner.run_debug("Query", verbose=True)
|
||||
```
|
||||
|
||||
1. **Import errors when running tests**
|
||||
|
||||
- **Issue**: `ModuleNotFoundError: No module named 'google.adk'`
|
||||
|
||||
- **Solution**: Ensure you're using the virtual environment:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
python -m pytest tests/
|
||||
```
|
||||
|
||||
1. **Session state not persisting between calls**
|
||||
|
||||
- **Issue**: Agent doesn't remember previous interactions
|
||||
|
||||
- **Solution**: Use the same `user_id` and `session_id` across calls:
|
||||
|
||||
```python
|
||||
await runner.run_debug("First query", user_id="alice", session_id="debug")
|
||||
await runner.run_debug("Follow-up", user_id="alice", session_id="debug")
|
||||
```
|
||||
|
||||
1. **Output truncation issues**
|
||||
|
||||
- **Issue**: Long tool responses are truncated with "..."
|
||||
|
||||
- **Solution**: This is by design to keep debug output readable. For full responses, use:
|
||||
|
||||
```python
|
||||
events = await runner.run_debug("Query", quiet=True)
|
||||
# Process events programmatically for full content
|
||||
```
|
||||
|
||||
1. **API key errors**
|
||||
|
||||
- **Issue**: Authentication failures or missing API key
|
||||
|
||||
- **Solution**: Ensure your Google API key is set:
|
||||
|
||||
```bash
|
||||
export GOOGLE_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
`run_debug()` is designed for debugging and experimentation only. For production use requiring:
|
||||
|
||||
- Custom session/memory services (Spanner, Cloud SQL)
|
||||
- Fine-grained event processing
|
||||
- Error recovery and resumability
|
||||
- Performance optimization
|
||||
|
||||
Use the standard `run_async()` method instead.
|
||||
@@ -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.
|
||||
|
||||
"""Runner debug example demonstrating simplified agent interaction."""
|
||||
|
||||
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.
|
||||
|
||||
"""Example agent for demonstrating run_debug helper method."""
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
|
||||
|
||||
def get_weather(city: str, tool_context: ToolContext) -> str:
|
||||
"""Get weather information for a city.
|
||||
|
||||
Args:
|
||||
city: Name of the city to get weather for.
|
||||
tool_context: Tool context for session state.
|
||||
|
||||
Returns:
|
||||
Weather information as a string.
|
||||
"""
|
||||
# Store query history in session state
|
||||
if "weather_queries" not in tool_context.state:
|
||||
tool_context.state["weather_queries"] = [city]
|
||||
else:
|
||||
tool_context.state["weather_queries"] = tool_context.state[
|
||||
"weather_queries"
|
||||
] + [city]
|
||||
|
||||
# Mock weather data for demonstration
|
||||
weather_data = {
|
||||
"San Francisco": "Foggy, 15°C (59°F)",
|
||||
"New York": "Sunny, 22°C (72°F)",
|
||||
"London": "Rainy, 12°C (54°F)",
|
||||
"Tokyo": "Clear, 25°C (77°F)",
|
||||
"Paris": "Cloudy, 18°C (64°F)",
|
||||
}
|
||||
|
||||
return weather_data.get(
|
||||
city, f"Weather data not available for {city}. Try a major city."
|
||||
)
|
||||
|
||||
|
||||
def get_stock_price(ticker: str) -> str:
|
||||
"""Get the current stock price for a given ticker symbol.
|
||||
|
||||
This tool demonstrates how function calls are displayed in run_debug().
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol (e.g., GOOGL, AAPL, MSFT).
|
||||
|
||||
Returns:
|
||||
Stock price information as a string.
|
||||
"""
|
||||
prices = {
|
||||
"GOOGL": "175.50 USD",
|
||||
"AAPL": "225.00 USD",
|
||||
"MSFT": "420.00 USD",
|
||||
"AMZN": "190.00 USD",
|
||||
"NVDA": "125.00 USD",
|
||||
}
|
||||
ticker = ticker.upper()
|
||||
if ticker in prices:
|
||||
return f"Price for {ticker}: {prices[ticker]}"
|
||||
return f"Stock ticker {ticker} not found in database."
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.5-flash-lite",
|
||||
name="agent",
|
||||
description="A helpful assistant demonstrating run_debug() helper method",
|
||||
instruction="""You are a helpful assistant that can:
|
||||
1. Provide weather information for major cities
|
||||
2. Provide stock prices for major tech companies
|
||||
3. Remember previous queries in the conversation
|
||||
|
||||
When users ask about weather, use the get_weather tool.
|
||||
When users ask for stock prices, use the get_stock_price tool.
|
||||
Be friendly and conversational.""",
|
||||
tools=[get_weather, get_stock_price],
|
||||
)
|
||||
@@ -0,0 +1,261 @@
|
||||
# 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.
|
||||
|
||||
"""Demonstrates the run_debug() helper method for simplified agent interaction."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from google.adk.runners import InMemoryRunner
|
||||
|
||||
from . import agent
|
||||
|
||||
|
||||
async def example_minimal():
|
||||
"""Minimal usage - just 2 lines for debugging."""
|
||||
print("------------------------------------")
|
||||
print("Example 1: Minimal Debug Usage")
|
||||
print("------------------------------------")
|
||||
|
||||
# Create runner
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
# Debug with just 2 lines
|
||||
await runner.run_debug("What's the weather in San Francisco?")
|
||||
|
||||
|
||||
async def example_multiple_messages():
|
||||
"""Debug with multiple messages in sequence."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 2: Multiple Messages")
|
||||
print("------------------------------------")
|
||||
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
# Pass multiple messages as a list
|
||||
await runner.run_debug([
|
||||
"Hi there!",
|
||||
"What's the weather in Tokyo?",
|
||||
"How about New York?",
|
||||
"What's the stock price of GOOGL?",
|
||||
])
|
||||
|
||||
|
||||
async def example_conversation_persistence():
|
||||
"""Demonstrate conversation persistence during debugging."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 3: Session Persistence")
|
||||
print("------------------------------------")
|
||||
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
# First interaction
|
||||
await runner.run_debug("Hi, I'm planning a trip to Europe")
|
||||
|
||||
# Second interaction - continues same session
|
||||
await runner.run_debug("What's the weather in Paris?")
|
||||
|
||||
# Third interaction - agent remembers context
|
||||
await runner.run_debug("And London?")
|
||||
|
||||
# Fourth interaction - referring to previous messages
|
||||
await runner.run_debug("Which city had better weather?")
|
||||
|
||||
|
||||
async def example_separate_sessions():
|
||||
"""Debug with multiple separate sessions."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 4: Separate Sessions")
|
||||
print("------------------------------------")
|
||||
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
# Alice's session
|
||||
print("\n-- Alice's session --")
|
||||
await runner.run_debug(
|
||||
"What's the weather in San Francisco?",
|
||||
user_id="alice",
|
||||
session_id="alice_debug",
|
||||
)
|
||||
|
||||
# Bob's session (separate)
|
||||
print("\n-- Bob's session --")
|
||||
await runner.run_debug(
|
||||
"What is the price of AAPL?", user_id="bob", session_id="bob_debug"
|
||||
)
|
||||
|
||||
# Continue Alice's session
|
||||
print("\n-- Back to Alice's session --")
|
||||
await runner.run_debug(
|
||||
"Should I bring an umbrella?",
|
||||
user_id="alice",
|
||||
session_id="alice_debug",
|
||||
)
|
||||
|
||||
|
||||
async def example_with_tools():
|
||||
"""Demonstrate tool calls and responses with verbose flag."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 5: Tool Calls (verbose flag)")
|
||||
print("------------------------------------")
|
||||
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
print("\n-- Default (verbose=False) - Clean output --")
|
||||
# Without verbose: Only shows final agent responses
|
||||
await runner.run_debug([
|
||||
"What's the weather in Tokyo?",
|
||||
"Check MSFT stock price",
|
||||
])
|
||||
|
||||
print("\n-- With verbose=True - Detailed output --")
|
||||
# With verbose: Shows tool calls as [Calling tool: ...] and [Tool result: ...]
|
||||
await runner.run_debug(
|
||||
[
|
||||
"What's the weather in Paris?",
|
||||
"What's the stock price of NVDA?",
|
||||
],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
|
||||
async def example_capture_events():
|
||||
"""Capture events for inspection during debugging."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 6: Capture Events (No Print)")
|
||||
print("------------------------------------")
|
||||
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
# Capture events without printing for inspection
|
||||
events = await runner.run_debug(
|
||||
["Get weather for London", "Get stock price of AMZN"],
|
||||
quiet=True,
|
||||
)
|
||||
|
||||
# Inspect the captured events
|
||||
print(f"Captured {len(events)} events")
|
||||
for i, event in enumerate(events):
|
||||
if event.content and event.content.parts:
|
||||
for part in event.content.parts:
|
||||
if part.text:
|
||||
print(f" Event {i+1}: {event.author} - Text: {len(part.text)} chars")
|
||||
elif part.function_call:
|
||||
print(
|
||||
f" Event {i+1}: {event.author} - Tool call:"
|
||||
f" {part.function_call.name}"
|
||||
)
|
||||
elif part.function_response:
|
||||
print(f" Event {i+1}: {event.author} - Tool response received")
|
||||
|
||||
|
||||
async def example_with_run_config():
|
||||
"""Demonstrate using RunConfig for advanced settings."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 7: Advanced Configuration")
|
||||
print("------------------------------------")
|
||||
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
|
||||
runner = InMemoryRunner(agent=agent.root_agent)
|
||||
|
||||
# Custom configuration - RunConfig supports:
|
||||
# - support_cfc: Control function calling behavior
|
||||
# - response_modalities: Output modalities (for LIVE API)
|
||||
# - speech_config: Speech settings (for LIVE API)
|
||||
config = RunConfig(
|
||||
support_cfc=False, # Disable controlled function calling
|
||||
)
|
||||
|
||||
await runner.run_debug(
|
||||
"Explain what tools you have available", run_config=config
|
||||
)
|
||||
|
||||
|
||||
async def example_comparison():
|
||||
"""Show before/after comparison of boilerplate reduction."""
|
||||
print("\n------------------------------------")
|
||||
print("Example 8: Before vs After Comparison")
|
||||
print("------------------------------------")
|
||||
|
||||
print("\nBefore (7-8 lines of boilerplate):")
|
||||
print("""
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.genai import types
|
||||
|
||||
APP_NAME = "default"
|
||||
USER_ID = "default"
|
||||
session_service = InMemorySessionService()
|
||||
runner = Runner(agent=agent, app_name=APP_NAME, session_service=session_service)
|
||||
session = await session_service.create_session(
|
||||
app_name=APP_NAME, user_id=USER_ID, session_id="default"
|
||||
)
|
||||
content = types.Content(role="user", parts=[types.Part.from_text("Hi")])
|
||||
async for event in runner.run_async(
|
||||
user_id=USER_ID, session_id=session.id, new_message=content
|
||||
):
|
||||
if event.content and event.content.parts:
|
||||
print(event.content.parts[0].text)
|
||||
""")
|
||||
|
||||
print("\nAfter (just 2 lines):")
|
||||
print("""
|
||||
runner = InMemoryRunner(agent=agent)
|
||||
await runner.run_debug("Hi")
|
||||
""")
|
||||
|
||||
print("\nThat's a 75% reduction in boilerplate.")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run all debug examples."""
|
||||
print("ADK run_debug() Helper Method Examples")
|
||||
print("=======================================")
|
||||
print("Demonstrating all capabilities:\n")
|
||||
print("1. Minimal usage (2 lines)")
|
||||
print("2. Multiple messages")
|
||||
print("3. Session persistence")
|
||||
print("4. Separate sessions")
|
||||
print("5. Tool calls")
|
||||
print("6. Event capture")
|
||||
print("7. Advanced configuration")
|
||||
print("8. Before/after comparison")
|
||||
|
||||
await example_minimal()
|
||||
await example_multiple_messages()
|
||||
await example_conversation_persistence()
|
||||
await example_separate_sessions()
|
||||
await example_with_tools()
|
||||
await example_capture_events()
|
||||
await example_with_run_config()
|
||||
await example_comparison()
|
||||
|
||||
print("\n=======================================")
|
||||
print("All examples completed.")
|
||||
print("\nHow different part types appear:")
|
||||
print(" Text: agent > Hello world (always shown)")
|
||||
print("\nWith verbose=True only:")
|
||||
print(
|
||||
" Tool call: agent > [Calling tool: get_stock_price({'ticker':"
|
||||
" 'GOOGL'})]"
|
||||
)
|
||||
print(" Tool result: agent > [Tool result: Price for GOOGL: 175.50 USD]")
|
||||
print("\nNote: When models have code execution enabled (verbose=True):")
|
||||
print(" Code exec: agent > [Executing python code...]")
|
||||
print(" Code output: agent > [Code output: Result: 42]")
|
||||
print(" Inline data: agent > [Inline data: image/png]")
|
||||
print(" File ref: agent > [File: gs://bucket/file.pdf]")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What's the stock price of GOOGL?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"ticker": "GOOGL"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_stock_price"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_stock_price",
|
||||
"response": {
|
||||
"result": "Price for GOOGL: 175.50 USD"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The current stock price for GOOGL is 175.50 USD."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What's the stock price of NVDA?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"ticker": "NVDA"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_stock_price"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_stock_price",
|
||||
"response": {
|
||||
"result": "Price for NVDA: 125.00 USD"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The current stock price for NVDA is 125.00 USD."
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "What is the weather in San Francisco?"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionCall": {
|
||||
"args": {
|
||||
"city": "San Francisco"
|
||||
},
|
||||
"id": "fc-1",
|
||||
"name": "get_weather"
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"longRunningToolIds": [],
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"actions": {
|
||||
"stateDelta": {
|
||||
"weather_queries": [
|
||||
"San Francisco"
|
||||
]
|
||||
}
|
||||
},
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"functionResponse": {
|
||||
"id": "fc-1",
|
||||
"name": "get_weather",
|
||||
"response": {
|
||||
"result": "Foggy, 15\u00b0C (59\u00b0F)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather in San Francisco is foggy, with a temperature of 15\u00b0C (59\u00b0F). Is there anything else I can help you with?"
|
||||
}
|
||||
],
|
||||
"role": "model"
|
||||
},
|
||||
"finishReason": "STOP",
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "agent@1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user