Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b9623c7
chore: Formatting/Lint fixes
holtskinner May 15, 2025
6fb5d91
Fix markdown lint errors
holtskinner May 15, 2025
b701b0c
Further markdown linting
holtskinner May 15, 2025
c788794
Update gitignore and spelling
holtskinner May 15, 2025
3645322
Move markdown lint
holtskinner May 15, 2025
54e7d6f
Update .github/actions/spelling/allow.txt
holtskinner May 15, 2025
b498fd2
Merge branch 'main' into lint-fixes
holtskinner May 15, 2025
673cb11
Add examples to jscpd ignore
holtskinner May 15, 2025
d962df9
Merge branch 'lint-fixes' of https://github.com/google/a2a-python int…
holtskinner May 15, 2025
bf8c6b2
Fix ruff lint errors
holtskinner May 15, 2025
5c102ae
Rename markdown lint config
holtskinner May 15, 2025
e6743ae
Spelling
holtskinner May 15, 2025
383a371
Update JSCPD to 0
holtskinner May 15, 2025
d206962
Add init.py
holtskinner May 15, 2025
9c0d110
Ignore commitlint
holtskinner May 15, 2025
65b5aa2
move markdown lint file
holtskinner May 15, 2025
bf4f21f
Add conventional-commit-lint.yaml
holtskinner May 15, 2025
2d6151a
Merge branch 'main' into lint-fixes
holtskinner May 15, 2025
3028fc6
Fix markdown/MyPy issues
holtskinner May 15, 2025
db064cf
Prettier
holtskinner May 15, 2025
e308305
Fixed Copy/paste error
holtskinner May 15, 2025
bddb1dc
Merge branch 'lint-fixes' of https://github.com/google/a2a-python int…
holtskinner May 15, 2025
e0a34af
Add .nox to .gitignore
holtskinner May 15, 2025
81073fe
Adjust spelling
holtskinner May 15, 2025
a2a51bf
Formatting
holtskinner May 15, 2025
71d8dd4
Change back only check changed
holtskinner May 15, 2025
aed5652
Undo JSCPD fix
holtskinner May 15, 2025
771ffd1
Formatting
holtskinner May 15, 2025
803958e
Update noxfile to autoflake python 3.13
holtskinner May 15, 2025
12ee89d
Add noxfile to spelling ignore
holtskinner May 15, 2025
0e1475e
Spelling
holtskinner May 15, 2025
22c6646
Prettier yaml
holtskinner May 15, 2025
61c0ebd
Merge branch 'main' into lint-fixes
holtskinner May 15, 2025
6492448
Fix spelling excludes
holtskinner May 15, 2025
7fbc228
Merge branch 'lint-fixes' of https://github.com/google/a2a-python int…
holtskinner May 15, 2025
be18797
Set JSCPD to 3
holtskinner May 15, 2025
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
19 changes: 19 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
ACard
AClient
AError
ARequest
ARun
AServer
AStarlette
EUR
GBP
INR
JPY
JSONRPCt
Llm
aconnect
adk
autouse
cla
cls
coc
codegen
coro
datamodel
dunders
genai
gle
inmemory
langgraph
lifecycles
linting
oauthoidc
opensource
socio
sse
tagwords
vulnz
2 changes: 2 additions & 0 deletions .github/actions/spelling/excludes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@
^\Q.github/workflows/linter.yaml\E$
\.gitignore\E$
\.vscode/
noxfile.py
\.ruff.toml$
2 changes: 2 additions & 0 deletions .github/conventional-commit-lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
enabled: true
always_check_pr_title: true
2 changes: 1 addition & 1 deletion .github/linters/.jscpd.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"ignore": ["**/.github/**", "**/.git/**", "**/tests/**"],
"ignore": ["**/.github/**", "**/.git/**", "**/tests/**", "**/examples/**"],
"threshold": 3,
"reporters": ["html", "markdown"]
}
4 changes: 4 additions & 0 deletions .github/linters/.markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"MD034": false,
"MD013": false
}
2 changes: 2 additions & 0 deletions .github/workflows/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ jobs:
VALIDATE_CHECKOV: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_TYPESCRIPT_STANDARD: false
VALIDATE_GIT_COMMITLINT: false
MARKDOWN_CONFIG_FILE: .markdownlint.json
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ __pycache__
.ruff_cache
.venv
coverage.xml
spec.json
.nox
4 changes: 2 additions & 2 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ offensive, or harmful.

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
representing a project or community include using an official project email
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
Expand Down Expand Up @@ -93,4 +93,4 @@ available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

Note: A version of this file is also available in the
[New Project repo](https://github.com/google/new-project/blob/master/docs/code-of-conduct.md).
[New Project repository](https://github.com/google/new-project/blob/master/docs/code-of-conduct.md).
Empty file.
14 changes: 7 additions & 7 deletions examples/google_adk/birthday_planner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ This agent helps plan birthday parties. It has access to a Calendar Agent that i

## Running the example

1. Create the .env file with your API Key
1. Create the `.env` file with your API Key

```bash
echo "GOOGLE_API_KEY=your_api_key_here" > .env
```
```bash
echo "GOOGLE_API_KEY=your_api_key_here" > .env
```

2. Run the Calendar Agent. See examples/google_adk/calendar_agent.

3. Run the example

```
uv run .
```
```sh
uv run .
```
Empty file.
13 changes: 8 additions & 5 deletions examples/google_adk/birthday_planner/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import click
import uvicorn

from adk_agent_executor import ADKAgentExecutor
from dotenv import load_dotenv

Expand All @@ -18,6 +19,7 @@
AgentSkill,
)


load_dotenv()

logging.basicConfig()
Expand All @@ -39,11 +41,12 @@ def wrapper(*args, **kwargs):
)
def main(host: str, port: int, calendar_agent: str):
# Verify an API key is set. Not required if using Vertex AI APIs, since those can use gcloud credentials.
if not os.getenv('GOOGLE_GENAI_USE_VERTEXAI') == 'TRUE':
if not os.getenv('GOOGLE_API_KEY'):
raise Exception(
'GOOGLE_API_KEY environment variable not set and GOOGLE_GENAI_USE_VERTEXAI is not TRUE.'
)
if os.getenv('GOOGLE_GENAI_USE_VERTEXAI') != 'TRUE' and not os.getenv(
'GOOGLE_API_KEY'
):
raise ValueError(
'GOOGLE_API_KEY environment variable not set and GOOGLE_GENAI_USE_VERTEXAI is not TRUE.'
)

skill = AgentSkill(
id='plan_parties',
Expand Down
42 changes: 21 additions & 21 deletions examples/google_adk/birthday_planner/adk_agent_executor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import asyncio
import logging
from collections.abc import AsyncGenerator
from typing import Any, AsyncIterable

from collections.abc import AsyncGenerator, AsyncIterable
from typing import Any
from uuid import uuid4

import httpx

from google.adk import Runner
from google.adk.agents import LlmAgent, RunConfig
from google.adk.artifacts import InMemoryArtifactService
Expand Down Expand Up @@ -42,14 +44,15 @@
from a2a.utils import get_text_parts
from a2a.utils.errors import ServerError


logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

AUTH_TASK_POLLING_DELAY_SECONDS = 0.2


class A2ARunConfig(RunConfig):
"""Custom override of ADK RunConfig to smuggle extra data through the event loop"""
"""Custom override of ADK RunConfig to smuggle extra data through the event loop."""

model_config = ConfigDict(
arbitrary_types_allowed=True,
Expand All @@ -66,7 +69,7 @@ def __init__(self, calendar_agent_url):
name='birthday_planner_agent',
description='An agent that helps manage birthday parties.',
after_tool_callback=self._handle_auth_required_task,
instruction=f"""
instruction="""
You are an agent that helps plan birthday parties.

Your job as a party planner is to act as a sounding board and idea generator for
Expand Down Expand Up @@ -96,7 +99,7 @@ def _run_agent(
session_id,
new_message: types.Content,
task_updater: TaskUpdater,
) -> AsyncGenerator[Event, None]:
) -> AsyncGenerator[Event]:
return self.runner.run_async(
session_id=session_id,
user_id='self',
Expand All @@ -111,7 +114,7 @@ async def _handle_auth_required_task(
tool_context: ToolContext,
tool_response: dict,
) -> dict | None:
"""Handle requests that return auth-required"""
"""Handle requests that return auth-required."""
if tool.name != 'message_calendar_agent':
return None
if not tool_context.state.get('task_suspended'):
Expand Down Expand Up @@ -165,7 +168,7 @@ async def _process_request(
task_updater.add_artifact(response)
task_updater.complete()
break
elif calls := event.get_function_calls():
if calls := event.get_function_calls():
for call in calls:
# Provide an update on what we're doing.
if call.name == 'message_calendar_agent':
Expand Down Expand Up @@ -305,36 +308,34 @@ async def _get_agent_task(self, task_id) -> Task:


def convert_a2a_parts_to_genai(parts: list[Part]) -> list[types.Part]:
"""Convert a list of A2A Part types into a list of Google GenAI Part types."""
"""Convert a list of A2A Part types into a list of Google Gen AI Part types."""
return [convert_a2a_part_to_genai(part) for part in parts]


def convert_a2a_part_to_genai(part: Part) -> types.Part:
"""Convert a single A2A Part type into a Google GenAI Part type."""
"""Convert a single A2A Part type into a Google Gen AI Part type."""
part = part.root
if isinstance(part, TextPart):
return types.Part(text=part.text)
elif isinstance(part, FilePart):
if isinstance(part, FilePart):
if isinstance(part.file, FileWithUri):
return types.Part(
file_data=types.FileData(
file_uri=part.file.uri, mime_type=part.file.mime_type
)
)
elif isinstance(part.file, FileWithBytes):
if isinstance(part.file, FileWithBytes):
return types.Part(
inline_data=types.Blob(
data=part.file.bytes, mime_type=part.file.mime_type
)
)
else:
raise ValueError(f'Unsupported file type: {type(part.file)}')
else:
raise ValueError(f'Unsupported part type: {type(part)}')
raise ValueError(f'Unsupported file type: {type(part.file)}')
raise ValueError(f'Unsupported part type: {type(part)}')


def convert_genai_parts_to_a2a(parts: list[types.Part]) -> list[Part]:
"""Convert a list of Google GenAI Part types into a list of A2A Part types."""
"""Convert a list of Google Gen AI Part types into a list of A2A Part types."""
return [
convert_genai_part_to_a2a(part)
for part in parts
Expand All @@ -343,17 +344,17 @@ def convert_genai_parts_to_a2a(parts: list[types.Part]) -> list[Part]:


def convert_genai_part_to_a2a(part: types.Part) -> Part:
"""Convert a single Google GenAI Part type into an A2A Part type."""
"""Convert a single Google Gen AI Part type into an A2A Part type."""
if part.text:
return TextPart(text=part.text)
elif part.file_data:
if part.file_data:
return FilePart(
file=FileWithUri(
uri=part.file_data.file_uri,
mime_type=part.file_data.mime_type,
)
)
elif part.inline_data:
if part.inline_data:
return Part(
root=FilePart(
file=FileWithBytes(
Expand All @@ -362,5 +363,4 @@ def convert_genai_part_to_a2a(part: types.Part) -> Part:
)
)
)
else:
raise ValueError(f'Unsupported part type: {part}')
raise ValueError(f'Unsupported part type: {part}')
16 changes: 8 additions & 8 deletions examples/google_adk/calendar_agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ This example shows how to create an A2A Server that uses an ADK-based Agent that

1. Create the .env file with your API Key and OAuth2.0 Client details

```bash
echo "GOOGLE_API_KEY=your_api_key_here" > .env
echo "GOOGLE_CLIENT_ID=your_client_id_here" >> .env
echo "GOOGLE_CLIENT_SECRET=your_client_secret_here" >> .env
```
```bash
echo "GOOGLE_API_KEY=your_api_key_here" > .env
echo "GOOGLE_CLIENT_ID=your_client_id_here" >> .env
echo "GOOGLE_CLIENT_SECRET=your_client_secret_here" >> .env
```

2. Run the example

```
uv run .
```
```bash
uv run .
```
Empty file.
8 changes: 3 additions & 5 deletions examples/langgraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@ An example LangGraph agent that helps with currency conversion.

## Getting started

1. Extract the zip file and cd to examples folder

2. Create an environment file with your API key:
1. Create an environment file with your API key:

```bash
echo "GOOGLE_API_KEY=your_api_key_here" > .env
```

3. Start the server
2. Start the server

```bash
uv run .
```

4. Run the test client
3. Run the test client

```bash
uv run test_client.py
Expand Down
Empty file.
3 changes: 2 additions & 1 deletion examples/langgraph/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
)
from langchain_core.tools import tool # type: ignore
from langchain_google_genai import ChatGoogleGenerativeAI
from pydantic import BaseModel

from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent # type: ignore
from pydantic import BaseModel


logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def format(session):
session.run(
'pyupgrade',
'--exit-zero-even-if-changed',
'--py311-plus',
'--py313-plus',
*lint_paths_py,
)
session.run(
Expand Down
18 changes: 8 additions & 10 deletions src/a2a/server/events/event_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ async def consume_all(self) -> AsyncGenerator[Event]:
raise self._exception
try:
# We use a timeout when waiting for an event from the queue.
# This is required because it allows the loop to check if
# `self._exception` has been set by the `agent_task_callback`.
# This is required because it allows the loop to check if
# `self._exception` has been set by the `agent_task_callback`.
# Without the timeout, loop might hang indefinitely if no events are
# enqueued by the agent and the agent simply threw an exception
event = await asyncio.wait_for(self.queue.dequeue_event(), timeout=self._timeout)
event = await asyncio.wait_for(
self.queue.dequeue_event(), timeout=self._timeout
)
logger.debug(
f'Dequeued event of type: {type(event)} in consume_all.'
)
Expand Down Expand Up @@ -83,16 +85,12 @@ async def consume_all(self) -> AsyncGenerator[Event]:
logger.debug('Stopping event consumption in consume_all.')
self.queue.close()
break
except asyncio.TimeoutError:
except TimeoutError:
# continue polling until there is a final event
continue
except asyncio.QueueShutDown:
break





def agent_task_callback(self, agent_task: asyncio.Task[None]):
if agent_task.exception() is not None:
self._exception = agent_task.exception()
if agent_task.exception() is not None:
self._exception = agent_task.exception()
Loading
Loading