Skip to content

Commit 870bb6d

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

File tree

10 files changed

+285
-26
lines changed

10 files changed

+285
-26
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

+137-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;
@@ -306,41 +398,73 @@ public Builder user(String user) {
306398
return this;
307399
}
308400

401+
/**
402+
* @deprecated use {@link #N(Integer)} instead.
403+
*/
404+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
309405
public Builder withN(Integer n) {
310406
this.options.setN(n);
311407
return this;
312408
}
313409

410+
/**
411+
* @deprecated use {@link #model(String)} instead.
412+
*/
413+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
314414
public Builder withModel(String model) {
315415
this.options.setModel(model);
316416
return this;
317417
}
318418

419+
/**
420+
* @deprecated use {@link #quality(String)} instead.
421+
*/
422+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
319423
public Builder withQuality(String quality) {
320424
this.options.setQuality(quality);
321425
return this;
322426
}
323427

428+
/**
429+
* @deprecated use {@link #responseFormat(String)} instead.
430+
*/
431+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
324432
public Builder withResponseFormat(String responseFormat) {
325433
this.options.setResponseFormat(responseFormat);
326434
return this;
327435
}
328436

437+
/**
438+
* @deprecated use {@link #width(Integer)} instead.
439+
*/
440+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
329441
public Builder withWidth(Integer width) {
330442
this.options.setWidth(width);
331443
return this;
332444
}
333445

446+
/**
447+
* @deprecated use {@link #height(Integer)} instead.
448+
*/
449+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
334450
public Builder withHeight(Integer height) {
335451
this.options.setHeight(height);
336452
return this;
337453
}
338454

455+
/**
456+
* @deprecated use {@link #style(String)} instead.
457+
*/
458+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
339459
public Builder withStyle(String style) {
340460
this.options.setStyle(style);
341461
return this;
342462
}
343463

464+
/**
465+
* @deprecated use {@link #user(String)} instead.
466+
*/
467+
@Deprecated(forRemoval = true, since = "1.0.0-M8")
344468
public Builder withUser(String user) {
345469
this.options.setUser(user);
346470
return this;

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -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

+1-1
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)