Repository navigation
perf(vertex/gemini): eliminate O(n²) json.loads on accumulated streaming chunks (#26181) #29296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3263,7 +3263,12 @@ def __init__( | |
|
|
||
| self.streaming_response = streaming_response | ||
| self.chunk_type: Literal["valid_json", "accumulated_json"] = "valid_json" | ||
| self.accumulated_json = "" | ||
| # Buffer SSE shards as a list to avoid O(n²) string concatenation when | ||
| # Gemini emits multi-MB tool-call payloads as many small chunks. The | ||
| # parser only joins+json.loads when the trailing brace/bracket suggests | ||
| # a complete envelope, instead of re-parsing the growing buffer on every | ||
| # chunk and freezing the event loop. | ||
| self.accumulated_json_chunks: list = [] | ||
| self.sent_first_chunk = False | ||
| self.logging_obj = logging_obj | ||
| self.response_headers = response_headers or {} | ||
|
|
@@ -3486,16 +3491,22 @@ def handle_accumulated_json_chunk( | |
| chunk = litellm.CustomStreamWrapper._strip_sse_data_from_chunk(chunk) or "" | ||
| message = chunk.replace("\n\n", "") | ||
|
|
||
| # Accumulate JSON data | ||
| self.accumulated_json += message | ||
|
|
||
| # Try to parse the accumulated JSON | ||
| self.accumulated_json_chunks.append(message) | ||
| # Cheap completeness heuristic: only join + json.loads when the latest | ||
| # shard looks like it might have closed the envelope. Avoids parsing | ||
| # the growing buffer on every chunk -- the empty-string flush from | ||
| # __next__/__anext__ at StopIteration still forces a final attempt. | ||
| _stripped = message.rstrip() | ||
| if _stripped and _stripped[-1] not in ("}", "]"): | ||
| return None | ||
| _full_json = "".join(self.accumulated_json_chunks) | ||
| if not _full_json: | ||
| return None | ||
| try: | ||
| _data = json.loads(self.accumulated_json) | ||
| self.accumulated_json = "" # reset after successful parsing | ||
| _data = json.loads(_full_json) | ||
| self.accumulated_json_chunks = [] | ||
| return self.chunk_parser(chunk=_data) | ||
| except json.JSONDecodeError: | ||
| # If it's not valid JSON yet, continue to the next event | ||
| return None | ||
|
|
||
| def _common_chunk_parsing_logic( | ||
|
|
@@ -3522,7 +3533,7 @@ def __next__(self): | |
| try: | ||
| chunk = self.response_iterator.__next__() | ||
| except StopIteration: | ||
| if self.chunk_type == "accumulated_json" and self.accumulated_json: | ||
| if self.chunk_type == "accumulated_json" and self.accumulated_json_chunks: | ||
| return self.handle_accumulated_json_chunk(chunk="") | ||
| raise StopIteration | ||
|
Comment on lines
+3536
to
3538
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| except ValueError as e: | ||
|
|
@@ -3544,7 +3555,7 @@ async def __anext__(self): | |
| try: | ||
| chunk = await self.async_response_iterator.__anext__() | ||
| except StopAsyncIteration: | ||
| if self.chunk_type == "accumulated_json" and self.accumulated_json: | ||
| if self.chunk_type == "accumulated_json" and self.accumulated_json_chunks: | ||
| return self.handle_accumulated_json_chunk(chunk="") | ||
| raise StopAsyncIteration | ||
| except ValueError as e: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
listis valid but imprecise — the list holdsstrelements only. Usinglist[str]makes the contract explicit for static analysis and readers.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!