Skip to content

fix(inference): keep a stream retryable until it emits generation - #2219

Open
u9g wants to merge 1 commit into
mainfrom
fix/llm-retryable-content-only
Open

fix(inference): keep a stream retryable until it emits generation#2219
u9g wants to merge 1 commit into
mainfrom
fix/llm-retryable-content-only

Conversation

@u9g

@u9g u9g commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Port of livekit/agents#6697agents/src/inference/llm.ts is a line-for-line equivalent of the Python file and carries the identical bug.

What

retryable was cleared by any chunk reaching the caller, including chunks that carry no output — a usage block, or provider metadata such as the inference gateway's deployment/tier stamp or a Gemini thought signature.

The gateway stamps extra_content.livekit onto every delta it forwards, including the leading contentless one, and parseChoice returns a real ChatChunk for a delta whose only payload is extra_content (the if (!content && !deltaExtra) return undefined guard passes on deltaExtra). So every streamed response through the gateway went unretryable from its first chunk. Only failures landing before that first chunk could still recover.

Diagnosed from a Python simulation run, where a mid-stream stall failed the turn outright with nothing generated and nothing a retry could have duplicated. The JS path has had no reported incident, but the code is the same.

Change

retryable is now cleared on text or a tool call — the output a retry would actually repeat. The retryable = false on the usage chunk is dropped for the same reason: token counts aren't output either.

Tests

agents/src/inference/llm.test.ts, using the existing client.chat.completions.create stubbing pattern, with a stub that yields chunks then throws. The metadata chunk in the fixtures is the literal extra_content.livekit payload observed in production.

  • a stall after provider metadata alone retries (3 attempts at maxRetry: 2)
  • a stall after generated text does not retry (1 attempt)

Confirmed failing without the fix (expected 1 to be 3). Full suite: 1518 passed across 111 files; tsc --noEmit clean.

Not ported

Python also gained an httpx.TimeoutException clause, so a timeout waiting on the stream body is reported as APITimeoutError rather than APIConnectionError. The same structural gap exists here — OpenAI.APIConnectionTimeoutError only catches what the OpenAI client mapped — but I could not establish which concrete error class a mid-stream stall produces in Node, and there is no precedent in this repo to follow. A guessed class name would be a check that silently never matches, so it needs a reproduction first.

@u9g
u9g requested a review from a team as a code owner August 4, 2026 19:47
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ba1d5ba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Port of the python fix. retryable was cleared by any chunk reaching the
caller, including ones that carry no output: a usage block, or provider
metadata such as the gateway's deployment stamp or a Gemini thought
signature. Since the gateway stamps its leading (contentless) delta, every
streamed response went unretryable from its first chunk -- so a mid-stream
stall failed the turn outright rather than retrying, with nothing generated
and nothing for the retry to duplicate.

Clear retryable only on text or a tool call, the output a retry would
actually repeat.
@u9g
u9g force-pushed the fix/llm-retryable-content-only branch from eaf251f to ba1d5ba Compare August 7, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants