Skip to content

Commit fef454b

Browse files
authored
feat: add gpt-5 models (#4086)
- update gpt-5 tests - add verbosity parameter - udpate documentation and add tests Fixes #4086 Auto-cherry-pick to 1.0.x Signed-off-by: Alexandros Pappas <[email protected]>
1 parent a376869 commit fef454b

File tree

4 files changed

+113
-19
lines changed

4 files changed

+113
-19
lines changed

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,15 @@ public class OpenAiChatOptions implements ToolCallingChatOptions {
196196
*/
197197
private @JsonProperty("reasoning_effort") String reasoningEffort;
198198

199+
/**
200+
* verbosity: string or null
201+
* Optional - Defaults to medium
202+
* Constrains the verbosity of the model's response. Lower values will result in more concise responses, while higher values will result in more verbose responses.
203+
* Currently supported values are low, medium, and high.
204+
* If specified, the model will use web search to find relevant information to answer the user's question.
205+
*/
206+
private @JsonProperty("verbosity") String verbosity;
207+
199208
/**
200209
* This tool searches the web for relevant results to use in a response.
201210
*/
@@ -268,6 +277,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) {
268277
.metadata(fromOptions.getMetadata())
269278
.reasoningEffort(fromOptions.getReasoningEffort())
270279
.webSearchOptions(fromOptions.getWebSearchOptions())
280+
.verbosity(fromOptions.getVerbosity())
271281
.build();
272282
}
273283

@@ -564,6 +574,14 @@ public void setWebSearchOptions(WebSearchOptions webSearchOptions) {
564574
this.webSearchOptions = webSearchOptions;
565575
}
566576

577+
public String getVerbosity() {
578+
return this.verbosity;
579+
}
580+
581+
public void setVerbosity(String verbosity) {
582+
this.verbosity = verbosity;
583+
}
584+
567585
@Override
568586
public OpenAiChatOptions copy() {
569587
return OpenAiChatOptions.fromOptions(this);
@@ -609,7 +627,8 @@ public boolean equals(Object o) {
609627
&& Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store)
610628
&& Objects.equals(this.metadata, other.metadata)
611629
&& Objects.equals(this.reasoningEffort, other.reasoningEffort)
612-
&& Objects.equals(this.webSearchOptions, other.webSearchOptions);
630+
&& Objects.equals(this.webSearchOptions, other.webSearchOptions)
631+
&& Objects.equals(this.verbosity, other.verbosity);
613632
}
614633

615634
@Override
@@ -802,6 +821,11 @@ public Builder webSearchOptions(WebSearchOptions webSearchOptions) {
802821
return this;
803822
}
804823

824+
public Builder verbosity(String verbosity) {
825+
this.options.verbosity = verbosity;
826+
return this;
827+
}
828+
805829
public OpenAiChatOptions build() {
806830
return this.options;
807831
}

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

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -482,18 +482,37 @@ public enum ChatModel implements ChatModelDescription {
482482
GPT_5("gpt-5"),
483483

484484
/**
485-
* <b>GPT-5 (2025-08-07)</b> is a specific snapshot of the GPT-5 model from August
486-
* 7, 2025, providing enhanced capabilities for complex reasoning and
487-
* problem-solving tasks.
485+
* GPT-5 mini is a faster, more cost-efficient version of GPT-5. It's great for
486+
* well-defined tasks and precise prompts.
488487
* <p>
489-
* Note: GPT-5 models require temperature=1.0 (default value). Custom temperature
490-
* values are not supported and will cause errors.
488+
* Model ID: gpt-5-mini
489+
* <p>
490+
* See:
491+
* <a href="https://platform.openai.com/docs/models/gpt-5-mini">gpt-5-mini</a>
492+
*/
493+
GPT_5_MINI("gpt-5-mini"),
494+
495+
/**
496+
* GPT-5 Nano is the fastest, cheapest version of GPT-5. It's great for
497+
* summarization and classification tasks.
491498
* <p>
492-
* Model ID: gpt-5-2025-08-07
499+
* Model ID: gpt-5-nano
493500
* <p>
494-
* See: <a href="https://platform.openai.com/docs/models/gpt-5">gpt-5</a>
501+
* See:
502+
* <a href="https://platform.openai.com/docs/models/gpt-5-nano">gpt-5-nano</a>
503+
*/
504+
GPT_5_NANO("gpt-5-nano"),
505+
506+
/**
507+
* GPT-5 Chat points to the GPT-5 snapshot currently used in ChatGPT. GPT-5
508+
* accepts both text and image inputs, and produces text outputs.
509+
* <p>
510+
* Model ID: gpt-5-chat-latest
511+
* <p>
512+
* See: <a href=
513+
* "https://platform.openai.com/docs/models/gpt-5-chat-latest">gpt-5-chat-latest</a>
495514
*/
496-
GPT_5_2025_08_07("gpt-5-2025-08-07"),
515+
GPT_5_CHAT_LATEST("gpt-5-chat-latest"),
497516

498517
/**
499518
* <b>GPT-4o</b> (“o” for “omni”) is the versatile, high-intelligence flagship
@@ -1064,6 +1083,7 @@ public enum OutputModality {
10641083
* Currently supported values are low, medium, and high. Reducing reasoning effort can
10651084
* result in faster responses and fewer tokens used on reasoning in a response.
10661085
* @param webSearchOptions Options for web search.
1086+
* @param verbosity Controls the verbosity of the model's response.
10671087
*/
10681088
@JsonInclude(Include.NON_NULL)
10691089
public record ChatCompletionRequest(// @formatter:off
@@ -1094,7 +1114,8 @@ public record ChatCompletionRequest(// @formatter:off
10941114
@JsonProperty("parallel_tool_calls") Boolean parallelToolCalls,
10951115
@JsonProperty("user") String user,
10961116
@JsonProperty("reasoning_effort") String reasoningEffort,
1097-
@JsonProperty("web_search_options") WebSearchOptions webSearchOptions) {
1117+
@JsonProperty("web_search_options") WebSearchOptions webSearchOptions,
1118+
@JsonProperty("verbosity") String verbosity) {
10981119

10991120
/**
11001121
* Shortcut constructor for a chat completion request with the given messages, model and temperature.
@@ -1106,7 +1127,7 @@ public record ChatCompletionRequest(// @formatter:off
11061127
public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model, Double temperature) {
11071128
this(messages, model, null, null, null, null, null, null, null, null, null, null, null, null, null,
11081129
null, null, null, false, null, temperature, null,
1109-
null, null, null, null, null, null);
1130+
null, null, null, null, null, null, null);
11101131
}
11111132

11121133
/**
@@ -1120,7 +1141,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
11201141
this(messages, model, null, null, null, null, null, null,
11211142
null, null, null, List.of(OutputModality.AUDIO, OutputModality.TEXT), audio, null, null,
11221143
null, null, null, stream, null, null, null,
1123-
null, null, null, null, null, null);
1144+
null, null, null, null, null, null, null);
11241145
}
11251146

11261147
/**
@@ -1135,7 +1156,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
11351156
public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model, Double temperature, boolean stream) {
11361157
this(messages, model, null, null, null, null, null, null, null, null, null,
11371158
null, null, null, null, null, null, null, stream, null, temperature, null,
1138-
null, null, null, null, null, null);
1159+
null, null, null, null, null, null, null);
11391160
}
11401161

11411162
/**
@@ -1151,7 +1172,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
11511172
List<FunctionTool> tools, Object toolChoice) {
11521173
this(messages, model, null, null, null, null, null, null, null, null, null,
11531174
null, null, null, null, null, null, null, false, null, 0.8, null,
1154-
tools, toolChoice, null, null, null, null);
1175+
tools, toolChoice, null, null, null, null, null);
11551176
}
11561177

11571178
/**
@@ -1164,7 +1185,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
11641185
public ChatCompletionRequest(List<ChatCompletionMessage> messages, Boolean stream) {
11651186
this(messages, null, null, null, null, null, null, null, null, null, null,
11661187
null, null, null, null, null, null, null, stream, null, null, null,
1167-
null, null, null, null, null, null);
1188+
null, null, null, null, null, null, null);
11681189
}
11691190

11701191
/**
@@ -1177,7 +1198,7 @@ public ChatCompletionRequest streamOptions(StreamOptions streamOptions) {
11771198
return new ChatCompletionRequest(this.messages, this.model, this.store, this.metadata, this.frequencyPenalty, this.logitBias, this.logprobs,
11781199
this.topLogprobs, this.maxTokens, this.maxCompletionTokens, this.n, this.outputModalities, this.audioParameters, this.presencePenalty,
11791200
this.responseFormat, this.seed, this.serviceTier, this.stop, this.stream, streamOptions, this.temperature, this.topP,
1180-
this.tools, this.toolChoice, this.parallelToolCalls, this.user, this.reasoningEffort, this.webSearchOptions);
1201+
this.tools, this.toolChoice, this.parallelToolCalls, this.user, this.reasoningEffort, this.webSearchOptions, this.verbosity);
11811202
}
11821203

11831204
/**

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

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void validateReasoningTokens() {
7777
"If a train travels 100 miles in 2 hours, what is its average speed?", ChatCompletionMessage.Role.USER);
7878
ChatCompletionRequest request = new ChatCompletionRequest(List.of(userMessage), "o1", null, null, null, null,
7979
null, null, null, null, null, null, null, null, null, null, null, null, false, null, null, null, null,
80-
null, null, null, "low", null);
80+
null, null, null, "low", null, null);
8181
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);
8282

8383
assertThat(response).isNotNull();
@@ -159,7 +159,7 @@ void streamOutputAudio() {
159159
}
160160

161161
@ParameterizedTest(name = "{0} : {displayName}")
162-
@EnumSource(names = { "GPT_5", "GPT_5_2025_08_07" })
162+
@EnumSource(names = { "GPT_5", "GPT_5_CHAT_LATEST", "GPT_5_MINI", "GPT_5_NANO" })
163163
void chatCompletionEntityWithNewModels(OpenAiApi.ChatModel modelName) {
164164
ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage("Hello world", Role.USER);
165165
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(
@@ -172,4 +172,50 @@ void chatCompletionEntityWithNewModels(OpenAiApi.ChatModel modelName) {
172172
assertThat(response.getBody().model()).containsIgnoringCase(modelName.getValue());
173173
}
174174

175+
@ParameterizedTest(name = "{0} : {displayName}")
176+
@EnumSource(names = { "GPT_5_NANO" })
177+
void chatCompletionEntityWithNewModelsAndLowVerbosity(OpenAiApi.ChatModel modelName) {
178+
ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage(
179+
"What is the answer to the ultimate question of life, the universe, and everything?", Role.USER);
180+
181+
ChatCompletionRequest request = new ChatCompletionRequest(List.of(chatCompletionMessage), // messages
182+
modelName.getValue(), null, null, null, null, null, null, null, null, null, null, null, null, null,
183+
null, null, null, false, null, 1.0, null, null, null, null, null, null, null, "low");
184+
185+
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);
186+
187+
assertThat(response).isNotNull();
188+
assertThat(response.getBody()).isNotNull();
189+
assertThat(response.getBody().choices()).isNotEmpty();
190+
assertThat(response.getBody().choices().get(0).message().content()).isNotEmpty();
191+
assertThat(response.getBody().model()).containsIgnoringCase(modelName.getValue());
192+
}
193+
194+
@ParameterizedTest(name = "{0} : {displayName}")
195+
@EnumSource(names = { "GPT_5", "GPT_5_MINI", "GPT_5_NANO" })
196+
void chatCompletionEntityWithGpt5ModelsAndTemperatureShouldFail(OpenAiApi.ChatModel modelName) {
197+
ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage("Hello world", Role.USER);
198+
ChatCompletionRequest request = new ChatCompletionRequest(List.of(chatCompletionMessage), modelName.getValue(),
199+
0.8);
200+
201+
assertThatThrownBy(() -> this.openAiApi.chatCompletionEntity(request)).isInstanceOf(RuntimeException.class)
202+
.hasMessageContaining("Unsupported value");
203+
}
204+
205+
@ParameterizedTest(name = "{0} : {displayName}")
206+
@EnumSource(names = { "GPT_5_CHAT_LATEST" })
207+
void chatCompletionEntityWithGpt5ChatAndTemperatureShouldSucceed(OpenAiApi.ChatModel modelName) {
208+
ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage("Hello world", Role.USER);
209+
ChatCompletionRequest request = new ChatCompletionRequest(List.of(chatCompletionMessage), modelName.getValue(),
210+
0.8);
211+
212+
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);
213+
214+
assertThat(response).isNotNull();
215+
assertThat(response.getBody()).isNotNull();
216+
assertThat(response.getBody().choices()).isNotEmpty();
217+
assertThat(response.getBody().choices().get(0).message().content()).isNotEmpty();
218+
assertThat(response.getBody().model()).containsIgnoringCase(modelName.getValue());
219+
}
220+
175221
}

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,10 @@ The `JSON_SCHEMA` type enables link:https://platform.openai.com/docs/guides/stru
181181

182182
[NOTE]
183183
====
184-
When using GPT-5 models (`gpt-5`, `gpt-5-2025-08-07`), the temperature parameter must be set to `1.0` (the default value). These models do not support custom temperature values and will return an error if any other temperature value is specified.
184+
When using GPT-5 models such as `gpt-5`, `gpt-5-mini`, and `gpt-5-nano`, the `temperature` parameter is not supported.
185+
These models are optimized for reasoning and do not use temperature.
186+
Specifying a temperature value will result in an error.
187+
In contrast, conversational models like `gpt-5-chat` do support the `temperature` parameter.
185188
====
186189

187190
NOTE: You can override the common `spring.ai.openai.base-url` and `spring.ai.openai.api-key` for the `ChatModel` and `EmbeddingModel` implementations.

0 commit comments

Comments
 (0)