feat: Add reasoningEffort parameter to OpenAI API and Chat Options

This commit introduces the `reasoningEffort` parameter to the OpenAI API integration, allowing control over the reasoning effort used by models like `o1-mini`.

Changes:
- Adds `reasoningEffort` field to `OpenAiApi.ChatCompletionRequest`.
- Adds `reasoningEffort` field and builder method to `OpenAiChatOptions`.

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
This commit is contained in:
Alexandros Pappas
2025-02-03 19:56:40 +01:00
committed by Ilayaperumal Gopinathan
parent b525309e8c
commit 35101e75ae
3 changed files with 57 additions and 9 deletions

View File

@@ -182,6 +182,15 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
* Developer-defined tags and values used for filtering completions in the <a href="https://platform.openai.com/chat-completions">dashboard</a>.
*/
private @JsonProperty("metadata") Map<String, String> metadata;
/**
* Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high.
* Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
* Optional. Defaults to medium.
* Only for 'o1' models.
*/
private @JsonProperty("reasoning_effort") String reasoningEffort;
/**
* OpenAI Tool Function Callbacks to register with the ChatModel.
* For Prompt Options the functionCallbacks are automatically enabled for the duration of the prompt execution.
@@ -256,6 +265,7 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
.toolContext(fromOptions.getToolContext())
.store(fromOptions.getStore())
.metadata(fromOptions.getMetadata())
.reasoningEffort(fromOptions.getReasoningEffort())
.build();
}
@@ -520,6 +530,14 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
this.metadata = metadata;
}
public String getReasoningEffort() {
return this.reasoningEffort;
}
public void setReasoningEffort(String reasoningEffort) {
this.reasoningEffort = reasoningEffort;
}
@Override
public OpenAiChatOptions copy() {
return OpenAiChatOptions.fromOptions(this);
@@ -532,7 +550,7 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
this.streamOptions, this.seed, this.stop, this.temperature, this.topP, this.tools, this.toolChoice,
this.user, this.parallelToolCalls, this.functionCallbacks, this.functions, this.httpHeaders,
this.proxyToolCalls, this.toolContext, this.outputModalities, this.outputAudio, this.store,
this.metadata);
this.metadata, this.reasoningEffort);
}
@Override
@@ -563,7 +581,8 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
&& Objects.equals(this.proxyToolCalls, other.proxyToolCalls)
&& Objects.equals(this.outputModalities, other.outputModalities)
&& Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store)
&& Objects.equals(this.metadata, other.metadata);
&& Objects.equals(this.metadata, other.metadata)
&& Objects.equals(this.reasoningEffort, other.reasoningEffort);
}
@Override
@@ -740,6 +759,11 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
return this;
}
public Builder reasoningEffort(String reasoningEffort) {
this.options.reasoningEffort = reasoningEffort;
return this;
}
public OpenAiChatOptions build() {
return this.options;
}

View File

@@ -58,6 +58,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Mariusz Bernacki
* @author Thomas Vitale
* @author David Frizelle
* @author Alexandros Pappas
*/
public class OpenAiApi {
@@ -913,7 +914,8 @@ public class OpenAiApi {
@JsonProperty("tools") List<FunctionTool> tools,
@JsonProperty("tool_choice") Object toolChoice,
@JsonProperty("parallel_tool_calls") Boolean parallelToolCalls,
@JsonProperty("user") String user) {
@JsonProperty("user") String user,
@JsonProperty("reasoning_effort") String reasoningEffort) {
/**
* Shortcut constructor for a chat completion request with the given messages, model and temperature.
@@ -925,7 +927,7 @@ public class OpenAiApi {
public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model, Double temperature) {
this(messages, model, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, false, null, temperature, null,
null, null, null, null);
null, null, null, null, null);
}
/**
@@ -939,7 +941,7 @@ public class OpenAiApi {
this(messages, model, null, null, null, null, null, null,
null, null, null, List.of(OutputModality.AUDIO, OutputModality.TEXT), audio, null, null,
null, null, null, stream, null, null, null,
null, null, null, null);
null, null, null, null, null);
}
/**
@@ -954,7 +956,7 @@ public class OpenAiApi {
public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model, Double temperature, boolean stream) {
this(messages, model, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, stream, null, temperature, null,
null, null, null, null);
null, null, null, null, null);
}
/**
@@ -970,7 +972,7 @@ public class OpenAiApi {
List<FunctionTool> tools, Object toolChoice) {
this(messages, model, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, false, null, 0.8, null,
tools, toolChoice, null, null);
tools, toolChoice, null, null, null);
}
/**
@@ -983,7 +985,7 @@ public class OpenAiApi {
public ChatCompletionRequest(List<ChatCompletionMessage> messages, Boolean stream) {
this(messages, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, stream, null, null, null,
null, null, null, null);
null, null, null, null, null);
}
/**
@@ -996,7 +998,7 @@ public class OpenAiApi {
return new ChatCompletionRequest(this.messages, this.model, this.store, this.metadata, this.frequencyPenalty, this.logitBias, this.logprobs,
this.topLogprobs, this.maxTokens, this.maxCompletionTokens, this.n, this.outputModalities, this.audioParameters, this.presencePenalty,
this.responseFormat, this.seed, this.serviceTier, this.stop, this.stream, streamOptions, this.temperature, this.topP,
this.tools, this.toolChoice, this.parallelToolCalls, this.user);
this.tools, this.toolChoice, this.parallelToolCalls, this.user, this.reasoningEffort);
}
/**

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.Base64;
import java.util.List;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import reactor.core.publisher.Flux;
@@ -40,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Christian Tzolov
* @author Thomas Vitale
* @author Alexandros Pappas
*/
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
public class OpenAiApiIT {
@@ -66,6 +68,26 @@ public class OpenAiApiIT {
assertThat(response.collectList().block()).isNotNull();
}
@Test
@Disabled("The reasoning_effort option is only available in o1 models.")
void validateReasoningTokens() {
ChatCompletionMessage userMessage = new ChatCompletionMessage(
"If a train travels 100 miles in 2 hours, what is its average speed?", ChatCompletionMessage.Role.USER);
ChatCompletionRequest request = new ChatCompletionRequest(List.of(userMessage), "o1", null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, false, null, null, null, null,
null, null, null, "low");
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);
assertThat(response).isNotNull();
assertThat(response.getBody()).isNotNull();
OpenAiApi.Usage.CompletionTokenDetails completionTokenDetails = response.getBody()
.usage()
.completionTokenDetails();
assertThat(completionTokenDetails).isNotNull();
assertThat(completionTokenDetails.reasoningTokens()).isPositive();
}
@Test
void embeddings() {
ResponseEntity<EmbeddingList<Embedding>> response = this.openAiApi