Skip to content

Commit c535a6c

Browse files
Added Additional Examples for ADK
1 parent 614ef17 commit c535a6c

34 files changed

+2931
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# ADK French Translation Agent with A2A Client
2+
3+
This example shows how to create an A2A Server that uses an ADK-based Agent for translating text to French. This agent uses a Gemini model directly.
4+
5+
## Prerequisites
6+
7+
- Python 3.13 or higher
8+
- [UV](https://docs.astral.sh/uv/)
9+
- A `GOOGLE_API_KEY` for using Gemini models.
10+
11+
## Running the example
12+
13+
1. Create the `.env` file in this directory with your API Key:
14+
15+
```bash
16+
echo "GOOGLE_API_KEY=your_google_api_key_here" > .env
17+
```
18+
19+
2. Start the server:
20+
21+
```sh
22+
uv run . --port 10011
23+
```
24+
(The default port is 10011, but you can change it with the `--port` option).
25+
26+
3. You can test this agent by sending requests to its endpoint (e.g., `http://localhost:10011`) using an A2A client or by having an orchestrator agent delegate tasks to it. The agent's ID for delegation is `french_translator`.
27+
28+
Example interaction (conceptual):
29+
- User (to orchestrator): "Translate 'Hello, how are you?' to French."
30+
- Orchestrator: Delegates to `french_translator`.
31+
- French Translator (this agent): Returns "Bonjour, comment ça va ?"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import asyncio
2+
import functools
3+
import logging
4+
import os
5+
import sys
6+
7+
import click
8+
import uvicorn
9+
10+
from dotenv import load_dotenv
11+
12+
from a2a.server.apps import A2AStarletteApplication
13+
from a2a.server.request_handlers import DefaultRequestHandler
14+
from a2a.server.tasks import InMemoryTaskStore
15+
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
16+
17+
from adk_agent_executor import ADKFrenchTranslationAgentExecutor
18+
19+
20+
load_dotenv()
21+
22+
logging.basicConfig()
23+
logger = logging.getLogger(__name__)
24+
logger.setLevel(logging.INFO)
25+
26+
27+
@click.command()
28+
@click.option('--host', 'host', default='localhost')
29+
@click.option('--port', 'port', default=10011)
30+
def main(host: str, port: int):
31+
# Ensure GOOGLE_API_KEY is set for the Gemini model used by this agent.
32+
if not os.getenv('GOOGLE_API_KEY'):
33+
logger.error('GOOGLE_API_KEY environment variable not set. This agent may not function correctly.')
34+
35+
# Define the capabilities and skills of this French Translation Agent.
36+
skill = AgentSkill(
37+
id='translate_to_french',
38+
name='Translate to French',
39+
description='Translates provided text into French.',
40+
tags=['translation', 'french', 'language'],
41+
examples=[
42+
'Translate "Hello, world!" to French',
43+
'Could you translate "Good morning" into French?',
44+
],
45+
)
46+
47+
agent_executor = ADKFrenchTranslationAgentExecutor()
48+
agent_card = AgentCard(
49+
name='ADK French Translation Agent',
50+
description='I can translate text into French.',
51+
url=f'http://{host}:{port}/',
52+
version='1.0.0',
53+
defaultInputModes=['text'],
54+
defaultOutputModes=['text'],
55+
capabilities=AgentCapabilities(streaming=True),
56+
skills=[skill]
57+
)
58+
request_handler = DefaultRequestHandler(
59+
agent_executor=agent_executor, task_store=InMemoryTaskStore()
60+
)
61+
app = A2AStarletteApplication(agent_card, request_handler)
62+
63+
logger.info(f"Starting French Translation Agent server on http://{host}:{port}")
64+
logger.info(f"This agent is identified by 'french_translator' for delegation.")
65+
uvicorn.run(app.build(), host=host, port=port)
66+
67+
68+
if __name__ == '__main__':
69+
main()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
from google.adk.agents import LlmAgent
3+
4+
async def create_french_translation_agent() -> LlmAgent:
5+
"""Constructs the ADK French Translation agent."""
6+
# Ensure GOOGLE_API_KEY is set for Gemini model usage.
7+
if not os.getenv("GOOGLE_API_KEY"):
8+
print("Warning: GOOGLE_API_KEY environment variable not set. This agent may not function correctly.")
9+
10+
return LlmAgent(
11+
model='gemini-1.5-flash',
12+
name='french_translation_agent_adk',
13+
description='An agent that translates text to French.',
14+
instruction="""You are a translation assistant. The user will provide text.
15+
Translate the provided text into French.
16+
Only return the translated French text. Do not include any other commentary or explanations.
17+
Example: If the user provides "Hello, how are you?", you should return "Bonjour, comment ça va ?"
18+
""",
19+
tools=[],
20+
)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# mypy: ignore-errors
2+
import asyncio
3+
import logging
4+
5+
from collections.abc import AsyncGenerator, AsyncIterable
6+
from typing import Any
7+
from uuid import uuid4
8+
9+
from google.adk import Runner
10+
from google.adk.agents import LlmAgent, RunConfig
11+
from google.adk.artifacts import InMemoryArtifactService
12+
from google.adk.events import Event
13+
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
14+
from google.adk.sessions import InMemorySessionService
15+
from google.genai import types as genai_types
16+
from pydantic import ConfigDict
17+
18+
from a2a.client import A2AClient
19+
from a2a.server.agent_execution import AgentExecutor, RequestContext
20+
from a2a.server.events.event_queue import EventQueue
21+
from a2a.server.tasks import TaskUpdater
22+
from a2a.types import (
23+
Artifact,
24+
FilePart,
25+
FileWithBytes,
26+
FileWithUri,
27+
GetTaskRequest,
28+
GetTaskSuccessResponse,
29+
Message,
30+
MessageSendParams,
31+
Part,
32+
Role,
33+
SendMessageRequest,
34+
SendMessageSuccessResponse,
35+
Task,
36+
TaskQueryParams,
37+
TaskState,
38+
TaskStatus,
39+
TextPart,
40+
UnsupportedOperationError,
41+
)
42+
from a2a.utils import get_text_parts
43+
from a2a.utils.errors import ServerError
44+
45+
from adk_agent import create_french_translation_agent
46+
47+
48+
logger = logging.getLogger(__name__)
49+
logger.setLevel(logging.DEBUG)
50+
51+
52+
class ADKFrenchTranslationAgentExecutor(AgentExecutor):
53+
"""An AgentExecutor that runs an ADK-based French Translation Agent."""
54+
55+
def __init__(self):
56+
# Initialize the ADK agent and runner.
57+
self._agent = asyncio.run(create_french_translation_agent())
58+
self.runner = Runner(
59+
app_name=self._agent.name,
60+
agent=self._agent,
61+
artifact_service=InMemoryArtifactService(),
62+
session_service=InMemorySessionService(),
63+
memory_service=InMemoryMemoryService(),
64+
)
65+
66+
def _run_agent(
67+
self,
68+
session_id: str,
69+
new_message: genai_types.Content,
70+
task_updater: TaskUpdater, # This parameter is not used in this method.
71+
) -> AsyncGenerator[Event, None]:
72+
"""Runs the ADK agent with the given message."""
73+
return self.runner.run_async(
74+
session_id=session_id,
75+
user_id='self', # The user ID for the ADK session.
76+
new_message=new_message,
77+
)
78+
79+
async def _process_request(
80+
self,
81+
new_message: genai_types.Content,
82+
session_id: str,
83+
task_updater: TaskUpdater,
84+
) -> AsyncIterable[TaskStatus | Artifact]:
85+
"""Processes the incoming request by running the ADK agent."""
86+
session = await self._upsert_session(
87+
session_id,
88+
)
89+
session_id = session.id
90+
async for event in self._run_agent(
91+
session_id, new_message, task_updater # Pass task_updater to _run_agent
92+
):
93+
logger.debug('Received ADK event: %s', event)
94+
if event.is_final_response():
95+
# If the ADK agent provides a final response, convert it to A2A artifact and complete the task.
96+
response = convert_genai_parts_to_a2a(event.content.parts)
97+
logger.debug('Yielding final response: %s', response)
98+
task_updater.add_artifact(response)
99+
task_updater.complete()
100+
break
101+
elif not event.get_function_calls():
102+
# If it's not a final response and no function calls, it's an interim update.
103+
logger.debug('Yielding update response')
104+
task_updater.update_status(
105+
TaskState.working,
106+
message=task_updater.new_agent_message(
107+
convert_genai_parts_to_a2a(event.content.parts)
108+
),
109+
)
110+
else:
111+
# This agent does not use tools, so function calls are unexpected.
112+
logger.debug('Skipping event with function call: %s', event.get_function_calls())
113+
114+
115+
async def execute(
116+
self,
117+
context: RequestContext,
118+
event_queue: EventQueue,
119+
):
120+
"""Executes the agent's logic based on the incoming A2A request."""
121+
updater = TaskUpdater(event_queue, context.task_id, context.context_id)
122+
if not context.current_task:
123+
updater.submit()
124+
updater.start_work()
125+
await self._process_request(
126+
genai_types.UserContent(
127+
parts=convert_a2a_parts_to_genai(context.message.parts),
128+
),
129+
context.context_id,
130+
updater,
131+
)
132+
133+
async def cancel(self, context: RequestContext, event_queue: EventQueue):
134+
raise ServerError(error=UnsupportedOperationError())
135+
136+
async def _upsert_session(self, session_id: str):
137+
"""Retrieves or creates an ADK session."""
138+
return await self.runner.session_service.get_session(
139+
app_name=self.runner.app_name, user_id='self', session_id=session_id
140+
) or await self.runner.session_service.create_session(
141+
app_name=self.runner.app_name, user_id='self', session_id=session_id
142+
)
143+
144+
145+
def convert_a2a_parts_to_genai(parts: list[Part]) -> list[genai_types.Part]:
146+
"""Converts a list of A2A Part objects to a list of Google GenAI Part objects."""
147+
return [convert_a2a_part_to_genai(part) for part in parts]
148+
149+
150+
def convert_a2a_part_to_genai(part: Part) -> genai_types.Part:
151+
"""Converts a single A2A Part object to a Google GenAI Part object."""
152+
part = part.root
153+
if isinstance(part, TextPart):
154+
return genai_types.Part(text=part.text)
155+
if isinstance(part, FilePart):
156+
if isinstance(part.file, FileWithUri):
157+
return genai_types.Part(
158+
file_data=genai_types.FileData(
159+
file_uri=part.file.uri, mime_type=part.file.mime_type
160+
)
161+
)
162+
if isinstance(part.file, FileWithBytes):
163+
return genai_types.Part(
164+
inline_data=genai_types.Blob(
165+
data=part.file.bytes, mime_type=part.file.mime_type
166+
)
167+
)
168+
raise ValueError(f'Unsupported file type: {type(part.file)}')
169+
raise ValueError(f'Unsupported part type: {type(part)}')
170+
171+
172+
def convert_genai_parts_to_a2a(parts: list[genai_types.Part]) -> list[Part]:
173+
"""Converts a list of Google GenAI Part objects to a list of A2A Part objects."""
174+
return [
175+
convert_genai_part_to_a2a(part)
176+
for part in parts
177+
if (part.text or part.file_data or part.inline_data)
178+
]
179+
180+
181+
def convert_genai_part_to_a2a(part: genai_types.Part) -> Part:
182+
"""Converts a single Google GenAI Part object to an A2A Part object."""
183+
if part.text:
184+
return TextPart(text=part.text)
185+
if part.file_data:
186+
return FilePart(
187+
file=FileWithUri(
188+
uri=part.file_data.file_uri,
189+
mime_type=part.file_data.mime_type,
190+
)
191+
)
192+
if part.inline_data:
193+
return Part(
194+
root=FilePart(
195+
file=FileWithBytes(
196+
bytes=part.inline_data.data,
197+
mime_type=part.inline_data.mime_type,
198+
)
199+
)
200+
)
201+
raise ValueError(f'Unsupported part type: {part}')
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[project]
2+
name = "adk-french-translation-agent-example"
3+
version = "0.1.0"
4+
description = "French translation agent example using ADK"
5+
readme = "README.md"
6+
requires-python = ">=3.13"
7+
dependencies = [
8+
"a2a-sdk",
9+
"click>=8.1.8",
10+
"dotenv>=0.9.9",
11+
"httpx>=0.28.1",
12+
"google-genai>=1.9.0", # For Gemini model
13+
"google-adk>=1.0.0",
14+
"pydantic>=2.11.4",
15+
"python-dotenv>=1.1.0",
16+
"uvicorn>=0.34.2"
17+
]
18+
19+
[tool.hatch.build.targets.wheel]
20+
packages = ["."]
21+
22+
[tool.uv.sources]
23+
a2a-sdk = { workspace = true }
24+
25+
[build-system]
26+
requires = ["hatchling"]
27+
build-backend = "hatchling.build"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# ADK MCP Brave Search Agent with A2A Client
2+
3+
This example shows how to create an A2A Server that uses an ADK-based Agent that performs Brave Searches via the Model Context Protocol (MCP).
4+
5+
## Prerequisites
6+
7+
- Python 3.13 or higher
8+
- [UV](https://docs.astral.sh/uv/)
9+
- A Brave Search API Key (required for the Brave Search MCP server)
10+
11+
## Running the example
12+
13+
1. Create the `.env` file with your API Key in this directory:
14+
15+
```bash
16+
echo "BRAVE_API_KEY=your_brave_api_key_here" > .env
17+
```
18+
(Ensure your `GOOGLE_API_KEY` is also present if other examples or parts of the ADK setup require it, though this specific agent only needs `BRAVE_API_KEY`.)
19+
20+
21+
2. Start the server
22+
23+
```sh
24+
uv run .
25+
```
26+
27+
3. Run the test client
28+
29+
```sh
30+
uv run test_client.py
31+
```
32+
33+
Or the CLI client:
34+
```sh
35+
uv run cli_client.py --query "your search query"

0 commit comments

Comments
 (0)