You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Package:agent-framework-openai (affects agent-framework-foundry, which inherits the parser) Versions checked: 1.10.0, 1.13.0, 1.14.3 (latest on PyPI) and main @ 6c3c58a4b2d8834ad3dce3d0b01ad10f7edaf352 — all affected File:python/packages/openai/agent_framework_openai/_chat_client.py, _parse_chunk_from_openai
Summary
When the hosted image_generation tool is used with stream=True and partial_images is not
requested (the default, 0), the generated image never reaches the AgentResponse. The final
render is delivered by the Responses API on response.output_item.done as a completed image_generation_call item whose result holds the base64 image, but the streaming parser has no
branch for that item. It only creates image_generation_tool_call / image_generation_tool_result
contents from response.image_generation_call.partial_image events, i.e. from progressive preview
frames. The non-streaming parser handles the completed item correctly, so streaming and
non-streaming are not at parity.
Consequences with the default tool configuration:
AgentResponse.messages contains no image content (only text_reasoning / text).
HistoryProviders persist a message with no reference to the image.
Any consumer built on the documented content types silently loses the image; the only way to get
it is to dig into response.raw_representation (which is also what the Foundry docs sample does).
Reproduction
importasynciofromagent_frameworkimportAgentfromagent_framework_foundryimportFoundryChatClientfromazure.identityimportAzureCliCredentialasyncdefmain():
client=FoundryChatClient(
project_endpoint="<project endpoint>",
model="gpt-5", # same on gpt-5.4, gpt-5.6-terracredential=AzureCliCredential(),
default_headers={"x-ms-oai-image-generation-deployment": "gpt-image-2"},
)
agent=Agent(
client=client,
instructions="You are a helpful assistant.",
tools=[client.get_image_generation_tool(model="gpt-image-2")], # no partial_images
)
stream=agent.run("generate an image of a black cat with yellow eyes", stream=True)
asyncforupdateinstream:
ev=update.raw_representation.raw_representationifgetattr(ev, "type", None) =="response.output_item.done"andev.item.type=="image_generation_call":
print("provider sent final image, base64 length:", len(ev.item.result)) # ~2,000,000final=awaitstream.get_final_response()
print([c.typeforminfinal.messagesforcinm.contents])
# observed: ['text_reasoning', 'text']# expected: [..., 'image_generation_tool_call', 'image_generation_tool_result', 'text']asyncio.run(main())
Raw SSE sequence observed from Foundry (identical on every model, no partial frames):
Passing partial_images=1 makes the image appear (via the partial_image handler), which confirms
the diagnosis, but it persists a preview frame rather than the final render and depends on the
service emitting previews.
Code
Non-streaming path maps the completed item (works):
logger.debug("Unparsed event of type: %s: %s", event.type, event)
response.output_item.added lists ImageGenerationCall in its comment of handled types but has no case for it either (line 3295).
Note the drop is silent: because the outer match does hit case "response.output_item.done", the
image item never reaches the case _: fallback, so not even the "Unparsed event of type ..." debug
line is emitted for it. With agent_framework.openai at DEBUG the only trace is the earlier output_item.added event being reported as unparsed:
_chat_client.py:_parse_chunk_from_openai - Unparsed event of type: response.output_item.added: ResponseOutputItemAddedEvent(item=ImageGenerationCall(id='ig_...', result=None, status='in_progress', ...
_chat_client.py:_parse_chunk_from_openai - Unparsed event of type: response.image_generation_call.in_progress: ...
_chat_client.py:_parse_chunk_from_openai - Unparsed event of type: response.image_generation_call.generating: ...
_chat_client.py:_parse_chunk_from_openai - Unparsed event of type: response.image_generation_call.completed: ...
(nothing for response.output_item.done, which carries the 2.5 MB result)
Origin
The streaming image handling was introduced by PR Python: Add tool call/result content types and update connectors and samples #2971 "Add tool call/result content types and
update connectors and samples" (merged 2026-01-08, 3f7ea350dc), in python/packages/core/agent_framework/openai/_responses_client.py. It added the partial_image
handler and the accompanying sample openai_responses_client_streaming_image_generation.py, which
sets partial_images, so the final-item case was never exercised.
Bisecting every commit of _chat_client.py since then: no revision has ever handled image_generation_call on response.output_item.done. This is a gap since the feature was added,
not a regression from a specific release. It surfaced for us in the second week of September 2026
because the Foundry Responses endpoint used to emit one partial_image frame even without partial_images (which the handler above picked up) and stopped doing so; the same code, same
dependency versions and same deployments that returned images up to 2026-09-08 return none now.
The documented contract (OpenAI image generation guide: partial_images 0 → only the final image,
delivered on the completed item) was never handled by the streaming parser.
Also verified: the stored response (responses.retrieve) returns the image item with "result": null, and no include value restores it, so the streamed output_item.done event is
the only moment the bytes are available. Dropping it there loses the image for good.
Proposed fix
Add an image_generation_call branch to the response.output_item.done handler mirroring the
non-streaming mapping (call + result content, result as a data: URI). Patch against main:
Package:
agent-framework-openai(affectsagent-framework-foundry, which inherits the parser)Versions checked: 1.10.0, 1.13.0, 1.14.3 (latest on PyPI) and
main@6c3c58a4b2d8834ad3dce3d0b01ad10f7edaf352— all affectedFile:
python/packages/openai/agent_framework_openai/_chat_client.py,_parse_chunk_from_openaiSummary
When the hosted
image_generationtool is used withstream=Trueandpartial_imagesis notrequested (the default,
0), the generated image never reaches theAgentResponse. The finalrender is delivered by the Responses API on
response.output_item.doneas a completedimage_generation_callitem whoseresultholds the base64 image, but the streaming parser has nobranch for that item. It only creates
image_generation_tool_call/image_generation_tool_resultcontents from
response.image_generation_call.partial_imageevents, i.e. from progressive previewframes. The non-streaming parser handles the completed item correctly, so streaming and
non-streaming are not at parity.
Consequences with the default tool configuration:
AgentResponse.messagescontains no image content (onlytext_reasoning/text).HistoryProviders persist a message with no reference to the image.it is to dig into
response.raw_representation(which is also what the Foundry docs sample does).Reproduction
Raw SSE sequence observed from Foundry (identical on every model, no partial frames):
Passing
partial_images=1makes the image appear (via thepartial_imagehandler), which confirmsthe diagnosis, but it persists a preview frame rather than the final render and depends on the
service emitting previews.
Code
Non-streaming path maps the completed item (works):
agent-framework/python/packages/openai/agent_framework_openai/_chat_client.py
Lines 2985 to 3007 in 6c3c58a
Streaming path only maps
partial_imageevents:agent-framework/python/packages/openai/agent_framework_openai/_chat_client.py
Lines 3441 to 3471 in 6c3c58a
Streaming
response.output_item.donehandlesreasoning,mcp_call,web_search_call,file_search_call, shell items,custom_tool_call,tool_search_call— but notimage_generation_call:agent-framework/python/packages/openai/agent_framework_openai/_chat_client.py
Lines 3568 to 3623 in 6c3c58a
response.output_item.addedlistsImageGenerationCallin its comment of handled types but has nocasefor it either (line 3295).Note the drop is silent: because the outer
matchdoes hitcase "response.output_item.done", theimage item never reaches the
case _:fallback, so not even the"Unparsed event of type ..."debugline is emitted for it. With
agent_framework.openaiat DEBUG the only trace is the earlieroutput_item.addedevent being reported as unparsed:Origin
update connectors and samples" (merged 2026-01-08,
3f7ea350dc), inpython/packages/core/agent_framework/openai/_responses_client.py. It added thepartial_imagehandler and the accompanying sample
openai_responses_client_streaming_image_generation.py, whichsets
partial_images, so the final-item case was never exercised.5e056b672e) moved the parser toagent_framework_openai/_chat_client.pyunchanged in this respect.
partial_imagehandler and its sample were written around preview frames from the start._chat_client.pysince then: no revision has ever handledimage_generation_callonresponse.output_item.done. This is a gap since the feature was added,not a regression from a specific release. It surfaced for us in the second week of September 2026
because the Foundry Responses endpoint used to emit one
partial_imageframe even withoutpartial_images(which the handler above picked up) and stopped doing so; the same code, samedependency versions and same deployments that returned images up to 2026-09-08 return none now.
The documented contract (OpenAI image generation guide:
partial_images0 → only the final image,delivered on the completed item) was never handled by the streaming parser.
Also verified: the stored response (
responses.retrieve) returns the image item with"result": null, and noincludevalue restores it, so the streamedoutput_item.doneevent isthe only moment the bytes are available. Dropping it there loses the image for good.
Proposed fix
Add an
image_generation_callbranch to theresponse.output_item.donehandler mirroring thenon-streaming mapping (call + result content, result as a
data:URI). Patch againstmain:case "response.output_item.done": done_item = event.item - if getattr(done_item, "type", None) == "reasoning": + if getattr(done_item, "type", None) == "image_generation_call": + image_result = getattr(done_item, "result", None) + if image_result: + image_id = getattr(done_item, "id", None) + contents.append( + Content.from_image_generation_tool_call( + image_id=image_id, raw_representation=done_item + ) + ) + contents.append( + Content.from_image_generation_tool_result( + image_id=image_id, + outputs=Content.from_uri( + uri=f"data:{detect_media_type_from_base64(data_str=image_result) or 'image/png'}" + f";base64,{image_result}", + raw_representation=image_result, + ), + raw_representation=done_item, + ) + ) + elif getattr(done_item, "type", None) == "reasoning": encrypted_content = getattr(done_item, "encrypted_content", None)When
partial_imagesis also set, consumers already keep the last result perimage_id, so thefinal render simply supersedes the preview frames.