Skip to content

RuntimeWarning: coroutine 'LLMService._run_function_call' was never awaited when a function call is cancelled by user-turn-start interruption race (distinct from #3588) #4339

@elliottventures

Description

@elliottventures

pipecat version

1.0.0 (from pypi)

Python version

3.14.4

Operating System

macOS (Apple Silicon)

Issue description

A RuntimeWarning fires — and more importantly the Gemini Live session stalls for ~10 seconds — when a function call dispatched by GeminiLiveLLMService is cancelled by an interruption broadcast originating from TranscriptionUserTurnStartStrategy.

This is distinct from #3588:

#3588's fix (a finally: cancel_task(timeout_task) block) targets the inner timeout handler; it does not address this outer-coroutine leak on interruption cancellation.

The warning is a symptom. The substantive problem is that Gemini Live never receives the function-call result, and its session sits idle producing zero completion tokens for ~10 seconds until the next user utterance triggers a server-side VAD interruption and unsticks it. For an agent that performs many tool calls in a conversation, this is a meaningful UX regression, not cosmetic.

Reproduction steps

  1. Use examples/realtime/realtime-gemini-live-function-calling.py with GeminiLiveLLMService (not Vertex), Daily transport in direct mode (-t daily -d).
  2. Speak a question that fires a registered function (e.g. "What's the weather in Tokyo?"). The very first turn of a fresh session reliably triggers the race; later turns hit it occasionally.
  3. Observe:
    • _handle_msg_input_transcription logs the user text
    • _on_user_turn_started fires broadcast_interruption
    • _run_function_call starts
    • _cancel_function_call fires within ~1 ms
    • RuntimeWarning: coroutine 'LLMService._run_function_call' was never awaited is emitted
    • The next 10+ seconds show TTFB: 10.421s, prompt tokens: 0, completion tokens: 0 — Gemini produces nothing because it's still waiting for the cancelled tool result
    • Only the next user utterance's Gemini-VAD interruption signal unblocks the session

Expected behavior

The function call should either:

  • Not be cancelled by a same-turn interruption broadcast (the tool was requested by the current turn; interrupting the current turn shouldn't kill it), or
  • If cancellation is required, its outer coroutine should be awaited/cancelled cleanly and Gemini should be notified so it doesn't stall waiting for an undelivered result.

No RuntimeWarning in either case.

Actual behavior

  • RuntimeWarning: coroutine 'LLMService._run_function_call' was never awaited
  • Session stalls for ~10 seconds (0 completion tokens, TTFB: 10.421s)
  • Stalled state only clears when the next user utterance triggers a fresh VAD interruption

Logs (trimmed to the critical window)

18:15:28.682 [Transcription:user] [Oh, what's the weather in Tokyo?]
18:15:28.683 _on_user_turn_started (TranscriptionUserTurnStartStrategy#0)
18:15:28.683 broadcast_interruption
18:15:28.687 _on_user_turn_stopped (TurnAnalyzerUserTurnStopStrategy#0)
18:15:28.689 Cancelling function call [get_current_weather:fc_13054767619604724024]...
18:15:28.690 Function call [get_current_weather:...] has been cancelled
/path/to/python3.14/asyncio/events.py:94: RuntimeWarning:
  coroutine 'LLMService._run_function_call' was never awaited
18:15:28.696 FunctionCallsStartedFrame: ['get_current_weather:...']
18:15:28.697 FunctionCallCancelFrame: [get_current_weather:...]
18:15:39.110 Gemini VAD: interrupted signal received
18:15:39.111 broadcast_interruption (GeminiLiveLLMService#0)
18:15:39.111 TTFB: 10.421s
18:15:39.112 prompt tokens: 0, completion tokens: 0
18:15:41.925 [Transcription:user] [Recommend a restaurant in Paris.]

Potential direction (not a fix, just a hypothesis)

LLMUserAggregator broadcasts interruption on _on_user_turn_started without regard for whether the current turn's LLM response is emitting a tool call. An interruption that cancels a just-fired tool call from the same turn is arguably wrong — the tool was requested by this turn, not a stale prior one.

Options worth considering:

  1. Suppress interruption-driven cancellation for function calls emitted within the same turn window as the interruption.
  2. Still cancel, but send a synthetic error result back to Gemini so it doesn't stall waiting.
  3. Ensure the outer _run_function_call coroutine is properly awaited on the cancellation path so the warning at minimum goes away.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions