From be37dee88afdfba153845e9b20890a23e0168476 Mon Sep 17 00:00:00 2001 From: rmotgi1227 Date: Mon, 11 May 2026 14:51:28 -0700 Subject: [PATCH 1/2] fix(streaming): handle duplicate-index list entries in accumulate_delta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the very first streaming chunk for a key contains a list value (e.g. tool_calls), the early-return branches in accumulate_delta copied the raw list wholesale instead of routing it through the index-based merge path. This caused duplicate-index entries in that first chunk to be stored as separate list elements rather than merged, leaving orphaned entries that subsequent chunks could not reach. Fix: when key is absent or acc_value is None and delta_value is a list, initialize acc[key] to [] and fall through to the existing list-merge logic so that every chunk — including the first — goes through proper index deduplication. Fixes #3203, see also #3201. --- src/openai/lib/streaming/_deltas.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/openai/lib/streaming/_deltas.py b/src/openai/lib/streaming/_deltas.py index a5e1317612..7e46545b17 100644 --- a/src/openai/lib/streaming/_deltas.py +++ b/src/openai/lib/streaming/_deltas.py @@ -6,13 +6,24 @@ def accumulate_delta(acc: dict[object, object], delta: dict[object, object]) -> dict[object, object]: for key, delta_value in delta.items(): if key not in acc: - acc[key] = delta_value - continue + if is_list(delta_value): + # Initialize an empty list so that list-merge logic below + # handles duplicate-index entries even on the very first chunk. + acc[key] = [] + else: + acc[key] = delta_value + continue acc_value = acc[key] if acc_value is None: - acc[key] = delta_value - continue + if is_list(delta_value): + # Same as above: route list deltas through the merge path + # instead of copying the raw array wholesale. + acc[key] = [] + acc_value = acc[key] + else: + acc[key] = delta_value + continue # the `index` property is used in arrays of objects so it should # not be accumulated like other values e.g. From 6cfb5f65fc727785c8e15e6621147f60f86944f5 Mon Sep 17 00:00:00 2001 From: rmotgi1227 Date: Mon, 11 May 2026 14:59:44 -0700 Subject: [PATCH 2/2] fix: guard extend() path against empty acc_value list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python's all() returns True on an empty sequence, so initializing acc[key] = [] and then hitting the `all(isinstance(...) for x in acc_value)` check would evaluate to True and call extend() — dumping the entire raw list in wholesale, same bug as before. Adding `acc_value and` before the all() check means an empty list skips the extend shortcut and falls through to the per-index merge loop instead. --- src/openai/lib/streaming/_deltas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openai/lib/streaming/_deltas.py b/src/openai/lib/streaming/_deltas.py index 7e46545b17..b56666770e 100644 --- a/src/openai/lib/streaming/_deltas.py +++ b/src/openai/lib/streaming/_deltas.py @@ -44,7 +44,7 @@ def accumulate_delta(acc: dict[object, object], delta: dict[object, object]) -> elif is_list(acc_value) and is_list(delta_value): # for lists of non-dictionary items we'll only ever get new entries # in the array, existing entries will never be changed - if all(isinstance(x, (str, int, float)) for x in acc_value): + if acc_value and all(isinstance(x, (str, int, float)) for x in acc_value): acc_value.extend(delta_value) continue