first commit
This commit is contained in:
4
agent_dhal/agentdhal_agentchat/tools/__init__.py
Normal file
4
agent_dhal/agentdhal_agentchat/tools/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from ._agent import AgentTool
|
||||
from ._team import TeamTool
|
||||
|
||||
__all__ = ["AgentTool", "TeamTool"]
|
||||
93
agent_dhal/agentdhal_agentchat/tools/_agent.py
Normal file
93
agent_dhal/agentdhal_agentchat/tools/_agent.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from agentdhal_core import Component, ComponentModel
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Self
|
||||
|
||||
from agentdhal_agentchat.agents import BaseChatAgent
|
||||
|
||||
from ._task_runner_tool import TaskRunnerTool
|
||||
|
||||
|
||||
class AgentToolConfig(BaseModel):
|
||||
"""Configuration for the AgentTool."""
|
||||
|
||||
agent: ComponentModel
|
||||
"""The agent to be used for running the task."""
|
||||
|
||||
return_value_as_last_message: bool = False
|
||||
"""Whether to return the value as the last message of the task result."""
|
||||
|
||||
|
||||
class AgentTool(TaskRunnerTool, Component[AgentToolConfig]):
|
||||
"""Tool that can be used to run a task using an agent.
|
||||
|
||||
The tool returns the result of the task execution as a :class:`~agentdhal_agentchat.base.TaskResult` object.
|
||||
|
||||
.. important::
|
||||
When using AgentTool, you **must** disable parallel tool calls in the model client configuration
|
||||
to avoid concurrency issues. Agents cannot run concurrently as they maintain internal state
|
||||
that would conflict with parallel execution. For example, set ``parallel_tool_calls=False``
|
||||
for :class:`~agentdhal_extensions.models.openai.OpenAIChatCompletionClient` and
|
||||
:class:`~agentdhal_extensions.models.openai.AzureOpenAIChatCompletionClient`.
|
||||
|
||||
Args:
|
||||
agent (BaseChatAgent): The agent to be used for running the task.
|
||||
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:`~agentdhal_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: ...").
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
|
||||
from agentdhal_agentchat.agents import AssistantAgent
|
||||
from agentdhal_agentchat.tools import AgentTool
|
||||
from agentdhal_agentchat.ui import Console
|
||||
from agentdhal_extensions.models.openai import OpenAIChatCompletionClient
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
model_client = OpenAIChatCompletionClient(model="gpt-4.1")
|
||||
writer = AssistantAgent(
|
||||
name="writer",
|
||||
description="A writer agent for generating text.",
|
||||
model_client=model_client,
|
||||
system_message="Write well.",
|
||||
)
|
||||
writer_tool = AgentTool(agent=writer)
|
||||
|
||||
# Create model client with parallel tool calls disabled for the main agent
|
||||
main_model_client = OpenAIChatCompletionClient(model="gpt-4.1", parallel_tool_calls=False)
|
||||
assistant = AssistantAgent(
|
||||
name="assistant",
|
||||
model_client=main_model_client,
|
||||
tools=[writer_tool],
|
||||
system_message="You are a helpful assistant.",
|
||||
)
|
||||
await Console(assistant.run_stream(task="Write a poem about the sea."))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
"""
|
||||
|
||||
component_config_schema = AgentToolConfig
|
||||
component_provider_override = "agentdhal_agentchat.tools.AgentTool"
|
||||
|
||||
def __init__(self, agent: BaseChatAgent, return_value_as_last_message: bool = False) -> None:
|
||||
self._agent = agent
|
||||
super().__init__(
|
||||
agent, agent.name, agent.description, return_value_as_last_message=return_value_as_last_message
|
||||
)
|
||||
|
||||
def _to_config(self) -> AgentToolConfig:
|
||||
return AgentToolConfig(
|
||||
agent=self._agent.dump_component(),
|
||||
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)
|
||||
72
agent_dhal/agentdhal_agentchat/tools/_task_runner_tool.py
Normal file
72
agent_dhal/agentdhal_agentchat/tools/_task_runner_tool.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from abc import ABC
|
||||
from typing import Annotated, Any, AsyncGenerator, List, Mapping
|
||||
|
||||
from agentdhal_core import CancellationToken
|
||||
from agentdhal_core.tools import BaseStreamTool
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..agents import BaseChatAgent
|
||||
from ..base import TaskResult
|
||||
from ..messages import BaseAgentEvent, BaseChatMessage
|
||||
from ..teams import BaseGroupChat
|
||||
|
||||
|
||||
class TaskRunnerToolArgs(BaseModel):
|
||||
"""Input for the TaskRunnerTool."""
|
||||
|
||||
task: Annotated[str, "The task to be executed."]
|
||||
|
||||
|
||||
class TaskRunnerTool(BaseStreamTool[TaskRunnerToolArgs, BaseAgentEvent | BaseChatMessage, TaskResult], ABC):
|
||||
"""An base class for tool that can be used to run a task using a team or an agent."""
|
||||
|
||||
component_type = "tool"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
task_runner: BaseGroupChat | BaseChatAgent,
|
||||
name: str,
|
||||
description: str,
|
||||
return_value_as_last_message: bool,
|
||||
) -> None:
|
||||
self._task_runner = task_runner
|
||||
self._return_value_as_last_message = return_value_as_last_message
|
||||
super().__init__(
|
||||
args_type=TaskRunnerToolArgs,
|
||||
return_type=TaskResult,
|
||||
name=name,
|
||||
description=description,
|
||||
strict=True,
|
||||
)
|
||||
|
||||
async def run(self, args: TaskRunnerToolArgs, cancellation_token: CancellationToken) -> TaskResult:
|
||||
"""Run the task and return the result."""
|
||||
return await self._task_runner.run(task=args.task, cancellation_token=cancellation_token)
|
||||
|
||||
async def run_stream(
|
||||
self, args: TaskRunnerToolArgs, cancellation_token: CancellationToken
|
||||
) -> AsyncGenerator[BaseAgentEvent | BaseChatMessage | TaskResult, None]:
|
||||
"""Run the task and yield events or messages as they are produced, the final :class:`TaskResult`
|
||||
will be yielded at the end."""
|
||||
async for event in self._task_runner.run_stream(task=args.task, cancellation_token=cancellation_token):
|
||||
yield event
|
||||
|
||||
def return_value_as_string(self, value: TaskResult) -> str:
|
||||
"""Convert the task result to a string."""
|
||||
if self._return_value_as_last_message:
|
||||
if value.messages and isinstance(value.messages[-1], BaseChatMessage):
|
||||
return value.messages[-1].to_model_text()
|
||||
raise ValueError("The last message is not a BaseChatMessage.")
|
||||
parts: List[str] = []
|
||||
for message in value.messages:
|
||||
if isinstance(message, BaseChatMessage):
|
||||
if message.source == "user":
|
||||
continue
|
||||
parts.append(f"{message.source}: {message.to_model_text()}")
|
||||
return "\n\n".join(parts)
|
||||
|
||||
async def save_state_json(self) -> Mapping[str, Any]:
|
||||
return await self._task_runner.save_state()
|
||||
|
||||
async def load_state_json(self, state: Mapping[str, Any]) -> None:
|
||||
await self._task_runner.load_state(state)
|
||||
133
agent_dhal/agentdhal_agentchat/tools/_team.py
Normal file
133
agent_dhal/agentdhal_agentchat/tools/_team.py
Normal file
@@ -0,0 +1,133 @@
|
||||
from agentdhal_core import Component, ComponentModel
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Self
|
||||
|
||||
from agentdhal_agentchat.teams import BaseGroupChat
|
||||
|
||||
from ._task_runner_tool import TaskRunnerTool
|
||||
|
||||
|
||||
class TeamToolConfig(BaseModel):
|
||||
"""Configuration for the TeamTool."""
|
||||
|
||||
name: str
|
||||
"""The name of the tool."""
|
||||
description: str
|
||||
"""The name and description of the tool."""
|
||||
team: ComponentModel
|
||||
"""The team to be used for running the task."""
|
||||
return_value_as_last_message: bool = False
|
||||
"""Whether to return the value as the last message of the task result."""
|
||||
|
||||
|
||||
class TeamTool(TaskRunnerTool, Component[TeamToolConfig]):
|
||||
"""Tool that can be used to run a task.
|
||||
|
||||
The tool returns the result of the task execution as a :class:`~agentdhal_agentchat.base.TaskResult` object.
|
||||
|
||||
.. important::
|
||||
When using TeamTool, you **must** disable parallel tool calls in the model client configuration
|
||||
to avoid concurrency issues. Teams cannot run concurrently as they maintain internal state
|
||||
that would conflict with parallel execution. For example, set ``parallel_tool_calls=False``
|
||||
for :class:`~agentdhal_extensions.models.openai.OpenAIChatCompletionClient` and
|
||||
:class:`~agentdhal_extensions.models.openai.AzureOpenAIChatCompletionClient`.
|
||||
|
||||
Args:
|
||||
team (BaseGroupChat): The team to be used for running the task.
|
||||
name (str): The name of the tool.
|
||||
description (str): The description of the tool.
|
||||
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:`~agentdhal_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: ...").
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from agentdhal_agentchat.agents import AssistantAgent
|
||||
from agentdhal_agentchat.conditions import SourceMatchTermination
|
||||
from agentdhal_agentchat.teams import RoundRobinGroupChat
|
||||
from agentdhal_agentchat.tools import TeamTool
|
||||
from agentdhal_agentchat.ui import Console
|
||||
from agentdhal_extensions.models.openai import OpenAIChatCompletionClient
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Disable parallel tool calls when using TeamTool
|
||||
model_client = OpenAIChatCompletionClient(model="gpt-4.1")
|
||||
|
||||
writer = AssistantAgent(name="writer", model_client=model_client, system_message="You are a helpful assistant.")
|
||||
reviewer = AssistantAgent(
|
||||
name="reviewer", model_client=model_client, system_message="You are a critical reviewer."
|
||||
)
|
||||
summarizer = AssistantAgent(
|
||||
name="summarizer",
|
||||
model_client=model_client,
|
||||
system_message="You combine the review and produce a revised response.",
|
||||
)
|
||||
team = RoundRobinGroupChat(
|
||||
[writer, reviewer, summarizer], termination_condition=SourceMatchTermination(sources=["summarizer"])
|
||||
)
|
||||
|
||||
# Create a TeamTool that uses the team to run tasks, returning the last message as the result.
|
||||
tool = TeamTool(
|
||||
team=team,
|
||||
name="writing_team",
|
||||
description="A tool for writing tasks.",
|
||||
return_value_as_last_message=True,
|
||||
)
|
||||
|
||||
# Create model client with parallel tool calls disabled for the main agent
|
||||
main_model_client = OpenAIChatCompletionClient(model="gpt-4.1", parallel_tool_calls=False)
|
||||
main_agent = AssistantAgent(
|
||||
name="main_agent",
|
||||
model_client=main_model_client,
|
||||
system_message="You are a helpful assistant that can use the writing tool.",
|
||||
tools=[tool],
|
||||
)
|
||||
# For handling each events manually.
|
||||
# async for message in main_agent.run_stream(
|
||||
# task="Write a short story about a robot learning to love.",
|
||||
# ):
|
||||
# print(message)
|
||||
# Use Console to display the messages in a more readable format.
|
||||
await Console(
|
||||
main_agent.run_stream(
|
||||
task="Write a short story about a robot learning to love.",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
asyncio.run(main())
|
||||
"""
|
||||
|
||||
component_config_schema = TeamToolConfig
|
||||
component_provider_override = "agentdhal_agentchat.tools.TeamTool"
|
||||
|
||||
def __init__(
|
||||
self, team: BaseGroupChat, name: str, description: str, return_value_as_last_message: bool = False
|
||||
) -> None:
|
||||
self._team = team
|
||||
super().__init__(team, name, description, return_value_as_last_message=return_value_as_last_message)
|
||||
|
||||
def _to_config(self) -> TeamToolConfig:
|
||||
return TeamToolConfig(
|
||||
name=self._name,
|
||||
description=self._description,
|
||||
team=self._team.dump_component(),
|
||||
return_value_as_last_message=self._return_value_as_last_message,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_config(cls, config: TeamToolConfig) -> Self:
|
||||
return cls(
|
||||
BaseGroupChat.load_component(config.team),
|
||||
config.name,
|
||||
config.description,
|
||||
config.return_value_as_last_message,
|
||||
)
|
||||
Reference in New Issue
Block a user