{"record":{"id":"00ca7229f4481203","repo":"deepset-ai/haystack","slug":"tools-must-be-a-list-of-tool-and-or-toolset-object","errorCode":null,"errorMessage":"tools must be a list of Tool and/or Toolset objects, a Toolset, or a list of tool names (strings).","messagePattern":"tools must be a list of Tool and/or Toolset objects, a Toolset, or a list of tool names \\(strings\\)\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"haystack/components/agents/agent.py","lineNumber":822,"sourceCode":"            or if any provided tool name is not valid.\n        :raises TypeError: If tools is not a list of Tool objects, a Toolset, or a list of tool names (strings).\n        \"\"\"\n        # Toolsets are spawned per run (see _spawn_tools / _select_tools_by_name) so concurrent runs\n        # sharing the same configured Toolset don't corrupt each other's run-scoped state.\n        if tools is None:\n            return _spawn_tools(tools=self.tools)\n\n        if isinstance(tools, list) and all(isinstance(t, str) for t in tools):\n            return _select_tools_by_name(self.tools, cast(list[str], tools))\n\n        if isinstance(tools, (Toolset, list)):\n            selected = cast(ToolsType, tools)  # mypy can't narrow the Union type from the isinstance checks\n            # Per-run tools are not covered by the Agent's own warm_up(), so warm them up here.\n            # warm_up() is expected to be idempotent, so re-warming on every run is cheap.\n            warm_up_tools(tools=selected)\n            return _spawn_tools(tools=selected)\n\n        raise TypeError(\n            \"tools must be a list of Tool and/or Toolset objects, a Toolset, or a list of tool names (strings).\"\n        )\n\n    def run(\n        self,\n        messages: list[ChatMessage],\n        streaming_callback: StreamingCallbackT | None = None,\n        *,\n        generation_kwargs: dict[str, Any] | None = None,\n        tools: ToolsType | list[str] | None = None,\n        hook_context: dict[str, Any] | None = None,\n        **kwargs: Any,\n    ) -> dict[str, Any]:\n        \"\"\"\n        Process messages and execute tools until an exit condition is met.\n\n        :param messages: List of Haystack ChatMessage objects to process.\n        :param streaming_callback: A callback that will be invoked when a response is streamed from the LLM.","sourceCodeStart":804,"sourceCodeEnd":840,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/agents/agent.py#L804-L840","documentation":"Agent._select_tools raises this TypeError when the `tools` argument passed to run()/select-tools has a shape that is neither a Toolset, a list of Tool/Toolset objects, nor a list of tool-name strings. The library validates the input against a Union type at runtime because Python cannot enforce it statically.","triggerScenarios":"Passing tools as a single Tool (not in a list and not a Toolset), a list containing strings mixed with Tool objects, a dict of tools, None other than allowed sentinel, or any other non-conforming type to Agent tool selection.","commonSituations":"Config mistakes where tools are loaded from YAML/JSON as plain dicts, forgetting to wrap a single Tool in a list, or passing tool names mixed with Tool instances.","solutions":["Wrap single Tool objects in a list: tools=[my_tool]","Pass only tool-name strings OR Tool/Toolset objects, never a mix of strings and objects","If tools come from serialized config, reconstruct Tool objects via Tool.from_dict before passing","Check the API docs for ToolsType and conform to one of its variants"],"exampleFix":"// before\nagent.run(messages, tools=my_tool)\n// after\nagent.run(messages, tools=[my_tool])","handlingStrategy":"validation","validationCode":"def valid_tools(tools):\n    from haystack.tools import Tool, Toolset\n    if isinstance(tools, Toolset):\n        return True\n    if isinstance(tools, list):\n        return all(isinstance(t, (Tool, Toolset)) or isinstance(t, str) for t in tools) and \\\n               not (any(isinstance(t, str) for t in tools) and any(isinstance(t, (Tool, Toolset)) for t in tools))\n    return False","typeGuard":"def is_tools_type(tools) -> bool:\n    from haystack.tools import Tool, Toolset\n    if isinstance(tools, Toolset):\n        return True\n    return isinstance(tools, list) and (\n        all(isinstance(t, (Tool, Toolset)) for t in tools)\n        or all(isinstance(t, str) for t in tools)\n    )","tryCatchPattern":"try:\n    agent.run(messages, tools=tools)\nexcept TypeError as e:\n    if \"tools must be\" in str(e):\n        tools = [tools] if isinstance(tools, Tool) else list(tools)\n    else:\n        raise","preventionTips":["Wrap single Tool objects in a list","Never mix name strings with Tool/Toolset objects in one list","Reconstruct Tool objects from serialized config instead of passing raw dicts"],"tags":["python","type-validation","agent","tools"],"backgroundTag":"invalid-argument-type","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}