diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai-sdk/src/test/java/org/springframework/ai/model/openaisdk/autoconfigure/OpenAiSdkImagePropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai-sdk/src/test/java/org/springframework/ai/model/openaisdk/autoconfigure/OpenAiSdkImagePropertiesTests.java index 60ddf53fe32..9f6637fbf4b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai-sdk/src/test/java/org/springframework/ai/model/openaisdk/autoconfigure/OpenAiSdkImagePropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai-sdk/src/test/java/org/springframework/ai/model/openaisdk/autoconfigure/OpenAiSdkImagePropertiesTests.java @@ -102,7 +102,9 @@ public void imageOptionsTest() { "spring.ai.openai-sdk.image.options.responseFormat=url", "spring.ai.openai-sdk.image.options.size=1024x1792", "spring.ai.openai-sdk.image.options.style=vivid", - "spring.ai.openai-sdk.image.options.user=userXYZ" + "spring.ai.openai-sdk.image.options.user=userXYZ", + "spring.ai.openai-sdk.image.options.outputFormat=jpeg", + "spring.ai.openai-sdk.image.options.outputCompression=75" ) // @formatter:on .withConfiguration(SpringAiTestAutoConfigurations.of(OpenAiSdkImageAutoConfiguration.class)) @@ -122,6 +124,8 @@ public void imageOptionsTest() { assertThat(imageProperties.getOptions().getSize()).isEqualTo("1024x1792"); assertThat(imageProperties.getOptions().getStyle()).isEqualTo("vivid"); assertThat(imageProperties.getOptions().getUser()).isEqualTo("userXYZ"); + assertThat(imageProperties.getOptions().getOutputFormat()).isEqualTo("jpeg"); + assertThat(imageProperties.getOptions().getOutputCompression()).isEqualTo(75); }); } diff --git a/models/spring-ai-openai-sdk/src/main/java/org/springframework/ai/openaisdk/OpenAiSdkImageOptions.java b/models/spring-ai-openai-sdk/src/main/java/org/springframework/ai/openaisdk/OpenAiSdkImageOptions.java index 711c1e2e9a5..062a071cdf8 100644 --- a/models/spring-ai-openai-sdk/src/main/java/org/springframework/ai/openaisdk/OpenAiSdkImageOptions.java +++ b/models/spring-ai-openai-sdk/src/main/java/org/springframework/ai/openaisdk/OpenAiSdkImageOptions.java @@ -82,6 +82,17 @@ public class OpenAiSdkImageOptions extends AbstractOpenAiSdkOptions implements I */ private String user; + /** + * The output format of the generated images. Must be one of png, jpeg, or webp. + */ + private String outputFormat; + + /** + * The compression level (0-100) for lossy formats like jpeg and webp. Only applies + * when outputFormat is jpeg or webp. + */ + private Integer outputCompression; + public static Builder builder() { return new Builder(); } @@ -160,6 +171,22 @@ public void setStyle(String style) { this.style = style; } + public String getOutputFormat() { + return this.outputFormat; + } + + public void setOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + } + + public Integer getOutputCompression() { + return this.outputCompression; + } + + public void setOutputCompression(Integer outputCompression) { + this.outputCompression = outputCompression; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { @@ -169,20 +196,23 @@ public boolean equals(Object o) { return Objects.equals(this.n, that.n) && Objects.equals(this.width, that.width) && Objects.equals(this.height, that.height) && Objects.equals(this.quality, that.quality) && Objects.equals(this.responseFormat, that.responseFormat) && Objects.equals(this.size, that.size) - && Objects.equals(this.style, that.style) && Objects.equals(this.user, that.user); + && Objects.equals(this.style, that.style) && Objects.equals(this.user, that.user) + && Objects.equals(this.outputFormat, that.outputFormat) + && Objects.equals(this.outputCompression, that.outputCompression); } @Override public int hashCode() { return Objects.hash(this.n, this.width, this.height, this.quality, this.responseFormat, this.size, this.style, - this.user); + this.user, this.outputFormat, this.outputCompression); } @Override public String toString() { return "OpenAiSdkImageOptions{" + "n=" + this.n + ", width=" + this.width + ", height=" + this.height + ", quality='" + this.quality + '\'' + ", responseFormat='" + this.responseFormat + '\'' + ", size='" - + this.size + '\'' + ", style='" + this.style + '\'' + ", user='" + this.user + '\'' + '}'; + + this.size + '\'' + ", style='" + this.style + '\'' + ", user='" + this.user + '\'' + + ", outputFormat='" + this.outputFormat + '\'' + ", outputCompression=" + this.outputCompression + '}'; } public ImageGenerateParams toOpenAiImageGenerateParams(ImagePrompt imagePrompt) { @@ -220,6 +250,12 @@ else if (this.getModel() != null) { if (this.getUser() != null) { builder.user(this.getUser()); } + if (this.getOutputFormat() != null) { + builder.outputFormat(ImageGenerateParams.OutputFormat.of(this.getOutputFormat().toLowerCase())); + } + if (this.getOutputCompression() != null) { + builder.outputCompression(this.getOutputCompression().longValue()); + } return builder.build(); } @@ -256,6 +292,8 @@ public Builder from(OpenAiSdkImageOptions fromOptions) { this.options.setSize(fromOptions.getSize()); this.options.setStyle(fromOptions.getStyle()); this.options.setUser(fromOptions.getUser()); + this.options.setOutputFormat(fromOptions.getOutputFormat()); + this.options.setOutputCompression(fromOptions.getOutputCompression()); return this; } @@ -322,6 +360,12 @@ public Builder merge(ImageOptions from) { if (castFrom.getUser() != null) { this.options.setUser(castFrom.getUser()); } + if (castFrom.getOutputFormat() != null) { + this.options.setOutputFormat(castFrom.getOutputFormat()); + } + if (castFrom.getOutputCompression() != null) { + this.options.setOutputCompression(castFrom.getOutputCompression()); + } } return this; } @@ -421,6 +465,16 @@ public Builder style(String style) { return this; } + public Builder outputFormat(String outputFormat) { + this.options.setOutputFormat(outputFormat); + return this; + } + + public Builder outputCompression(Integer outputCompression) { + this.options.setOutputCompression(outputCompression); + return this; + } + public OpenAiSdkImageOptions build() { return this.options; } diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/image/openai-sdk-image.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/image/openai-sdk-image.adoc index ad7409d651b..1c5908b595a 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/image/openai-sdk-image.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/image/openai-sdk-image.adoc @@ -191,7 +191,7 @@ The prefix `spring.ai.openai-sdk.image` is the property prefix for configuring t |==== | Property | Description | Default -| spring.ai.openai-sdk.image.options.model | The model to use for image generation. Available models: `dall-e-2`, `dall-e-3`. See the https://platform.openai.com/docs/models[models] page for more information. | `dall-e-3` +| spring.ai.openai-sdk.image.options.model | The model to use for image generation. Available models: `dall-e-2`, `dall-e-3`, `gpt-image-1`. See the https://platform.openai.com/docs/models[models] page for more information. | `dall-e-3` | spring.ai.openai-sdk.image.options.n | The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only n=1 is supported. | - | spring.ai.openai-sdk.image.options.quality | The quality of the image that will be generated. `hd` creates images with finer details and greater consistency across the image. This parameter is only supported for `dall-e-3`. Available values: `standard`, `hd`. | - | spring.ai.openai-sdk.image.options.response-format | The format in which the generated images are returned. Must be one of `url` or `b64_json`. | - @@ -200,6 +200,8 @@ The prefix `spring.ai.openai-sdk.image` is the property prefix for configuring t | spring.ai.openai-sdk.image.options.height | The height of the generated images. Must be one of 256, 512, or 1024 for `dall-e-2`. | - | spring.ai.openai-sdk.image.options.style | The style of the generated images. Must be one of `vivid` or `natural`. Vivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images. This parameter is only supported for `dall-e-3`. | - | spring.ai.openai-sdk.image.options.user | A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. | - +| spring.ai.openai-sdk.image.options.output-format | The output format of the generated images. Must be one of `png`, `jpeg`, or `webp`. This parameter is supported for `gpt-image-1`. | - +| spring.ai.openai-sdk.image.options.output-compression | The compression level (0-100) for lossy formats like `jpeg` and `webp`. Lower values mean higher compression. This parameter is supported for `gpt-image-1`. | - |==== TIP: All properties prefixed with `spring.ai.openai-sdk.image.options` can be overridden at runtime by adding request-specific <> to the `ImagePrompt` call. @@ -229,6 +231,32 @@ ImageResponse response = imageModel.call( .build())); ---- +=== Using GPT Image 1 with Output Format Options + +The `gpt-image-1` model supports additional options for controlling the output format and compression level. This is useful when you need to generate images in specific formats like JPEG with controlled file sizes: + +[source,java] +---- +ImageResponse response = imageModel.call( + new ImagePrompt("A photorealistic landscape of mountains at sunset", + OpenAiSdkImageOptions.builder() + .model("gpt-image-1") + .N(1) + .width(1024) + .height(1024) + .outputFormat("jpeg") + .outputCompression(75) + .build())); +---- + +The `outputFormat` option accepts: + +* `png` - Lossless format, larger file size (default) +* `jpeg` - Lossy format, smaller file size with configurable compression +* `webp` - Modern format with good compression and quality balance + +The `outputCompression` option (0-100) controls the compression level for lossy formats (`jpeg` and `webp`). Lower values result in higher compression (smaller files, lower quality), while higher values preserve more quality (larger files). + TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai-sdk/src/main/java/org/springframework/ai/openaisdk/OpenAiSdkImageOptions.java[OpenAiSdkImageOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/image/ImageOptions.java[ImageOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java[ImageOptionsBuilder#builder()]. == Sample Controller