diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index a75c1414f..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python Debugger: Python File", - "type": "debugpy", - "request": "launch", - "program": "${file}" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9b388533a..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "tests" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true -} \ No newline at end of file diff --git a/examples/basic/agent_lifecycle_example.py b/examples/basic/agent_lifecycle_example.py index b4334a83b..e556e3990 100644 --- a/examples/basic/agent_lifecycle_example.py +++ b/examples/basic/agent_lifecycle_example.py @@ -4,7 +4,7 @@ from pydantic import BaseModel -from agents import Agent, AgentHooks, RunContextWrapper, Runner, Tool, function_tool +from agents import Action, Agent, AgentHooks, RunContextWrapper, Runner, Tool, function_tool class CustomAgentHooks(AgentHooks): @@ -28,10 +28,10 @@ async def on_handoff(self, context: RunContextWrapper, agent: Agent, source: Age f"### ({self.display_name}) {self.event_counter}: Agent {source.name} handed off to {agent.name}" ) - async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None: + async def on_tool_start(self, context: RunContextWrapper, agent: Agent, action: Action) -> None: self.event_counter += 1 print( - f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started tool {tool.name}" + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started tool {action.function_tool.name} with arguments {action.tool_call.arguments}" ) async def on_tool_end( diff --git a/examples/basic/lifecycle_example.py b/examples/basic/lifecycle_example.py index 02ce449f4..7c6371683 100644 --- a/examples/basic/lifecycle_example.py +++ b/examples/basic/lifecycle_example.py @@ -4,7 +4,7 @@ from pydantic import BaseModel -from agents import Agent, RunContextWrapper, RunHooks, Runner, Tool, Usage, function_tool +from agents import Action, Agent, RunContextWrapper, RunHooks, Runner, Tool, Usage, function_tool class ExampleHooks(RunHooks): @@ -26,10 +26,10 @@ async def on_agent_end(self, context: RunContextWrapper, agent: Agent, output: A f"### {self.event_counter}: Agent {agent.name} ended with output {output}. Usage: {self._usage_to_str(context.usage)}" ) - async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None: + async def on_tool_start(self, context: RunContextWrapper, agent: Agent, action: Action) -> None: self.event_counter += 1 print( - f"### {self.event_counter}: Tool {tool.name} started. Usage: {self._usage_to_str(context.usage)}" + f"### {self.event_counter}: Tool {action.function_tool.tool.name} started. Usage: {self._usage_to_str(context.usage)}" ) async def on_tool_end( diff --git a/src/agents/__init__.py b/src/agents/__init__.py index 7de17efdb..f7fc4982f 100644 --- a/src/agents/__init__.py +++ b/src/agents/__init__.py @@ -58,6 +58,7 @@ StreamEvent, ) from .tool import ( + Action, CodeInterpreterTool, ComputerTool, FileSearchTool, @@ -239,6 +240,7 @@ def enable_verbose_stdout_logging(): "MCPToolApprovalRequest", "MCPToolApprovalFunctionResult", "function_tool", + "Action", "Usage", "add_trace_processor", "agent_span", diff --git a/src/agents/_run_impl.py b/src/agents/_run_impl.py index a83af62a1..44481c1ad 100644 --- a/src/agents/_run_impl.py +++ b/src/agents/_run_impl.py @@ -78,6 +78,8 @@ LocalShellTool, MCPToolApprovalRequest, Tool, + ToolRunComputerAction, + ToolRunFunction, ) from .tool_context import ToolContext from .tracing import ( @@ -126,19 +128,6 @@ class ToolRunHandoff: handoff: Handoff tool_call: ResponseFunctionToolCall - -@dataclass -class ToolRunFunction: - tool_call: ResponseFunctionToolCall - function_tool: FunctionTool - - -@dataclass -class ToolRunComputerAction: - tool_call: ResponseComputerToolCall - computer_tool: ComputerTool - - @dataclass class ToolRunMCPApprovalRequest: request_item: McpApprovalRequest @@ -544,9 +533,9 @@ async def execute_function_tool_calls( context_wrapper: RunContextWrapper[TContext], config: RunConfig, ) -> list[FunctionToolResult]: - async def run_single_tool( - func_tool: FunctionTool, tool_call: ResponseFunctionToolCall - ) -> Any: + async def run_single_tool(action: ToolRunFunction) -> Any: + func_tool = action.function_tool + tool_call = action.tool_call with function_span(func_tool.name) as span_fn: tool_context = ToolContext.from_agent_context( context_wrapper, @@ -557,9 +546,9 @@ async def run_single_tool( span_fn.span_data.input = tool_call.arguments try: _, _, result = await asyncio.gather( - hooks.on_tool_start(tool_context, agent, func_tool), + hooks.on_tool_start(tool_context, agent, action), ( - agent.hooks.on_tool_start(tool_context, agent, func_tool) + agent.hooks.on_tool_start(tool_context, agent, action) if agent.hooks else _coro.noop_coroutine() ), @@ -591,8 +580,7 @@ async def run_single_tool( tasks = [] for tool_run in tool_runs: - function_tool = tool_run.function_tool - tasks.append(run_single_tool(function_tool, tool_run.tool_call)) + tasks.append(run_single_tool(tool_run)) results = await asyncio.gather(*tasks) @@ -1039,9 +1027,9 @@ async def execute( ) _, _, output = await asyncio.gather( - hooks.on_tool_start(context_wrapper, agent, action.computer_tool), + hooks.on_tool_start(context_wrapper, agent, action), ( - agent.hooks.on_tool_start(context_wrapper, agent, action.computer_tool) + agent.hooks.on_tool_start(context_wrapper, agent, action) if agent.hooks else _coro.noop_coroutine() ), diff --git a/src/agents/lifecycle.py b/src/agents/lifecycle.py index 2cce496c8..65ab8474b 100644 --- a/src/agents/lifecycle.py +++ b/src/agents/lifecycle.py @@ -4,7 +4,7 @@ from .agent import Agent, AgentBase from .run_context import RunContextWrapper, TContext -from .tool import Tool +from .tool import Action, Tool TAgent = TypeVar("TAgent", bound=AgentBase, default=AgentBase) @@ -39,8 +39,8 @@ async def on_handoff( async def on_tool_start( self, context: RunContextWrapper[TContext], - agent: TAgent, - tool: Tool, + agent: Agent[TContext], + action: Action, ) -> None: """Called before a tool is invoked.""" pass @@ -90,8 +90,8 @@ async def on_handoff( async def on_tool_start( self, context: RunContextWrapper[TContext], - agent: TAgent, - tool: Tool, + agent: Agent[TContext], + action: Action, ) -> None: """Called before a tool is invoked.""" pass diff --git a/src/agents/tool.py b/src/agents/tool.py index b967e899b..67ac224b6 100644 --- a/src/agents/tool.py +++ b/src/agents/tool.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Callable, Literal, Union, overload +from openai.types.responses import ResponseFunctionToolCall from openai.types.responses.file_search_tool_param import Filters, RankingOptions from openai.types.responses.response_computer_tool_call import ( PendingSafetyCheck, @@ -281,6 +282,19 @@ def name(self): ] """A tool that can be used in an agent.""" +@dataclass +class ToolRunFunction: + tool_call: ResponseFunctionToolCall + function_tool: FunctionTool + + +@dataclass +class ToolRunComputerAction: + tool_call: ResponseComputerToolCall + computer_tool: ComputerTool + +Action = Union[ToolRunFunction, ToolRunComputerAction] +"""An action that can be performed by an agent. It contains the tool call and the tool""" def default_tool_error_function(ctx: RunContextWrapper[Any], error: Exception) -> str: """The default tool error function, which just returns a generic error message.""" diff --git a/tests/test_agent_hooks.py b/tests/test_agent_hooks.py index a6c302dc8..935a50ce7 100644 --- a/tests/test_agent_hooks.py +++ b/tests/test_agent_hooks.py @@ -7,7 +7,7 @@ import pytest from typing_extensions import TypedDict -from agents.agent import Agent +from agents.agent import Action, Agent from agents.lifecycle import AgentHooks from agents.run import Runner from agents.run_context import RunContextWrapper, TContext @@ -53,7 +53,7 @@ async def on_tool_start( self, context: RunContextWrapper[TContext], agent: Agent[TContext], - tool: Tool, + action: Action, ) -> None: self.events["on_tool_start"] += 1 diff --git a/tests/test_computer_action.py b/tests/test_computer_action.py index a306b1841..dc343d59c 100644 --- a/tests/test_computer_action.py +++ b/tests/test_computer_action.py @@ -23,6 +23,7 @@ ) from agents import ( + Action, Agent, AgentHooks, AsyncComputer, @@ -32,9 +33,9 @@ RunContextWrapper, RunHooks, ) -from agents._run_impl import ComputerAction, RunImpl, ToolRunComputerAction +from agents._run_impl import ComputerAction, RunImpl from agents.items import ToolCallOutputItem -from agents.tool import ComputerToolSafetyCheckData +from agents.tool import ComputerToolSafetyCheckData, ToolRunComputerAction class LoggingComputer(Computer): @@ -224,9 +225,9 @@ def __init__(self) -> None: self.ended: list[tuple[Agent[Any], Any, str]] = [] async def on_tool_start( - self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any + self, context: RunContextWrapper[Any], agent: Agent[Any], action: Action, ) -> None: - self.started.append((agent, tool)) + self.started.append((agent, action.computer_tool)) async def on_tool_end( self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any, result: str @@ -243,9 +244,9 @@ def __init__(self) -> None: self.ended: list[tuple[Agent[Any], Any, str]] = [] async def on_tool_start( - self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any + self, context: RunContextWrapper[Any], agent: Agent[Any], action: Action, ) -> None: - self.started.append((agent, tool)) + self.started.append((agent, action.computer_tool)) async def on_tool_end( self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any, result: str