run-llama/llama_index · error · ValueError

Response must be provided to stop function.

Error message

Response must be provided to stop function.

What it means

default_stop_fn in multistep_query_engine.py reads stop_dict['query_bundle'] and raises if it is missing. The message text ('Response must be provided') is misleading — it actually means the MultiStepQueryEngine loop invoked the stop function with a dict that lacks the 'query_bundle' key (cast(None)). The stock loop always supplies it, so this almost always comes from a custom stop_fn that forwards an incomplete dict.

Source

Thrown at llama-index-core/llama_index/core/query_engine/multistep_query_engine.py:21

from llama_index.core.base.base_query_engine import BaseQueryEngine
from llama_index.core.base.response.schema import RESPONSE_TYPE
from llama_index.core.callbacks.schema import CBEventType, EventPayload
from llama_index.core.indices.query.query_transform.base import (
    StepDecomposeQueryTransform,
)
from llama_index.core.prompts.mixin import PromptMixinType
from llama_index.core.response_synthesizers import (
    BaseSynthesizer,
    get_response_synthesizer,
)
from llama_index.core.schema import NodeWithScore, QueryBundle, TextNode


def default_stop_fn(stop_dict: Dict) -> bool:
    """Stop function for multi-step query combiner."""
    query_bundle = cast(QueryBundle, stop_dict.get("query_bundle"))
    if query_bundle is None:
        raise ValueError("Response must be provided to stop function.")

    return "none" in query_bundle.query_str.lower()


class MultiStepQueryEngine(BaseQueryEngine):
    """
    Multi-step query engine.

    This query engine can operate over an existing base query engine,
    along with the multi-step query transform.

    Args:
        query_engine (BaseQueryEngine): A BaseQueryEngine object.
        query_transform (StepDecomposeQueryTransform): A StepDecomposeQueryTransform
            object.
        response_synthesizer (Optional[BaseSynthesizer]): A BaseSynthesizer
            object.
        num_steps (Optional[int]): Number of steps to run the multi-step query.

View on GitHub (pinned to afd0fef371)

Solutions

  1. Make your custom stop_fn read stop_dict.get('query_bundle') and handle it defensively — mirror the stock signature shown in default_stop_fn.
  2. If you just need the default behavior ('none' in the query string), omit stop_fn entirely and rely on default_stop_fn.
  3. Fix call sites that build stop dicts manually to always include the 'query_bundle' key with a QueryBundle value.
  4. For deterministic runs, prefer num_steps + early_stopping=False instead of a stop predicate — a simpler contract.

Example fix

# before
def my_stop(stop_dict):
    qb = stop_dict["query"]          # wrong key -> KeyError path / None -> ValueError
    return "enough" in qb
engine = MultiStepQueryEngine(..., stop_fn=my_stop)

# after
def my_stop(stop_dict):
    qb = stop_dict.get("query_bundle")   # exact key the loop supplies
    if qb is None:
        return True                      # be defensive instead of raising
    return "enough" in qb.query_str.lower()
engine = MultiStepQueryEngine(..., stop_fn=my_stop)
Defensive patterns

Strategy: validation

Validate before calling

from llama_index.core.query_engine.multistep_query_engine import default_stop_fn
from llama_index.core.schema import QueryBundle

STOP_DICT_KEYS = {"query_bundle", "new_query_cb"}

def safe_stop_fn(stop_dict: dict) -> bool:
    qb = stop_dict.get("query_bundle")
    if not isinstance(qb, QueryBundle):
        return True  # stop rather than crash on a malformed dict
    return "none" in qb.query_str.lower()

Type guard

from llama_index.core.schema import QueryBundle

def is_valid_stop_dict(stop_dict: dict) -> bool:
    return isinstance(stop_dict.get("query_bundle"), QueryBundle)

Prevention

When it happens

Trigger: Passing stop_fn to MultiStepQueryEngine where the callable expects a different dict shape (e.g. {'new_query_cb': ...} only), or calling a stop function copied from llama-index internals with a hand-built dict that omits 'query_bundle'. The built-in default_stop_fn itself never self-triggers because the engine's step loop passes {'query_bundle': qb, 'new_query_cb': cb}.

Common situations: Writing a custom early-stopping predicate based on outdated examples that used a different stop_dict schema; refactors that changed what the loop puts into the dict; version drift after the stop-function interface changed (there is a TODO in-source acknowledging the interface is rough).

Related errors


AI-assisted analysis of run-llama/llama_index@afd0fef371 (2026-08-15). Data as JSON: /api/errors/3a231e74205b2022. Report an issue: GitHub.