{"record":{"id":"63706668cf80fdc6","repo":"run-llama/llama_index","slug":"must-specify-num-steps-if-early-stopping-is-false","errorCode":null,"errorMessage":"Must specify num_steps if early_stopping is False.","messagePattern":"Must specify num_steps if early_stopping is False\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"llama-index-core/llama_index/core/query_engine/multistep_query_engine.py","lineNumber":70,"sourceCode":"        num_steps: Optional[int] = 3,\n        early_stopping: bool = True,\n        index_summary: str = \"None\",\n        stop_fn: Optional[Callable[[Dict], bool]] = None,\n    ) -> None:\n        self._query_engine = query_engine\n        self._query_transform = query_transform\n        self._response_synthesizer = response_synthesizer or get_response_synthesizer(\n            callback_manager=self._query_engine.callback_manager\n        )\n\n        self._index_summary = index_summary\n        self._num_steps = num_steps\n        self._early_stopping = early_stopping\n        # TODO: make interface to stop function better\n        self._stop_fn = stop_fn or default_stop_fn\n        # num_steps must be provided if early_stopping is False\n        if not self._early_stopping and self._num_steps is None:\n            raise ValueError(\"Must specify num_steps if early_stopping is False.\")\n\n        callback_manager = self._query_engine.callback_manager\n        super().__init__(callback_manager)\n\n    def _get_prompt_modules(self) -> PromptMixinType:\n        \"\"\"Get prompt sub-modules.\"\"\"\n        return {\n            \"response_synthesizer\": self._response_synthesizer,\n            \"query_transform\": self._query_transform,\n        }\n\n    def _query(self, query_bundle: QueryBundle) -> RESPONSE_TYPE:\n        with self.callback_manager.event(\n            CBEventType.QUERY, payload={EventPayload.QUERY_STR: query_bundle.query_str}\n        ) as query_event:\n            nodes, source_nodes, metadata = self._query_multistep(query_bundle)\n\n            final_response = self._response_synthesizer.synthesize(","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/run-llama/llama_index/blob/afd0fef371831f9bda13e5af7167cf4e981278ab/llama-index-core/llama_index/core/query_engine/multistep_query_engine.py#L52-L88","documentation":"MultiStepQueryEngine's constructor enforces a coherence rule: if early_stopping is False, the loop must know exactly when to stop, so num_steps must be a positive int. Omitting num_steps while passing early_stopping=False raises ValueError('Must specify num_steps if early_stopping is False.') before any query runs.","triggerScenarios":"MultiStepQueryEngine(query_engine=..., query_transform=MultiStepQueryTransform(...), early_stopping=False) with num_steps left at its default None. Also triggered by passing num_steps=None explicitly, or by copying example code that relied on defaults after changing early_stopping.","commonSituations":"Disabling early stopping to force a fixed exploration depth but forgetting the step budget; upgrading code where an older version defaulted num_steps; interactive experimentation where early_stopping is toggled per run while num_steps is only set in one branch.","solutions":["Set num_steps: MultiStepQueryEngine(..., num_steps=3, early_stopping=False).","Or keep early_stopping=True (default) and let the stop function decide, in which case num_steps stays optional.","Validate config before construction in code that builds engines from a config dict (see validation snippet).","Pick num_steps based on how many query rewrites the task needs — 2-4 covers most multi-hop QA setups."],"exampleFix":"# before\nengine = MultiStepQueryEngine(\n    query_engine=inner,\n    query_transform=MultiStepQueryTransform(index_summary=summary),\n    early_stopping=False,        # num_steps omitted -> ValueError\n)\n\n# after\nengine = MultiStepQueryEngine(\n    query_engine=inner,\n    query_transform=MultiStepQueryTransform(index_summary=summary),\n    num_steps=3,                  # explicit step budget\n    early_stopping=False,\n)","handlingStrategy":"validation","validationCode":"def build_multistep_engine(inner, transform, *, num_steps=None, early_stopping=True, **kw):\n    if not early_stopping and num_steps is None:\n        raise ValueError(\"num_steps is required when early_stopping=False\")\n    from llama_index.core.query_engine import MultiStepQueryEngine\n    return MultiStepQueryEngine(\n        query_engine=inner, query_transform=transform,\n        num_steps=num_steps, early_stopping=early_stopping, **kw,\n    )","typeGuard":"def is_valid_multistep_config(num_steps, early_stopping) -> bool:\n    return early_stopping or (isinstance(num_steps, int) and num_steps > 0)","tryCatchPattern":null,"preventionTips":["Whenever you set early_stopping=False, set num_steps in the same change — treat them as coupled.","Validate engine configs from files/dicts before constructing MultiStepQueryEngine.","Add a config-schema check (e.g. pydantic settings model) for engine constructors that accept None defaults."],"tags":["llama-index","multi-step","query-engine","configuration","validation"],"backgroundTag":null,"analyzedSha":"afd0fef371831f9bda13e5af7167cf4e981278ab","analyzedAt":"2026-08-15T05:42:58.429Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}