deepset-ai/haystack · error · TypeError

Items in the tools list must be Tool or Toolset instances.

Error message

Items in the tools list must be Tool or Toolset instances.

What it means

serialize_tools_or_toolset in haystack/tools/serde_utils.py serializes toolsets and tool lists for pipeline serialization (to_dict). When given a list, each item must be a Tool or Toolset and is serialized via its to_dict(); any other item raises TypeError. This guards pipeline save/loading against corrupted tool registries.

Solutions

  1. Ensure every element of the tools list is an instantiated Tool or Toolset before serializing
  2. Replace raw dict representations with proper Tool.from_dict(...) / Toolset instances
  3. Filter out None or placeholder entries before to_dict()
  4. If you have a single Tool, confirm you're using the intended Toolset/Tool to_dict path

Example fix

// before
toolset.to_dict()  # tools contains raw dicts

// after
tools = [Tool.from_dict(d) if isinstance(d, dict) else d for d in raw_tools]
ts = Toolset(tools=tools)
ts.to_dict()
Defensive patterns

Strategy: validation

Validate before calling

from haystack.tools import Tool, Toolset

def validate_serializable_tools(tools) -> None:
    if tools is None or isinstance(tools, Toolset):
        return
    for i, item in enumerate(tools):
        if not isinstance(item, (Tool, Toolset)):
            raise TypeError(f"tools[{i}] is {type(item).__name__}; must be Tool or Toolset before to_dict()")

Type guard

def is_serializable_tools_list(tools) -> bool:
    from haystack.tools import Tool, Toolset
    return tools is None or isinstance(tools, Toolset) or (
        isinstance(tools, list) and all(isinstance(i, (Tool, Toolset)) for i in tools)
    )

Try / catch

try:
    data = toolset.to_dict()
except TypeError as e:
    logger.error("Tool registry corrupted for serialization: %s", e)
    raise

Prevention

When it happens

Trigger: Calling to_dict() on a Toolset/serializable component whose tools list contains non-Tool/non-Toolset items (dicts, raw callables, None, partially constructed objects).

Common situations: Manually building a tools list and inserting raw dicts or un-instantiated factories; deserialization bugs that left placeholders in the list; passing a single Tool (not in a list) with the list-handling path expected.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/1184005177bad2d8. Report an issue: GitHub.

Appendix: source

Thrown at haystack/tools/serde_utils.py:33

def serialize_tools_or_toolset(tools: "ToolsType | None") -> dict[str, Any] | list[dict[str, Any]] | None:
    """
    Serialize tools or toolsets to dictionaries.

    :param tools: A Toolset, a list of Tools and/or Toolsets, or None
    :returns: Serialized representation preserving Tool/Toolset boundaries when provided
    """
    if tools is None:
        return None
    if isinstance(tools, Toolset):
        return tools.to_dict()
    if isinstance(tools, list):
        serialized: list[dict[str, Any]] = []
        for item in tools:
            if isinstance(item, (Toolset, Tool)):
                serialized.append(item.to_dict())
            else:
                raise TypeError("Items in the tools list must be Tool or Toolset instances.")
        return serialized
    raise TypeError("tools must be Toolset, list[Union[Tool, Toolset]], or None")


def deserialize_tools_or_toolset_inplace(data: dict[str, Any], key: str = "tools") -> None:
    """
    Deserialize a list of Tools and/or Toolsets, or a single Toolset in a dictionary inplace.

    :param data:
        The dictionary with the serialized data.
    :param key:
        The key in the dictionary where the list of Tools and/or Toolsets, or single Toolset is stored.
    """
    if key in data:
        serialized_tools = data[key]

        if serialized_tools is None:
            return

View on GitHub (pinned to e318778c9b)