Skip to content

Feat: add gpt 5 models and verbosity param #4086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,15 @@ public class OpenAiChatOptions implements ToolCallingChatOptions {
*/
private @JsonProperty("reasoning_effort") String reasoningEffort;

/**
* verbosity: string or null
* Optional - Defaults to medium
* 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.
* Currently supported values are low, medium, and high.
* If specified, the model will use web search to find relevant information to answer the user's question.
*/
private @JsonProperty("verbosity") String verbosity;

/**
* This tool searches the web for relevant results to use in a response.
*/
Expand Down Expand Up @@ -268,6 +277,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) {
.metadata(fromOptions.getMetadata())
.reasoningEffort(fromOptions.getReasoningEffort())
.webSearchOptions(fromOptions.getWebSearchOptions())
.verbosity(fromOptions.getVerbosity())
.build();
}

Expand Down Expand Up @@ -564,6 +574,14 @@ public void setWebSearchOptions(WebSearchOptions webSearchOptions) {
this.webSearchOptions = webSearchOptions;
}

public String getVerbosity() {
return this.verbosity;
}

public void setVerbosity(String verbosity) {
this.verbosity = verbosity;
}

@Override
public OpenAiChatOptions copy() {
return OpenAiChatOptions.fromOptions(this);
Expand Down Expand Up @@ -609,7 +627,8 @@ public boolean equals(Object o) {
&& Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store)
&& Objects.equals(this.metadata, other.metadata)
&& Objects.equals(this.reasoningEffort, other.reasoningEffort)
&& Objects.equals(this.webSearchOptions, other.webSearchOptions);
&& Objects.equals(this.webSearchOptions, other.webSearchOptions)
&& Objects.equals(this.verbosity, other.verbosity);
}

@Override
Expand Down Expand Up @@ -802,6 +821,11 @@ public Builder webSearchOptions(WebSearchOptions webSearchOptions) {
return this;
}

public Builder verbosity(String verbosity) {
this.options.verbosity = verbosity;
return this;
}

public OpenAiChatOptions build() {
return this.options;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,18 +482,39 @@ public enum ChatModel implements ChatModelDescription {
GPT_5("gpt-5"),

/**
* <b>GPT-5 (2025-08-07)</b> is a specific snapshot of the GPT-5 model from August
* 7, 2025, providing enhanced capabilities for complex reasoning and
* problem-solving tasks.
* GPT-5 Chat points to the GPT-5 snapshot currently used in ChatGPT. GPT-5 is our
* next-generation, high-intelligence flagship model. It accepts both text and
* image inputs, and produces text outputs.
* <p>
* Note: GPT-5 models require temperature=1.0 (default value). Custom temperature
* values are not supported and will cause errors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not the case anymore? In that case, we need to update the docs changed in this commit: https://github.com/spring-projects/spring-ai/pull/4068/files

* Model ID: gpt-5-chat-latest
* <p>
* See: <a href=
* "https://platform.openai.com/docs/models/gpt-5-chat-latest">gpt-5-chat-latest</a>
*/
GPT_5_CHAT_LATEST("gpt-5-chat-latest"),

/**
* GPT-5 mini is a faster, more cost-efficient version of GPT-5. It's great for
* well-defined tasks and precise prompts.
* <p>
* Model ID: gpt-5-2025-08-07
* Model ID: gpt-5-mini
* <p>
* See: <a href="https://platform.openai.com/docs/models/gpt-5">gpt-5</a>
* See:
* <a href="https://platform.openai.com/docs/models/gpt-5-mini">gpt-5-mini</a>
*/
GPT_5_MINI("gpt-5-mini"),

// add gpt-5-nano
/**
* GPT-5 Nano is our fastest, cheapest version of GPT-5. It's great for
* summarization and classification tasks.
* <p>
* Model ID: gpt-5-nano
* <p>
* See:
* <a href="https://platform.openai.com/docs/models/gpt-5-nano">gpt-5-nano</a>
*/
GPT_5_2025_08_07("gpt-5-2025-08-07"),
GPT_5_NANO("gpt-5-nano"),

/**
* <b>GPT-4o</b> (“o” for “omni”) is the versatile, high-intelligence flagship
Expand Down Expand Up @@ -1064,6 +1085,7 @@ public enum OutputModality {
* 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.
* @param webSearchOptions Options for web search.
* @param verbosity Controls the verbosity of the model's response.
*/
@JsonInclude(Include.NON_NULL)
public record ChatCompletionRequest(// @formatter:off
Expand Down Expand Up @@ -1094,7 +1116,8 @@ public record ChatCompletionRequest(// @formatter:off
@JsonProperty("parallel_tool_calls") Boolean parallelToolCalls,
@JsonProperty("user") String user,
@JsonProperty("reasoning_effort") String reasoningEffort,
@JsonProperty("web_search_options") WebSearchOptions webSearchOptions) {
@JsonProperty("web_search_options") WebSearchOptions webSearchOptions,
@JsonProperty("verbosity") String verbosity) {

/**
* Shortcut constructor for a chat completion request with the given messages, model and temperature.
Expand All @@ -1106,7 +1129,7 @@ public record ChatCompletionRequest(// @formatter:off
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, null, null, null, null);
}

/**
Expand All @@ -1120,7 +1143,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
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, null, null, null, null);
}

/**
Expand All @@ -1135,7 +1158,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
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, null, null, null, null);
}

/**
Expand All @@ -1151,7 +1174,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
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, null, null);
tools, toolChoice, null, null, null, null, null);
}

/**
Expand All @@ -1164,7 +1187,7 @@ public ChatCompletionRequest(List<ChatCompletionMessage> messages, String model,
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, null, null, null, null);
}

/**
Expand All @@ -1177,7 +1200,7 @@ public ChatCompletionRequest streamOptions(StreamOptions streamOptions) {
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.reasoningEffort, this.webSearchOptions);
this.tools, this.toolChoice, this.parallelToolCalls, this.user, this.reasoningEffort, this.webSearchOptions, this.verbosity);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void validateReasoningTokens() {
"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", null);
null, null, null, "low", null, null);
ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);

assertThat(response).isNotNull();
Expand Down Expand Up @@ -159,7 +159,7 @@ void streamOutputAudio() {
}

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

@ParameterizedTest(name = "{0} : {displayName}")
@EnumSource(names = { "GPT_5_NANO" })
void chatCompletionEntityWithNewModelsAndLowVerbosity(OpenAiApi.ChatModel modelName) {
ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage(
"What is the answer to the ultimate question of life, the universe, and everything?", Role.USER);

ChatCompletionRequest request = new ChatCompletionRequest(List.of(chatCompletionMessage), // messages
modelName.getValue(), null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, false, null, 1.0, null, null, null, null, null, null, null, "low");

ResponseEntity<ChatCompletion> response = this.openAiApi.chatCompletionEntity(request);

assertThat(response).isNotNull();
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().choices()).isNotEmpty();
assertThat(response.getBody().choices().get(0).message().content()).isNotEmpty();
assertThat(response.getBody().model()).containsIgnoringCase(modelName.getValue());

}

}