Align AzureOpenAiChatOptions with Azure ChatCompletionsOptions

Add missing options from Azure ChatCompletionsOptions to Spring AI
AzureOpenAiChatOptions. The following fields have been added:

- seed
- logprobs
- topLogprobs
- enhancements

This change ensures better alignment between the two option sets,
improving compatibility and feature parity.

Resolves https://github.com/spring-projects/spring-ai/issues/889
This commit is contained in:
Soby Chacko
2024-09-20 17:09:28 -04:00
committed by Mark Pollack
parent 35e6113233
commit 42dcb45f32
3 changed files with 166 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-2024 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.azure.openai;
import java.util.ArrayList;
@@ -92,6 +93,7 @@ import reactor.core.publisher.Mono;
* @author Thomas Vitale
* @author luocongqiu
* @author timostark
* @author Soby Chacko
* @see ChatModel
* @see com.azure.ai.openai.OpenAIClient
*/
@@ -456,6 +458,18 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
mergedAzureOptions.setModel(fromAzureOptions.getModel() != null ? fromAzureOptions.getModel()
: toSpringAiOptions.getDeploymentName());
mergedAzureOptions
.setSeed(fromAzureOptions.getSeed() != null ? fromAzureOptions.getSeed() : toSpringAiOptions.getSeed());
mergedAzureOptions.setLogprobs((fromAzureOptions.isLogprobs() != null && fromAzureOptions.isLogprobs())
|| (toSpringAiOptions.isLogprobs() != null && toSpringAiOptions.isLogprobs()));
mergedAzureOptions.setTopLogprobs(fromAzureOptions.getTopLogprobs() != null ? fromAzureOptions.getTopLogprobs()
: toSpringAiOptions.getTopLogProbs());
mergedAzureOptions.setEnhancements(fromAzureOptions.getEnhancements() != null
? fromAzureOptions.getEnhancements() : toSpringAiOptions.getEnhancements());
return mergedAzureOptions;
}
@@ -520,6 +534,22 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
mergedAzureOptions.setResponseFormat(toAzureResponseFormat(fromSpringAiOptions.getResponseFormat()));
}
if (fromSpringAiOptions.getSeed() != null) {
mergedAzureOptions.setSeed(fromSpringAiOptions.getSeed());
}
if (fromSpringAiOptions.isLogprobs() != null) {
mergedAzureOptions.setLogprobs(fromSpringAiOptions.isLogprobs());
}
if (fromSpringAiOptions.getTopLogProbs() != null) {
mergedAzureOptions.setTopLogprobs(fromSpringAiOptions.getTopLogProbs());
}
if (fromSpringAiOptions.getEnhancements() != null) {
mergedAzureOptions.setEnhancements(fromSpringAiOptions.getEnhancements());
}
return mergedAzureOptions;
}
@@ -566,6 +596,19 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
if (fromOptions.getResponseFormat() != null) {
copyOptions.setResponseFormat(fromOptions.getResponseFormat());
}
if (fromOptions.getSeed() != null) {
copyOptions.setSeed(fromOptions.getSeed());
}
copyOptions.setLogprobs(fromOptions.isLogprobs());
if (fromOptions.getTopLogprobs() != null) {
copyOptions.setTopLogprobs(fromOptions.getTopLogprobs());
}
if (fromOptions.getEnhancements() != null) {
copyOptions.setEnhancements(fromOptions.getEnhancements());
}
return copyOptions;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-2024 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.azure.openai;
import java.util.ArrayList;
@@ -21,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.azure.ai.openai.models.AzureChatEnhancementConfiguration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -40,6 +42,7 @@ import org.stringtemplate.v4.compiler.CodeGenerator.primary_return;
*
* @author Christian Tzolov
* @author Thomas Vitale
* @author Soby Chacko
*/
@JsonInclude(Include.NON_NULL)
public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptions {
@@ -165,6 +168,37 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptio
@JsonIgnore
private Boolean proxyToolCalls;
/**
* Seed value for deterministic sampling such that the same seed and parameters return
* the same result.
*/
@JsonProperty(value = "seed")
private Long seed;
/**
* Whether to return log probabilities of the output tokens or not. If true, returns
* the log probabilities of each output token returned in the `content` of `message`.
* This option is currently not available on the `gpt-4-vision-preview` model.
*/
@JsonProperty(value = "log_probs")
private Boolean logprobs;
/*
* An integer between 0 and 5 specifying the number of most likely tokens to return at
* each token position, each with an associated log probability. `logprobs` must be
* set to `true` if this parameter is used.
*/
@JsonProperty(value = "top_log_probs")
private Integer topLogProbs;
/*
* If provided, the configuration options for available Azure OpenAI chat
* enhancements.
*/
@NestedConfigurationProperty
@JsonIgnore
private AzureChatEnhancementConfiguration enhancements;
public static Builder builder() {
return new Builder();
}
@@ -259,6 +293,30 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptio
return this;
}
public Builder withSeed(Long seed) {
Assert.notNull(seed, "seed must not be null");
this.options.seed = seed;
return this;
}
public Builder withLogprobs(Boolean logprobs) {
Assert.notNull(logprobs, "logprobs must not be null");
this.options.logprobs = logprobs;
return this;
}
public Builder withTopLogprobs(Integer topLogprobs) {
Assert.notNull(topLogprobs, "topLogprobs must not be null");
this.options.topLogProbs = topLogprobs;
return this;
}
public Builder withEnhancements(AzureChatEnhancementConfiguration enhancements) {
Assert.notNull(enhancements, "enhancements must not be null");
this.options.enhancements = enhancements;
return this;
}
public AzureOpenAiChatOptions build() {
return this.options;
}
@@ -404,6 +462,38 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptio
return null;
}
public Long getSeed() {
return this.seed;
}
public void setSeed(Long seed) {
this.seed = seed;
}
public Boolean isLogprobs() {
return this.logprobs;
}
public void setLogprobs(Boolean logprobs) {
this.logprobs = logprobs;
}
public Integer getTopLogProbs() {
return this.topLogProbs;
}
public void setTopLogProbs(Integer topLogProbs) {
this.topLogProbs = topLogProbs;
}
public AzureChatEnhancementConfiguration getEnhancements() {
return this.enhancements;
}
public void setEnhancements(AzureChatEnhancementConfiguration enhancements) {
this.enhancements = enhancements;
}
@Override
public Boolean getProxyToolCalls() {
return this.proxyToolCalls;
@@ -432,6 +522,10 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions, ChatOptio
.withFunctionCallbacks(fromOptions.getFunctionCallbacks())
.withFunctions(fromOptions.getFunctions())
.withResponseFormat(fromOptions.getResponseFormat())
.withSeed(fromOptions.getSeed())
.withLogprobs(fromOptions.isLogprobs())
.withTopLogprobs(fromOptions.getTopLogProbs())
.withEnhancements(fromOptions.getEnhancements())
.build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-2024 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.
@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.azure.openai;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.models.AzureChatEnhancementConfiguration;
import com.azure.ai.openai.models.AzureChatOCREnhancementConfiguration;
import com.azure.ai.openai.models.ChatCompletionsJsonResponseFormat;
import com.azure.ai.openai.models.ChatCompletionsTextResponseFormat;
import org.junit.jupiter.api.Test;
@@ -34,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
* @author Soby Chacko
*/
public class AzureChatCompletionsOptionsTests {
@@ -42,6 +46,9 @@ public class AzureChatCompletionsOptionsTests {
OpenAIClient mockClient = Mockito.mock(OpenAIClient.class);
AzureChatEnhancementConfiguration mockAzureChatEnhancementConfiguration = Mockito
.mock(AzureChatEnhancementConfiguration.class);
var defaultOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName("DEFAULT_MODEL")
.withTemperature(66.6)
@@ -53,6 +60,10 @@ public class AzureChatCompletionsOptionsTests {
.withStop(List.of("foo", "bar"))
.withTopP(0.69)
.withUser("user")
.withSeed(123L)
.withLogprobs(true)
.withTopLogprobs(5)
.withEnhancements(mockAzureChatEnhancementConfiguration)
.withResponseFormat(AzureOpenAiResponseFormat.TEXT)
.build();
@@ -72,8 +83,15 @@ public class AzureChatCompletionsOptionsTests {
assertThat(requestOptions.getStop()).isEqualTo(List.of("foo", "bar"));
assertThat(requestOptions.getTopP()).isEqualTo(0.69);
assertThat(requestOptions.getUser()).isEqualTo("user");
assertThat(requestOptions.getSeed()).isEqualTo(123L);
assertThat(requestOptions.isLogprobs()).isTrue();
assertThat(requestOptions.getTopLogprobs()).isEqualTo(5);
assertThat(requestOptions.getEnhancements()).isEqualTo(mockAzureChatEnhancementConfiguration);
assertThat(requestOptions.getResponseFormat()).isInstanceOf(ChatCompletionsTextResponseFormat.class);
AzureChatEnhancementConfiguration anotherMockAzureChatEnhancementConfiguration = Mockito
.mock(AzureChatEnhancementConfiguration.class);
var runtimeOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName("PROMPT_MODEL")
.withTemperature(99.9)
@@ -85,6 +103,10 @@ public class AzureChatCompletionsOptionsTests {
.withStop(List.of("foo", "bar"))
.withTopP(0.111)
.withUser("user2")
.withSeed(1234L)
.withLogprobs(true)
.withTopLogprobs(4)
.withEnhancements(anotherMockAzureChatEnhancementConfiguration)
.withResponseFormat(AzureOpenAiResponseFormat.JSON)
.build();
@@ -102,6 +124,10 @@ public class AzureChatCompletionsOptionsTests {
assertThat(requestOptions.getStop()).isEqualTo(List.of("foo", "bar"));
assertThat(requestOptions.getTopP()).isEqualTo(0.111);
assertThat(requestOptions.getUser()).isEqualTo("user2");
assertThat(requestOptions.getSeed()).isEqualTo(1234L);
assertThat(requestOptions.isLogprobs()).isTrue();
assertThat(requestOptions.getTopLogprobs()).isEqualTo(4);
assertThat(requestOptions.getEnhancements()).isEqualTo(anotherMockAzureChatEnhancementConfiguration);
assertThat(requestOptions.getResponseFormat()).isInstanceOf(ChatCompletionsJsonResponseFormat.class);
}