deepset-ai/haystack · error · ValueError

Tool execution requires at least one tool.

Error message

Tool execution requires at least one tool.

What it means

_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.

Source

Thrown at haystack/components/agents/tool_calling.py:54

_StateKeys = set[str] | _AllStateKeys


class ToolNotFoundException(Exception):
    """Exception raised when a tool is not found in the list of available tools."""

    def __init__(self, tool_name: str, available_tools: list[str]) -> None:
        message = f"Tool '{tool_name}' not found. Available tools: {', '.join(available_tools)}"
        super().__init__(message)


def _validate_and_prepare_tools(tools: ToolsType) -> dict[str, Tool]:
    """
    Flatten, deduplicate-check, and index tools by name.

    :raises ValueError: If no tools are provided or if duplicate tool names are found.
    """
    if not tools:
        raise ValueError("Tool execution requires at least one tool.")

    available_tools = flatten_tools_or_toolsets(tools)
    _check_duplicate_tool_names(available_tools)
    tool_names = [tool.name for tool in available_tools]

    return dict(zip(tool_names, available_tools, strict=True))


def _merge_tool_outputs_into_state(tool: Tool, result: Any, state: State) -> None:
    """
    Write tool outputs into State according to the tool's `outputs_to_state` mapping.

    :raises RuntimeError: If writing an output value into the state fails.
    """
    if not isinstance(result, dict):
        return

    for state_key, config in (tool.outputs_to_state or {}).items():

View on GitHub (pinned to e318778c9b)

Solutions

  1. Pass at least one Tool or Toolset to the execution call
  2. Check upstream filtering logic that may have emptied the tools list
  3. If the Agent legitimately needs no tools, don't route tool_call messages to it

Example fix

// before
run_tools(tools=[])
// after
run_tools(tools=[my_tool])
Defensive patterns

Strategy: validation

Validate before calling

if not tools:
    raise ValueError("Refusing to run tools: list is empty")

Type guard

def has_tools(tools) -> bool:
    return bool(tools)

Try / catch

try:
    result = run_tools(tools=tools, tool_call=call)
except ValueError as e:
    if "at least one tool" in str(e):
        result = fallback_answer_without_tools(call)
    else:
        raise

Prevention

When it happens

Trigger: Calling _run_tool/_run_tool_async with tools=[] or None coercing to empty; an Agent configured with no tools receiving a tool_call message.

Common situations: Config where tools were filtered out upstream (all names rejected), or invoking low-level tool execution helpers directly with an empty list.

Related errors


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