Uh oh!
There was an error while loading. Please reload this page.
fix(inference): keep a stream retryable until it emits generation - #6697
Open
u9g wants to merge 2 commits into
Open
fix(inference): keep a stream retryable until it emits generation#6697u9g wants to merge 2 commits into
u9g wants to merge 2 commits into
Conversation
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.
…ection error httpx raises ReadTimeout while the stream body is being consumed, outside the openai client's error mapping, so it fell through to the catch-all and was reported as APIConnectionError.
toubatbrian
approved these changes
Aug 4, 2026
longcw
approved these changes
Aug 5, 2026
| chat_chunk = self._parse_choice(chunk.id, choice, thinking_filter) | ||
| if chat_chunk is not None: | ||
| retryable = False | ||
| if _carries_generation(chat_chunk): |
Contributor
There was a problem hiding this comment.
nit: _carries_generation can be an inline condition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
retryablewas 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.livekitonto every delta it forwards, including the leading contentless one, and_parse_choicereturns a realChatChunkfor a delta whose only payload isextra_content. So every streamed response through the gateway went unretryable from its first chunk. Only failures landing before that first chunk could still recover.The consequence, from a simulation run: a mid-stream stall past the inter-chunk timeout failed the turn outright, with
partial=''— nothing generated, nothing a retry could have duplicated, and the caller had heard nothing. Two other turns in the same run stalled identically but before their first chunk, retried, and recovered silently (their TTFTs decompose exactly as10.0s timeout + 0.1s first-retry interval + a fast second attempt, against a p50 of 0.44s).Change
retryableis now cleared on text or a tool call — the output a retry would actually repeat. Two commits:retryableguard itself. Also drops theretryable = Falseon the usage chunk: token counts aren't output either. In practice that line rarely mattered (withinclude_usage, usage arrives last, after content has already cleared the flag), but it's the same defect.httpx.TimeoutExceptionclause. Only the request call runs inside the openai client's error mapping, so a timeout waiting on the stream body arrived as a raw httpx exception, fell through to the catch-all, and was reported asAPIConnectionError— which is what made the original failure read as a connection problem.APITimeoutErrorsubclassesAPIConnectionError, so no behavior changes; errors are just named correctly.Tests
tests/test_inference_llm_retry.py— hermetic, via a mock transport that yields SSE chunks then stalls. The metadata chunk in the fixtures is the literalextra_content.livekitpayload observed in production.max_retry=2)APITimeoutErrorEach confirmed failing without its corresponding fix. Full
--unitsuite: 1737 passed.Related
A gateway-side stopgap and a TypeScript port are going up alongside this; I'll cross-link them here once they exist.