From eabef5125b1109cc50dbef8cde78b4b9603fae62 Mon Sep 17 00:00:00 2001 From: Tai An Date: Tue, 21 Apr 2026 12:28:10 -0700 Subject: [PATCH] =?UTF-8?q?perf:=20replace=20O(n=C2=B2)=20accumulated=5Fjs?= =?UTF-8?q?on=20with=20list=20+=20completeness=20heuristic=20in=20chat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- litellm/llms/anthropic/chat/handler.py | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index f8e61d0166a..d58cdab8411 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -543,7 +543,7 @@ class ModelResponseIterator: # For handling partial JSON chunks from fragmentation # See: https://github.com/BerriAI/litellm/issues/17473 - self.accumulated_json: str = "" + self.accumulated_json_chunks: list = [] self.chunk_type: Literal["valid_json", "accumulated_json"] = "valid_json" # Track current content block type to avoid emitting tool calls for non-tool blocks @@ -1100,16 +1100,16 @@ class ModelResponseIterator: Returns: ModelResponseStream if JSON is complete, None if still accumulating """ - # Accumulate JSON data - self.accumulated_json += data_str - - # Try to parse the accumulated JSON + self.accumulated_json_chunks.append(data_str) + _stripped = data_str.rstrip() + if not _stripped or _stripped[-1] not in ('}', ']'): + return None + _full_json = "".join(self.accumulated_json_chunks) try: - data_json = json.loads(self.accumulated_json) - self.accumulated_json = "" # Reset after successful parsing + data_json = json.loads(_full_json) + self.accumulated_json_chunks = [] return self.chunk_parser(chunk=data_json) except json.JSONDecodeError: - # If it's not valid JSON yet, continue to the next chunk return None def _parse_sse_data(self, str_line: str) -> Optional[ModelResponseStream]: @@ -1147,10 +1147,11 @@ class ModelResponseIterator: chunk = self.response_iterator.__next__() except StopIteration: # If we have accumulated JSON when stream ends, try to parse it - if self.accumulated_json: + if self.accumulated_json_chunks: + _full_json = "".join(self.accumulated_json_chunks) try: - data_json = json.loads(self.accumulated_json) - self.accumulated_json = "" + data_json = json.loads(_full_json) + self.accumulated_json_chunks = [] return self.chunk_parser(chunk=data_json) except json.JSONDecodeError: pass @@ -1198,10 +1199,11 @@ class ModelResponseIterator: chunk = await self.async_response_iterator.__anext__() except StopAsyncIteration: # If we have accumulated JSON when stream ends, try to parse it - if self.accumulated_json: + if self.accumulated_json_chunks: + _full_json = "".join(self.accumulated_json_chunks) try: - data_json = json.loads(self.accumulated_json) - self.accumulated_json = "" + data_json = json.loads(_full_json) + self.accumulated_json_chunks = [] return self.chunk_parser(chunk=data_json) except json.JSONDecodeError: pass