Skip to content

Commit 5e13f09

Browse files
Refactor OpenAI Image API and add support for the new model
Signed-off-by: jonghoonpark <[email protected]>
1 parent 6c85aea commit 5e13f09

File tree

10 files changed

+258
-31
lines changed

10 files changed

+258
-31
lines changed

models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageModel.java

+59
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
* @author Christian Tzolov
5151
* @author Hyunjoon Choi
5252
* @author Thomas Vitale
53+
* @author Jonghoon Park
5354
* @since 0.8.0
5455
*/
5556
public class OpenAiImageModel implements ImageModel {
@@ -88,7 +89,9 @@ public class OpenAiImageModel implements ImageModel {
8889
* @param openAiImageApi The OpenAiImageApi instance to be used for interacting with
8990
* the OpenAI Image API.
9091
* @throws IllegalArgumentException if openAiImageApi is null
92+
* @deprecated use {@link OpenAiImageModel.Builder} instead.
9193
*/
94+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
9295
public OpenAiImageModel(OpenAiImageApi openAiImageApi) {
9396
this(openAiImageApi, OpenAiImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
9497
}
@@ -99,7 +102,9 @@ public OpenAiImageModel(OpenAiImageApi openAiImageApi) {
99102
* the OpenAI Image API.
100103
* @param options The OpenAiImageOptions to configure the image model.
101104
* @param retryTemplate The retry template.
105+
* @deprecated use {@link OpenAiImageModel.Builder} instead.
102106
*/
107+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
103108
public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions options, RetryTemplate retryTemplate) {
104109
this(openAiImageApi, options, retryTemplate, ObservationRegistry.NOOP);
105110
}
@@ -111,7 +116,9 @@ public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions option
111116
* @param options The OpenAiImageOptions to configure the image model.
112117
* @param retryTemplate The retry template.
113118
* @param observationRegistry The ObservationRegistry used for instrumentation.
119+
* @deprecated use {@link OpenAiImageModel.Builder} instead.
114120
*/
121+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
115122
public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions options, RetryTemplate retryTemplate,
116123
ObservationRegistry observationRegistry) {
117124
Assert.notNull(openAiImageApi, "OpenAiImageApi must not be null");
@@ -198,6 +205,14 @@ private ImagePrompt buildRequestImagePrompt(ImagePrompt imagePrompt) {
198205
.height(ModelOptionsUtils.mergeOption(runtimeOptions.getHeight(), this.defaultOptions.getHeight()))
199206
.style(ModelOptionsUtils.mergeOption(runtimeOptions.getStyle(), this.defaultOptions.getStyle()))
200207
// Handle OpenAI specific image options
208+
.background(
209+
ModelOptionsUtils.mergeOption(runtimeOptions.getBackground(), this.defaultOptions.getBackground()))
210+
.moderation(
211+
ModelOptionsUtils.mergeOption(runtimeOptions.getModeration(), this.defaultOptions.getModeration()))
212+
.outputCompression(ModelOptionsUtils.mergeOption(runtimeOptions.getOutputCompression(),
213+
this.defaultOptions.getOutputCompression()))
214+
.outputFormat(ModelOptionsUtils.mergeOption(runtimeOptions.getOutputFormat(),
215+
this.defaultOptions.getOutputFormat()))
201216
.quality(ModelOptionsUtils.mergeOption(runtimeOptions.getQuality(), this.defaultOptions.getQuality()))
202217
.user(ModelOptionsUtils.mergeOption(runtimeOptions.getUser(), this.defaultOptions.getUser()))
203218
.build();
@@ -214,4 +229,48 @@ public void setObservationConvention(ImageModelObservationConvention observation
214229
this.observationConvention = observationConvention;
215230
}
216231

232+
public static OpenAiImageModel.Builder builder() {
233+
return new OpenAiImageModel.Builder();
234+
}
235+
236+
public static final class Builder {
237+
238+
private OpenAiImageApi openAiImageApi;
239+
240+
private OpenAiImageOptions defaultOptions = OpenAiImageOptions.builder().build();
241+
242+
private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE;
243+
244+
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
245+
246+
private Builder() {
247+
}
248+
249+
public OpenAiImageModel.Builder openAiImageApi(OpenAiImageApi openAiImageApi) {
250+
this.openAiImageApi = openAiImageApi;
251+
return this;
252+
}
253+
254+
public OpenAiImageModel.Builder defaultOptions(OpenAiImageOptions defaultOptions) {
255+
this.defaultOptions = defaultOptions;
256+
return this;
257+
}
258+
259+
public OpenAiImageModel.Builder retryTemplate(RetryTemplate retryTemplate) {
260+
this.retryTemplate = retryTemplate;
261+
return this;
262+
}
263+
264+
public OpenAiImageModel.Builder observationRegistry(ObservationRegistry observationRegistry) {
265+
this.observationRegistry = observationRegistry;
266+
return this;
267+
}
268+
269+
public OpenAiImageModel build() {
270+
return new OpenAiImageModel(this.openAiImageApi, this.defaultOptions, this.retryTemplate,
271+
this.observationRegistry);
272+
}
273+
274+
}
275+
217276
}

models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageOptions.java

+105-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
2828
*
2929
* @author Mark Pollack
3030
* @author Christian Tzolov
31+
* @author Jonghoon Park
3132
* @since 0.8.0
3233
*/
3334
@JsonInclude(JsonInclude.Include.NON_NULL)
@@ -46,6 +47,39 @@ public class OpenAiImageOptions implements ImageOptions {
4647
@JsonProperty("model")
4748
private String model;
4849

50+
/**
51+
* Allows to set transparency for the background of the generated image(s). This
52+
* parameter is only supported for `gpt-image-1`. Must be one of `transparent`,
53+
* `opaque` or `auto` (default value). When `auto` is used, the model will
54+
* automatically determine the best background for the image. If `transparent`, the
55+
* output format needs to support transparency, so it should be set to either `png`
56+
* (default value) or `webp`.
57+
*/
58+
@JsonProperty("background")
59+
private String background;
60+
61+
/**
62+
* Control the content-moderation level for images generated by `gpt-image-1`. Must be
63+
* either `low` for less restrictive filtering or `auto` (default value).
64+
*/
65+
@JsonProperty("moderation")
66+
private String moderation;
67+
68+
/**
69+
* The compression level (0-100%) for the generated images. This parameter is only
70+
* supported for `gpt-image-1` with the `webp` or `jpeg` output formats, and defaults
71+
* to 100.
72+
*/
73+
@JsonProperty("output_compression")
74+
private Integer outputCompression;
75+
76+
/**
77+
* The format in which the generated images are returned. This parameter is only
78+
* supported for `gpt-image-1`. Must be one of `png`, `jpeg`, or `webp`.
79+
*/
80+
@JsonProperty("output_format")
81+
private String outputFormat;
82+
4983
/**
5084
* The width of the generated images. Must be one of 256, 512, or 1024 for dall-e-2.
5185
* This property is interconnected with the 'size' property - setting both width and
@@ -75,19 +109,22 @@ public class OpenAiImageOptions implements ImageOptions {
75109
private String quality;
76110

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

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

170+
public String getBackground() {
171+
return background;
172+
}
173+
174+
public void setBackground(String background) {
175+
this.background = background;
176+
}
177+
178+
public String getModeration() {
179+
return moderation;
180+
}
181+
182+
public void setModeration(String moderation) {
183+
this.moderation = moderation;
184+
}
185+
186+
public Integer getOutputCompression() {
187+
return outputCompression;
188+
}
189+
190+
public void setOutputCompression(Integer outputCompression) {
191+
this.outputCompression = outputCompression;
192+
}
193+
194+
public String getOutputFormat() {
195+
return outputFormat;
196+
}
197+
198+
public void setOutputFormat(String outputFormat) {
199+
this.outputFormat = outputFormat;
200+
}
201+
133202
public String getQuality() {
134203
return this.quality;
135204
}
@@ -246,14 +315,17 @@ public boolean equals(Object o) {
246315

247316
@Override
248317
public int hashCode() {
249-
return Objects.hash(this.n, this.model, this.width, this.height, this.quality, this.responseFormat, this.size,
250-
this.style, this.user);
318+
return Objects.hash(this.n, this.model, this.background, this.moderation, this.outputCompression,
319+
this.outputFormat, this.width, this.height, this.quality, this.responseFormat, this.size, this.style,
320+
this.user);
251321
}
252322

253323
@Override
254324
public String toString() {
255-
return "OpenAiImageOptions{" + "n=" + this.n + ", model='" + this.model + '\'' + ", width=" + this.width
256-
+ ", height=" + this.height + ", quality='" + this.quality + '\'' + ", responseFormat='"
325+
return "OpenAiImageOptions{" + "n=" + this.n + ", model='" + this.model + '\'' + ", background='"
326+
+ this.background + '\'' + ", moderation='" + this.moderation + '\'' + ", outputCompression='"
327+
+ this.outputCompression + '\'' + ", outputFormat='" + this.outputFormat + '\'' + ", width="
328+
+ this.width + ", height=" + this.height + ", quality='" + this.quality + '\'' + ", responseFormat='"
257329
+ this.responseFormat + '\'' + ", size='" + this.size + '\'' + ", style='" + this.style + '\''
258330
+ ", user='" + this.user + '\'' + '}';
259331
}
@@ -276,6 +348,26 @@ public Builder model(String model) {
276348
return this;
277349
}
278350

351+
public Builder background(String background) {
352+
this.options.setBackground(background);
353+
return this;
354+
}
355+
356+
public Builder moderation(String moderation) {
357+
this.options.setModeration(moderation);
358+
return this;
359+
}
360+
361+
public Builder outputCompression(Integer outputCompression) {
362+
this.options.setOutputCompression(outputCompression);
363+
return this;
364+
}
365+
366+
public Builder outputFormat(String outputFormat) {
367+
this.options.setOutputFormat(outputFormat);
368+
return this;
369+
}
370+
279371
public Builder quality(String quality) {
280372
this.options.setQuality(quality);
281373
return this;

models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -91,6 +91,11 @@ public static Builder builder() {
9191
*/
9292
public enum ImageModel {
9393

94+
/**
95+
* The state-of-the-art image generation model released in April 2025.
96+
*/
97+
GPT_IMAGE_1("gpt-image-1"),
98+
9499
/**
95100
* The latest DALL·E model released in Nov 2023.
96101
*/
@@ -120,6 +125,10 @@ public String getValue() {
120125
public record OpenAiImageRequest(
121126
@JsonProperty("prompt") String prompt,
122127
@JsonProperty("model") String model,
128+
@JsonProperty("background") String background,
129+
@JsonProperty("moderation") String moderation,
130+
@JsonProperty("output_compression") Integer outputCompression,
131+
@JsonProperty("output_format") String outputFormat,
123132
@JsonProperty("n") Integer n,
124133
@JsonProperty("quality") String quality,
125134
@JsonProperty("response_format") String responseFormat,
@@ -128,7 +137,7 @@ public record OpenAiImageRequest(
128137
@JsonProperty("user") String user) {
129138

130139
public OpenAiImageRequest(String prompt, String model) {
131-
this(prompt, model, null, null, null, null, null, null);
140+
this(prompt, model, null, null, null, null, null, null,null,null,null,null);
132141
}
133142
}
134143

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiTestConfiguration.java

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

8080
@Bean
8181
public OpenAiImageModel openAiImageModel(OpenAiImageApi imageApi) {
82-
return new OpenAiImageModel(imageApi);
82+
return OpenAiImageModel.builder().openAiImageApi(imageApi).build();
8383
}
8484

8585
@Bean

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiRetryTests.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -123,8 +123,11 @@ public void beforeEach() {
123123
.responseFormat(TranscriptResponseFormat.JSON)
124124
.build(),
125125
this.retryTemplate);
126-
this.imageModel = new OpenAiImageModel(this.openAiImageApi, OpenAiImageOptions.builder().build(),
127-
this.retryTemplate);
126+
this.imageModel = OpenAiImageModel.builder()
127+
.openAiImageApi(this.openAiImageApi)
128+
.defaultOptions(OpenAiImageOptions.builder().build())
129+
.retryTemplate(this.retryTemplate)
130+
.build();
128131
}
129132

130133
@Test

0 commit comments

Comments
 (0)