Describe the bug
Toolset.add() silently drops all tools when a not-yet-warmed parent adds a Toolset subclass that materializes its tools in warm_up().
The method only warms the object being added when the parent is already warmed up. It then immediately flattens a child Toolset with list(tool). For a lazy child, that list is empty. Because only the flattened tools are retained, a later parent.warm_up() cannot recover them.
This contradicts the comment in Toolset.add() that the source is warmed before flattening, and affects custom Toolset implementations that use the documented dynamic-loading pattern.
Error message
No exception is raised. The tools disappear silently:
ADD_REPRO {'lazy_warmups': 0, 'parent_tools': []}
Expected behavior
A child Toolset should be warmed before it is flattened, regardless of whether the parent has already been warmed. Existing behavior for adding a plain Tool to a cold parent should remain unchanged: that tool should still be warmed when the parent is warmed.
Additional context
The relevant branch is in haystack/tools/toolset.py::Toolset.add():
if self._is_warmed_up and hasattr(tool, "warm_up"):
tool.warm_up()
new_tools = [tool] if isinstance(tool, Tool) else list(tool)
A focused fix could warm every child Toolset before flattening while retaining the current parent-state condition for plain Tool objects. Regression coverage should include a cold parent, idempotent child warm-up, retained tools, and duplicate-name validation after lazy loading.
I am happy to prepare a focused PR with regression tests if maintainers confirm that this issue is open for external contributions.
To Reproduce
Run the following against current main:
from haystack.tools import Tool, Toolset
def make_tool(name: str) -> Tool:
return Tool(
name=name,
description=f"{name} tool",
parameters={"type": "object", "properties": {}},
function=lambda: f"{name} result",
)
class LazyToolset(Toolset):
def __init__(self) -> None:
super().__init__([])
self.warmups = 0
def warm_up(self) -> None:
if self._is_warmed_up:
return
self.warmups += 1
self.tools.append(make_tool("lazy"))
self._is_warmed_up = True
lazy = LazyToolset()
parent = Toolset()
parent.add(lazy)
print({"lazy_warmups": lazy.warmups, "parent_tools": [tool.name for tool in parent]})
Actual output:
{'lazy_warmups': 0, 'parent_tools': []}
FAQ Check
System:
- OS: Windows 11
- Python: 3.13 (Hatch
test environment)
- Haystack version:
main at 4c83c80fb55ef76c67c115e2fabab6732674f3cb
Describe the bug
Toolset.add()silently drops all tools when a not-yet-warmed parent adds aToolsetsubclass that materializes its tools inwarm_up().The method only warms the object being added when the parent is already warmed up. It then immediately flattens a child
Toolsetwithlist(tool). For a lazy child, that list is empty. Because only the flattened tools are retained, a laterparent.warm_up()cannot recover them.This contradicts the comment in
Toolset.add()that the source is warmed before flattening, and affects custom Toolset implementations that use the documented dynamic-loading pattern.Error message
No exception is raised. The tools disappear silently:
Expected behavior
A child
Toolsetshould be warmed before it is flattened, regardless of whether the parent has already been warmed. Existing behavior for adding a plainToolto a cold parent should remain unchanged: that tool should still be warmed when the parent is warmed.Additional context
The relevant branch is in
haystack/tools/toolset.py::Toolset.add():A focused fix could warm every child
Toolsetbefore flattening while retaining the current parent-state condition for plainToolobjects. Regression coverage should include a cold parent, idempotent child warm-up, retained tools, and duplicate-name validation after lazy loading.I am happy to prepare a focused PR with regression tests if maintainers confirm that this issue is open for external contributions.
To Reproduce
Run the following against current
main:Actual output:
FAQ Check
System:
testenvironment)mainat4c83c80fb55ef76c67c115e2fabab6732674f3cb