Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ async def main() -> None:
description="A math expert assistant.",
model_client_stream=True,
)
math_agent_tool = AgentTool(math_agent, return_value_as_last_message=True)
math_agent_tool = AgentTool(
math_agent,
return_value_as_last_message=True,
description="Answer math questions. Does not have additional tools attached.",
)

chemistry_agent = AssistantAgent(
"chemistry_expert",
Expand All @@ -133,7 +137,11 @@ async def main() -> None:
description="A chemistry expert assistant.",
model_client_stream=True,
)
chemistry_agent_tool = AgentTool(chemistry_agent, return_value_as_last_message=True)
chemistry_agent_tool = AgentTool(
chemistry_agent,
return_value_as_last_message=True,
description="Answer chemistry questions. Does not have additional tools attached.",
)

agent = AssistantAgent(
"assistant",
Expand All @@ -150,6 +158,11 @@ async def main() -> None:
asyncio.run(main())
```

When wrapping agents as tools, expose only the delegated capability you intend the
caller to use. The `AgentTool` name and description control the tool surface shown
to the caller, but they do not enforce authorization; attach separately scoped tools
or workbenches to the wrapped agent when you need least-privilege execution.

For more advanced multi-agent orchestrations and workflows, read
[AgentChat documentation](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/index.html).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class AgentToolConfig(BaseModel):
agent: ComponentModel
"""The agent to be used for running the task."""

name: str | None = None
"""Optional tool name to expose to the caller."""

description: str | None = None
"""Optional tool description to expose to the caller."""

return_value_as_last_message: bool = False
"""Whether to return the value as the last message of the task result."""

Expand All @@ -31,12 +37,23 @@ class AgentTool(TaskRunnerTool, Component[AgentToolConfig]):

Args:
agent (BaseChatAgent): The agent to be used for running the task.
name (str | None, optional): The name of the tool exposed to the caller. If not provided,
the wrapped agent's name is used.
description (str | None, optional): The description of the tool exposed to the caller. If not provided,
the wrapped agent's description is used. This only controls the prompt-visible tool metadata;
it does not enforce an authorization boundary. Use separately scoped tools or workbenches on the
wrapped agent to enforce least privilege.
return_value_as_last_message (bool): Whether to use the last message content of the task result
as the return value of the tool in :meth:`~autogen_agentchat.tools.TaskRunnerTool.return_value_as_string`.
If set to True, the last message content will be returned as a string.
If set to False, the tool will return all messages in the task result as a string concatenated together,
with each message prefixed by its source (e.g., "writer: ...", "assistant: ...").

.. versionadded:: v0.7.6

The ``name`` and ``description`` parameters allow exposing a narrower task-specific capability surface
than the wrapped agent's full identity.

Example:

.. code-block:: python
Expand Down Expand Up @@ -76,18 +93,37 @@ async def main() -> None:
component_config_schema = AgentToolConfig
component_provider_override = "autogen_agentchat.tools.AgentTool"

def __init__(self, agent: BaseChatAgent, return_value_as_last_message: bool = False) -> None:
def __init__(
self,
agent: BaseChatAgent,
return_value_as_last_message: bool = False,
*,
name: str | None = None,
description: str | None = None,
) -> None:
self._agent = agent
self._name_override = name
self._description_override = description
super().__init__(
agent, agent.name, agent.description, return_value_as_last_message=return_value_as_last_message
agent,
name if name is not None else agent.name,
description if description is not None else agent.description,
return_value_as_last_message=return_value_as_last_message,
)

def _to_config(self) -> AgentToolConfig:
return AgentToolConfig(
agent=self._agent.dump_component(),
name=self._name_override,
description=self._description_override,
return_value_as_last_message=self._return_value_as_last_message,
)

@classmethod
def _from_config(cls, config: AgentToolConfig) -> Self:
return cls(BaseChatAgent.load_component(config.agent), config.return_value_as_last_message)
return cls(
BaseChatAgent.load_component(config.agent),
config.return_value_as_last_message,
name=config.name,
description=config.description,
)
31 changes: 31 additions & 0 deletions python/packages/autogen-agentchat/tests/test_task_runner_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,44 @@ def test_agent_tool_component() -> None:
tool = AgentTool(agent=agent)
config = tool.dump_component()
assert config.provider == "autogen_agentchat.tools.AgentTool"
assert "name" not in config.config
assert "description" not in config.config

tool2 = AgentTool.load_component(config)
assert isinstance(tool2, AgentTool)
assert tool2.name == agent.name
assert tool2.description == agent.description


def test_agent_tool_component_with_scoped_tool_metadata() -> None:
"""Test AgentTool can expose scoped metadata without mutating the wrapped agent."""
model_client = ReplayChatCompletionClient(["test"])
agent = AssistantAgent(
name="repo_agent",
model_client=model_client,
description="Can read, write, and execute repository tasks.",
)
tool = AgentTool(
agent=agent,
name="repo_reader",
description="Read-only repository helper. Does not write files or execute commands.",
)

assert tool.name == "repo_reader"
assert tool.description == "Read-only repository helper. Does not write files or execute commands."
assert agent.name == "repo_agent"
assert agent.description == "Can read, write, and execute repository tasks."

config = tool.dump_component()
assert config.config["name"] == "repo_reader"
assert config.config["description"] == "Read-only repository helper. Does not write files or execute commands."

tool2 = AgentTool.load_component(config)
assert isinstance(tool2, AgentTool)
assert tool2.name == "repo_reader"
assert tool2.description == "Read-only repository helper. Does not write files or execute commands."


@pytest.mark.asyncio
async def test_team_tool() -> None:
"""Test running a task with TeamTool."""
Expand Down