{"record":{"id":"c74b04d966700c89","repo":"run-llama/llama_index","slug":"llm-must-be-a-function-calling-llm-to-use-handoff","errorCode":null,"errorMessage":"llm must be a function calling LLM to use handoff","messagePattern":"llm must be a function calling LLM to use handoff","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"llama-index-core/llama_index/core/agent/workflow/codeact_agent.py","lineNumber":204,"sourceCode":"            signature = inspect.signature(fn)\n            fn_name: str = fn.__name__\n            docstring: Optional[str] = inspect.getdoc(fn)\n\n            tool_description = f\"def {fn_name}{signature!s}:\"\n            if docstring:\n                tool_description += f'\\n  \"\"\"\\n{docstring}\\n  \"\"\"\\n'\n\n            tool_description += \"\\n  ...\\n\"\n            tool_descriptions.append(tool_description)\n\n        return \"\\n\\n\".join(tool_descriptions)\n\n    async def _get_response(\n        self, current_llm_input: List[ChatMessage], tools: Sequence[BaseTool]\n    ) -> ChatResponse:\n        if any(tool.metadata.name == \"handoff\" for tool in tools):\n            if not isinstance(self.llm, FunctionCallingLLM):\n                raise ValueError(\"llm must be a function calling LLM to use handoff\")\n\n            tools = [tool for tool in tools if tool.metadata.name == \"handoff\"]\n            return await self.llm.achat_with_tools(\n                tools=tools, chat_history=current_llm_input\n            )\n        else:\n            return await self.llm.achat(current_llm_input)\n\n    async def _get_streaming_response(\n        self,\n        ctx: AgentContext,\n        current_llm_input: List[ChatMessage],\n        tools: Sequence[BaseTool],\n    ) -> Tuple[ChatResponse, str]:\n        if any(tool.metadata.name == \"handoff\" for tool in tools):\n            if not isinstance(self.llm, FunctionCallingLLM):\n                raise ValueError(\"llm must be a function calling LLM to use handoff\")\n","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/run-llama/llama_index/blob/afd0fef371831f9bda13e5af7167cf4e981278ab/llama-index-core/llama_index/core/agent/workflow/codeact_agent.py#L186-L222","documentation":"Async twin of the Context check: FunctionTool.acall raises when the tool's function requires a Context parameter but ctx_param_name is not present in the merged kwargs before awaiting self._async_fn. The Context must be provided by the async agent/workflow runtime or passed manually in tests.","triggerScenarios":"Awaiting tool.acall('question') on a tool whose fn/async_fn signature includes ctx: Context; running tools through a custom async loop that doesn't inject Context; calling acall with only the LLM-provided arguments.","commonSituations":"Unit-testing async context tools without a workflow; custom agent orchestration bypassing AgentWorkflow's ctx injection; migrating sync agents to workflows where ctx threading became mandatory.","solutions":["Run the tool inside AgentWorkflow/Workflow so Context flows in automatically.","For direct calls, create and pass Context: await tool.acall(query='hi', ctx=Context(workflow)).","Drop the ctx parameter if the tool is pure/stateless.","Check tool.requires_context and tool.ctx_param_name before manual invocation."],"exampleFix":"# before\nout = await tool.acall(query=\"hi\")  # ValueError: Context is required for this tool\n\n# after\nfrom llama_index.core.workflow.context import Context\nctx = Context(workflow=MyWorkflow())\nout = await tool.acall(query=\"hi\", ctx=ctx)","handlingStrategy":"validation","validationCode":"if tool.requires_context and tool.ctx_param_name not in kwargs:\n    kwargs[tool.ctx_param_name] = ctx\nawait tool.acall(**kwargs)","typeGuard":"def tool_needs_context(tool) -> bool:\n    return bool(getattr(tool, \"requires_context\", False))","tryCatchPattern":"try:\n    out = await tool.acall(**kwargs)\nexcept ValueError as e:\n    if \"Context is required\" in str(e):\n        out = await tool.acall(**kwargs, **{tool.ctx_param_name: make_context()})\n    else:\n        raise","preventionTips":["Use AgentWorkflow to invoke async context tools.","Create Context(workflow=...) for direct acall tests.","Remove ctx params from stateless tools."],"tags":["tools","async","context","workflows"],"backgroundTag":null,"analyzedSha":"afd0fef371831f9bda13e5af7167cf4e981278ab","analyzedAt":"2026-08-15T05:42:58.429Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}