From 708222ef5c1de1f7cbd37caee431c725149ee9e4 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Wed, 27 May 2026 18:53:31 +0800 Subject: [PATCH] fix: allow structured results in tool-end hooks --- src/agents/lifecycle.py | 4 +-- tests/test_agent_hooks.py | 2 +- tests/test_agent_llm_hooks.py | 2 +- tests/test_computer_action.py | 4 +-- tests/test_global_hooks.py | 2 +- tests/test_run_hooks.py | 54 ++++++++++++++++++++++++++++++-- tests/test_run_step_execution.py | 12 +++---- 7 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/agents/lifecycle.py b/src/agents/lifecycle.py index 2ca7484739..497434d629 100644 --- a/src/agents/lifecycle.py +++ b/src/agents/lifecycle.py @@ -87,7 +87,7 @@ async def on_tool_end( context: RunContextWrapper[TContext], agent: TAgent, tool: Tool, - result: str, + result: Any, ) -> None: """Called immediately after a local tool is invoked. @@ -161,7 +161,7 @@ async def on_tool_end( context: RunContextWrapper[TContext], agent: TAgent, tool: Tool, - result: str, + result: Any, ) -> None: """Called immediately after a local tool is invoked. diff --git a/tests/test_agent_hooks.py b/tests/test_agent_hooks.py index b97f2763e7..1cd8eebbd6 100644 --- a/tests/test_agent_hooks.py +++ b/tests/test_agent_hooks.py @@ -67,7 +67,7 @@ async def on_tool_end( context: RunContextWrapper[TContext], agent: Agent[TContext], tool: Tool, - result: str, + result: Any, ) -> None: self.events["on_tool_end"] += 1 if isinstance(context, ToolContext): diff --git a/tests/test_agent_llm_hooks.py b/tests/test_agent_llm_hooks.py index 16dcec9c83..e5be2360b0 100644 --- a/tests/test_agent_llm_hooks.py +++ b/tests/test_agent_llm_hooks.py @@ -47,7 +47,7 @@ async def on_tool_end( context: RunContextWrapper[TContext], agent: Agent[TContext], tool: Tool, - result: str, + result: Any, ) -> None: self.events["on_tool_end"] += 1 diff --git a/tests/test_computer_action.py b/tests/test_computer_action.py index 3aa908c66c..430dd2cc91 100644 --- a/tests/test_computer_action.py +++ b/tests/test_computer_action.py @@ -510,7 +510,7 @@ async def on_tool_start( self.started.append((agent, tool)) async def on_tool_end( - self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any, result: str + self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any, result: Any ) -> None: self.ended.append((agent, tool, result)) @@ -529,7 +529,7 @@ async def on_tool_start( self.started.append((agent, tool)) async def on_tool_end( - self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any, result: str + self, context: RunContextWrapper[Any], agent: Agent[Any], tool: Any, result: Any ) -> None: self.ended.append((agent, tool, result)) diff --git a/tests/test_global_hooks.py b/tests/test_global_hooks.py index d6780d6217..0a4cf52bdb 100644 --- a/tests/test_global_hooks.py +++ b/tests/test_global_hooks.py @@ -65,7 +65,7 @@ async def on_tool_end( context: RunContextWrapper[TContext], agent: Agent[TContext], tool: Tool, - result: str, + result: Any, ) -> None: self.events["on_tool_end"] += 1 if isinstance(context, ToolContext): diff --git a/tests/test_run_hooks.py b/tests/test_run_hooks.py index 9b433853d3..2ed721da87 100644 --- a/tests/test_run_hooks.py +++ b/tests/test_run_hooks.py @@ -10,7 +10,7 @@ from agents.models.interface import Model from agents.run import Runner from agents.run_context import AgentHookContext, RunContextWrapper, TContext -from agents.tool import Tool +from agents.tool import Tool, function_tool from agents.tool_context import ToolContext from tests.test_agent_llm_hooks import AgentHooksForTests @@ -60,7 +60,7 @@ async def on_tool_end( context: RunContextWrapper[TContext], agent: Agent[TContext], tool: Tool, - result: str, + result: Any, ) -> None: self.events["on_tool_end"] += 1 if isinstance(context, ToolContext): @@ -386,3 +386,53 @@ async def test_streamed_run_hooks_count_tool_and_handoff_invocations(): assert hooks.events["on_agent_start"] == 2 assert hooks.events["on_agent_end"] == 1 assert len(hooks.tool_context_ids) == 2 + + +@pytest.mark.asyncio +async def test_run_hooks_receive_structured_tool_result(): + class RecordingRunHooks(RunHooks): + def __init__(self): + self.result: Any = None + + async def on_tool_end( + self, + context: RunContextWrapper[Any], + agent: Agent[Any], + tool: Tool, + result: Any, + ) -> None: + self.result = result + + class RecordingAgentHooks(AgentHooks): + def __init__(self): + self.result: Any = None + + async def on_tool_end( + self, + context: RunContextWrapper[Any], + agent: Agent[Any], + tool: Tool, + result: Any, + ) -> None: + self.result = result + + @function_tool + def get_metadata() -> dict[str, Any]: + return {"status": "ok", "count": 1} + + run_hooks = RecordingRunHooks() + agent_hooks = RecordingAgentHooks() + model = FakeModel() + agent = Agent(name="test", model=model, tools=[get_metadata], hooks=agent_hooks) + + model.add_multiple_turn_outputs( + [ + [get_function_tool_call("get_metadata", "{}")], + [get_text_message("done")], + ] + ) + + await Runner.run(agent, input="user_message", hooks=run_hooks) + + assert run_hooks.result == {"status": "ok", "count": 1} + assert agent_hooks.result == {"status": "ok", "count": 1} diff --git a/tests/test_run_step_execution.py b/tests/test_run_step_execution.py index 53d3b88164..4b9d3ea3ac 100644 --- a/tests/test_run_step_execution.py +++ b/tests/test_run_step_execution.py @@ -1018,7 +1018,7 @@ async def on_tool_end( context: RunContextWrapper[Any], agent: Agent[Any], tool, - result: str, + result: Any, ) -> None: if tool.name != "ok_tool": return @@ -1117,7 +1117,7 @@ async def on_tool_end( context: RunContextWrapper[Any], agent: Agent[Any], tool, - result: str, + result: Any, ) -> None: seen_values.append(("hook", tool_state.get())) @@ -1249,7 +1249,7 @@ async def on_tool_end( context: RunContextWrapper[Any], agent: Agent[Any], tool, - result: str, + result: Any, ) -> None: self.results[tool.name] = result if tool.name == "ok_tool": @@ -1308,7 +1308,7 @@ async def on_tool_end( context: RunContextWrapper[Any], agent: Agent[Any], tool, - result: str, + result: Any, ) -> None: if tool.name == "waiting_tool": on_tool_end_called.set() @@ -1378,7 +1378,7 @@ async def on_tool_end( context: RunContextWrapper[Any], agent: Agent[Any], tool, - result: str, + result: Any, ) -> None: on_tool_end_called.set() @@ -1922,7 +1922,7 @@ async def on_tool_end( context: RunContextWrapper[Any], agent: Agent[Any], tool, - result: str, + result: Any, ) -> None: if tool.name != "ok_tool": return