Skip to content

Commit 4c5001c

Browse files
Image generation support
1 parent 5412bdd commit 4c5001c

4 files changed

Lines changed: 369 additions & 18 deletions

File tree

routers/chat.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -382,15 +382,8 @@ async def iterate_stream(s, response_id: str = "") -> AsyncGenerator[str, None]:
382382
continue
383383

384384
case ResponseImageGenCallPartialImageEvent():
385-
# Display partial image as it streams in
386-
img_html = (
387-
f'<div class="imageOutput">'
388-
f'<img src="data:image/png;base64,{event.partial_image_b64}" '
389-
f'alt="Partial image (generating...)" '
390-
f'onclick="openImagePreview(this.src)" style="cursor:pointer" />'
391-
f'</div>'
392-
)
393-
yield sse_format("imageOutput", img_html)
385+
# Partial preview — skip; final image emitted by OutputItemDone
386+
continue
394387

395388
case ResponseContentPartAddedEvent():
396389
# This event indicates the start of annotations; skip creating a new assistantMessage

static/styles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,10 @@ pre {
626626
order: 3;
627627
}
628628

629+
.assistantMessage:empty {
630+
display: none;
631+
}
632+
629633
.userMessage,
630634
.assistantMessage,
631635
.codeMessage,
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""
2+
Live integration tests for image generation tool support.
3+
4+
These tests hit the real OpenAI API to observe:
5+
1. The image_generation tool payload is accepted
6+
2. The streaming event sequence and types
7+
3. Whether text deltas are sent alongside image generation calls
8+
9+
Requires a valid OPENAI_API_KEY in .env or environment.
10+
Mark: all tests use @pytest.mark.live to allow selective runs.
11+
"""
12+
13+
import pytest
14+
from openai import AsyncOpenAI
15+
16+
from conftest import REAL_API_KEY
17+
18+
_REAL_API_KEY = REAL_API_KEY
19+
_has_real_key = bool(_REAL_API_KEY)
20+
21+
pytestmark = [
22+
pytest.mark.live,
23+
pytest.mark.skipif(not _has_real_key, reason="No real OPENAI_API_KEY available"),
24+
]
25+
26+
MODEL = "gpt-4.1-mini"
27+
28+
IG_TOOL = {"type": "image_generation"}
29+
30+
31+
@pytest.fixture
32+
async def client():
33+
c = AsyncOpenAI(api_key=_REAL_API_KEY)
34+
yield c
35+
await c.close()
36+
37+
38+
class TestImageGenerationEventSequence:
39+
"""Observe the real event sequence from an image generation call."""
40+
41+
@pytest.mark.anyio
42+
async def test_image_generation_event_types(self, client: AsyncOpenAI):
43+
"""Log all event types from an image generation request."""
44+
stream = await client.responses.create(
45+
model=MODEL,
46+
tools=[IG_TOOL],
47+
input="Generate an image of a small red circle on a white background.",
48+
stream=True,
49+
)
50+
51+
events = []
52+
async with stream as event_stream:
53+
async for event in event_stream:
54+
name = type(event).__name__
55+
events.append((name, event))
56+
57+
event_types = [e[0] for e in events]
58+
59+
# Print all event types for observation
60+
print("\n=== Image Generation Event Types ===")
61+
for i, (name, event) in enumerate(events):
62+
extra = ""
63+
if hasattr(event, "item_id"):
64+
extra += f" item_id={event.item_id}"
65+
if hasattr(event, "output_index"):
66+
extra += f" output_index={event.output_index}"
67+
if hasattr(event, "type") and isinstance(event.type, str):
68+
extra += f" type={event.type}"
69+
# For output item events, show the item type
70+
if hasattr(event, "item") and hasattr(event.item, "type"):
71+
extra += f" item.type={event.item.type}"
72+
print(f" [{i:3d}] {name}{extra}")
73+
74+
# Check for text deltas
75+
has_text_deltas = "ResponseTextDeltaEvent" in event_types
76+
print(f"\n=== Text deltas present: {has_text_deltas} ===")
77+
if has_text_deltas:
78+
text_events = [(i, e) for i, (n, e) in enumerate(events)
79+
if n == "ResponseTextDeltaEvent"]
80+
print(f" Count: {len(text_events)}")
81+
sample_text = "".join(
82+
e.delta for _, e in text_events[:20]
83+
if hasattr(e, "delta")
84+
)
85+
print(f" Sample text: {sample_text[:200]}")
86+
87+
# Check for image generation events
88+
ig_event_names = [n for n in event_types if "ImageGen" in n]
89+
print(f"\n=== Image generation events: {ig_event_names} ===")
90+
91+
# Stream must complete
92+
assert "ResponseCompletedEvent" in event_types
93+
94+
@pytest.mark.anyio
95+
async def test_image_generation_produces_image_events(self, client: AsyncOpenAI):
96+
"""An image generation request should produce image-specific events."""
97+
stream = await client.responses.create(
98+
model=MODEL,
99+
tools=[IG_TOOL],
100+
input="Draw a simple blue square.",
101+
stream=True,
102+
)
103+
104+
event_types = []
105+
async with stream as event_stream:
106+
async for event in event_stream:
107+
event_types.append(type(event).__name__)
108+
109+
# Should have at least some image generation events
110+
ig_events = [e for e in event_types if "ImageGen" in e]
111+
assert len(ig_events) >= 1, (
112+
f"Expected image generation events, got: {event_types}"
113+
)
114+
115+
assert "ResponseCompletedEvent" in event_types
116+
117+
@pytest.mark.anyio
118+
async def test_text_deltas_alongside_image_generation(self, client: AsyncOpenAI):
119+
"""Check whether the model sends text alongside image generation."""
120+
stream = await client.responses.create(
121+
model=MODEL,
122+
tools=[IG_TOOL],
123+
input="Generate an image of a green triangle and describe what you created.",
124+
stream=True,
125+
)
126+
127+
event_types = []
128+
text_content = []
129+
async with stream as event_stream:
130+
async for event in event_stream:
131+
name = type(event).__name__
132+
event_types.append(name)
133+
if name == "ResponseTextDeltaEvent" and hasattr(event, "delta"):
134+
text_content.append(event.delta)
135+
136+
has_text = "ResponseTextDeltaEvent" in event_types
137+
has_image = any("ImageGen" in e for e in event_types)
138+
139+
print("\n=== Results ===")
140+
print(f" Has text deltas: {has_text}")
141+
print(f" Has image gen events: {has_image}")
142+
if text_content:
143+
print(f" Text: {''.join(text_content)[:300]}")
144+
145+
# This test is observational — it always passes but logs the behavior
146+
assert "ResponseCompletedEvent" in event_types

0 commit comments

Comments
 (0)