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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copy this file to .env and fill in your API key
# Get your API key at: https://aistudio.google.com/apikey

GEMINI_API_KEY=your_gemini_api_key_here

# Optional: Use Vertex AI instead of Gemini API
# GOOGLE_GENAI_USE_VERTEXAI=TRUE
15 changes: 15 additions & 0 deletions samples/community/agent/adk/gemini_enterprise/v0_9/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Ignore common Python build artifacts
__pycache__/
*.pyc
*.pyo
build/
dist/
*.egg-info/
.venv/
venv/

.adk/

# Do not upload local environment / secrets
.env
.env.example
20 changes: 20 additions & 0 deletions samples/community/agent/adk/gemini_enterprise/v0_9/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Local environment / secrets — never commit
.env

# Python build artifacts
__pycache__/
*.pyc
*.pyo
build/
dist/
*.egg-info/

# Virtual environments
.venv/
venv/

# ADK local state
.adk/

# Generated prompt dump (from `python prompt_builder.py`)
generated_prompt.txt
121 changes: 121 additions & 0 deletions samples/community/agent/adk/gemini_enterprise/v0_9/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# A2UI v0.9 Demo agent sample

This sample uses the Agent Development Kit (ADK) and A2UI SDK to
create an "A2UI v0.9 Demo" agent that can be deployed to GCP Cloud Run. The agent
showcases what can be built with Gemini Enterprise A2UI v0.9, rendering rich,
interactive UIs from the Material component catalog and the Gemini Enterprise
custom components (`Canvas`, `IFrameSrcdoc`, `IFrameUrl`).

Ask the agent **"what can you do?"** and it renders an A2UI `Canvas` listing the available demos. It can demo:

- **Material components**: cards, text, buttons, icons, images, badges.
- **Forms & inputs**: `MaterialInput`, `MaterialSelect`, `MaterialCheckbox`,
`MaterialRadioButton`, `MaterialSlideToggle`, `MaterialSlider`,
`MaterialChips`, `MaterialButtonToggle`, `MaterialDatepicker`,
`MaterialTimepicker`.
- **Tabs & layout**: `MaterialTabs`, `MaterialExpansionPanel`,
`MaterialGridList`, `MaterialRow`, `MaterialColumn`.
- **Data display**: `MaterialTable`, `MaterialProgressBar`,
`MaterialProgressSpinner`.
- **Dialogs & menus**: `MaterialDialog`, `MaterialMenu`.
- **Canvas**: render content in a resizable side panel (`Canvas` root).
- **Iframe**: `IFrameSrcdoc` (sandboxed agent-supplied HTML) and `IFrameUrl`
(allowlisted embedded URL).
- **Restaurant finder**: find restaurants and book a table.

This started as a port of the upstream
[A2UI restaurant_finder sample](https://github.com/a2ui-project/a2ui/tree/main/samples/agent/adk/restaurant_finder)
with these notable differences:

1. **A2UI v0.9 only.** Support for A2UI v0.8 has been removed. The agent only
advertises and serves the A2UI v0.9 extension.
2. **Gemini Enterprise composite catalog.** Instead of the bundled
`BasicCatalog`, the agent uses the A2UI v0.9 Gemini Enterprise composite
catalog (`gemini_enterprise_composite_catalog.json`), which unions the
standard Material catalog, the basic catalog, and the Gemini Enterprise
custom components (`Canvas`, `IFrameSrcdoc`, `IFrameUrl`). Example UI
templates under `examples/0.9/` use `Material*`, `Canvas`, and `IFrame*`
components.
3. **Generic demo.** The agent is no longer restaurant-specific; it generates
whichever UI best demonstrates the components requested.

## Prerequisites

- Python 3.14 or higher
Comment thread
yuantian-agentspace marked this conversation as resolved.
- [uv](https://docs.astral.sh/uv/)
- Access to an LLM and API key

## Running the Sample

1. Create an environment file with your API key:

`cp .env.example .env`

Edit `.env` with your actual API key (do not commit .env)

2. Run the agent server:

`uv run .`

3. In another terminal window:

- verify that the agent is available via A2A:

```
curl http://localhost:10002/.well-known/agent-card.json
```

- send a message to the agent:

```
curl http://localhost:10002 \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"text": "What can you do?"}],
"messageId": "1"
}
}
}'
```

## Deploying to Cloud Run

`deploy.sh` deploys the agent to Cloud Run from source:

```
./deploy.sh <PROJECT_ID> <SERVICE_NAME> [MODEL_NAME]
```

## Updating the component catalog

`gemini_enterprise_composite_catalog.json` is a copy of the public A2UI v0.9
Gemini Enterprise composite catalog:

```
https://www.gstatic.com/vertexaisearch/a2ui/v0_9/gemini_enterprise_composite_catalog.json
```

If the catalog adds or removes components, also update the example templates in
`examples/0.9/` so they continue to validate (the agent loads examples with
`validate_examples=True`).

## Disclaimer

Important: The sample code provided is for demonstration purposes and
illustrates the mechanics of A2UI and the Agent-to-Agent (A2A) protocol. When
building production applications, it is critical to treat any agent operating
outside of your direct control as a potentially untrusted entity.

All operational data received from an external agent—including its AgentCard,
messages, artifacts, and task statuses—should be handled as untrusted input. Any
UI definition or data stream received must be treated as untrusted. Developers
are responsible for implementing appropriate security measures—such as input
sanitization, Content Security Policies (CSP), strict isolation for optional
embedded content, and secure credential handling—to protect their systems and
users.
15 changes: 15 additions & 0 deletions samples/community/agent/adk/gemini_enterprise/v0_9/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2025 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
#
# https://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
98 changes: 98 additions & 0 deletions samples/community/agent/adk/gemini_enterprise/v0_9/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright 2025 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
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os

from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from agent import A2uiDemoAgent
from agent_executor import A2uiDemoAgentExecutor
import click
from dotenv import load_dotenv
from starlette.middleware.cors import CORSMiddleware
from starlette.staticfiles import StaticFiles

load_dotenv()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class MissingAPIKeyError(Exception):
"""Exception for missing API key."""


@click.command()
@click.option("--host", default="localhost")
@click.option("--port", default=10002)
def main(host, port):
try:
# Check for API key only if Vertex AI is not configured
if not os.getenv("GOOGLE_GENAI_USE_VERTEXAI") == "TRUE":
if not os.getenv("GEMINI_API_KEY"):
raise MissingAPIKeyError(
"GEMINI_API_KEY environment variable not set and"
" GOOGLE_GENAI_USE_VERTEXAI is not TRUE."
)

base_url = f"http://{host}:{port}"

agent = A2uiDemoAgent(base_url=base_url)

agent_executor = A2uiDemoAgentExecutor(agent)

request_handler = DefaultRequestHandler(
agent_executor=agent_executor,
task_store=InMemoryTaskStore(),
)
server = A2AStarletteApplication(
agent_card=agent.agent_card, http_handler=request_handler
)
import uvicorn

app = server.build()

app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"http://localhost:\d+",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Serve restaurant images from the local `images/` directory if
# present. The directory only contains binary image assets and may be
# omitted; the server still starts (image URLs will simply 404).
images_dir = os.path.join(os.path.dirname(__file__), "images")
if os.path.isdir(images_dir):
app.mount("/static", StaticFiles(directory=images_dir), name="static")
else:
logger.warning(
"No 'images' directory found at %s; /static will not be served.",
images_dir,
)

uvicorn.run(app, host=host, port=port)
except MissingAPIKeyError as e:
logger.error(f"Error: {e}")
exit(1)
except Exception as e:
logger.error(f"An error occurred during server startup: {e}")
exit(1)


if __name__ == "__main__":
main()
Loading
Loading