diff --git a/haystack/components/agents/agent.py b/haystack/components/agents/agent.py index 06c1379eb9..c322c7f727 100644 --- a/haystack/components/agents/agent.py +++ b/haystack/components/agents/agent.py @@ -743,6 +743,7 @@ class Agent: state.set("tool_call_counts", {tool.name: 0 for tool in flat_tools}) state.set("exit_reason", None) state.set("continue_run", False) + state.set("tools", flat_tools) state.set("hook_context", hook_context or {}) streaming_callback = select_streaming_callback( # type: ignore[call-overload] diff --git a/releasenotes/notes/agent-before-run-tools-f5ee03eb231cfea2.yaml b/releasenotes/notes/agent-before-run-tools-f5ee03eb231cfea2.yaml new file mode 100644 index 0000000000..d3fe0999ee --- /dev/null +++ b/releasenotes/notes/agent-before-run-tools-f5ee03eb231cfea2.yaml @@ -0,0 +1,7 @@ +--- +enhancements: + - | + ``before_run`` hooks can now read ``state.data["tools"]``. The key was previously written only once the first step + had started, so a ``before_run`` hook hit a ``KeyError``. It holds a snapshot of the tools available at that point, + refreshed before every LLM call, so with a dynamic toolset such as ``SearchableToolset`` a ``before_run`` hook sees + only the tools discovered so far. diff --git a/test/components/agents/test_agent_hooks.py b/test/components/agents/test_agent_hooks.py index 910f9e8593..1ff08dd506 100644 --- a/test/components/agents/test_agent_hooks.py +++ b/test/components/agents/test_agent_hooks.py @@ -196,6 +196,16 @@ class TestAgentHooksValidation: class TestBeforeRunHook: + def test_tools_are_available_before_the_first_step(self): + available_tool_names: list[str] = [] + + def record_tools(state: State) -> None: + available_tool_names.extend(tool.name for tool in state.data["tools"]) + + agent = _agent(MockChatGenerator(), tools=[save], hooks={"before_run": [hook(record_tools)]}) + agent.run(messages=[ChatMessage.from_user("hi")]) + assert available_tool_names == ["save"] + def test_runs_once_across_multi_step_run(self): agent = _agent( MockChatGenerator(),