fix(google_genai): preserve raw bytes in Gemini streamGenerateContent SSE framing - #30602
Draft
mateo-berri wants to merge 4 commits into
Draft
fix(google_genai): preserve raw bytes in Gemini streamGenerateContent SSE framing#30602mateo-berri wants to merge 4 commits into
mateo-berri wants to merge 4 commits into
Conversation
… SSE framing The Gemini generate-content streaming iterators framed events with httpx aiter_lines/iter_lines, which split on str.splitlines boundaries. That set includes U+2028, U+2029, U+0085 and form feed, characters Gemini emits raw inside data: JSON for thinking and text content. A single SSE event therefore got sliced mid-payload and rejoined with a newline, producing invalid JSON and google.genai.errors.UnknownApiResponseError "Failed to parse response as JSON". The failure was intermittent because it only triggered when the response happened to carry one of those separators, which correlates with large thinking responses. Buffer the raw byte stream and split only on real SSE frame delimiters (\r\n\r\n, \n\n, \r\r). This keeps payload bytes intact regardless of content and still reassembles large inlineData blobs across chunk boundaries, the case the previous aiter_lines change was meant to fix. Fixes LIT-3775
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Add sync and async regression tests for a final event that arrives without a trailing SSE delimiter, exercising the end-of-stream flush so the last event is never dropped.
…ponse Drops 4 reportUnknown* basedpyright findings that the bytes-buffering rewrite would otherwise leak from the untyped response parameter.
…itellm_lit_3775_gemini_streaming_json # Conflicts: # any-discipline-budget.json
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 free
to 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.
Relevant issues
Fixes #30471 (and the related earlier report #28777)
Linear ticket
LIT-3775
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
make test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewScreenshots / Proof of Fix
Repro is the Google GenAI SDK pointed at the proxy's
streamGenerateContentpass-through, which is what the reporter used. Run a proxy against a real Vertex/Gemini model with thinking enabled so the response carries thought/text content (the trigger is any Unicode line separator such as U+2028, U+2029 or U+0085 emitted raw in thedata:JSON; large thinking responses make it likely).vertex_ai/gemini-3.1-pro-previewmodel onglobal)Before the fix some events fail to parse (
Failed to parse response as JSON); after the fix every line printsokType
🐛 Bug Fix
Changes
The Gemini generate-content streaming iterators in
litellm/google_genai/streaming_iterator.pyframed SSE events using httpxaiter_lines/iter_lines. Those methods split onstr.splitlinesboundaries, which go beyond\n/\rto include U+2028, U+2029, U+0085, form feed and friends. Gemini emits those characters raw inside thedata:JSON for thinking and text content, so a single SSE event was being sliced mid-payload and then rejoined with a\n, yielding invalid JSON andgoogle.genai.errors.UnknownApiResponseError. It looked intermittent because it only fired when the response happened to contain one of those separators, which correlates with larger thinking responses.The fix buffers the raw byte stream and splits only on real SSE frame delimiters (
\r\n\r\n,\n\n,\r\r). Payload bytes are now preserved regardless of content, and largeinlineDatablobs that span multiple HTTP chunks are still reassembled into a single event, which is the case the earlieraiter_lineschange (#30270) was meant to address; this keeps that behavior while removing the corruption.Tests in
tests/test_litellm/google_genai/test_google_genai_streaming_iterator.pynow mock byte iteration and add a regression that feeds an event whose JSON carries raw U+2028/U+2029/U+0085 split across small byte chunks, asserting one complete event that parses back to the original text. There is also coverage for reassembling chunk-splitinlineDataand for splitting multiple events out of one buffer. The two streaming mocks intests/unified_google_tests/test_google_ai_studio.pywere updated fromaiter_linestoaiter_bytesto match the new path.Generated by Claude Code