-
Notifications
You must be signed in to change notification settings - Fork 493
feat(a2a): add A2AAgent class as an implementation of the agent interface for remote A2A protocol based agents #1174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| """ | ||
| return run_async(lambda: self.invoke_async(prompt, **kwargs)) | ||
|
|
||
| async def stream_async( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stream async needs to return AgentResult for it to work with graph/swarm https://github.com/strands-agents/sdk-python/blob/main/src/strands/multiagent/graph.py#L810
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have this documented anywhere? Or is it only applicable internally?
16d4a59 to
48a006f
Compare
…face for remote A2A protocol based agents
…d function + reduce use of Any type
…y constructor parameters
48a006f to
182f9a4
Compare
|
Can we also support parent agent's streaming support with callback_handler in |
| RuntimeError: If no response received from agent. | ||
| """ | ||
| async for event in await self._send_message(prompt): | ||
| return convert_response_to_agent_result(event) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that first event is the complete response though, right? What if it's not?
| if not self.description and self._agent_card.description: | ||
| self.description = self._agent_card.description | ||
|
|
||
| logger.info("agent=<%s>, endpoint=<%s> | discovered agent card", self.name, self.endpoint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be debug I think? I'm not sure we really use info anywhere throughout
| if self._a2a_client is None: | ||
| agent_card = await self._get_agent_card() | ||
|
|
||
| if self._a2a_client_factory is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on simplifying to:
factory = self._a2a_client_factory or _create_default_factory()
self._a2a_client = factory.create(agent_card)
| ValueError: If prompt is None. | ||
| RuntimeError: If no response received from agent. | ||
| """ | ||
| async for event in await self._send_message(prompt): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we delegate this implementation to stream_async for the events?
| if self._owns_client and self._httpx_client is not None: | ||
| try: | ||
| client = self._httpx_client | ||
| run_async(lambda: client.aclose()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check to ensure that this works correctly? IIRC, @dbschmigelski indicated there were problems trying to do async stuff from del
|
|
||
| if final_event is not None: | ||
| result = convert_response_to_agent_result(final_event) | ||
| yield AgentResultEvent(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to yield one final event that becomes the "result" of the tool: https://strandsagents.com/latest/documentation/docs/user-guide/concepts/tools/python-tools/#tool-streaming
I think as is, this results in a stringified version of AgentResultEvent - is that what we want?
| a2a_agent = A2AAgent(endpoint="http://localhost:9000") | ||
|
|
||
| # Invoke our A2A server | ||
| result = a2a_agent("Hello there!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have tests for all of the invoke/stream methods to ensure that they work e2e, when the client/server(s) are configured for streaming and non-streaming mode
| A2AResponse: TypeAlias = tuple[Task, TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None] | Message | ||
|
|
||
|
|
||
| class A2AStreamEvent(TypedEvent): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mkmeral have we started exposing these events publicly yet?
|
|
||
|
|
||
| class A2AStreamEvent(TypedEvent): | ||
| """Event that wraps streamed A2A types.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this emitted for every single update that we get from the remote server? Even final updates etc?
If so, can we document that
| from strands.agent.a2a_agent import A2AAgent | ||
|
|
||
|
|
||
| def test_a2a_agent(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there non-strands A2A servers that are easy to spin up? I wonder if we could/should test against those as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callout that I appreciate this PR description & code sample.
Description
Adds an A2AAgent class that makes it simple to consume remote A2A agents and invoke them like any other Strands Agents.
Implements the Protocol in #1126.
Example usage:
Output:
Standalone invocation of A2AAgent objects also works:
Follow-ups:
Related Issues
#907
Documentation PR
TODO
Type of Change
New feature
Testing
How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli
hatch run prepareChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.