Skip to content

Commit a0a4e24

Browse files
committed
feat: support current image generation tool options
1 parent 544b8b0 commit a0a4e24

4 files changed

Lines changed: 155 additions & 4 deletions

File tree

‎examples/tools/image_generator.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ async def main():
3434
instructions="Always use the image generation tool when the user asks for a new image.",
3535
tools=[
3636
ImageGenerationTool(
37-
tool_config={"type": "image_generation", "quality": "low"},
37+
tool_config={
38+
"type": "image_generation",
39+
"model": "gpt-image-2.5-flare",
40+
"action": "generate",
41+
"size": "1536x864",
42+
# Use xhigh or max for higher quality with GPT Image 2.5 models.
43+
"quality": "low",
44+
},
3845
)
3946
],
4047
)

‎src/agents/models/openai_responses.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2262,7 +2262,7 @@ def _convert_tool(
22622262
None,
22632263
)
22642264
elif isinstance(tool, ImageGenerationTool):
2265-
return tool.tool_config, None
2265+
return cast(ResponsesToolParam, tool.tool_config), None
22662266
elif isinstance(tool, CodeInterpreterTool):
22672267
return tool.tool_config, None
22682268
elif isinstance(tool, LocalShellTool):

‎src/agents/tool.py‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,8 +1162,25 @@ def name(self):
11621162
class ImageGenerationTool:
11631163
"""A tool that allows the LLM to generate images."""
11641164

1165-
tool_config: ImageGeneration
1166-
"""The tool config, which includes image generation settings."""
1165+
# Keep indexed mutation and reassignment of upstream TypedDict configurations compatible.
1166+
tool_config: Any
1167+
"""Responses API image generation settings, including `type="image_generation"`.
1168+
1169+
Accepts the OpenAI SDK's typed config or a dictionary for API options not yet
1170+
reflected in its types. For example, set `model="gpt-image-2.5-sunburst"` or
1171+
`model="gpt-image-2.5-flare"` with `quality="xhigh"` or `quality="max"`.
1172+
Settings are forwarded unchanged; the API validates model-specific support.
1173+
"""
1174+
1175+
@overload
1176+
def __init__(self, tool_config: dict[str, Any]) -> None: ...
1177+
1178+
@overload
1179+
def __init__(self, tool_config: ImageGeneration) -> None: ...
1180+
1181+
def __init__(self, tool_config: ImageGeneration | dict[str, Any]) -> None:
1182+
# Separate overloads keep newer dictionary literals from using the older TypedDict schema.
1183+
self.tool_config = tool_config
11671184

11681185
@property
11691186
def name(self):

‎tests/models/test_openai_responses.py‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import copy
45
import json
56
import logging
67
from types import SimpleNamespace
@@ -13,13 +14,15 @@
1314
from openai.types.responses.response import IncompleteDetails
1415
from openai.types.responses.response_create_params import ContextManagement, PromptCacheOptions
1516
from openai.types.responses.response_usage import ResponseUsage
17+
from openai.types.responses.tool_param import ImageGeneration
1618
from openai.types.shared.reasoning import Reasoning
1719

1820
from agents import (
1921
Agent,
2022
AsyncComputer,
2123
Computer,
2224
ComputerTool,
25+
ImageGenerationTool,
2326
ModelSettings,
2427
ModelTracing,
2528
Runner,
@@ -249,6 +252,130 @@ async def handler(request: httpx2.Request) -> httpx2.Response:
249252
assert body.get("include", []) == expected_include
250253

251254

255+
@pytest.mark.allow_call_model_methods
256+
@pytest.mark.asyncio
257+
@pytest.mark.parametrize("stream", [False, True], ids=["non_streaming", "streaming"])
258+
@pytest.mark.parametrize(
259+
"tool",
260+
[
261+
ImageGenerationTool(ImageGeneration(type="image_generation")),
262+
ImageGenerationTool(
263+
ImageGeneration(
264+
type="image_generation",
265+
model="gpt-image-1",
266+
quality="high",
267+
action="edit",
268+
input_fidelity="high",
269+
input_image_mask={"file_id": "file-mask"},
270+
)
271+
),
272+
ImageGenerationTool(
273+
tool_config={
274+
"type": "image_generation",
275+
"model": "gpt-image-2.5-sunburst",
276+
"quality": "max",
277+
"action": "generate",
278+
"size": "1536x864",
279+
"background": "transparent",
280+
"output_format": "webp",
281+
"output_compression": 0,
282+
"partial_images": 0,
283+
"moderation": "auto",
284+
}
285+
),
286+
ImageGenerationTool(
287+
tool_config={
288+
"type": "image_generation",
289+
"model": "gpt-image-2.5-flare",
290+
"quality": "xhigh",
291+
"action": "auto",
292+
"size": "auto",
293+
"output_format": "png",
294+
"partial_images": 2,
295+
}
296+
),
297+
],
298+
ids=["defaults", "typed_edit", "sunburst_max", "flare_xhigh"],
299+
)
300+
async def test_image_generation_options_reach_responses_request(
301+
stream: bool, tool: ImageGenerationTool
302+
) -> None:
303+
"""Use the real client's serializer to check options beyond its current annotations."""
304+
expected_config = copy.deepcopy(tool.tool_config)
305+
request_bodies: list[dict[str, Any]] = []
306+
307+
async def handler(request: httpx2.Request) -> httpx2.Response:
308+
request_bodies.append(json.loads(request.content))
309+
if stream:
310+
event = _response_completed_frame("resp-id", sequence_number=0)
311+
return httpx2.Response(
312+
200,
313+
content=f"event: response.completed\ndata: {event}\n\n",
314+
headers={"content-type": "text/event-stream"},
315+
)
316+
return httpx2.Response(
317+
200,
318+
content=get_response_obj([]).model_dump_json(),
319+
headers={"content-type": "application/json"},
320+
)
321+
322+
async with httpx2.AsyncClient(transport=httpx2.MockTransport(handler)) as http_client:
323+
agent = Agent(
324+
name="Image generator",
325+
model=OpenAIResponsesModel(
326+
model="gpt-5.5",
327+
openai_client=AsyncOpenAI(api_key="test-key", http_client=http_client),
328+
),
329+
tools=[tool],
330+
)
331+
prompt = [
332+
{
333+
"role": "user",
334+
"content": [
335+
{"type": "input_text", "text": "Generate an image, or edit this image."},
336+
{"type": "input_image", "file_id": "file-image", "detail": "auto"},
337+
],
338+
}
339+
]
340+
if stream:
341+
result = Runner.run_streamed(agent, prompt)
342+
async for _ in result.stream_events():
343+
pass
344+
else:
345+
await Runner.run(agent, prompt)
346+
347+
assert len(request_bodies) == 1
348+
assert request_bodies[0]["tools"] == [expected_config]
349+
assert tool.tool_config == expected_config
350+
assert request_bodies[0]["model"] == "gpt-5.5"
351+
352+
353+
def test_image_generation_config_remains_mutable() -> None:
354+
config: ImageGeneration = {"type": "image_generation", "quality": "low"}
355+
tool = ImageGenerationTool(config)
356+
assert tool.tool_config is config
357+
tool.tool_config["quality"] = "high"
358+
assert config["quality"] == "high"
359+
360+
replacement: ImageGeneration = {"type": "image_generation", "quality": "auto"}
361+
tool.tool_config = replacement
362+
assert tool.tool_config is replacement
363+
364+
tool.tool_config = {
365+
"type": "image_generation",
366+
"model": "gpt-image-2.5-flare",
367+
"quality": "xhigh",
368+
}
369+
tool.tool_config["quality"] = "max"
370+
assert Converter.convert_tools([tool], []).tools == [
371+
{
372+
"type": "image_generation",
373+
"model": "gpt-image-2.5-flare",
374+
"quality": "max",
375+
}
376+
]
377+
378+
252379
class DummyWSConnection:
253380
def __init__(self, frames: list[str]):
254381
self._frames = frames

0 commit comments

Comments
 (0)