first commit

This commit is contained in:
DigiJ
2026-03-13 12:56:43 -07:00
commit 159cf9fcfe
309 changed files with 64584 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
from ._agent import AgentTool
from ._team import TeamTool
__all__ = ["AgentTool", "TeamTool"]

View 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)

View 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)

View 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,
)