Skip to content

fix: prevent llm fallback retries after output - #7233

Open
chenghao-mou wants to merge 4 commits into
mainfrom
chenghao/fix/AGT-3492-python-llm-fallback-retries
Open

fix: prevent llm fallback retries after output#7233
chenghao-mou wants to merge 4 commits into
mainfrom
chenghao/fix/AGT-3492-python-llm-fallback-retries

Conversation

@chenghao-mou

@chenghao-mou chenghao-mou commented Sep 11, 2026

Copy link
Copy Markdown
Member

Child and outer retries could replay text or tool calls after provider output. Both now use the shared retry guard when retry_on_chunk_sent=False. Preserve pre-output, metadata-only, and explicit opt-in retries.

Addresses AGT-3492. Builds on @dtran26's diagnosis and reproducers in agents-js#2477 and the outer-retry fix in agents-js#2480.

Validated: 57 fallback tests and make check; 2,933 broader tests passed before the final cleanup. Excluded baseline Google credential and missing livekit-server failures, reproduced on main.

Initial prompt and agent context

Model: GPT-6

Fix the Python LLM fallback adapter’s outer retry bug in livekit/agents.

We investigated livekit/agents-js#2477 and confirmed that Python shares one part of the bug: when a provider emits text or tool calls, then raises a retryable APIError, retry_on_chunk_sent=False stops provider fallback but does not stop LLMStream’s outer retry loop. Generation restarts and appends duplicate output.

Verified Python revision: 4de6232. Fetch current main and check whether this still applies.

Relevant paths:

  • livekit-agents/livekit/agents/llm/fallback_adapter.py: _run re-raises the original error after output without changing retryability.
  • livekit-agents/livekit/agents/llm/llm.py: _main_task retries that error.
  • AgentSession passes connection options with max_retry=3 by default. Direct adapter.chat() defaults to zero outer retries.

A controlled provider reproduces this without credentials or network calls:

  1. First request emits “The answer.”, then raises APITimeoutError(retryable=True).
  2. Next request succeeds with “The answer.”
  3. With retry_on_chunk_sent=False and outer max_retry=3, output becomes “The answer.The answer.”
    Tool-call emissions duplicate through the same path.

Python already isolates errors by stream, accepts successful child retries, and cancels active children correctly. Those JS-specific failures do not need porting. Python’s Inference provider marks errors non-retryable after output, but custom providers can expose the adapter bug.

Please:

  • Find or create a Linear ticket before creating the branch.
  • Reproduce the bug, then make the smallest coherent fix.
  • Cover text and tool-call replay, preserve pre-output retries and metadata-only behavior, and preserve explicit retry_on_chunk_sent=True.
  • Run relevant tests and required checks.
  • Leave unrelated working-tree changes untouched.

Local investigation:

  • /tmp/agents-js-2477-investigation/python-parity.md
  • /tmp/agents-js-2477-investigation/python_parity.py
    The harness asserts the observed bug; update expectations for regression tests.

Related Node fix: livekit/agents-js#2480
Original diagnosis and reproducer: livekit/agents-js#2477 by dtran26. Preserve contributor credit if that work shapes the fix.

Do not post comments, replies, or resolve threads on my behalf.

let's create a draft PR.

Mark post-output API errors non-retryable when retry_on_chunk_sent is false,
so the outer LLMStream retry loop cannot replay text or tool calls.
Preserve retries before output and explicit opt-in retries after output.

Addresses AGT-3492
Based on the diagnosis and reproducers in livekit/agents-js#2477.
Related fix: livekit/agents-js#2480

Co-authored-by: Dan Tran <7690403+dtran26@users.noreply.github.com>
@chenghao-mou
chenghao-mou marked this pull request as ready for review September 11, 2026 14:26
@chenghao-mou
chenghao-mou requested a review from a team as a code owner September 11, 2026 14:26
devin-ai-integration[bot]

This comment was marked as resolved.

Track text and tool-call output when each chunk enters the stream channel,
before a provider can raise and retry within the same event-loop turn.
Apply the fallback adapter's retry policy to foreground child streams.
Preserve metadata-only retries, recovery probes, and explicit opt-in retries.

Addresses AGT-3492
Fixes Devin's "Child retries still replay output" finding on e295c64.
Apply the output retry policy to the fallback stream so both retry loops
use LLMStream's guard. Remove the adapter's duplicate error assignment.
Keep the fallback retry policy and main's caller-span initialization.
chenghao-mou added a commit to livekit/agents-js that referenced this pull request Sep 14, 2026
Apply the fallback output policy to foreground child streams and the outer
stream. Record output at enqueue time, then mark the original APIError
non-retryable in the shared retry guard. Preserve pre-output, recovery,
direct provider, and explicit opt-in retries.

Port the retry guard from livekit/agents#7233 at 50a5924b7e0e.
Addresses AGT-3492
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant