Skip to content

Commit 35101e7

Browse files
apappascsilayaperumalg
authored andcommitted
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 <[email protected]> Signed-off-by: Alexandros Pappas <[email protected]>
1 parent b525309 commit 35101e7

File tree

3 files changed

+57
-9
lines changed

3 files changed

+57
-9
lines changed

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ public class OpenAiChatOptions implements FunctionCallingOptions {
182182
* Developer-defined tags and values used for filtering completions in the <a href="https://platform.openai.com/chat-completions">dashboard</a>.
183183
*/
184184
private @JsonProperty("metadata") Map<String, String> metadata;
185+
186+
/**
187+
* Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high.
188+
* Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
189+
* Optional. Defaults to medium.
190+
* Only for 'o1' models.
191+
*/
192+
private @JsonProperty("reasoning_effort") String reasoningEffort;
193+
185194
/**
186195
* OpenAI Tool Function Callbacks to register with the ChatModel.
187196
* For Prompt Options the functionCallbacks are automatically enabled for the duration of the prompt execution.
@@ -256,6 +265,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) {
256265
.toolContext(fromOptions.getToolContext())
257266
.store(fromOptions.getStore())
258267
.metadata(fromOptions.getMetadata())
268+
.reasoningEffort(fromOptions.getReasoningEffort())
259269
.build();
260270
}
261271

@@ -520,6 +530,14 @@ public void setMetadata(Map<String, String> metadata) {
520530
this.metadata = metadata;
521531
}
522532

533+
public String getReasoningEffort() {
534+
return this.reasoningEffort;
535+
}
536+
537+
public void setReasoningEffort(String reasoningEffort) {
538+
this.reasoningEffort = reasoningEffort;
539+
}
540+
523541
@Override
524542
public OpenAiChatOptions copy() {
525543
return OpenAiChatOptions.fromOptions(this);
@@ -532,7 +550,7 @@ public int hashCode() {
532550
this.streamOptions, this.seed, this.stop, this.temperature, this.topP, this.tools, this.toolChoice,
533551
this.user, this.parallelToolCalls, this.functionCallbacks, this.functions, this.httpHeaders,
534552
this.proxyToolCalls, this.toolContext, this.outputModalities, this.outputAudio, this.store,
535-
this.metadata);
553+
this.metadata, this.reasoningEffort);
536554
}
537555

538556
@Override
@@ -563,7 +581,8 @@ public boolean equals(Object o) {
563581
&& Objects.equals(this.proxyToolCalls, other.proxyToolCalls)
564582
&& Objects.equals(this.outputModalities, other.outputModalities)
565583
&& Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store)
566-
&& Objects.equals(this.metadata, other.metadata);
584+
&& Objects.equals(this.metadata, other.metadata)
585+
&& Objects.equals(this.reasoningEffort, other.reasoningEffort);
567586
}
568587

569588
@Override
@@ -740,6 +759,11 @@ public Builder metadata(Map<String, String> metadata) {
740759
return this;
741760
}
742761

762+
public Builder reasoningEffort(String reasoningEffort) {
763+
this.options.reasoningEffort = reasoningEffort;
764+
return this;
765+
}
766+
743767
public OpenAiChatOptions build() {
744768
return this.options;
745769
}

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
* @author Mariusz Bernacki
5959
* @author Thomas Vitale
6060
* @author David Frizelle
61+
* @author Alexandros Pappas
6162
*/
6263
public class OpenAiApi {
6364

@@ -913,7 +914,8 @@ public record ChatCompletionRequest(// @formatter:off
913914
@JsonProperty("tools") List<FunctionTool> tools,
914915
@JsonProperty("tool_choice") Object toolChoice,
915916
@JsonProperty("parallel_tool_calls") Boolean parallelToolCalls,
916-
@JsonProperty("user") String user) {
917+
@JsonProperty("user") String user,
918+
@JsonProperty("reasoning_effort") String reasoningEffort) {
917919

918920
/**
919921
* Shortcut constructor for a chat completion request with the given messages, model and temperature.
@@ -925,7 +927,7 @@ public record ChatCompletionRequest(// @formatter:off
925927
public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model, Double temperature) {
926928
this(messages, model, null, null, null, null, null, null, null, null, null, null, null, null, null,
927929
null, null, null, false, null, temperature, null,
928-
null, null, null, null);
930+
null, null, null, null, null);
929931
}
930932

931933
/**
@@ -939,7 +941,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
939941
this(messages, model, null, null, null, null, null, null,
940942
null, null, null, List.of(OutputModality.AUDIO, OutputModality.TEXT), audio, null, null,
941943
null, null, null, stream, null, null, null,
942-
null, null, null, null);
944+
null, null, null, null, null);
943945
}
944946

945947
/**
@@ -954,7 +956,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
954956
public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model, Double temperature, boolean stream) {
955957
this(messages, model, null, null, null, null, null, null, null, null, null,
956958
null, null, null, null, null, null, null, stream, null, temperature, null,
957-
null, null, null, null);
959+
null, null, null, null, null);
958960
}
959961

960962
/**
@@ -970,7 +972,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
970972
List<FunctionTool> tools, Object toolChoice) {
971973
this(messages, model, null, null, null, null, null, null, null, null, null,
972974
null, null, null, null, null, null, null, false, null, 0.8, null,
973-
tools, toolChoice, null, null);
975+
tools, toolChoice, null, null, null);
974976
}
975977

976978
/**
@@ -983,7 +985,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
983985
public ChatCompletionRequest(List<ChatCompletionMessage> messages, Boolean stream) {
984986
this(messages, null, null, null, null, null, null, null, null, null, null,
985987
null, null, null, null, null, null, null, stream, null, null, null,
986-
null, null, null, null);
988+
null, null, null, null, null);
987989
}
988990

989991
/**
@@ -996,7 +998,7 @@ public ChatCompletionRequest streamOptions(StreamOptions streamOptions) {
996998
return new ChatCompletionRequest(this.messages, this.model, this.store, this.metadata, this.frequencyPenalty, this.logitBias, this.logprobs,
997999
this.topLogprobs, this.maxTokens, this.maxCompletionTokens, this.n, this.outputModalities, this.audioParameters, this.presencePenalty,
9981000
this.responseFormat, this.seed, this.serviceTier, this.stop, this.stream, streamOptions, this.temperature, this.topP,
999-
this.tools, this.toolChoice, this.parallelToolCalls, this.user);
1001+
this.tools, this.toolChoice, this.parallelToolCalls, this.user, this.reasoningEffort);
10001002
}
10011003

10021004
/**

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Base64;
2121
import java.util.List;
2222

23+
import org.junit.jupiter.api.Disabled;
2324
import org.junit.jupiter.api.Test;
2425
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
2526
import reactor.core.publisher.Flux;
@@ -40,6 +41,7 @@
4041
/**
4142
* @author Christian Tzolov
4243
* @author Thomas Vitale
44+
* @author Alexandros Pappas
4345
*/
4446
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
4547
public class OpenAiApiIT {
@@ -66,6 +68,26 @@ void chatCompletionStream() {
6668
assertThat(response.collectList().block()).isNotNull();
6769
}
6870

71+
@Test
72+
@Disabled("The reasoning_effort option is only available in o1 models.")
73+
void validateReasoningTokens() {
74+
ChatCompletionMessage userMessage = new ChatCompletionMessage(
75+
"If a train travels 100 miles in 2 hours, what is its average speed?", ChatCompletionMessage.Role.USER);
76+
ChatCompletionRequest request = new ChatCompletionRequest(List.of(userMessage), "o1", null, null, null, null,
77+
null, null, null, null, null, null, null, null, null, null, null, null, false, null, null, null, null,
78+
null, null, null, "low");
79+
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);
80+
81+
assertThat(response).isNotNull();
82+
assertThat(response.getBody()).isNotNull();
83+
84+
OpenAiApi.Usage.CompletionTokenDetails completionTokenDetails = response.getBody()
85+
.usage()
86+
.completionTokenDetails();
87+
assertThat(completionTokenDetails).isNotNull();
88+
assertThat(completionTokenDetails.reasoningTokens()).isPositive();
89+
}
90+
6991
@Test
7092
void embeddings() {
7193
ResponseEntity<EmbeddingList<Embedding>> response = this.openAiApi

0 commit comments

Comments
 (0)