diff --git a/docs-website/docs/pipeline-components/agents-1/agent.mdx b/docs-website/docs/pipeline-components/agents-1/agent.mdx index 8d5eeec5746..c84d7c24595 100644 --- a/docs-website/docs/pipeline-components/agents-1/agent.mdx +++ b/docs-website/docs/pipeline-components/agents-1/agent.mdx @@ -66,7 +66,7 @@ The `exit_reason` output tells you why the agent stopped, which makes it easy to - `"text"`: the model returned a complete reply with no tool calls. - `"length"`: the model reached its output-token limit. `last_message` may contain a partial response. - `"content_filter"`: a content filter stopped the model response. `last_message` may contain a partial response. -- the name of the tool that satisfied a tool exit condition. In this case `last_message` is that tool's result — a tool-result `ChatMessage` whose `text` is empty — so `exit_reason` tells you how to consume it. +- the name of the tool that satisfied a tool exit condition. Its result is the last tool-result `ChatMessage` in `messages` whose `tool_call_result.origin.tool_name` matches `exit_reason`; read it from `tool_call_result.result`. When the model calls several tools in one reply, this may not be `last_message`. - `"max_agent_steps"`: the agent reached `max_agent_steps` before meeting an exit condition. - a custom reason set by a hook through the `stop_run` state key, such as `"token_budget_exceeded"` from the [`TokenBudgetHook`](./token-budget.mdx). diff --git a/docs-website/versioned_docs/version-3.2/pipeline-components/agents-1/agent.mdx b/docs-website/versioned_docs/version-3.2/pipeline-components/agents-1/agent.mdx index 8d5eeec5746..c84d7c24595 100644 --- a/docs-website/versioned_docs/version-3.2/pipeline-components/agents-1/agent.mdx +++ b/docs-website/versioned_docs/version-3.2/pipeline-components/agents-1/agent.mdx @@ -66,7 +66,7 @@ The `exit_reason` output tells you why the agent stopped, which makes it easy to - `"text"`: the model returned a complete reply with no tool calls. - `"length"`: the model reached its output-token limit. `last_message` may contain a partial response. - `"content_filter"`: a content filter stopped the model response. `last_message` may contain a partial response. -- the name of the tool that satisfied a tool exit condition. In this case `last_message` is that tool's result — a tool-result `ChatMessage` whose `text` is empty — so `exit_reason` tells you how to consume it. +- the name of the tool that satisfied a tool exit condition. Its result is the last tool-result `ChatMessage` in `messages` whose `tool_call_result.origin.tool_name` matches `exit_reason`; read it from `tool_call_result.result`. When the model calls several tools in one reply, this may not be `last_message`. - `"max_agent_steps"`: the agent reached `max_agent_steps` before meeting an exit condition. - a custom reason set by a hook through the `stop_run` state key, such as `"token_budget_exceeded"` from the [`TokenBudgetHook`](./token-budget.mdx). diff --git a/haystack/components/agents/agent.py b/haystack/components/agents/agent.py index dc72a9433e1..ce556e28b5a 100644 --- a/haystack/components/agents/agent.py +++ b/haystack/components/agents/agent.py @@ -866,9 +866,10 @@ def run( - "exit_reason": Why the Agent stopped, useful for routing the output downstream (e.g. with a `ConditionalRouter`). One of: `"text"` (the model returned a complete reply with no tool calls), `"length"` or `"content_filter"` (the model returned an incomplete reply, which may contain partial - text), the name of the tool that satisfied a tool exit condition (in which case `last_message` is that - tool's result), or `"max_agent_steps"` (the Agent hit `max_agent_steps` before meeting an exit - condition), or a custom reason a hook supplied through the `stop_run` state key. + text), the name of the tool that satisfied a tool exit condition (its result is the last tool message in + `messages` whose `tool_call_result.origin.tool_name` matches it, which may not be `last_message` when + the model called several tools at once), or `"max_agent_steps"` (the Agent hit `max_agent_steps` before + meeting an exit condition), or a custom reason a hook supplied through the `stop_run` state key. - Any additional keys defined in the `state_schema`. """ agent_inputs = {"messages": messages, "streaming_callback": streaming_callback, **kwargs} @@ -952,9 +953,10 @@ async def run_async( - "exit_reason": Why the Agent stopped, useful for routing the output downstream (e.g. with a `ConditionalRouter`). One of: `"text"` (the model returned a complete reply with no tool calls), `"length"` or `"content_filter"` (the model returned an incomplete reply, which may contain partial - text), the name of the tool that satisfied a tool exit condition (in which case `last_message` is that - tool's result), or `"max_agent_steps"` (the Agent hit `max_agent_steps` before meeting an exit - condition), or a custom reason a hook supplied through the `stop_run` state key. + text), the name of the tool that satisfied a tool exit condition (its result is the last tool message in + `messages` whose `tool_call_result.origin.tool_name` matches it, which may not be `last_message` when + the model called several tools at once), or `"max_agent_steps"` (the Agent hit `max_agent_steps` before + meeting an exit condition), or a custom reason a hook supplied through the `stop_run` state key. - Any additional keys defined in the `state_schema`. """ agent_inputs = {"messages": messages, "streaming_callback": streaming_callback, **kwargs} diff --git a/test/components/agents/test_agent.py b/test/components/agents/test_agent.py index 08e568fd3ac..54502d90380 100644 --- a/test/components/agents/test_agent.py +++ b/test/components/agents/test_agent.py @@ -1111,7 +1111,6 @@ def test_exit_condition_exits(self, weather_tool): assert "last_message" in result assert isinstance(result["last_message"], ChatMessage) assert result["messages"][-1] == result["last_message"] - # The exit reason is the tool that triggered the exit, and `last_message` is that tool's result. assert result["exit_reason"] == "weather_tool" def test_exit_condition_on_tool_provided_at_runtime(self, weather_tool): @@ -1254,6 +1253,7 @@ def test_tool_exit_reports_the_first_matching_tool(self, weather_tool, component ) result = agent.run([ChatMessage.from_user("Go")]) assert result["exit_reason"] == "parrot" + assert result["last_message"].tool_call_result.origin.tool_name == "weather_tool" def test_max_steps_exit(self, weather_tool, caplog): """Exhausting `max_agent_steps` before meeting an exit condition reports `"max_agent_steps"`."""