|
30 | 30 | AIFunction, |
31 | 31 | HostedCodeInterpreterTool, |
32 | 32 | HostedFileSearchTool, |
33 | | - HostedImageGenerationTool, |
34 | 33 | HostedMCPTool, |
35 | 34 | HostedWebSearchTool, |
36 | 35 | ToolProtocol, |
@@ -270,59 +269,102 @@ def _tools_to_response_tools( |
270 | 269 | else None, |
271 | 270 | ) |
272 | 271 | ) |
273 | | - case HostedImageGenerationTool(): |
274 | | - image_tool: dict[str, Any] = {"type": "image_generation"} |
275 | | - |
276 | | - # Handle base parameters as direct attributes |
277 | | - if tool.size is not None: |
278 | | - image_tool["size"] = tool.size |
279 | | - if tool.quality is not None: |
280 | | - image_tool["quality"] = tool.quality |
281 | | - if tool.background is not None: |
282 | | - image_tool["background"] = tool.background |
283 | | - |
284 | | - # Map developer-friendly parameter names to OpenAI API names |
285 | | - if tool.format is not None: |
286 | | - image_tool["output_format"] = tool.format |
287 | | - if tool.compression is not None: |
288 | | - image_tool["output_compression"] = tool.compression |
289 | | - |
290 | | - # Handle OpenAI Responses-specific parameters |
291 | | - openai_param_names = [ |
292 | | - "model", |
293 | | - "moderation", |
294 | | - "partial_images", |
295 | | - "input_fidelity", |
296 | | - "input_image_mask", |
297 | | - ] |
298 | | - for param in openai_param_names: |
299 | | - value = getattr(tool, param, None) |
300 | | - if value is not None: |
301 | | - # Validate OpenAI Responses-specific parameters |
302 | | - if param == "partial_images" and (not isinstance(value, int) or not (0 <= value <= 3)): |
303 | | - raise ValueError("partial_images must be an integer between 0 and 3") |
304 | | - if param == "input_fidelity" and value not in ("high", "low"): |
305 | | - raise ValueError("input_fidelity must be 'high' or 'low'") |
306 | | - if param == "input_image_mask": |
307 | | - if not isinstance(value, dict): |
308 | | - raise ValueError("input_image_mask must be a dictionary") |
309 | | - # Validate that it has at least one of the required fields |
310 | | - if not any(key in value for key in ["file_id", "image_url"]): |
311 | | - raise ValueError( |
312 | | - "input_image_mask must contain at least one of 'file_id' or 'image_url'" |
313 | | - ) |
314 | | - # Validate field types if present |
315 | | - if "file_id" in value and not isinstance(value["file_id"], str): |
316 | | - raise ValueError("input_image_mask.file_id must be a string") |
317 | | - if "image_url" in value and not isinstance(value["image_url"], str): |
318 | | - raise ValueError("input_image_mask.image_url must be a string") |
319 | | - image_tool[param] = value |
320 | | - |
321 | | - response_tools.append(image_tool) |
| 272 | + |
322 | 273 | case _: |
323 | 274 | logger.debug("Unsupported tool passed (type: %s)", type(tool)) |
324 | 275 | else: |
325 | | - response_tools.append(tool if isinstance(tool, dict) else dict(tool)) |
| 276 | + # Handle raw dictionary tools |
| 277 | + tool_dict = tool if isinstance(tool, dict) else dict(tool) |
| 278 | + |
| 279 | + # Special handling for image_generation tools |
| 280 | + if tool_dict.get("type") == "image_generation": |
| 281 | + # Create a copy to avoid modifying the original |
| 282 | + mapped_tool = tool_dict.copy() |
| 283 | + |
| 284 | + # Map user-friendly parameter names to OpenAI API parameter names |
| 285 | + parameter_mapping = { |
| 286 | + "format": "output_format", |
| 287 | + "compression": "output_compression", |
| 288 | + } |
| 289 | + |
| 290 | + for user_param, api_param in parameter_mapping.items(): |
| 291 | + if user_param in mapped_tool: |
| 292 | + # Map the parameter name and remove the old one |
| 293 | + mapped_tool[api_param] = mapped_tool.pop(user_param) |
| 294 | + |
| 295 | + # Validate all OpenAI image generation parameters |
| 296 | + |
| 297 | + # Background validation |
| 298 | + if "background" in mapped_tool: |
| 299 | + value = mapped_tool["background"] |
| 300 | + if value not in ("transparent", "opaque", "auto"): |
| 301 | + raise ValueError("background must be one of: 'transparent', 'opaque', 'auto'") |
| 302 | + |
| 303 | + # Input fidelity validation |
| 304 | + if "input_fidelity" in mapped_tool: |
| 305 | + value = mapped_tool["input_fidelity"] |
| 306 | + if value not in ("high", "low"): |
| 307 | + raise ValueError("input_fidelity must be 'high' or 'low'") |
| 308 | + |
| 309 | + # Input image mask validation |
| 310 | + if "input_image_mask" in mapped_tool: |
| 311 | + value = mapped_tool["input_image_mask"] |
| 312 | + if not isinstance(value, dict): |
| 313 | + raise ValueError("input_image_mask must be a dictionary") |
| 314 | + # Validate that it has at least one of the required fields |
| 315 | + if not any(key in value for key in ["file_id", "image_url"]): |
| 316 | + raise ValueError("input_image_mask must contain at least one of 'file_id' or 'image_url'") |
| 317 | + # Validate field types if present |
| 318 | + if "file_id" in value and not isinstance(value["file_id"], str): |
| 319 | + raise ValueError("input_image_mask.file_id must be a string") |
| 320 | + if "image_url" in value and not isinstance(value["image_url"], str): |
| 321 | + raise ValueError("input_image_mask.image_url must be a string") |
| 322 | + |
| 323 | + # Model validation |
| 324 | + if "model" in mapped_tool: |
| 325 | + value = mapped_tool["model"] |
| 326 | + if not isinstance(value, str): |
| 327 | + raise ValueError("model must be a string") |
| 328 | + |
| 329 | + # Moderation validation |
| 330 | + if "moderation" in mapped_tool: |
| 331 | + value = mapped_tool["moderation"] |
| 332 | + if not isinstance(value, str): |
| 333 | + raise ValueError("moderation must be a string") |
| 334 | + |
| 335 | + # Output compression validation |
| 336 | + if "output_compression" in mapped_tool: |
| 337 | + value = mapped_tool["output_compression"] |
| 338 | + if not isinstance(value, int) or not (1 <= value <= 100): |
| 339 | + raise ValueError("output_compression must be an integer between 1 and 100") |
| 340 | + |
| 341 | + # Output format validation |
| 342 | + if "output_format" in mapped_tool: |
| 343 | + value = mapped_tool["output_format"] |
| 344 | + if value not in ("png", "webp", "jpeg"): |
| 345 | + raise ValueError("output_format must be one of: 'png', 'webp', 'jpeg'") |
| 346 | + |
| 347 | + # Partial images validation |
| 348 | + if "partial_images" in mapped_tool: |
| 349 | + value = mapped_tool["partial_images"] |
| 350 | + if not isinstance(value, int) or not (0 <= value <= 3): |
| 351 | + raise ValueError("partial_images must be an integer between 0 and 3") |
| 352 | + |
| 353 | + # Quality validation |
| 354 | + if "quality" in mapped_tool: |
| 355 | + value = mapped_tool["quality"] |
| 356 | + if value not in ("low", "medium", "high", "auto"): |
| 357 | + raise ValueError("quality must be one of: 'low', 'medium', 'high', 'auto'") |
| 358 | + |
| 359 | + # Size validation |
| 360 | + if "size" in mapped_tool: |
| 361 | + value = mapped_tool["size"] |
| 362 | + if value not in ("1024x1024", "1024x1536", "1536x1024", "auto"): |
| 363 | + raise ValueError("size must be one of: '1024x1024', '1024x1536', '1536x1024', 'auto'") |
| 364 | + |
| 365 | + response_tools.append(mapped_tool) |
| 366 | + else: |
| 367 | + response_tools.append(tool_dict) |
326 | 368 | return response_tools |
327 | 369 |
|
328 | 370 | def _prepare_options(self, messages: MutableSequence[ChatMessage], chat_options: ChatOptions) -> dict[str, Any]: |
|
0 commit comments