Skip to content

docs: exit_reason's tool result is not always last_message - #12891

Merged
sjrl merged 4 commits into
deepset-ai:mainfrom
lets-order-some-fries:fix/agent-exit-reason-last-message
Sep 29, 2026
Merged

sjrl merged 4 commits into
deepset-ai:mainfrom
lets-order-some-fries:fix/agent-exit-reason-last-message

Conversation

@lets-order-some-fries

@lets-order-some-fries lets-order-some-fries commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Related Issues

Proposed Changes:

Agent.run and Agent.run_async documented exit_reason as "the name of the tool that satisfied a tool exit condition (in which case last_message is that tool's result)". That only holds when the exit tool's result happens to be the last message of the step. _check_exit_conditions checks every tool call regardless of order, while last_message is just the final message, so with parallel tool calls a ConditionalRouter that branches on exit_reason and then reads last_message can silently get another tool's output.

This is a docs-only change; behaviour is unchanged. The exit tool's result is now described as the last tool-result message in messages whose tool_call_result.origin.tool_name matches exit_reason, which may not be last_message when the model calls several tools in one reply.

  • haystack/components/agents/agent.py: the exit_reason entry in the run and run_async docstrings.
  • docs-website/docs/pipeline-components/agents-1/agent.mdx and docs-website/versioned_docs/version-3.2/pipeline-components/agents-1/agent.mdx: the same correction. These also described the tool-result message's text as empty; it is None.
  • test/components/agents/test_agent.py: removed a comment restating the old claim.

How did you test it?

test_tool_exit_reports_the_first_matching_tool already covered the parallel-call case but only asserted exit_reason; it now also asserts that last_message is the other tool's result.

  • pytest test/components/agents/ test/tools/: 621 passed, 16 skipped
  • pre-commit run --files on the four changed files: all hooks pass

Notes for the reviewer

versioned_docs/version-3.1 still has the old sentence. I left it because 3.2 is the current versioned page.

Checklist

  • I have read the contributors guidelines and the code of conduct.
  • I have updated the related issue with new insights and changes.
  • I have added unit tests and updated the docstrings.
  • I've used one of the conventional commit types for my PR title: fix:, feat:, build:, chore:, ci:, docs:, style:, refactor:, perf:, test: and added ! in case the PR includes breaking changes.
  • I have documented my code.
  • I have added a release note file, following the contributors guidelines. Not needed for a docs-only change.
  • I have run pre-commit hooks and fixed any issue.

🤖 Generated with Claude Code

Agent.run and Agent.run_async both documented exit_reason as "the name of
the tool that satisfied a tool exit condition (in which case `last_message`
is that tool's result)".

That holds only when the exit tool is the last one the model requested.
_check_exit_conditions is deliberately order-agnostic -- its docstring says
"the order of parallel tool calls does not matter" -- while last_message is
the final message of the step. With parallel tool calls the two disagree,
and because exit_reason is documented as useful for routing downstream, a
ConditionalRouter branching on it and then reading last_message silently
consumes a different tool's output.

The same claim was in three other places: the shipped docs page, which put
it more strongly and also described the message's text as empty when it is
None, and a comment in the tests. All four now say to locate the exit tool's
result by matching tool_call_result.origin.tool_name against exit_reason.

test_tool_exit_reports_the_first_matching_tool already built exactly this
parallel-call shape but asserted only exit_reason, which is why the mismatch
went unnoticed; it now pins last_message too.

Behaviour is unchanged. See deepset-ai#12890 for the alternative -- making
_check_exit_conditions report the exit tool whose result lands last, so the
original promise becomes true -- which changes which tool is reported when
two exit-condition tools fire in one step.
@lets-order-some-fries
lets-order-some-fries requested a review from a team as a code owner September 23, 2026 16:04
@lets-order-some-fries
lets-order-some-fries requested review from sjrl and removed request for a team September 23, 2026 16:04
@vercel

vercel Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

@lets-order-some-fries is attempting to deploy a commit to the deepset Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  haystack/components/agents
  agent.py
Project Total  

This report was generated by python-coverage-comment-action

Comment thread haystack/components/agents/agent.py Outdated
Comment thread haystack/components/agents/agent.py Outdated
Comment thread docs-website/docs/pipeline-components/agents-1/agent.mdx Outdated
Comment thread test/components/agents/test_agent.py Outdated
Comment thread test/components/agents/test_agent.py Outdated
Comment thread releasenotes/notes/agent-exit-reason-last-message-docs-4b1c7a9e2f6d08c3.yaml Outdated
@sjrl

sjrl commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Please also update docs-website/versioned_docs/version-3.2-unstable/pipeline-components/agents-1/agent.mdx, it still has the old text.

@sjrl

sjrl commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Thanks for opening this! We'd prefer to keep this a docs only change for resolving the issue.

@sjrl

sjrl commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Hey @lets-order-some-fries soft ping to ask if you can work on this, this week

@lets-order-some-fries

Copy link
Copy Markdown
Contributor Author

Sure yeah, I'll work on it today.

- Rewrap the exit_reason paragraph in the run() and run_async()
  docstrings, and say the *last* matching tool message there too:
  messages covers the whole run, so the same tool can have results from
  earlier steps (an errored call, or an exit an on_exit hook vetoed).
- docs page: take the suggested wording.
- Tests: drop the comments and the exit_result lookup; the last_message
  assertion covers it.
- Drop the release note; this is a docs-only change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review asked for version-3.2-unstable; deepset-ai#12918 has since promoted it
to version-3.2, the current versioned page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lets-order-some-fries

Copy link
Copy Markdown
Contributor Author

Thanks @sjrl, all addressed:

  • Rewrapped the exit_reason paragraph in both docstrings, and used your last matching message wording there as well, since the same reasoning applies.
  • Took your suggestions for the docs page and both tests, and dropped the release note.
  • docs: promote unstable docs for Haystack 3.2 #12918 promoted version-3.2-unstable to version-3.2 in the meantime, so I merged main and made the versioned change in versioned_docs/version-3.2/.

version-3.1 still has the old sentence. I left it since it isn't the current version, but I'm happy to update it too.

@sjrl sjrl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@sjrl
sjrl merged commit a4d46c0 into deepset-ai:main Sep 29, 2026
33 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:tests type:documentation Improvements on the docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent: exit_reason names a tool whose result is not last_message when tool calls run in parallel

2 participants