Files
wehub-resource-sync c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

247 lines
10 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import os
from typing import Any, Literal
from openai import AsyncOpenAI, OpenAI
from openai.types.image import Image
from haystack import component, default_from_dict, default_to_dict, logging
from haystack.utils import Secret
from haystack.utils.http_client import init_http_client
logger = logging.getLogger(__name__)
@component
class OpenAIImageGenerator:
"""
Generates images using OpenAI's image generation models such as `gpt-image-2`.
For details on OpenAI API parameters, see
[OpenAI documentation](https://developers.openai.com/api/reference/resources/images/methods/generate).
### Usage example
```python
from haystack.components.generators import OpenAIImageGenerator
image_generator = OpenAIImageGenerator()
response = image_generator.run("Show me a picture of a black cat.")
print(response)
```
"""
def __init__(
self,
model: str = "gpt-image-2",
quality: Literal["auto", "high", "medium", "low"] = "auto",
size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] = "1024x1024",
response_format: Literal["b64_json"] = "b64_json",
api_key: Secret = Secret.from_env_var("OPENAI_API_KEY"),
api_base_url: str | None = None,
organization: str | None = None,
timeout: float | None = None,
max_retries: int | None = None,
http_client_kwargs: dict[str, Any] | None = None,
) -> None:
"""
Creates an instance of OpenAIImageGenerator. Unless specified otherwise in `model`, uses OpenAI's gpt-image-2.
:param model: The model to use for image generation. Model names can be found in the
[OpenAI documentation](https://developers.openai.com/api/docs/models/all).
:param quality: The quality of the generated image. Can be "auto", "high", "medium", or "low".
:param size: The size of the generated images. One of 1024x1024, 1024x1536, 1536x1024, or "auto".
`gpt-image-2` also supports arbitrary sizes. You can find more information about supported sizes in
the [OpenAI documentation](https://developers.openai.com/api/reference/resources/images/methods/generate).
:param response_format: This parameter is ignored and only kept for backward compatibility.
:param api_key: The OpenAI API key to connect to OpenAI.
:param api_base_url: An optional base URL.
:param organization: The Organization ID, defaults to `None`.
:param timeout:
Timeout for OpenAI Client calls. If not set, it is inferred from the `OPENAI_TIMEOUT` environment variable
or set to 30.
:param max_retries:
Maximum retries to establish contact with OpenAI if it returns an internal error. If not set, it is inferred
from the `OPENAI_MAX_RETRIES` environment variable or set to 5.
:param http_client_kwargs:
A dictionary of keyword arguments to configure a custom `httpx.Client`or `httpx.AsyncClient`.
For more information, see the [HTTPX documentation](https://www.python-httpx.org/api/#client).
"""
self.model = model
if quality not in ["auto", "high", "medium", "low"]:
logger.warning("Invalid quality: {quality}. Defaulting to 'auto'.", quality=quality)
quality = "auto"
self.quality = quality
self.size = size
if response_format != "b64_json":
logger.warning("response_format is ignored. A base64-encoded image will be returned.")
self.api_key = api_key
self.api_base_url = api_base_url
self.organization = organization
self.timeout = timeout
self.max_retries = max_retries
self.http_client_kwargs = http_client_kwargs
self.client: OpenAI | None = None
self.async_client: AsyncOpenAI | None = None
def _client_kwargs(self) -> dict[str, Any]:
timeout = self.timeout if self.timeout is not None else float(os.environ.get("OPENAI_TIMEOUT", "30.0"))
max_retries = (
self.max_retries if self.max_retries is not None else int(os.environ.get("OPENAI_MAX_RETRIES", "5"))
)
return {
"api_key": self.api_key.resolve_value(),
"organization": self.organization,
"base_url": self.api_base_url,
"timeout": timeout,
"max_retries": max_retries,
}
def warm_up(self) -> None:
"""
Initializes the synchronous OpenAI client.
"""
if self.client is None:
self.client = OpenAI(
http_client=init_http_client(self.http_client_kwargs, async_client=False), **self._client_kwargs()
)
async def warm_up_async(self) -> None: # noqa: RUF029
"""
Initializes the asynchronous OpenAI client on the serving event loop.
"""
if self.async_client is None:
self.async_client = AsyncOpenAI(
http_client=init_http_client(self.http_client_kwargs, async_client=True), **self._client_kwargs()
)
def close(self) -> None:
"""
Releases the synchronous OpenAI client.
"""
if self.client is not None:
self.client.close()
self.client = None
async def close_async(self) -> None:
"""
Releases the asynchronous OpenAI client.
"""
if self.async_client is not None:
await self.async_client.close()
self.async_client = None
@component.output_types(images=list[str], revised_prompt=str)
def run(
self,
prompt: str,
size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] | None = None,
quality: Literal["auto", "high", "medium", "low"] | None = None,
response_format: Literal["b64_json"] | None = None, # noqa: ARG002
) -> dict[str, Any]:
"""
Invokes the image generation inference based on the provided prompt and generation parameters.
:param prompt: The prompt to generate the image.
:param size: If provided, overrides the size provided during initialization.
:param quality: If provided, overrides the quality provided during initialization.
:param response_format: This parameter is ignored and only kept for backward compatibility.
:returns:
A dictionary containing the generated list of images as base64 encoded JSON strings and the revised prompt.
The revised prompt is the prompt that was used to generate the image, if there was any revision
to the prompt made by OpenAI.
"""
self.warm_up()
# at this point the client is initialized, but mypy doesn't know that
assert self.client is not None
size = size or self.size
quality = quality or self.quality
response = self.client.images.generate(model=self.model, prompt=prompt, size=size, quality=quality, n=1)
image_str = ""
revised_prompt = ""
if response.data is not None:
image: Image = response.data[0]
image_str = image.b64_json or ""
revised_prompt = image.revised_prompt or ""
return {"images": [image_str], "revised_prompt": revised_prompt}
@component.output_types(images=list[str], revised_prompt=str)
async def run_async(
self,
prompt: str,
size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] | None = None,
quality: Literal["auto", "high", "medium", "low"] | None = None,
response_format: Literal["b64_json"] | None = None, # noqa: ARG002
) -> dict[str, Any]:
"""
Asynchronously invokes the image generation inference based on the provided prompt and generation parameters.
This is the asynchronous version of the `run` method. It has the same parameters and return values
but can be used with `await` in an async code.
:param prompt: The prompt to generate the image.
:param size: If provided, overrides the size provided during initialization.
:param quality: If provided, overrides the quality provided during initialization.
:param response_format: This parameter is ignored and only kept for backward compatibility.
:returns:
A dictionary containing the generated list of images as base64 encoded JSON strings and the revised prompt.
The revised prompt is the prompt that was used to generate the image, if there was any revision
to the prompt made by OpenAI.
"""
await self.warm_up_async()
# at this point the client is initialized, but mypy doesn't know that
assert self.async_client is not None
size = size or self.size
quality = quality or self.quality
response = await self.async_client.images.generate(
model=self.model, prompt=prompt, size=size, quality=quality, n=1
)
image_str = ""
revised_prompt = ""
if response.data is not None:
image: Image = response.data[0]
image_str = image.b64_json or ""
revised_prompt = image.revised_prompt or ""
return {"images": [image_str], "revised_prompt": revised_prompt}
def to_dict(self) -> dict[str, Any]:
"""
Serialize this component to a dictionary.
:returns:
The serialized component as a dictionary.
"""
return default_to_dict(
self,
model=self.model,
quality=self.quality,
size=self.size,
api_key=self.api_key,
api_base_url=self.api_base_url,
organization=self.organization,
http_client_kwargs=self.http_client_kwargs,
)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "OpenAIImageGenerator":
"""
Deserialize this component from a dictionary.
:param data:
The dictionary representation of this component.
:returns:
The deserialized component instance.
"""
return default_from_dict(cls, data)