-
Notifications
You must be signed in to change notification settings - Fork 204
feat(a2a): agents-as-tools #424
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
Open
jer96
wants to merge
6
commits into
strands-agents:main
Choose a base branch
from
jer96:agent-as-tools
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,087
−0
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
50425a9
feat(a2a): agents-as-tools
5b553ba
Update src/strands/tools/agent_tool_wrapper.py
jer96 e71ba0e
feat(agents-as-tools): type checking
6db3d46
feat(agents-as-tools): import cleanup
4bb1540
feat(agents-as-tools): integ tests
908c441
feat(agents-as-tools): pr comments
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
"""Agent tool wrapper that enables using Agent objects as tools.""" | ||
|
||
from typing import TYPE_CHECKING, Any | ||
|
||
if TYPE_CHECKING: | ||
from ..agent.agent import Agent | ||
|
||
from ..types.tools import AgentTool, ToolGenerator, ToolResult, ToolResultContent, ToolSpec, ToolUse | ||
|
||
|
||
class AgentToolWrapper(AgentTool): | ||
"""Wrapper that makes an Agent usable as a tool. | ||
|
||
This class enables the agents-as-tools pattern by wrapping an Agent | ||
and implementing the AgentTool interface. The wrapped agent can then | ||
be used as a tool by other agents. | ||
""" | ||
|
||
def __init__(self, agent: "Agent"): | ||
"""Initialize the agent tool wrapper. | ||
|
||
Args: | ||
agent: The Agent instance to wrap | ||
""" | ||
super().__init__() | ||
self._agent = agent | ||
self._validate_agent() | ||
self._name = agent.name | ||
self._description = agent.description or "" | ||
|
||
def _validate_agent(self) -> None: | ||
"""Check if agent has the required attributes and they are properly set.""" | ||
if ( | ||
not hasattr(self._agent, "name") | ||
or not hasattr(self._agent, "description") | ||
or not self._agent.name | ||
or self._agent.name == "Strands Agents" # Default agent name | ||
or not self._agent.description | ||
): | ||
raise ValueError( | ||
"Agent must have both 'name' and 'description' parameters " | ||
"to be used as a tool. 'name' must not be default agent name: 'Strands Agents'. " | ||
"Initialize the Agent with: " | ||
"Agent(name='tool_name', description='tool_description', ...)" | ||
) | ||
|
||
@property | ||
def tool_name(self) -> str: | ||
"""The unique name of the tool used for identification and invocation.""" | ||
return self._name | ||
|
||
@property | ||
def tool_spec(self) -> ToolSpec: | ||
"""Tool specification that describes its functionality and parameters.""" | ||
return ToolSpec( | ||
name=self._name, | ||
description=self._description, | ||
inputSchema={ | ||
"type": "object", | ||
"properties": {"prompt": {"type": "string", "description": "The prompt to send to the sub-agent"}}, | ||
"required": ["prompt"], | ||
}, | ||
) | ||
|
||
@property | ||
def tool_type(self) -> str: | ||
"""The type of the tool implementation.""" | ||
return "agent" | ||
|
||
async def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator: | ||
"""Stream tool events by delegating to the wrapped agent. | ||
|
||
Args: | ||
tool_use: The tool use request containing tool ID and parameters | ||
invocation_state: Context for the tool invocation, including agent state | ||
**kwargs: Additional keyword arguments for future extensibility | ||
|
||
Yields: | ||
Tool events with the last being the tool result | ||
""" | ||
try: | ||
# Extract the prompt from tool input | ||
prompt = tool_use["input"].get("prompt", "") | ||
|
||
# Invoke the sub-agent | ||
result = await self._agent.invoke_async(prompt) | ||
|
||
# Convert agent response to tool result format | ||
tool_result = ToolResult( | ||
toolUseId=tool_use["toolUseId"], status="success", content=[ToolResultContent(text=str(result))] | ||
) | ||
|
||
yield tool_result | ||
|
||
except Exception as e: | ||
# Return error result | ||
tool_result = ToolResult( | ||
toolUseId=tool_use["toolUseId"], | ||
status="error", | ||
content=[ToolResultContent(text=f"Error executing '{self._name}': {str(e)}")], | ||
) | ||
yield tool_result | ||
|
||
def get_display_properties(self) -> dict[str, str]: | ||
"""Get properties to display in UI representations of this tool. | ||
|
||
Returns: | ||
Dictionary of property names and their string values | ||
""" | ||
return { | ||
"Name": self.tool_name, | ||
"Type": self.tool_type, | ||
"Description": self._description, | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Note that that this ends up being a stateful tool. Unlike most other tools, once invoked this tool retains history (the agent messages), which means future invocations will keep the prior context. Similarly, agents as tools aren't really safe to share between agents because of the shared history and because of potential concurrency issues.
To that end, I wonder if additional options are needed for things like having a per-tool-invocation history or copying the agent as part of the tool so that it could be more easily shared but that also has it's own downsides.
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.
Related to session persistence, I'm also curious if this should be persisting it's tool state (the tool-agent's state) in the parent agent's state - otherwise we run into the problem where the parent agent cannot be persisted/hydrated correctly.
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.
for sure, great points! i see you've been advocating for these since the start: #84 (comment). 😄
but all in all, i'm more than happy to address any blocking items you have in this PR. otherwise, i will create followup tasks for these topics.