run-llama/llama_index · error · ValueError

llm must be a function calling LLM to use handoff

Error message

llm must be a function calling LLM to use handoff

What it means

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.

Source

Thrown at llama-index-core/llama_index/core/agent/workflow/codeact_agent.py:204

            signature = inspect.signature(fn)
            fn_name: str = fn.__name__
            docstring: Optional[str] = inspect.getdoc(fn)

            tool_description = f"def {fn_name}{signature!s}:"
            if docstring:
                tool_description += f'\n  """\n{docstring}\n  """\n'

            tool_description += "\n  ...\n"
            tool_descriptions.append(tool_description)

        return "\n\n".join(tool_descriptions)

    async def _get_response(
        self, current_llm_input: List[ChatMessage], tools: Sequence[BaseTool]
    ) -> ChatResponse:
        if any(tool.metadata.name == "handoff" for tool in tools):
            if not isinstance(self.llm, FunctionCallingLLM):
                raise ValueError("llm must be a function calling LLM to use handoff")

            tools = [tool for tool in tools if tool.metadata.name == "handoff"]
            return await self.llm.achat_with_tools(
                tools=tools, chat_history=current_llm_input
            )
        else:
            return await self.llm.achat(current_llm_input)

    async def _get_streaming_response(
        self,
        ctx: AgentContext,
        current_llm_input: List[ChatMessage],
        tools: Sequence[BaseTool],
    ) -> Tuple[ChatResponse, str]:
        if any(tool.metadata.name == "handoff" for tool in tools):
            if not isinstance(self.llm, FunctionCallingLLM):
                raise ValueError("llm must be a function calling LLM to use handoff")

View on GitHub (pinned to afd0fef371)

Solutions

  1. Run the tool inside AgentWorkflow/Workflow so Context flows in automatically.
  2. For direct calls, create and pass Context: await tool.acall(query='hi', ctx=Context(workflow)).
  3. Drop the ctx parameter if the tool is pure/stateless.
  4. Check tool.requires_context and tool.ctx_param_name before manual invocation.

Example fix

# before
out = await tool.acall(query="hi")  # ValueError: Context is required for this tool

# after
from llama_index.core.workflow.context import Context
ctx = Context(workflow=MyWorkflow())
out = await tool.acall(query="hi", ctx=ctx)
Defensive patterns

Strategy: validation

Validate before calling

if tool.requires_context and tool.ctx_param_name not in kwargs:
    kwargs[tool.ctx_param_name] = ctx
await tool.acall(**kwargs)

Type guard

def tool_needs_context(tool) -> bool:
    return bool(getattr(tool, "requires_context", False))

Try / catch

try:
    out = await tool.acall(**kwargs)
except ValueError as e:
    if "Context is required" in str(e):
        out = await tool.acall(**kwargs, **{tool.ctx_param_name: make_context()})
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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