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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,97 @@
# Computer Use Agent
This directory contains a computer use agent that can operate a browser to complete user tasks. The agent uses Playwright to control a Chromium browser and can interact with web pages by taking screenshots, clicking, typing, and navigating.
This agent is to demo the usage of ComputerUseToolset.
## Overview
The computer use agent consists of:
- `agent.py`: Main agent configuration using Google's gemini-2.5-computer-use-preview-10-2025 model
- `playwright.py`: Playwright-based computer implementation for browser automation
- `requirements.txt`: Python dependencies
## Setup
### 1. Install Python Dependencies
Install the required Python packages from the requirements file:
```bash
uv pip install -r contributing/samples/computer_use/requirements.txt
```
### 2. Install Playwright Dependencies
Install Playwright's system dependencies for Chromium:
```bash
playwright install-deps chromium
```
### 3. Install Chromium Browser
Install the Chromium browser for Playwright:
```bash
playwright install chromium
```
## Usage
### Running the Agent
To start the computer use agent, run the following command from the project root:
```bash
adk web contributing/samples
```
This will start the ADK web interface where you can interact with the computer_use agent.
### Example Queries
Once the agent is running, you can send queries like:
```
find me a flight from SF to Hawaii on next Monday, coming back on next Friday. start by navigating directly to flights.google.com
```
The agent will:
1. Open a browser window
1. Navigate to the specified website
1. Interact with the page elements to complete your task
1. Provide updates on its progress
### Other Example Tasks
- Book hotel reservations
- Search for products online
- Fill out forms
- Navigate complex websites
- Research information across multiple pages
## Technical Details
- **Model**: Uses Google's `gemini-2.5-computer-use-preview-10-2025` model for computer use capabilities
- **Browser**: Automated Chromium browser via Playwright
- **Screen Size**: Configured for 600x800 resolution
- **Tools**: Uses ComputerUseToolset for screen capture, clicking, typing, and scrolling
## Troubleshooting
If you encounter issues:
1. **Playwright not found**: Make sure you've run both `playwright install-deps chromium` and `playwright install chromium`
1. **Dependencies missing**: Verify all packages from `requirements.txt` are installed
1. **Browser crashes**: Check that your system supports Chromium and has sufficient resources
1. **Permission errors**: Ensure your user has permission to run browser automation tools
## Notes
- The agent operates in a controlled browser environment
- Screenshots are taken to help the agent understand the current state
- The agent will provide updates on its actions as it works
- Be patient as complex tasks may take some time to complete
+43
View File
@@ -0,0 +1,43 @@
# 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 os
import tempfile
from google.adk import Agent
from google.adk.tools.computer_use.computer_use_toolset import ComputerUseToolset
from .playwright import PlaywrightComputer
# Define user_data_dir path
profile_name = 'browser_profile_for_adk'
profile_path = os.path.join(tempfile.gettempdir(), profile_name)
os.makedirs(profile_path, exist_ok=True)
computer_with_profile = PlaywrightComputer(
screen_size=(1280, 936),
user_data_dir=profile_path,
)
# Create agent with the toolset using the new computer instance
root_agent = Agent(
model='gemini-2.5-computer-use-preview-10-2025',
name='hello_world_agent',
description=(
'computer use agent that can operate a browser on a computer to finish'
' user tasks'
),
instruction=""" you are a computer use agent """,
tools=[ComputerUseToolset(computer=computer_with_profile)],
)
@@ -0,0 +1,350 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import time
from typing import Literal
from typing import Optional
from google.adk.tools.computer_use.base_computer import BaseComputer
from google.adk.tools.computer_use.base_computer import ComputerEnvironment
from google.adk.tools.computer_use.base_computer import ComputerState
from playwright.async_api import async_playwright
import termcolor
from typing_extensions import override
# Define a mapping from the user-friendly key names to Playwright's expected key names.
# Playwright is generally good with case-insensitivity for these, but it's best to be canonical.
# See: https://playwright.dev/docs/api/class-keyboard#keyboard-press
# Keys like 'a', 'b', '1', '$' are passed directly.
PLAYWRIGHT_KEY_MAP = {
"backspace": "Backspace",
"tab": "Tab",
"return": "Enter", # Playwright uses 'Enter'
"enter": "Enter",
"shift": "Shift",
"control": "Control", # Or 'ControlOrMeta' for cross-platform Ctrl/Cmd
"alt": "Alt",
"escape": "Escape",
"space": "Space", # Can also just be " "
"pageup": "PageUp",
"pagedown": "PageDown",
"end": "End",
"home": "Home",
"left": "ArrowLeft",
"up": "ArrowUp",
"right": "ArrowRight",
"down": "ArrowDown",
"insert": "Insert",
"delete": "Delete",
"semicolon": ";", # For actual character ';'
"equals": "=", # For actual character '='
"multiply": "Multiply", # NumpadMultiply
"add": "Add", # NumpadAdd
"separator": "Separator", # Numpad specific
"subtract": "Subtract", # NumpadSubtract, or just '-' for character
"decimal": "Decimal", # NumpadDecimal, or just '.' for character
"divide": "Divide", # NumpadDivide, or just '/' for character
"f1": "F1",
"f2": "F2",
"f3": "F3",
"f4": "F4",
"f5": "F5",
"f6": "F6",
"f7": "F7",
"f8": "F8",
"f9": "F9",
"f10": "F10",
"f11": "F11",
"f12": "F12",
"command": "Meta", # 'Meta' is Command on macOS, Windows key on Windows
}
class PlaywrightComputer(BaseComputer):
"""Computer that controls Chromium via Playwright."""
def __init__(
self,
screen_size: tuple[int, int],
initial_url: str = "https://www.google.com",
search_engine_url: str = "https://www.google.com",
highlight_mouse: bool = False,
user_data_dir: Optional[str] = None,
):
self._initial_url = initial_url
self._screen_size = screen_size
self._search_engine_url = search_engine_url
self._highlight_mouse = highlight_mouse
self._user_data_dir = user_data_dir
@override
async def initialize(self):
print("Creating session...")
self._playwright = await async_playwright().start()
# Define common arguments for both launch types
browser_args = [
"--disable-blink-features=AutomationControlled",
"--disable-gpu",
]
if self._user_data_dir:
termcolor.cprint(
f"Starting playwright with persistent profile: {self._user_data_dir}",
color="yellow",
attrs=["bold"],
)
# Use a persistent context if user_data_dir is provided
self._context = await self._playwright.chromium.launch_persistent_context(
self._user_data_dir,
headless=False,
args=browser_args,
)
self._browser = self._context.browser
else:
termcolor.cprint(
"Starting playwright with a temporary profile.",
color="yellow",
attrs=["bold"],
)
# Launch a temporary browser instance if user_data_dir is not provided
self._browser = await self._playwright.chromium.launch(
args=browser_args,
headless=False,
)
self._context = await self._browser.new_context()
if not self._context.pages:
self._page = await self._context.new_page()
await self._page.goto(self._initial_url)
else:
self._page = self._context.pages[0] # Use existing page if any
await self._page.set_viewport_size({
"width": self._screen_size[0],
"height": self._screen_size[1],
})
termcolor.cprint(
f"Started local playwright.",
color="green",
attrs=["bold"],
)
@override
async def environment(self):
return ComputerEnvironment.ENVIRONMENT_BROWSER
@override
async def close(self, exc_type, exc_val, exc_tb):
if self._context:
self._context.close()
try:
self._browser.close()
except Exception as e:
# Browser was already shut down because of SIGINT or such.
if (
"Browser.close: Connection closed while reading from the driver"
in str(e)
):
pass
else:
raise
self._playwright.stop()
async def open_web_browser(self) -> ComputerState:
return await self.current_state()
async def click_at(self, x: int, y: int):
await self.highlight_mouse(x, y)
await self._page.mouse.click(x, y)
await self._page.wait_for_load_state()
return await self.current_state()
async def hover_at(self, x: int, y: int):
await self.highlight_mouse(x, y)
await self._page.mouse.move(x, y)
await self._page.wait_for_load_state()
return await self.current_state()
async def type_text_at(
self,
x: int,
y: int,
text: str,
press_enter: bool = True,
clear_before_typing: bool = True,
) -> ComputerState:
await self.highlight_mouse(x, y)
await self._page.mouse.click(x, y)
await self._page.wait_for_load_state()
if clear_before_typing:
await self.key_combination(["Control", "A"])
await self.key_combination(["Delete"])
await self._page.keyboard.type(text)
await self._page.wait_for_load_state()
if press_enter:
await self.key_combination(["Enter"])
await self._page.wait_for_load_state()
return await self.current_state()
async def _horizontal_document_scroll(
self, direction: Literal["left", "right"]
) -> ComputerState:
# Scroll by 50% of the viewport size.
horizontal_scroll_amount = await self.screen_size()[0] // 2
if direction == "left":
sign = "-"
else:
sign = ""
scroll_argument = f"{sign}{horizontal_scroll_amount}"
# Scroll using JS.
await self._page.evaluate(f"window.scrollBy({scroll_argument}, 0); ")
await self._page.wait_for_load_state()
return await self.current_state()
async def scroll_document(
self, direction: Literal["up", "down", "left", "right"]
) -> ComputerState:
if direction == "down":
return await self.key_combination(["PageDown"])
elif direction == "up":
return await self.key_combination(["PageUp"])
elif direction in ("left", "right"):
return await self._horizontal_document_scroll(direction)
else:
raise ValueError("Unsupported direction: ", direction)
async def scroll_at(
self,
x: int,
y: int,
direction: Literal["up", "down", "left", "right"],
magnitude: int,
) -> ComputerState:
await self.highlight_mouse(x, y)
await self._page.mouse.move(x, y)
await self._page.wait_for_load_state()
dx = 0
dy = 0
if direction == "up":
dy = -magnitude
elif direction == "down":
dy = magnitude
elif direction == "left":
dx = -magnitude
elif direction == "right":
dx = magnitude
else:
raise ValueError("Unsupported direction: ", direction)
await self._page.mouse.wheel(dx, dy)
await self._page.wait_for_load_state()
return await self.current_state()
async def wait(self, seconds: int) -> ComputerState:
await asyncio.sleep(seconds)
return await self.current_state()
async def go_back(self) -> ComputerState:
await self._page.go_back()
await self._page.wait_for_load_state()
return await self.current_state()
async def go_forward(self) -> ComputerState:
await self._page.go_forward()
await self._page.wait_for_load_state()
return await self.current_state()
async def search(self) -> ComputerState:
return await self.navigate(self._search_engine_url)
async def navigate(self, url: str) -> ComputerState:
await self._page.goto(url)
await self._page.wait_for_load_state()
return await self.current_state()
async def key_combination(self, keys: list[str]) -> ComputerState:
# Normalize all keys to the Playwright compatible version.
keys = [PLAYWRIGHT_KEY_MAP.get(k.lower(), k) for k in keys]
for key in keys[:-1]:
await self._page.keyboard.down(key)
await self._page.keyboard.press(keys[-1])
for key in reversed(keys[:-1]):
await self._page.keyboard.up(key)
return await self.current_state()
async def drag_and_drop(
self, x: int, y: int, destination_x: int, destination_y: int
) -> ComputerState:
await self.highlight_mouse(x, y)
await self._page.mouse.move(x, y)
await self._page.wait_for_load_state()
await self._page.mouse.down()
await self._page.wait_for_load_state()
await self.highlight_mouse(destination_x, destination_y)
await self._page.mouse.move(destination_x, destination_y)
await self._page.wait_for_load_state()
await self._page.mouse.up()
return await self.current_state()
async def current_state(self) -> ComputerState:
await self._page.wait_for_load_state()
# Even if Playwright reports the page as loaded, it may not be so.
# Add a manual sleep to make sure the page has finished rendering.
time.sleep(0.5)
screenshot_bytes = await self._page.screenshot(type="png", full_page=False)
return ComputerState(screenshot=screenshot_bytes, url=self._page.url)
async def screen_size(self) -> tuple[int, int]:
return self._screen_size
async def highlight_mouse(self, x: int, y: int):
if not self._highlight_mouse:
return
await self._page.evaluate(f"""
() => {{
const element_id = "playwright-feedback-circle";
const div = document.createElement('div');
div.id = element_id;
div.style.pointerEvents = 'none';
div.style.border = '4px solid red';
div.style.borderRadius = '50%';
div.style.width = '20px';
div.style.height = '20px';
div.style.position = 'fixed';
div.style.zIndex = '9999';
document.body.appendChild(div);
div.hidden = false;
div.style.left = {x} - 10 + 'px';
div.style.top = {y} - 10 + 'px';
setTimeout(() => {{
div.hidden = true;
}}, 2000);
}}
""")
# Wait a bit for the user to see the cursor.
time.sleep(1)
@@ -0,0 +1,4 @@
termcolor==3.1.0
playwright==1.52.0
browserbase==1.3.0
rich
@@ -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,52 @@
# 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 import Agent
from google.adk.tools import load_artifacts
from google.adk.tools.tool_context import ToolContext
from google.genai import types
async def generate_image(prompt: str, tool_context: 'ToolContext'):
"""Generates an image based on the prompt."""
from google.genai import Client
# Only Vertex AI supports image generation for now.
client = Client()
response = client.models.generate_images(
model='imagen-3.0-generate-002',
prompt=prompt,
config={'number_of_images': 1},
)
if not response.generated_images:
return {'status': 'failed'}
image_bytes = response.generated_images[0].image.image_bytes
await tool_context.save_artifact(
'image.png',
types.Part.from_bytes(data=image_bytes, mime_type='image/png'),
)
return {
'status': 'success',
'detail': 'Image generated successfully and stored in artifacts.',
'filename': 'image.png',
}
root_agent = Agent(
name='root_agent',
description="""An agent that generates images and answer questions about the images.""",
instruction="""You are an agent whose job is to generate or edit an image based on the user's prompt.
""",
tools=[generate_image, load_artifacts],
)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
# Multimodal Agent
## Overview
This sample demonstrates a simple standalone agent that supports multimodal input and output. It uses the "nano banana model" (`gemini-2.5-flash-image`) that can understand and generate images directly.
## Sample Inputs
- `An image of a banana with the question: "Is this banana ripe?"`
- `A text prompt: "Generate a picture of a banana split."`
## Graph
Since this is a simple standalone agent without tools, the flow is a direct interaction between the user and the agent:
```mermaid
graph TD
User -->|Sends Image + Text| Agent[Multimodal Agent]
Agent -->|Responds with Image + Text| User
```
## How To
This sample demonstrates:
1. **Multimodal Input**: The agent can process both text and image parts in the conversation history.
1. **Multimodal Output**: The agent can generate images directly in its response.
To run this sample, ensure you have the necessary environment variables set for the Gemini client.
@@ -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,20 @@
# 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 import Agent
root_agent = Agent(
name='multimodal_agent',
model='gemini-2.5-flash-image',
)
@@ -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,40 @@
# 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 import LlmAgent
from google.adk.apps.app import App
from google.adk.plugins.multimodal_tool_results_plugin import MultimodalToolResultsPlugin
from google.genai import types
APP_NAME = "multimodal_tool_results"
USER_ID = "test_user"
def get_image():
return [types.Part.from_uri(file_uri="gs://replace_with_your_image_uri")]
root_agent = LlmAgent(
name="image_describing_agent",
description="image describing agent",
instruction="""Whatever the user says, get the image using the get_image tool, and describe it.""",
tools=[get_image],
)
app = App(
name=APP_NAME,
root_agent=root_agent,
plugins=[MultimodalToolResultsPlugin()],
)
@@ -0,0 +1,142 @@
# Static Non-Text Content Sample Agent
This sample demonstrates ADK's static instruction feature with non-text content (images and files).
## Features Demonstrated
- **Static instructions with mixed content**: Text, images, and file references in a single static instruction
- **Reference ID generation**: Non-text parts are automatically given reference IDs (`inline_data_0`, `file_data_1`, etc.)
- **Gemini Files API integration**: Demonstrates uploading documents and using file_data
- **Mixed content types**: inline_data for images, file_data for documents
- **API variant detection**: Different behavior for Gemini API vs Vertex AI
- **GCS file references**: Support for both GCS URI and HTTPS URL access methods in Vertex AI
## Static Instruction Content
The agent includes:
1. **Text instructions**: Guide the agent on how to behave
1. **Sample image**: A 1x1 yellow pixel PNG (`sample_chart.png`) as inline binary data
**Gemini Developer API:**
3\. **Contributing guide**: A sample document uploaded to Gemini Files API and referenced via file_data
**Vertex AI:**
3\. **Research paper**: Gemma research paper from Google Cloud Storage via GCS file reference
4\. **AI research paper**: Same research paper accessed via HTTPS URL for comparison
## Content Used
**All API variants:**
- **Image**: Base64-encoded 1x1 yellow pixel PNG (embedded in code as `inline_data`)
**Gemini Developer API:**
- **Document**: Sample contributing guide text (uploaded to Gemini Files API as `file_data`)
- Contains sample guidelines and best practices for development
- Demonstrates Files API upload and file_data reference functionality
- Files are automatically cleaned up after 48 hours by the Gemini API
**Vertex AI:**
- **Gemma Research Paper**: Research paper accessed via GCS URI (as `file_data`)
- GCS URI: `gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf`
- Demonstrates native GCS file access in Vertex AI
- PDF format with technical AI research content about Gemini 1.5
- **AI Research Paper**: Same research paper accessed via HTTPS URL (as `file_data`)
- HTTPS URL: `https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/2403.05530.pdf`
- Demonstrates HTTPS file access in Vertex AI
- Agent can discover these are the same document and compare access methods
## Setup
### Setup API Credentials
Create a `.env` file in the project root with your API credentials:
```bash
# Choose Model Backend: 0 -> ML Dev, 1 -> Vertex
GOOGLE_GENAI_USE_ENTERPRISE=1
# ML Dev backend config
GOOGLE_API_KEY=your_google_api_key_here
# Vertex backend config
GOOGLE_CLOUD_PROJECT=your_project_id
GOOGLE_CLOUD_LOCATION=us-central1
```
The agent will automatically load environment variables on startup.
## Usage
### Default Test Prompts (Recommended)
```bash
cd contributing/samples
python -m static_non_text_content.main
```
This runs test prompts that demonstrate the static content features:
- **Gemini Developer API**: 4 prompts testing inline_data + Files API upload
- **Vertex AI**: 5 prompts testing inline_data + GCS/HTTPS file access comparison
### Interactive Mode
```bash
cd contributing/samples
adk run static_non_text_content
```
Use ADK's built-in interactive mode for free-form conversation.
### Single Prompt
```bash
cd contributing/samples
python -m static_non_text_content.main --prompt "What reference materials do you have access to?"
```
### With Debug Logging
```bash
cd contributing/samples
python -m static_non_text_content.main --debug --prompt "What is the Gemma research paper about?"
```
## Default Test Prompts
The sample automatically runs test prompts when no `--prompt` is specified:
**All API variants:**
1. "What reference materials do you have access to?"
1. "Can you describe the sample chart that was provided to you?"
1. "How do the inline image and file references in your instructions help you answer questions?"
**Gemini Developer API only:**
4\. "What does the contributing guide document say about best practices?"
**Vertex AI only (additional prompts):**
5\. "What is the Gemma research paper about and what are its key contributions?"
6\. "Can you compare the research papers you have access to? Are they related or different?"
**Gemini Developer API** tests: `inline_data` (image) + Files API `file_data` (uploaded document)
**Vertex AI** tests: `inline_data` (image) + GCS URI `file_data` + HTTPS URL `file_data` (same document via different access methods)
## How It Works
1. **Static Instruction Processing**: The `static_instruction` content is processed during agent initialization
1. **Reference Generation**: Non-text parts get references like `[Reference to inline binary data: inline_data_0 ('sample_chart.png', type: image/png)]` in the system instruction
1. **User Content Creation**: The actual binary data/file references are moved to user contents with proper role attribution
1. **Model Understanding**: The model receives both the descriptive references and the actual content for analysis
## Code Structure
- `agent.py`: Defines the agent with static instruction containing mixed content
- `main.py`: Runnable script with interactive and single-prompt modes
- `__init__.py`: Package initialization following ADK conventions
This sample serves as a test case for the static instruction with non-text parts feature using both `inline_data` and `file_data`.
@@ -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.
"""Static non-text content sample agent package."""
from . import agent
@@ -0,0 +1,226 @@
# 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.
"""Static non-text content sample agent demonstrating static instructions with non-text parts."""
import base64
from dotenv import load_dotenv
from google.adk.agents.llm_agent import Agent
from google.genai import types
# Load environment variables from .env file
load_dotenv()
# Sample image data (a simple 1x1 yellow pixel PNG)
SAMPLE_IMAGE_DATA = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
)
# Sample document content (simplified contributing guide)
SAMPLE_DOCUMENT = """# Contributing Guide
## Best Practices
1. **Code Quality**: Always write clean, well-documented code
2. **Testing**: Include comprehensive tests for new features
3. **Documentation**: Update documentation when adding new functionality
4. **Review Process**: Submit pull requests for code review
5. **Conventions**: Follow established coding conventions and style guides
## Guidelines
- Use meaningful variable and function names
- Write descriptive commit messages
- Keep functions small and focused
- Handle errors gracefully
- Consider performance implications
- Maintain backward compatibility when possible
This guide helps ensure consistent, high-quality contributions to the project.
"""
def create_static_instruction_with_file_upload():
"""Create static instruction content with both inline_data and file_data.
This function creates a static instruction that demonstrates both inline_data
(for images) and file_data (for documents). Always includes Files API upload,
and adds additional GCS file reference when using Vertex AI.
"""
import os
import tempfile
from google.adk.utils.variant_utils import get_google_llm_variant
from google.adk.utils.variant_utils import GoogleLLMVariant
from google import genai
# Determine API variant
api_variant = get_google_llm_variant()
print(f"Using API variant: {api_variant}")
# Prepare file data parts based on API variant
file_data_parts = []
if api_variant == GoogleLLMVariant.VERTEX_AI:
print("Using Vertex AI - adding GCS URI and HTTPS URL references")
# Add GCS file reference
file_data_parts.append(
types.Part(
file_data=types.FileData(
file_uri=(
"gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf"
),
mime_type="application/pdf",
display_name="Gemma Research Paper",
)
)
)
# Add the same document via HTTPS URL to demonstrate both access methods
file_data_parts.append(
types.Part(
file_data=types.FileData(
file_uri="https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/2403.05530.pdf",
mime_type="application/pdf",
display_name="AI Research Paper (HTTPS)",
)
)
)
additional_text = (
" You also have access to a Gemma research paper from GCS"
" and an AI research paper from HTTPS URL."
)
else:
print("Using Gemini Developer API - uploading to Files API")
client = genai.Client()
# Check if file already exists
display_name = "Contributing Guide"
uploaded_file = None
# List existing files to see if we already uploaded this document
existing_files = client.files.list()
for file in existing_files:
if file.display_name == display_name:
uploaded_file = file
print(f"Reusing existing file: {file.name} ({file.display_name})")
break
# If file doesn't exist, upload it
if uploaded_file is None:
# Create a temporary file with the sample document
with tempfile.NamedTemporaryFile(
mode="w", suffix=".md", delete=False
) as f:
f.write(SAMPLE_DOCUMENT)
temp_file_path = f.name
try:
# Upload the file to Gemini Files API
uploaded_file = client.files.upload(file=temp_file_path)
print(
"Uploaded new file:"
f" {uploaded_file.name} ({uploaded_file.display_name})"
)
finally:
# Clean up temporary file
if os.path.exists(temp_file_path):
os.unlink(temp_file_path)
# Add Files API file data part
file_data_parts.append(
types.Part(
file_data=types.FileData(
file_uri=uploaded_file.uri,
mime_type="text/markdown",
display_name="Contributing Guide",
)
)
)
additional_text = (
" You also have access to the contributing guide document."
)
# Create static instruction with mixed content
parts = [
types.Part.from_text(
text=(
"You are an AI assistant that analyzes images and documents."
" You have access to the following reference materials:"
)
),
# Add a sample image as inline_data
types.Part(
inline_data=types.Blob(
data=SAMPLE_IMAGE_DATA,
mime_type="image/png",
display_name="sample_chart.png",
)
),
types.Part.from_text(
text=f"This is a sample chart showing color data.{additional_text}"
),
]
# Add all file_data parts
parts.extend(file_data_parts)
# Add instruction text
if api_variant == GoogleLLMVariant.VERTEX_AI:
instruction_text = """
When users ask questions, you should:
1. Use the reference chart above to provide context when discussing visual data or charts
2. Reference the Gemma research paper (from GCS) when discussing AI research, model architectures, or technical details
3. Reference the AI research paper (from HTTPS) when discussing research topics
4. Be helpful and informative in your responses
5. Explain how the provided reference materials relate to their questions"""
else:
instruction_text = """
When users ask questions, you should:
1. Use the reference chart above to provide context when discussing visual data or charts
2. Reference the contributing guide document when explaining best practices and guidelines
3. Be helpful and informative in your responses
4. Explain how the provided reference materials relate to their questions"""
instruction_text += """
Remember: The reference materials above are available to help you provide better answers."""
parts.append(types.Part.from_text(text=instruction_text))
static_instruction_content = types.Content(parts=parts)
return static_instruction_content
# Create the root agent with Files API integration
root_agent = Agent(
name="static_non_text_content_demo_agent",
description=(
"Demonstrates static instructions with non-text content (inline_data"
" and file_data features)"
),
static_instruction=create_static_instruction_with_file_upload(),
instruction=(
"Please analyze the user's question and provide helpful insights."
" Reference the materials provided in your static instructions when"
" relevant."
),
)
@@ -0,0 +1,223 @@
"""Static non-text content sample agent main script."""
# 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 argparse
import asyncio
import logging
import sys
import time
from google.adk.cli.utils import logs
from google.adk.runners import InMemoryRunner
from . import agent
APP_NAME = "static_non_text_content_demo"
USER_ID = "demo_user"
logs.setup_adk_logger(level=logging.INFO)
async def call_agent_async(
runner, user_id: str, session_id: str, prompt: str
) -> str:
"""Helper function to call agent and return final response."""
from google.adk.agents.run_config import RunConfig
from google.genai import types
content = types.Content(
role="user", parts=[types.Part.from_text(text=prompt)]
)
final_response_text = ""
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=content,
run_config=RunConfig(save_input_blobs_as_artifacts=False),
):
if event.content and event.content.parts:
if text := "".join(part.text or "" for part in event.content.parts):
if event.author != "user":
final_response_text += text
return final_response_text or "No response received"
def process_arguments():
"""Parses command-line arguments."""
parser = argparse.ArgumentParser(
description=(
"A demo script that tests static instructions with non-text content."
),
epilog=(
"Example usage: \n\tpython -m static_non_text_content.main --prompt"
" 'What can you see in the reference chart?'\n\tpython -m"
" static_non_text_content.main --prompt 'What is the Gemma research"
" paper about?'\n\tpython -m static_non_text_content.main # Runs"
" default test prompts\n\tadk run"
" contributing/samples/static_non_text_content # Interactive mode\n"
),
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"--prompt",
type=str,
help=(
"Single prompt to send to the agent. If not provided, runs"
" default test prompts."
),
)
parser.add_argument(
"--debug",
action="store_true",
help="Enable debug logging to see internal processing details.",
)
return parser.parse_args()
async def run_default_test_prompts(runner):
"""Run default test prompts to demonstrate static content features."""
from google.adk.utils.variant_utils import get_google_llm_variant
from google.adk.utils.variant_utils import GoogleLLMVariant
api_variant = get_google_llm_variant()
print("=== Static Non-Text Content Demo Agent - Default Test Prompts ===")
print(
"Running test prompts to demonstrate inline_data and file_data"
" features..."
)
print(f"API Variant: {api_variant}")
print(
"Use 'adk run contributing/samples/static_non_text_content' for"
" interactive mode.\n"
)
# Create session
session = await runner.session_service.create_session(
app_name=APP_NAME, user_id=USER_ID
)
# Common test prompts for all API variants
test_prompts = [
"What reference materials do you have access to?",
"Can you describe the sample chart that was provided to you?",
(
"How do the inline image and file references in your instructions "
"help you answer questions?"
),
]
# Add API-specific prompts
if api_variant == GoogleLLMVariant.VERTEX_AI:
# Vertex AI has research papers instead of contributing guide
test_prompts.extend([
(
"What is the Gemma research paper about and what are its key "
"contributions?"
),
(
"Can you compare the research papers you have access to? Are they "
"related or different?"
),
])
else:
# Gemini Developer API has contributing guide document
test_prompts.append(
"What does the contributing guide document say about best practices?"
)
for i, prompt in enumerate(test_prompts, 1):
print(f"Test {i}/{len(test_prompts)}: {prompt}")
print("-" * 60)
try:
response = await call_agent_async(runner, USER_ID, session.id, prompt)
print(f"Response: {response}")
except (ConnectionError, TimeoutError, ValueError) as e:
print(f"Error: {e}")
print(f"\n{'=' * 60}\n")
async def single_prompt_mode(runner, prompt: str):
"""Run the agent with a single prompt."""
print("=== Static Non-Text Content Demo Agent - Single Prompt Mode ===")
print(f"Prompt: {prompt}")
print("-" * 50)
# Create session
session = await runner.session_service.create_session(
app_name=APP_NAME, user_id=USER_ID
)
response = await call_agent_async(runner, USER_ID, session.id, prompt)
print(f"Agent Response:\n{response}")
async def main():
args = process_arguments()
if args.debug:
logs.setup_adk_logger(level=logging.DEBUG)
print("Debug logging enabled. You'll see internal processing details.\n")
print("Initializing Static Non-Text Content Demo Agent...")
print(f"Agent: {agent.root_agent.name}")
print(f"Model: {agent.root_agent.model}")
print(f"Description: {agent.root_agent.description}")
# Show information about static instruction content
if agent.root_agent.static_instruction:
static_parts = agent.root_agent.static_instruction.parts
text_parts = sum(1 for part in static_parts if part.text)
image_parts = sum(1 for part in static_parts if part.inline_data)
file_parts = sum(1 for part in static_parts if part.file_data)
print("Static instruction contains:")
print(f" - {text_parts} text parts")
print(f" - {image_parts} inline image(s)")
print(f" - {file_parts} file reference(s)")
print("-" * 50)
runner = InMemoryRunner(
agent=agent.root_agent,
app_name=APP_NAME,
)
if args.prompt:
await single_prompt_mode(runner, args.prompt)
else:
await run_default_test_prompts(runner)
if __name__ == "__main__":
start_time = time.time()
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nExiting...")
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
sys.exit(1)
finally:
end_time = time.time()
print(f"\nExecution time: {end_time - start_time:.2f} seconds")