Skip to content

toolset refactor POC - #12271

Draft
anakin87 wants to merge 14 commits into
mainfrom
toolset-refactor
Draft

toolset refactor POC#12271
anakin87 wants to merge 14 commits into
mainfrom
toolset-refactor

Conversation

@anakin87

@anakin87 anakin87 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Related Issues

Toolset class has grown over time to support different use cases.

All these different aspects contributed to making the class complex and prone to some bugs, like #12009. While working on this, I also found some other potential bugs: _is_warmed_up flag prevented warming tools added later; Agent.warm_up() skipped toolsets that are empty until warmed up (MCPToolset implements a workaround for this).

Proposed Changes:

  • remove + support; make add only support Tool, no longer Toolset. This removes the need for all duplicated logic in _ToolsetWrapper. Users can still pass tools=[my_toolset, another_toolset]
  • keep minimal spawn() and get_selectable_tools() implementations in the base class, but move specific behaviors to SearchableToolset
  • rework warm_up: remove _is_warmed_up flag; make Agent.warm_up() call warm_up_tools on every call. This is more aligned with other warm_up methods in Haystack, but now a specific implementation must know how to avoid doing expensive work multiple times. (A similar change I think it would also be needed to make the Agent editable after initialization)

How did you test it?

CI, new tests, tested also with MCP and other Toolset integrations

Notes for the reviewer

In this PR, I am sharing my idea for the refactor.

I realize that it contains some breaking changes. For this reason, I'd be more inclined to just deprecate + and add(toolset) in the next Haystack version (e.g. 3.1) and then remove them in the following versions (e.g. 3.2). The other changes do not seem too breaking to me and I'd ship them right away once we agree on the shape. WDYT?

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.
  • I have run pre-commit hooks and fixed any issue.

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
haystack-docs Ready Ready Preview Aug 11, 2026 8:21am

Request Review

@github-actions

github-actions Bot commented Aug 7, 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
  utils.py 126
  haystack/tools
  searchable_toolset.py
  toolset.py 157
  haystack/tools/skills
  skill_toolset.py
Project Total  

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

@anakin87
anakin87 requested a review from sjrl August 10, 2026 13:04
@sjrl

sjrl commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@anakin87 I like the proposed changes! And I do think that it helps simplify the implementation of Toolset quite a bit. I agree with your sentiment that we should just deprecate + and add(Toolset) in version 3.1 since those are the bigger breaking changes.

Comment thread haystack/components/agents/utils.py Outdated
Comment thread haystack/components/agents/utils.py Outdated
Comment on lines 138 to 145
if spawned is not item:
# spawn() returned a per-run copy that already restricts itself to the selected tools.
selected.append(spawned)
else:
selected.append(item)
# No per-run copy: extract the selected tools from `selectable`, the same list the names were
# validated against. Iterating a dynamic Toolset instead could silently miss some of them.
selected.extend(tool for tool in selectable if tool.name in matched)
return selected

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.

Ahh I think I understand now (referring to the question in this comment). We do the check spawned is not item which is actually False for normal Toolsets so then we fall into the else branch.

I think that this is worth at least a dev comment explaining this behavior because reading this code in isolation I get the impression that spawned should always be different if the incoming item is a Toolset

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agree that was difficult to understand. Clarified in 3bc0b51

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.

2 participants