{"record":{"id":"7033347a00ee9043","repo":"deepset-ai/haystack","slug":"tool-execution-requires-at-least-one-tool","errorCode":null,"errorMessage":"Tool execution requires at least one tool.","messagePattern":"Tool execution requires at least one tool\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/components/agents/tool_calling.py","lineNumber":54,"sourceCode":"_StateKeys = set[str] | _AllStateKeys\n\n\nclass ToolNotFoundException(Exception):\n    \"\"\"Exception raised when a tool is not found in the list of available tools.\"\"\"\n\n    def __init__(self, tool_name: str, available_tools: list[str]) -> None:\n        message = f\"Tool '{tool_name}' not found. Available tools: {', '.join(available_tools)}\"\n        super().__init__(message)\n\n\ndef _validate_and_prepare_tools(tools: ToolsType) -> dict[str, Tool]:\n    \"\"\"\n    Flatten, deduplicate-check, and index tools by name.\n\n    :raises ValueError: If no tools are provided or if duplicate tool names are found.\n    \"\"\"\n    if not tools:\n        raise ValueError(\"Tool execution requires at least one tool.\")\n\n    available_tools = flatten_tools_or_toolsets(tools)\n    _check_duplicate_tool_names(available_tools)\n    tool_names = [tool.name for tool in available_tools]\n\n    return dict(zip(tool_names, available_tools, strict=True))\n\n\ndef _merge_tool_outputs_into_state(tool: Tool, result: Any, state: State) -> None:\n    \"\"\"\n    Write tool outputs into State according to the tool's `outputs_to_state` mapping.\n\n    :raises RuntimeError: If writing an output value into the state fails.\n    \"\"\"\n    if not isinstance(result, dict):\n        return\n\n    for state_key, config in (tool.outputs_to_state or {}).items():","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/agents/tool_calling.py#L36-L72","documentation":"_validate_and_prepare_tools raises this ValueError when the tools list given to tool execution is empty. Running tools requires at least one callable tool to dispatch LLM tool calls against.","triggerScenarios":"Calling _run_tool/_run_tool_async with tools=[] or None coercing to empty; an Agent configured with no tools receiving a tool_call message.","commonSituations":"Config where tools were filtered out upstream (all names rejected), or invoking low-level tool execution helpers directly with an empty list.","solutions":["Pass at least one Tool or Toolset to the execution call","Check upstream filtering logic that may have emptied the tools list","If the Agent legitimately needs no tools, don't route tool_call messages to it"],"exampleFix":"// before\nrun_tools(tools=[])\n// after\nrun_tools(tools=[my_tool])","handlingStrategy":"validation","validationCode":"if not tools:\n    raise ValueError(\"Refusing to run tools: list is empty\")","typeGuard":"def has_tools(tools) -> bool:\n    return bool(tools)","tryCatchPattern":"try:\n    result = run_tools(tools=tools, tool_call=call)\nexcept ValueError as e:\n    if \"at least one tool\" in str(e):\n        result = fallback_answer_without_tools(call)\n    else:\n        raise","preventionTips":["Check len(tools) before invoking tool execution helpers","Audit upstream filters that may empty the tools list","Configure Agents with at least one tool if tool_call messages are expected"],"tags":["python","agent","tools","validation"],"backgroundTag":"empty-tool-list","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}