{"record":{"id":"ee8f66a61a14fffd","repo":"run-llama/llama_index","slug":"max-iterations-of-max-iterations-reached-either","errorCode":null,"errorMessage":"Max iterations of {max_iterations} reached! Either something went wrong, or you can increase the max iterations with `.run(.., max_iterations=...)` or use `early_stopping_method='generate'` to generate a final response instead.","messagePattern":"Max iterations of (.+?) reached! Either something went wrong, or you can increase the max iterations with `\\.run\\(\\.\\., max_iterations=\\.\\.\\.\\)` or use `early_stopping_method='generate'` to generate a final response instead\\.","errorType":"exception","errorClass":"WorkflowRuntimeError","httpStatus":null,"severity":"error","filePath":"llama-index-core/llama_index/core/agent/workflow/base_agent.py","lineNumber":538,"sourceCode":"    @step\n    async def parse_agent_output(\n        self, ctx: Context, ev: AgentOutput\n    ) -> Union[StopEvent, AgentInput, ToolCall, None]:\n        max_iterations = await ctx.store.get(\n            \"max_iterations\", default=DEFAULT_MAX_ITERATIONS\n        )\n        num_iterations = await ctx.store.get(\"num_iterations\", default=0)\n        num_iterations += 1\n        await ctx.store.set(\"num_iterations\", num_iterations)\n\n        if num_iterations >= max_iterations:\n            early_stopping_method = await ctx.store.get(\n                \"early_stopping_method\", default=\"force\"\n            )\n            if early_stopping_method == \"generate\":\n                return await self._generate_early_stopping_response(ctx, max_iterations)\n            else:\n                raise WorkflowRuntimeError(\n                    f\"Max iterations of {max_iterations} reached! Either something went wrong, or you can \"\n                    \"increase the max iterations with `.run(.., max_iterations=...)` \"\n                    \"or use `early_stopping_method='generate'` to generate a final response instead.\"\n                )\n\n        memory: BaseMemory = await ctx.store.get(\"memory\")\n\n        if ev.retry_messages:\n            # Retry with the given messages to let the LLM fix potential errors\n            history = await memory.aget()\n            user_msg_str = await ctx.store.get(\"user_msg_str\")\n\n            return AgentInput(\n                input=[\n                    *history,\n                    ChatMessage(role=\"user\", content=user_msg_str),\n                    *ev.retry_messages,\n                ],","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/run-llama/llama_index/blob/afd0fef371831f9bda13e5af7167cf4e981278ab/llama-index-core/llama_index/core/agent/workflow/base_agent.py#L520-L556","documentation":"FunctionTool.__init__ requires at least one callables: both fn (sync) and async_fn (async) cannot be None. The tool wraps a function; without one there is nothing to execute, so construction fails immediately with ValueError.","triggerScenarios":"Calling FunctionTool(fn=None, metadata=...) with no async_fn; passing fn via keyword but under a wrong name so it lands as None; building FunctionTool from a config/dict where the function reference failed to resolve (e.g. ToolMetadata-only construction).","commonSituations":"Factory code that conditionally supplies fn (if cond: fn = ...) and misses the else branch; deserializing tools from JSON where the function pointer could not be re-imported; refactor renaming the fn parameter; confusing FunctionTool with FunctionTool.from_defaults, which auto-creates metadata.","solutions":["Pass a callable: FunctionTool(fn=my_func, metadata=ToolMetadata(...)).","For async-only tools, pass async_fn=my_async_func.","If constructing from metadata alone, use the appropriate tool class (e.g. a custom Tool subclass) or provide a no-op fn intentionally.","Debug factory code: assert fn is not None or async_fn is not None before constructing."],"exampleFix":"# before\nfn = None\nif config.enabled: fn = my_func  # else-branch forgotten\ntool = FunctionTool(fn=fn, metadata=md)  # ValueError\n\n# after\nfrom llama_index.core.tools import FunctionTool, ToolMetadata\nfn = my_func if config.enabled else fallback_func\ntool = FunctionTool(fn=fn, metadata=ToolMetadata(name='my', description='...'))","handlingStrategy":"validation","validationCode":"if fn is None and async_fn is None:\n    raise ValueError('refusing to build FunctionTool without a callable')\ntool = FunctionTool(fn=fn, async_fn=async_fn, metadata=md)","typeGuard":"from typing import Callable, Optional\n\ndef has_callable(fn: Optional[Callable], async_fn: Optional[Callable]) -> bool:\n    return fn is not None or async_fn is not None","tryCatchPattern":null,"preventionTips":["Construct tools via FunctionTool.from_defaults where possible.","Assert callable presence in factory loops before construction.","Verify config-driven tool descriptors resolved to importable functions."],"tags":["tools","function-tool","validation","llama-index"],"backgroundTag":null,"analyzedSha":"afd0fef371831f9bda13e5af7167cf4e981278ab","analyzedAt":"2026-08-15T05:42:58.429Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}