Skip to content

Python: [Bug]: Streamed code interpreter deltas get silently dropped when one chunk's text happens to start with another's #8956

Closed

Description

Description

Streaming a code_interpreter_tool_call (or code_interpreter_tool_result) in small chunks can silently drop one of the chunks instead of concatenating them, corrupting the code the model generated.

_merge_content_item_lists in agent_framework/_types.py is what ChatResponse.from_updates / AgentResponse.from_updates use to fold streamed inputs/outputs chunks for a code interpreter call together. It has to tell apart two situations: a provider sending a genuine incremental delta (concatenate it) versus a provider resending the complete value after streaming finishes (replace the accumulated text with it) — the OpenAI Responses API does the latter via its code_interpreter_call_code.done event, which repeats the full code once after a run of .delta events for the same call.

It tells these apart by checking whether one chunk's text is a string-prefix of the other (incoming_text.startswith(existing_text) / existing_text.startswith(incoming_text)), and if so assumes it's the full-resend case and throws the other one away. That's a guess, and it's wrong whenever two ordinary, unrelated deltas simply happen to line up that way — which isn't rare for code: repeated indentation, a closing paren arriving right after an opening one, a one-digit number followed by a longer one that starts with the same digit, etc. Nothing raises in this case; the earlier (or later) chunk is just quietly dropped.

Expected behavior

Two genuine streamed deltas for the same call should always concatenate in arrival order. Only a chunk that is actually a full resend should replace what's been accumulated so far.

Actual behavior

from agent_framework import ChatResponse, ChatResponseUpdate, Content

updates = [
    ChatResponseUpdate(
        contents=[Content.from_code_interpreter_tool_call(call_id="ci_1", inputs=[Content.from_text(text="(")])]
    ),
    ChatResponseUpdate(
        contents=[Content.from_code_interpreter_tool_call(call_id="ci_1", inputs=[Content.from_text(text="()")])]
    ),
]
resp = ChatResponse.from_updates(updates)
print("".join(i.text or "" for i in resp.messages[0].contents[0].inputs))

prints (), not ((). The first delta is discarded outright because "()" happens to start with "(". Swap the order ("()" then "(") and it's the opposite chunk that disappears, again leaving just ().

Code Sample

from agent_framework import ChatResponse, ChatResponseUpdate, Content

# A new line's leading indentation (4 spaces) happens to be a prefix of the next
# delta (the same 4 spaces followed by the line's actual content).
deltas = ["    ", "    print(i)\n"]
updates = [
    ChatResponseUpdate(
        contents=[Content.from_code_interpreter_tool_call(call_id="ci_1", inputs=[Content.from_text(text=d)])]
    )
    for d in deltas
]
resp = ChatResponse.from_updates(updates)
print(repr("".join(i.text or "" for i in resp.messages[0].contents[0].inputs)))
# prints '    print(i)\n' - expected '        print(i)\n' (both deltas concatenated)

Error Messages / Stack Traces

No exception is raised. The code the agent believes it ran (and may show the user, or re-send on the next turn) is silently truncated or duplicated.

Package Versions

agent-framework-core: 1.19.0, agent-framework-openai: 1.14.4

Python Version

Python 3.12

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 1, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Oct 1, 2026
  3. github-actions commented on Oct 1, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    The bug occurs in python/packages/core/agent_framework/_types.py::_merge_content_item_lists at lines 2343-2346 when consecutive code-interpreter text deltas have a prefix relationship. It affects both inputs and outputs for calls/results finalized by ChatResponse.from_updates or AgentResponse.from_updates. Minimal repro: aggregate one call ID with input deltas "(" then "()" and assert the resulting text is "(()"; the current result is "()".

    • Failing test: python/packages/core/tests/core/test_types.py::test_code_interpreter_tool_call_prefix_deltas_are_concatenated
    • Files examined: python/packages/core/agent_framework/_types.py, python/packages/core/tests/core/test_types.py, python/packages/core/pyproject.toml
    • Tests run: test_code_interpreter_tool_call_prefix_deltas_are_concatenated (failed), test_code_interpreter_tool_call_content_parses_inputs (passed), test_code_interpreter_tool_result_content_outputs (passed)
    • Reported version: agent-framework-core 1.19.0, agent-framework-openai 1.14.4
    • Current version: agent-framework-core 1.19.0
  4. added
    agentsUsage: [Issues, PRs], Target: Single agent
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 1, 2026
  5. cr-sbarbouche commented on Oct 27, 2026

    @cr-sbarbouche
    ContributorAuthor

    Closing this out - the underlying issue is fixed, just via a different (and better) mechanism than the one proposed in #8957.

    ADR 0044 / the content-identity-and-protocol-metadata spec added a first-class merge_mode: Literal["append", "replace"] field on Content (covering code_interpreter_tool_call/code_interpreter_tool_result among other types) and OpenAIChatClient now tags code_interpreter_call_code.delta as append and code_interpreter_call_code.done as replace. That removes the text-prefix guessing entirely, which is what was actually causing the drops here. Verified against current main:

    from agent_framework import ChatResponse, ChatResponseUpdate, Content
    
    updates = [
        ChatResponseUpdate(contents=[Content.from_code_interpreter_tool_call(call_id="ci_1", inputs=[Content.from_text(text="(")], merge_mode="append")]),
        ChatResponseUpdate(contents=[Content.from_code_interpreter_tool_call(call_id="ci_1", inputs=[Content.from_text(text="()")], merge_mode="append")]),
    ]
    resp = ChatResponse.from_updates(updates)
    print("".join(i.text or "" for i in resp.messages[0].contents[0].inputs))  # '(()' - correct

    and through the real client with a realistic indentation-collision delta pair, both orders, with the same correct result.

    #8957 is closed since it predates this and is no longer the right diff to land, but while working on it I found a separate, still-open bug in the same area (code interpreter streaming) - opening that as its own issue shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions