Skip to content

Apply the builder pattern to OpenAiImageModel and add support for the new gpt-image-1 model #2943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
* @author Christian Tzolov
* @author Hyunjoon Choi
* @author Thomas Vitale
* @author Jonghoon Park
* @since 0.8.0
*/
public class OpenAiImageModel implements ImageModel {
Expand Down Expand Up @@ -88,7 +89,9 @@ public class OpenAiImageModel implements ImageModel {
* @param openAiImageApi The OpenAiImageApi instance to be used for interacting with
* the OpenAI Image API.
* @throws IllegalArgumentException if openAiImageApi is null
* @deprecated use {@link OpenAiImageModel.Builder} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M8")
public OpenAiImageModel(OpenAiImageApi openAiImageApi) {
this(openAiImageApi, OpenAiImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
}
Expand All @@ -99,7 +102,9 @@ public OpenAiImageModel(OpenAiImageApi openAiImageApi) {
* the OpenAI Image API.
* @param options The OpenAiImageOptions to configure the image model.
* @param retryTemplate The retry template.
* @deprecated use {@link OpenAiImageModel.Builder} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M8")
public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions options, RetryTemplate retryTemplate) {
this(openAiImageApi, options, retryTemplate, ObservationRegistry.NOOP);
}
Expand All @@ -111,7 +116,9 @@ public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions option
* @param options The OpenAiImageOptions to configure the image model.
* @param retryTemplate The retry template.
* @param observationRegistry The ObservationRegistry used for instrumentation.
* @deprecated use {@link OpenAiImageModel.Builder} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M8")
public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions options, RetryTemplate retryTemplate,
ObservationRegistry observationRegistry) {
Assert.notNull(openAiImageApi, "OpenAiImageApi must not be null");
Expand Down Expand Up @@ -198,6 +205,14 @@ private ImagePrompt buildRequestImagePrompt(ImagePrompt imagePrompt) {
.height(ModelOptionsUtils.mergeOption(runtimeOptions.getHeight(), this.defaultOptions.getHeight()))
.style(ModelOptionsUtils.mergeOption(runtimeOptions.getStyle(), this.defaultOptions.getStyle()))
// Handle OpenAI specific image options
.background(
ModelOptionsUtils.mergeOption(runtimeOptions.getBackground(), this.defaultOptions.getBackground()))
.moderation(
ModelOptionsUtils.mergeOption(runtimeOptions.getModeration(), this.defaultOptions.getModeration()))
.outputCompression(ModelOptionsUtils.mergeOption(runtimeOptions.getOutputCompression(),
this.defaultOptions.getOutputCompression()))
.outputFormat(ModelOptionsUtils.mergeOption(runtimeOptions.getOutputFormat(),
this.defaultOptions.getOutputFormat()))
.quality(ModelOptionsUtils.mergeOption(runtimeOptions.getQuality(), this.defaultOptions.getQuality()))
.user(ModelOptionsUtils.mergeOption(runtimeOptions.getUser(), this.defaultOptions.getUser()))
.build();
Expand All @@ -214,4 +229,48 @@ public void setObservationConvention(ImageModelObservationConvention observation
this.observationConvention = observationConvention;
}

public static OpenAiImageModel.Builder builder() {
return new OpenAiImageModel.Builder();
}

public static final class Builder {

private OpenAiImageApi openAiImageApi;

private OpenAiImageOptions defaultOptions = OpenAiImageOptions.builder().build();

private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE;

private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;

private Builder() {
}

public OpenAiImageModel.Builder openAiImageApi(OpenAiImageApi openAiImageApi) {
this.openAiImageApi = openAiImageApi;
return this;
}

public OpenAiImageModel.Builder defaultOptions(OpenAiImageOptions defaultOptions) {
this.defaultOptions = defaultOptions;
return this;
}

public OpenAiImageModel.Builder retryTemplate(RetryTemplate retryTemplate) {
this.retryTemplate = retryTemplate;
return this;
}

public OpenAiImageModel.Builder observationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
return this;
}

public OpenAiImageModel build() {
return new OpenAiImageModel(this.openAiImageApi, this.defaultOptions, this.retryTemplate,
this.observationRegistry);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,6 +28,7 @@
*
* @author Mark Pollack
* @author Christian Tzolov
* @author Jonghoon Park
* @since 0.8.0
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
Expand All @@ -46,6 +47,39 @@ public class OpenAiImageOptions implements ImageOptions {
@JsonProperty("model")
private String model;

/**
* Allows to set transparency for the background of the generated image(s). This
* parameter is only supported for `gpt-image-1`. Must be one of `transparent`,
* `opaque` or `auto` (default value). When `auto` is used, the model will
* automatically determine the best background for the image. If `transparent`, the
* output format needs to support transparency, so it should be set to either `png`
* (default value) or `webp`.
*/
@JsonProperty("background")
private String background;

/**
* Control the content-moderation level for images generated by `gpt-image-1`. Must be
* either `low` for less restrictive filtering or `auto` (default value).
*/
@JsonProperty("moderation")
private String moderation;

/**
* The compression level (0-100%) for the generated images. This parameter is only
* supported for `gpt-image-1` with the `webp` or `jpeg` output formats, and defaults
* to 100.
*/
@JsonProperty("output_compression")
private Integer outputCompression;

/**
* The format in which the generated images are returned. This parameter is only
* supported for `gpt-image-1`. Must be one of `png`, `jpeg`, or `webp`.
*/
@JsonProperty("output_format")
private String outputFormat;

/**
* The width of the generated images. Must be one of 256, 512, or 1024 for dall-e-2.
* This property is interconnected with the 'size' property - setting both width and
Expand Down Expand Up @@ -75,19 +109,22 @@ public class OpenAiImageOptions implements ImageOptions {
private String quality;

/**
* The format in which the generated images are returned. Must be one of url or
* b64_json.
* The format in which generated images with `dall-e-2` and `dall-e-3` are returned.
* Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the
* image has been generated. This parameter isn't supported for `gpt-image-1` which
* will always return base64-encoded images.
*/
@JsonProperty("response_format")
private String responseFormat;

/**
* The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024 for
* dall-e-2. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models.
* This property is automatically computed when both width and height are set,
* following the format "widthxheight". When setting this property directly, it must
* follow the format "WxH" where W and H are valid integers. Invalid formats will
* result in null width and height values.
* The size of the generated images. Must be one of `1024x1024`, `1536x1024`
* (landscape), `1024x1536` (portrait), or `auto` (default value) for `gpt-image-1`,
* one of `256x256`, `512x512`, or `1024x1024` for `dall-e-2`, and one of `1024x1024`,
* `1792x1024`, or `1024x1792` for `dall-e-3`. This property is automatically computed
* when both width and height are set, following the format "widthxheight". When
* setting this property directly, it must follow the format "WxH" where W and H are
* valid integers. Invalid formats will result in null width and height values.
*/
@JsonProperty("size")
private String size;
Expand Down Expand Up @@ -130,6 +167,38 @@ public void setModel(String model) {
this.model = model;
}

public String getBackground() {
return background;
}

public void setBackground(String background) {
this.background = background;
}

public String getModeration() {
return moderation;
}

public void setModeration(String moderation) {
this.moderation = moderation;
}

public Integer getOutputCompression() {
return outputCompression;
}

public void setOutputCompression(Integer outputCompression) {
this.outputCompression = outputCompression;
}

public String getOutputFormat() {
return outputFormat;
}

public void setOutputFormat(String outputFormat) {
this.outputFormat = outputFormat;
}

public String getQuality() {
return this.quality;
}
Expand Down Expand Up @@ -246,14 +315,17 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(this.n, this.model, this.width, this.height, this.quality, this.responseFormat, this.size,
this.style, this.user);
return Objects.hash(this.n, this.model, this.background, this.moderation, this.outputCompression,
this.outputFormat, this.width, this.height, this.quality, this.responseFormat, this.size, this.style,
this.user);
}

@Override
public String toString() {
return "OpenAiImageOptions{" + "n=" + this.n + ", model='" + this.model + '\'' + ", width=" + this.width
+ ", height=" + this.height + ", quality='" + this.quality + '\'' + ", responseFormat='"
return "OpenAiImageOptions{" + "n=" + this.n + ", model='" + this.model + '\'' + ", background='"
+ this.background + '\'' + ", moderation='" + this.moderation + '\'' + ", outputCompression='"
+ this.outputCompression + '\'' + ", outputFormat='" + this.outputFormat + '\'' + ", width="
+ this.width + ", height=" + this.height + ", quality='" + this.quality + '\'' + ", responseFormat='"
+ this.responseFormat + '\'' + ", size='" + this.size + '\'' + ", style='" + this.style + '\''
+ ", user='" + this.user + '\'' + '}';
}
Expand All @@ -276,6 +348,26 @@ public Builder model(String model) {
return this;
}

public Builder background(String background) {
this.options.setBackground(background);
return this;
}

public Builder moderation(String moderation) {
this.options.setModeration(moderation);
return this;
}

public Builder outputCompression(Integer outputCompression) {
this.options.setOutputCompression(outputCompression);
return this;
}

public Builder outputFormat(String outputFormat) {
this.options.setOutputFormat(outputFormat);
return this;
}

public Builder quality(String quality) {
this.options.setQuality(quality);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -91,6 +91,11 @@ public static Builder builder() {
*/
public enum ImageModel {

/**
* The state-of-the-art image generation model released in April 2025.
*/
GPT_IMAGE_1("gpt-image-1"),

/**
* The latest DALL·E model released in Nov 2023.
*/
Expand Down Expand Up @@ -120,6 +125,10 @@ public String getValue() {
public record OpenAiImageRequest(
@JsonProperty("prompt") String prompt,
@JsonProperty("model") String model,
@JsonProperty("background") String background,
@JsonProperty("moderation") String moderation,
@JsonProperty("output_compression") Integer outputCompression,
@JsonProperty("output_format") String outputFormat,
@JsonProperty("n") Integer n,
@JsonProperty("quality") String quality,
@JsonProperty("response_format") String responseFormat,
Expand All @@ -128,7 +137,7 @@ public record OpenAiImageRequest(
@JsonProperty("user") String user) {

public OpenAiImageRequest(String prompt, String model) {
this(prompt, model, null, null, null, null, null, null);
this(prompt, model, null, null, null, null, null, null,null,null,null,null);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -79,7 +79,7 @@ public OpenAiAudioSpeechModel openAiAudioSpeechModel(OpenAiAudioApi api) {

@Bean
public OpenAiImageModel openAiImageModel(OpenAiImageApi imageApi) {
return new OpenAiImageModel(imageApi);
return OpenAiImageModel.builder().openAiImageApi(imageApi).build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -123,8 +123,11 @@ public void beforeEach() {
.responseFormat(TranscriptResponseFormat.JSON)
.build(),
this.retryTemplate);
this.imageModel = new OpenAiImageModel(this.openAiImageApi, OpenAiImageOptions.builder().build(),
this.retryTemplate);
this.imageModel = OpenAiImageModel.builder()
.openAiImageApi(this.openAiImageApi)
.defaultOptions(OpenAiImageOptions.builder().build())
.retryTemplate(this.retryTemplate)
.build();
}

@Test
Expand Down
Loading