Skip to content

Voice path silently drops unknown function calls instead of returning error to LLM #4933

@chasleslr

Description

@chasleslr

Describe the bug

When the LLM calls a function that isn't registered in the tool context, the voice path in generation.py silently skips the function call with continue, without adding an error result to tool_output.output. This causes the agent to stop responding entirely, leaving the user waiting indefinitely with no feedback.

This contrasts with llm/utils.py which correctly returns a FunctionCallResult with is_error=True that feeds back to the LLM.

To Reproduce

  1. Create an agent with tools registered
  2. Have instructions that reference a tool name that isn't actually registered (e.g., typo, or conditional tool that wasn't included)
  3. When the LLM calls the unregistered tool, the agent silently stops without saying anything

Expected behavior

The agent should return an error result to the LLM (similar to llm/utils.py), allowing the LLM to recover gracefully - either by retrying with a different approach or informing the user.

Code Analysis

Current behavior in generation.py (lines ~488-495):

if (function_tool := tool_ctx.function_tools.get(fnc_call.name)) is None:
    logger.warning(
        f"unknown AI function `{fnc_call.name}`",
        extra={"function": fnc_call.name, "speech_id": speech_handle.id},
    )
    continue  # ← Silently skips, nothing added to tool_output.output

Better behavior in llm/utils.py (lines ~20-30):

if function_tool is None:
    logger.warning(f"unknown AI function `{tool_call.name}`")
    return FunctionCallResult(
        fnc_call=fnc_call,
        fnc_call_out=FunctionCallOutput(
            name=tool_call.name,
            call_id=tool_call.call_id,
            output=f"Unknown function: {tool_call.name}",
            is_error=True,  # ← This feeds back to the LLM!
        ),
        ...
    )

The downstream effect in agent_activity.py (line ~2182):

if len(tool_output.output) > 0:
    # Only triggers LLM retry if there are tool outputs
    # When unknown function is skipped, this block is never entered

Suggested fix

In generation.py, instead of continue, add an error output:

if (function_tool := tool_ctx.function_tools.get(fnc_call.name)) is None:
    logger.warning(...)
    _tool_completed(make_tool_output(
        fnc_call=fnc_call,
        output=f"Unknown function: {fnc_call.name}",
        exception=ValueError(f"Unknown function: {fnc_call.name}")
    ))
    continue

Environment

  • livekit-agents version: 1.4.2
  • Python version: 3.13

Impact

This is a significant UX issue - the user is left hanging with no response and no indication of what went wrong. It can happen when:

  • Tool registration is conditional and a code path misses adding a tool
  • Instructions reference tools that were renamed/removed
  • LLM hallucinates a tool name

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions