Skip to content

Commit 80ae082

Browse files
committed
Refactor code base
Change-Id: Ib8754fe89591cde3a2e15a2927eac2dc62088e31
1 parent 990d5ff commit 80ae082

39 files changed

+3680
-707
lines changed

examples/helloworld/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Hello World example agent that only returns Message events
2+
3+
## Getting started
4+
5+
1. Start the server
6+
```bash
7+
uv run .
8+
```
9+
10+
4. Run the test client
11+
```bash
12+
uv run test_client.py
13+
```

examples/helloworld/__init__.py

Whitespace-only changes.

examples/helloworld/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from agent_executor import HelloWorldAgentExecutor
22

3-
from a2a.server import A2AServer, DefaultA2ARequestHandler
3+
from a2a.server import A2AServer
4+
from a2a.server.request_handlers import DefaultA2ARequestHandler
45
from a2a.types import (
56
AgentAuthentication,
67
AgentCapabilities,

examples/helloworld/agent_executor.py

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,18 @@
44
from typing import Any
55
from uuid import uuid4
66

7-
from a2a.server import AgentExecutor
7+
from typing_extensions import override
8+
9+
from a2a.server.agent_execution import BaseAgentExecutor
10+
from a2a.server.events import EventQueue
811
from a2a.types import (
9-
CancelTaskRequest,
10-
CancelTaskResponse,
11-
JSONRPCErrorResponse,
1212
Message,
1313
Part,
1414
Role,
1515
SendMessageRequest,
16-
SendMessageResponse,
17-
SendMessageStreamingRequest,
18-
SendMessageStreamingResponse,
19-
SendMessageStreamingSuccessResponse,
20-
SendMessageSuccessResponse,
16+
SendStreamingMessageRequest,
2117
Task,
22-
TaskResubscriptionRequest,
2318
TextPart,
24-
UnsupportedOperationError,
2519
)
2620

2721

@@ -37,57 +31,40 @@ async def stream(self) -> AsyncGenerator[dict[str, Any], None]:
3731
yield {'content': 'World', 'done': True}
3832

3933

40-
class HelloWorldAgentExecutor(AgentExecutor):
34+
class HelloWorldAgentExecutor(BaseAgentExecutor):
4135
"""Test AgentProxy Implementation."""
4236

4337
def __init__(self):
4438
self.agent = HelloWorldAgent()
4539

40+
@override
4641
async def on_message_send(
47-
self, request: SendMessageRequest, task: Task | None
48-
) -> SendMessageResponse:
42+
self,
43+
request: SendMessageRequest,
44+
event_queue: EventQueue,
45+
task: Task | None,
46+
) -> None:
4947
result = await self.agent.invoke()
5048

5149
message: Message = Message(
5250
role=Role.agent,
5351
parts=[Part(root=TextPart(text=result))],
5452
messageId=str(uuid4()),
5553
)
54+
event_queue.enqueue_event(message)
5655

57-
return SendMessageResponse(
58-
root=SendMessageSuccessResponse(id=request.id, result=message)
59-
)
60-
61-
async def on_message_stream( # type: ignore
62-
self, request: SendMessageStreamingRequest, task: Task | None
63-
) -> AsyncGenerator[SendMessageStreamingResponse, None]:
56+
@override
57+
async def on_message_stream(
58+
self,
59+
request: SendStreamingMessageRequest,
60+
event_queue: EventQueue,
61+
task: Task | None,
62+
) -> None:
6463
async for chunk in self.agent.stream():
6564
message: Message = Message(
6665
role=Role.agent,
6766
parts=[Part(root=TextPart(text=chunk['content']))],
6867
messageId=str(uuid4()),
6968
final=chunk['done'],
7069
)
71-
yield SendMessageStreamingResponse(
72-
root=SendMessageStreamingSuccessResponse(
73-
id=request.id, result=message
74-
)
75-
)
76-
77-
async def on_cancel(
78-
self, request: CancelTaskRequest, task: Task
79-
) -> CancelTaskResponse:
80-
return CancelTaskResponse(
81-
root=JSONRPCErrorResponse(
82-
id=request.id, error=UnsupportedOperationError()
83-
)
84-
)
85-
86-
async def on_resubscribe( # type: ignore
87-
self, request: TaskResubscriptionRequest, task: Task
88-
) -> AsyncGenerator[SendMessageStreamingResponse, None]:
89-
yield SendMessageStreamingResponse(
90-
root=JSONRPCErrorResponse(
91-
id=request.id, error=UnsupportedOperationError()
92-
)
93-
)
70+
event_queue.enqueue_event(message)

examples/helloworld/pyproject.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[project]
2+
name = "helloworld"
3+
version = "0.1.0"
4+
description = "HelloWorld agent example that only returns Messages"
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
dependencies = [
8+
"a2a",
9+
"click>=8.1.8",
10+
"dotenv>=0.9.9",
11+
"httpx>=0.28.1",
12+
"langchain-google-genai>=2.1.4",
13+
"langgraph>=0.4.1",
14+
"pydantic>=2.11.4",
15+
"python-dotenv>=1.1.0",
16+
]
17+
18+
[tool.hatch.build.targets.wheel]
19+
packages = ["."]
20+
21+
[tool.uv.sources]
22+
a2a = { workspace = true }
23+
24+
[build-system]
25+
requires = ["hatchling"]
26+
build-backend = "hatchling.build"

examples/helloworld/uv.lock

Lines changed: 1187 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/langgraph/__main__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from agent_executor import CurrencyAgentExecutor
88
from dotenv import load_dotenv
99

10-
from a2a.server import A2AServer, DefaultA2ARequestHandler, InMemoryTaskStore
10+
11+
from a2a.server.request_handlers import DefaultA2ARequestHandler
12+
from a2a.server import A2AServer
1113
from a2a.types import (
1214
AgentAuthentication,
1315
AgentCapabilities,
@@ -27,11 +29,8 @@ def main(host: str, port: int):
2729
print('GOOGLE_API_KEY environment variable not set.')
2830
sys.exit(1)
2931

30-
task_store = InMemoryTaskStore()
31-
3232
request_handler = DefaultA2ARequestHandler(
33-
agent_executor=CurrencyAgentExecutor(task_store=task_store),
34-
task_store=task_store,
33+
agent_executor=CurrencyAgentExecutor()
3534
)
3635

3736
server = A2AServer(

examples/langgraph/agent_executor.py

Lines changed: 26 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,66 @@
1-
from collections.abc import AsyncGenerator
21
from typing import Any
32

43
from agent import CurrencyAgent
54
from helpers import (
6-
create_task_obj,
75
process_streaming_agent_response,
86
update_task_with_agent_response,
97
)
8+
from typing_extensions import override
109

11-
from a2a.server import AgentExecutor, TaskStore
10+
from a2a.server.agent_execution import BaseAgentExecutor
11+
from a2a.server.events.event_queue import EventQueue
1212
from a2a.types import (
13-
CancelTaskRequest,
14-
CancelTaskResponse,
15-
JSONRPCErrorResponse,
1613
MessageSendParams,
1714
SendMessageRequest,
18-
SendMessageResponse,
19-
SendMessageStreamingRequest,
20-
SendMessageStreamingResponse,
21-
SendMessageStreamingSuccessResponse,
22-
SendMessageSuccessResponse,
15+
SendStreamingMessageRequest,
2316
Task,
24-
TaskNotCancelableError,
25-
TaskResubscriptionRequest,
2617
TextPart,
27-
UnsupportedOperationError,
2818
)
19+
from a2a.utils import create_task_obj
2920

3021

31-
class CurrencyAgentExecutor(AgentExecutor):
22+
class CurrencyAgentExecutor(BaseAgentExecutor):
3223
"""Currency AgentExecutor Example."""
3324

34-
def __init__(self, task_store: TaskStore):
25+
def __init__(self):
3526
self.agent = CurrencyAgent()
36-
self.task_store = task_store
3727

28+
@override
3829
async def on_message_send(
39-
self, request: SendMessageRequest, task: Task | None
40-
) -> SendMessageResponse:
30+
self,
31+
request: SendMessageRequest,
32+
event_queue: EventQueue,
33+
task: Task | None,
34+
) -> None:
4135
"""Handler for 'message/send' requests."""
4236
params: MessageSendParams = request.params
4337
query = self._get_user_query(params)
4438

4539
if not task:
4640
task = create_task_obj(params)
47-
await self.task_store.save(task)
4841

4942
# invoke the underlying agent
5043
agent_response: dict[str, Any] = self.agent.invoke(
5144
query, task.contextId
5245
)
53-
5446
update_task_with_agent_response(task, agent_response)
55-
return SendMessageResponse(
56-
root=SendMessageSuccessResponse(id=request.id, result=task)
57-
)
58-
59-
async def on_message_stream( # type: ignore
60-
self, request: SendMessageStreamingRequest, task: Task | None
61-
) -> AsyncGenerator[SendMessageStreamingResponse, None]:
47+
event_queue.enqueue_event(task)
48+
49+
@override
50+
async def on_message_stream(
51+
self,
52+
request: SendStreamingMessageRequest,
53+
event_queue: EventQueue,
54+
task: Task | None,
55+
) -> None:
6256
"""Handler for 'message/sendStream' requests."""
6357
params: MessageSendParams = request.params
6458
query = self._get_user_query(params)
6559

6660
if not task:
6761
task = create_task_obj(params)
68-
await self.task_store.save(task)
62+
# emit the initial task so it is persisted to TaskStore
63+
event_queue.enqueue_event(task)
6964

7065
# kickoff the streaming agent and process responses
7166
async for item in self.agent.stream(query, task.contextId):
@@ -74,37 +69,9 @@ async def on_message_stream( # type: ignore
7469
)
7570

7671
if task_artifact_update_event:
77-
yield SendMessageStreamingResponse(
78-
root=SendMessageStreamingSuccessResponse(
79-
id=request.id, result=task_artifact_update_event
80-
)
81-
)
82-
83-
yield SendMessageStreamingResponse(
84-
root=SendMessageStreamingSuccessResponse(
85-
id=request.id, result=task_status_event
86-
)
87-
)
72+
event_queue.enqueue_event(task_artifact_update_event)
8873

89-
async def on_cancel(
90-
self, request: CancelTaskRequest, task: Task
91-
) -> CancelTaskResponse:
92-
"""Handler for 'tasks/cancel' requests."""
93-
return CancelTaskResponse(
94-
root=JSONRPCErrorResponse(
95-
id=request.id, error=TaskNotCancelableError()
96-
)
97-
)
98-
99-
async def on_resubscribe( # type: ignore
100-
self, request: TaskResubscriptionRequest, task: Task
101-
) -> AsyncGenerator[SendMessageStreamingResponse, None]:
102-
"""Handler for 'tasks/resubscribe' requests."""
103-
yield SendMessageStreamingResponse(
104-
root=JSONRPCErrorResponse(
105-
id=request.id, error=UnsupportedOperationError()
106-
)
107-
)
74+
event_queue.enqueue_event(task_status_event)
10875

10976
def _get_user_query(self, task_send_params: MessageSendParams) -> str:
11077
"""Helper to get user query from task send params."""

examples/langgraph/helpers.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from a2a.types import (
66
Artifact,
77
Message,
8-
MessageSendParams,
98
Part,
109
Role,
1110
Task,
@@ -17,19 +16,6 @@
1716
)
1817

1918

20-
def create_task_obj(message_send_params: MessageSendParams) -> Task:
21-
"""Create a new task object."""
22-
if not message_send_params.message.contextId:
23-
message_send_params.message.contextId = str(uuid4())
24-
25-
return Task(
26-
id=str(uuid4()),
27-
contextId=message_send_params.message.contextId,
28-
status=TaskStatus(state=TaskState.submitted),
29-
history=[message_send_params.message],
30-
)
31-
32-
3319
def update_task_with_agent_response(
3420
task: Task, agent_response: dict[str, Any]
3521
) -> None:
@@ -38,13 +24,19 @@ def update_task_with_agent_response(
3824
parts: list[Part] = [Part(root=TextPart(text=agent_response['content']))]
3925
if agent_response['require_user_input']:
4026
task.status.state = TaskState.input_required
41-
task.status.message = Message(
27+
message = Message(
4228
messageId=str(uuid4()),
4329
role=Role.agent,
4430
parts=parts,
4531
)
32+
task.status.message = message
33+
if not task.history:
34+
task.history = []
35+
36+
task.history.append(message)
4637
else:
4738
task.status.state = TaskState.completed
39+
task.status.message = None
4840
if not task.artifacts:
4941
task.artifacts = []
5042

@@ -83,13 +75,15 @@ def process_streaming_agent_response(
8375
if artifact:
8476
task_artifact_update_event = TaskArtifactUpdateEvent(
8577
taskId=task.id,
78+
contextId=task.contextId,
8679
artifact=artifact,
8780
append=False,
8881
lastChunk=True,
8982
)
9083

9184
task_status_event = TaskStatusUpdateEvent(
9285
taskId=task.id,
86+
contextId=task.contextId,
9387
status=TaskStatus(
9488
state=task_state,
9589
message=message,

0 commit comments

Comments
 (0)