Add the support of Azure OpenAI image generation

* Add docs
This commit is contained in:
Benoit Moussaud
2024-05-27 15:16:19 +02:00
committed by Mark Pollack
parent 574138a26a
commit a30cc9d84d
9 changed files with 837 additions and 1 deletions

View File

@@ -0,0 +1,190 @@
package org.springframework.ai.azure.openai;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.models.ImageGenerationOptions;
import com.azure.ai.openai.models.ImageGenerationQuality;
import com.azure.ai.openai.models.ImageGenerationResponseFormat;
import com.azure.ai.openai.models.ImageGenerationStyle;
import com.azure.ai.openai.models.ImageSize;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.azure.openai.metadata.AzureOpenAiImageGenerationMetadata;
import org.springframework.ai.azure.openai.metadata.AzureOpenAiImageResponseMetadata;
import org.springframework.ai.image.Image;
import org.springframework.ai.image.ImageGeneration;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.image.ImageResponseMetadata;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import java.util.List;
import static java.lang.String.format;
/**
* {@link ImageModel} implementation for {@literal Microsoft Azure AI} backed by
* {@link OpenAIClient}.
*
* @author Benoit Moussaud
* @see ImageModel
* @see com.azure.ai.openai.OpenAIClient
* @since 1.0.0 M1
*/
public class AzureOpenAiImageModel implements ImageModel {
private static final String DEFAULT_DEPLOYMENT_NAME = AzureOpenAiImageOptions.DEFAULT_IMAGE_MODEL;
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private final OpenAIClient openAIClient;
private final AzureOpenAiImageOptions defaultOptions;
public AzureOpenAiImageModel(OpenAIClient openAIClient) {
this(openAIClient, AzureOpenAiImageOptions.builder().withDeploymentName(DEFAULT_DEPLOYMENT_NAME).build());
}
public AzureOpenAiImageModel(OpenAIClient microsoftOpenAiClient, AzureOpenAiImageOptions options) {
Assert.notNull(microsoftOpenAiClient, "com.azure.ai.openai.OpenAIClient must not be null");
Assert.notNull(options, "AzureOpenAiChatOptions must not be null");
this.openAIClient = microsoftOpenAiClient;
this.defaultOptions = options;
}
public AzureOpenAiImageOptions getDefaultOptions() {
return defaultOptions;
}
@Override
public ImageResponse call(ImagePrompt imagePrompt) {
ImageGenerationOptions imageGenerationOptions = toOpenAiImageOptions(imagePrompt);
String deploymentOrModelName = getDeploymentName(imagePrompt);
if (logger.isTraceEnabled()) {
logger.trace("Azure ImageGenerationOptions call {} with the following options : {} ", deploymentOrModelName,
toPrettyJson(imageGenerationOptions));
}
var images = openAIClient.getImageGenerations(deploymentOrModelName, imageGenerationOptions);
if (logger.isTraceEnabled()) {
logger.trace("Azure ImageGenerations: {}", toPrettyJson(images));
}
List<ImageGeneration> imageGenerations = images.getData().stream().map(entry -> {
var image = new Image(entry.getUrl(), entry.getBase64Data());
var metadata = new AzureOpenAiImageGenerationMetadata(entry.getRevisedPrompt());
return new ImageGeneration(image, metadata);
}).toList();
ImageResponseMetadata openAiImageResponseMetadata = AzureOpenAiImageResponseMetadata.from(images);
return new ImageResponse(imageGenerations, openAiImageResponseMetadata);
}
private String toPrettyJson(Object object) {
ObjectMapper objectMapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.registerModule(new JavaTimeModule());
try {
return objectMapper.writeValueAsString(object);
}
catch (JsonProcessingException e) {
return "JsonProcessingException:" + e + " [" + object.toString() + "]";
}
}
/**
* Return the deployment-name if provided or use the model name.
* @param prompt the image prompt
* @return Return the deployment-name if provided or use the model name.
*/
private String getDeploymentName(ImagePrompt prompt) {
var runtimeImageOptions = prompt.getOptions();
if (this.defaultOptions != null) {
// Merge options fixed in beta7
// https://github.com/Azure/azure-sdk-for-java/issues/38183
runtimeImageOptions = ModelOptionsUtils.merge(runtimeImageOptions, this.defaultOptions,
AzureOpenAiImageOptions.class);
}
if (runtimeImageOptions != null) {
if (runtimeImageOptions instanceof AzureOpenAiImageOptions runtimeAzureOpenAiImageOptions) {
if (runtimeAzureOpenAiImageOptions.getDeploymentName() != null) {
return runtimeAzureOpenAiImageOptions.getDeploymentName();
}
}
}
// By default the one provided in the image prompt
return prompt.getOptions().getModel();
}
private ImageGenerationOptions toOpenAiImageOptions(ImagePrompt prompt) {
if (prompt.getInstructions().size() > 1) {
throw new RuntimeException(format("implementation support 1 image instruction only, found %s",
prompt.getInstructions().size()));
}
if (prompt.getInstructions().isEmpty()) {
throw new RuntimeException("please provide image instruction, current is empty");
}
var instructions = prompt.getInstructions().get(0).getText();
var runtimeImageOptions = prompt.getOptions();
ImageGenerationOptions imageGenerationOptions = new ImageGenerationOptions(instructions);
if (this.defaultOptions != null) {
// Merge options fixed in beta7
// https://github.com/Azure/azure-sdk-for-java/issues/38183
runtimeImageOptions = ModelOptionsUtils.merge(runtimeImageOptions, this.defaultOptions,
AzureOpenAiImageOptions.class);
}
if (runtimeImageOptions != null) {
// Handle portable image options
if (runtimeImageOptions.getN() != null) {
imageGenerationOptions.setN(runtimeImageOptions.getN());
}
if (runtimeImageOptions.getModel() != null) {
imageGenerationOptions.setModel(runtimeImageOptions.getModel());
}
if (runtimeImageOptions.getResponseFormat() != null) {
// b64_json or url
imageGenerationOptions.setResponseFormat(
ImageGenerationResponseFormat.fromString(runtimeImageOptions.getResponseFormat()));
}
if (runtimeImageOptions.getWidth() != null && runtimeImageOptions.getHeight() != null) {
imageGenerationOptions.setSize(
ImageSize.fromString(runtimeImageOptions.getWidth() + "x" + runtimeImageOptions.getHeight()));
}
// Handle OpenAI specific image options
if (runtimeImageOptions instanceof AzureOpenAiImageOptions runtimeAzureOpenAiImageOptions) {
if (runtimeAzureOpenAiImageOptions.getQuality() != null) {
imageGenerationOptions
.setQuality(ImageGenerationQuality.fromString(runtimeAzureOpenAiImageOptions.getQuality()));
}
if (runtimeAzureOpenAiImageOptions.getStyle() != null) {
imageGenerationOptions
.setStyle(ImageGenerationStyle.fromString(runtimeAzureOpenAiImageOptions.getStyle()));
}
if (runtimeAzureOpenAiImageOptions.getUser() != null) {
imageGenerationOptions.setUser(runtimeAzureOpenAiImageOptions.getUser());
}
}
}
return imageGenerationOptions;
}
}

View File

@@ -0,0 +1,287 @@
package org.springframework.ai.azure.openai;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.springframework.ai.image.ImageOptions;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The configuration information for a image generation request.
*
* @author Benoit Moussaud
* @since 1.0.0 M1
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AzureOpenAiImageOptions implements ImageOptions {
public static final String DEFAULT_IMAGE_MODEL = ImageModel.DALL_E_3.getValue();
/**
* The number of images to generate. Must be between 1 and 10. For dall-e-3, only n=1
* is supported.
*/
@JsonProperty("n")
private Integer n;
/**
* The model dall-e-3 or dall-e-2 By default dall-e-3
*/
@JsonProperty(value = "model")
private String model = ImageModel.DALL_E_3.value;
/**
* The deployment name as defined in Azure Open AI Studio when creating a deployment
* backed by an Azure OpenAI base model.
*/
@JsonProperty(value = "deployment_name")
private String deploymentName;
/**
* The width of the generated images. Must be one of 256, 512, or 1024 for dall-e-2.
*/
@JsonProperty("size_width")
private Integer width;
/**
* The height of the generated images. Must be one of 256, 512, or 1024 for dall-e-2.
*/
@JsonProperty("size_height")
private Integer height;
/**
* The quality of the image that will be generated. hd creates images with finer
* details and greater consistency across the image. This param is only supported for
* dall-e-3. standard or hd
*/
@JsonProperty("quality")
private String quality;
/**
* The format in which the generated images are returned. Must be one of url or
* b64_json.
*/
@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.
*/
@JsonProperty("size")
private String size;
/**
* 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 param is
* only supported for dall-e-3. natural or vivid
*/
@JsonProperty("style")
private String style;
/**
* A unique identifier representing your end-user, which can help OpenAI to monitor
* and detect abuse.
*/
@JsonProperty("user")
private String user;
public Integer getN() {
return n;
}
@Override
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public void setN(Integer n) {
this.n = n;
}
public Integer getWidth() {
return width;
}
public void setWidth(Integer width) {
this.width = width;
this.size = this.width + "x" + this.height;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
this.size = this.width + "x" + this.height;
}
public String getResponseFormat() {
return responseFormat;
}
public void setResponseFormat(String responseFormat) {
this.responseFormat = responseFormat;
}
public String getSize() {
if (this.size != null) {
return this.size;
}
return (this.width != null && this.height != null) ? this.width + "x" + this.height : null;
}
public void setSize(String size) {
this.size = size;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getQuality() {
return quality;
}
public void setQuality(String quality) {
this.quality = quality;
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
public String getDeploymentName() {
return deploymentName;
}
public void setDeploymentName(String deploymentName) {
this.deploymentName = deploymentName;
}
public static Builder builder() {
return new Builder();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof AzureOpenAiImageOptions that))
return false;
return Objects.equals(n, that.n) && Objects.equals(model, that.model)
&& Objects.equals(deploymentName, that.deploymentName) && Objects.equals(width, that.width)
&& Objects.equals(height, that.height) && Objects.equals(quality, that.quality)
&& Objects.equals(responseFormat, that.responseFormat) && Objects.equals(size, that.size)
&& Objects.equals(style, that.style) && Objects.equals(user, that.user);
}
@Override
public int hashCode() {
return Objects.hash(n, model, deploymentName, width, height, quality, responseFormat, size, style, user);
}
@Override
public String toString() {
return "AzureOpenAiImageOptions{" + "n=" + n + ", model='" + model + '\'' + ", deploymentName='"
+ deploymentName + '\'' + ", width=" + width + ", height=" + height + ", quality='" + quality + '\''
+ ", responseFormat='" + responseFormat + '\'' + ", size='" + size + '\'' + ", style='" + style + '\''
+ ", user='" + user + '\'' + '}';
}
public static class Builder {
private final AzureOpenAiImageOptions options;
private Builder() {
this.options = new AzureOpenAiImageOptions();
}
public Builder withN(Integer n) {
options.setN(n);
return this;
}
public Builder withModel(String model) {
options.setModel(model);
return this;
}
public Builder withDeploymentName(String deploymentName) {
options.setDeploymentName(deploymentName);
return this;
}
public Builder withResponseFormat(String responseFormat) {
options.setResponseFormat(responseFormat);
return this;
}
public Builder withWidth(Integer width) {
options.setWidth(width);
return this;
}
public Builder withHeight(Integer height) {
options.setHeight(height);
return this;
}
public Builder withUser(String user) {
options.setUser(user);
return this;
}
public AzureOpenAiImageOptions build() {
return options;
}
public Builder withStyle(String style) {
options.setStyle(style);
return this;
}
}
public enum ImageModel {
/**
* The latest DALL·E model released in Nov 2023.
*/
DALL_E_3("dall-e-3"),
/**
* The previous DALL·E model released in Nov 2022. The 2nd iteration of DALL·E
* with more realistic, accurate, and 4x greater resolution images than the
* original model.
*/
DALL_E_2("dall-e-2");
private final String value;
ImageModel(String model) {
this.value = model;
}
public String getValue() {
return this.value;
}
}
}

View File

@@ -0,0 +1,43 @@
package org.springframework.ai.azure.openai.metadata;
import org.springframework.ai.image.ImageGenerationMetadata;
import java.util.Objects;
/**
* Represents the metadata for image generation using Azure OpenAI.
*
* @author Benoit Moussaud
* @since 1.0.0 M1
*/
public class AzureOpenAiImageGenerationMetadata implements ImageGenerationMetadata {
private final String revisedPrompt;
public AzureOpenAiImageGenerationMetadata(String revisedPrompt) {
this.revisedPrompt = revisedPrompt;
}
public String getRevisedPrompt() {
return revisedPrompt;
}
public String toString() {
return "AzureOpenAiImageGenerationMetadata{" + "revisedPrompt='" + revisedPrompt + '\'' + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof AzureOpenAiImageGenerationMetadata that))
return false;
return Objects.equals(revisedPrompt, that.revisedPrompt);
}
@Override
public int hashCode() {
return Objects.hash(revisedPrompt);
}
}

View File

@@ -0,0 +1,55 @@
package org.springframework.ai.azure.openai.metadata;
import com.azure.ai.openai.models.ImageGenerations;
import org.springframework.ai.image.ImageResponseMetadata;
import org.springframework.util.Assert;
import java.util.HashMap;
import java.util.Objects;
/**
* Represents metadata associated with an image response from the Azure OpenAI image
* model. It provides additional information about the generative response from the Azure
* OpenAI image model, including the creation timestamp of the generated image.
*
* @author Benoit Moussaud
* @since 1.0.0 M1
*/
public class AzureOpenAiImageResponseMetadata extends HashMap<String, Object> implements ImageResponseMetadata {
private final Long created;
public static AzureOpenAiImageResponseMetadata from(ImageGenerations openAiImageResponse) {
Assert.notNull(openAiImageResponse, "OpenAiImageResponse must not be null");
return new AzureOpenAiImageResponseMetadata(openAiImageResponse.getCreatedAt().toEpochSecond());
}
protected AzureOpenAiImageResponseMetadata(Long created) {
this.created = created;
}
@Override
public Long getCreated() {
return this.created;
}
@Override
public String toString() {
return "AzureOpenAiImageResponseMetadata{" + "created=" + created + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof AzureOpenAiImageResponseMetadata that))
return false;
return Objects.equals(created, that.created);
}
@Override
public int hashCode() {
return Objects.hash(created);
}
}

View File

@@ -0,0 +1,79 @@
package org.springframework.ai.azure.openai.image;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
import org.springframework.ai.azure.openai.AzureOpenAiImageOptions;
import org.springframework.ai.azure.openai.metadata.AzureOpenAiImageGenerationMetadata;
import org.springframework.ai.image.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = AzureOpenAiImageModelIT.TestConfiguration.class)
@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+")
@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+")
public class AzureOpenAiImageModelIT {
@Autowired
protected ImageModel imageModel;
@Test
void imageAsUrlTest() {
var options = ImageOptionsBuilder.builder().withHeight(1024).withWidth(1024).build();
var instructions = """
A light cream colored mini golden doodle with a sign that contains the message "I'm on my way to BARCADE!".""";
ImagePrompt imagePrompt = new ImagePrompt(instructions, options);
ImageResponse imageResponse = imageModel.call(imagePrompt);
assertThat(imageResponse.getResults()).hasSize(1);
ImageResponseMetadata imageResponseMetadata = imageResponse.getMetadata();
assertThat(imageResponseMetadata.getCreated()).isPositive();
var generation = imageResponse.getResult();
Image image = generation.getOutput();
assertThat(image.getUrl()).isNotEmpty();
// System.out.println(image.getUrl());
assertThat(image.getB64Json()).isNull();
var imageGenerationMetadata = generation.getMetadata();
Assertions.assertThat(imageGenerationMetadata).isInstanceOf(AzureOpenAiImageGenerationMetadata.class);
AzureOpenAiImageGenerationMetadata openAiImageGenerationMetadata = (AzureOpenAiImageGenerationMetadata) imageGenerationMetadata;
assertThat(openAiImageGenerationMetadata).isNotNull();
assertThat(openAiImageGenerationMetadata.getRevisedPrompt()).isNotBlank();
}
@SpringBootConfiguration
public static class TestConfiguration {
@Bean
public OpenAIClient openAIClient() {
return new OpenAIClientBuilder().credential(new AzureKeyCredential(System.getenv("AZURE_OPENAI_API_KEY")))
.endpoint(System.getenv("AZURE_OPENAI_ENDPOINT"))
.buildClient();
}
@Bean
public AzureOpenAiImageModel azureOpenAiImageModel(OpenAIClient openAIClient) {
return new AzureOpenAiImageModel(openAIClient,
AzureOpenAiImageOptions.builder().withDeploymentName("Dalle3").build());
}
}
}

View File

@@ -44,6 +44,7 @@
*** xref:api/embeddings/minimax-embeddings.adoc[MiniMax]
*** xref:api/embeddings/zhipuai-embeddings.adoc[ZhiPu AI]
** xref:api/imageclient.adoc[]
*** xref:api/image/azure-openai-image.adoc[Azure OpenAI]
*** xref:api/image/openai-image.adoc[OpenAI]
*** xref:api/image/stabilityai-image.adoc[Stability]
*** xref:api/image/zhipuai-image.adoc[ZhiPuAI]

View File

@@ -0,0 +1,129 @@
= Azure OpenAI Image Generation
Spring AI supports DALL-E, the Image generation model from Azure OpenAI.
== Prerequisites
Obtain your Azure OpenAI `endpoint` and `api-key` from the Azure OpenAI Service section on the link:https://portal.azure.com[Azure Portal].
Spring AI defines a configuration property named `spring.ai.azure.openai.api-key` that you should set to the value of the `API Key` obtained from Azure.
There is also a configuration property named `spring.ai.azure.openai.endpoint` that you should set to the endpoint URL obtained when provisioning your model in Azure.
Exporting environment variables is one way to set these configuration properties:
[source,shell]
----
export SPRING_AI_AZURE_OPENAI_API_KEY=<INSERT KEY HERE>
export SPRING_AI_AZURE_OPENAI_ENDPOINT=<INSERT ENDPOINT URL HERE>
----
=== Deployment Name
To use run Azure AI applications, create an Azure AI Deployment through the [Azure AI Portal](https://oai.azure.com/portal).
In Azure, each client must specify a `Deployment Name` to connect to the Azure OpenAI service.
It's essential to understand that the `Deployment Name` is different from the model you choose to deploy
For instance, a deployment named 'MyImgAiDeployment' could be configured to use either the `Dalle3` model or the `Dalle2` model.
For now, to keep things simple, you can create a deployment using the following settings:
Deployment Name: `MyImgAiDeployment`
Model Name: `Dalle3`
This Azure configuration will align with the default configurations of the Spring Boot Azure AI Starter and its Autoconfiguration feature.
If you use a different Deployment Name, update the configuration property accordingly:
```
spring.ai.azure.openai.image.options.deployment-name=<my deployment name>
```
The different deployment structures of Azure OpenAI and OpenAI leads to a property in the Azure OpenAI client library named `deploymentOrModelName`.
This is because in OpenAI there is no `Deployment Name`, only a `Model Name`.
=== Add Repositories and BOM
Spring AI artifacts are published in Spring Milestone and Snapshot repositories. Refer to the xref:getting-started.adoc#repositories[Repositories] section to add these repositories to your build system.
To help with dependency management, Spring AI provides a BOM (bill of materials) to ensure that a consistent version of Spring AI is used throughout the entire project. Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build system.
== Auto-configuration
Spring AI provides Spring Boot auto-configuration for the Azure OpenAI Chat Client.
To enable it add the following dependency to your project's Maven `pom.xml` file:
[source, xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency>
----
or to your Gradle `build.gradle` build file.
[source,groovy]
----
dependencies {
implementation 'org.springframework.ai:spring-ai-azure-openai-spring-boot-starter'
}
----
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
=== Image Generation Properties
The prefix `spring.ai.openai.image` is the property prefix that lets you configure the `ImageModel` implementation for OpenAI.
[cols="3,5,1"]
|====
| Property | Description | Default
| spring.ai.azure.openai.image.enabled | Enable OpenAI image model. | true
| spring.ai.azure.openai.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.azure.openai.image.options.model | The model to use for image generation. | AzureOpenAiImageOptions.DEFAULT_IMAGE_MODEL
| spring.ai.azure.openai.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. | -
| spring.ai.azure.openai.image.options.response_format | The format in which the generated images are returned. Must be one of URL or b64_json. | -
| `spring.ai.openai.image.options.size` | 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. | -
| `spring.ai.openai.image.options.size_width` | The width of the generated images. Must be one of 256, 512, or 1024 for dall-e-2. | -
| `spring.ai.openai.image.options.size_height`| The height of the generated images. Must be one of 256, 512, or 1024 for dall-e-2. | -
| `spring.ai.openai.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.image.options.user` | A unique identifier representing your end-user, which can help Azure OpenAI to monitor and detect abuse. | -
|====
==== Connection Properties
The prefix `spring.ai.openai` is used as the property prefix that lets you connect to Azure OpenAI.
[cols="3,5,1"]
|====
| Property | Description | Default
| spring.ai.azure.openai.endpoint | The URL to connect to | https://my-dalle3.openai.azure.com/
| spring.ai.azure.openai.apiKey | The API Key | -
|====
== Runtime Options [[image-options]]
The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageOptions.java[OpenAiImageOptions.java] provides model configurations, such as the model to use, the quality, the size, etc.
On start-up, the default options can be configured with the `AzureOpenAiImageModel(OpenAiImageApi openAiImageApi)` constructor and the `withDefaultOptions(OpenAiImageOptions defaultOptions)` method. Alternatively, use the `spring.ai.azure.openai.image.options.*` properties described previously.
At runtime you can override the default options by adding new, request specific, options to the `ImagePrompt` call.
For example to override the OpenAI specific options such as quality and the number of images to create, use the following code example:
[source,java]
----
ImageResponse response = azureOpenaiImageModel.call(
new ImagePrompt("A light cream colored mini golden doodle",
OpenAiImageOptions.builder()
.withQuality("hd")
.withN(4)
.withHeight(1024)
.withWidth(1024).build())
);
----
TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiImageOptions.java[AzureOpenAiImageOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/image/ImageOptions.java[ImageOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java[ImageOptionsBuilder#builder()].

View File

@@ -24,6 +24,7 @@ import com.azure.core.util.ClientOptions;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -39,7 +40,7 @@ import org.springframework.util.CollectionUtils;
@AutoConfiguration
@ConditionalOnClass({ OpenAIClientBuilder.class, AzureOpenAiChatModel.class })
@EnableConfigurationProperties({ AzureOpenAiChatProperties.class, AzureOpenAiEmbeddingProperties.class,
AzureOpenAiConnectionProperties.class })
AzureOpenAiConnectionProperties.class, AzureOpenAiImageOptionsProperties.class })
public class AzureOpenAiAutoConfiguration {
@Bean
@@ -89,4 +90,13 @@ public class AzureOpenAiAutoConfiguration {
return manager;
}
@Bean
@ConditionalOnProperty(prefix = AzureOpenAiImageOptionsProperties.CONFIG_PREFIX, name = "enabled",
havingValue = "true", matchIfMissing = true)
public AzureOpenAiImageModel azureOpenAiImageClient(OpenAIClient openAIClient,
AzureOpenAiImageOptionsProperties imageProperties) {
return new AzureOpenAiImageModel(openAIClient, imageProperties.getOptions());
}
}

View File

@@ -0,0 +1,42 @@
package org.springframework.ai.autoconfigure.azure.openai;
import org.springframework.ai.azure.openai.AzureOpenAiImageOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
* Configuration properties for Azure OpenAI image generation options.
*
* @author Benoit Moussaud
* @since 1.0.0 M1
*/
@ConfigurationProperties(AzureOpenAiImageOptionsProperties.CONFIG_PREFIX)
public class AzureOpenAiImageOptionsProperties {
public static final String CONFIG_PREFIX = "spring.ai.azure.openai.image";
/**
* Enable Azure OpenAI chat client.
*/
private boolean enabled = true;
@NestedConfigurationProperty
private AzureOpenAiImageOptions options = AzureOpenAiImageOptions.builder().build();
public AzureOpenAiImageOptions getOptions() {
return options;
}
public void setOptions(AzureOpenAiImageOptions options) {
this.options = options;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}