diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index ac3743831cc..56589e5b572 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 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. @@ -45,6 +45,7 @@ * @author Christian Tzolov * @author Mariusz Bernacki * @author Thomas Vitale + * @author Ilayaperumal Gopinathan * @since 0.8.0 */ @JsonInclude(Include.NON_NULL) @@ -173,7 +174,14 @@ public class OpenAiChatOptions implements FunctionCallingOptions { * Defaults to true. */ private @JsonProperty("parallel_tool_calls") Boolean parallelToolCalls; - + /** + * Whether to store the output of this chat completion request for use in our model distillation or evals products. + */ + private @JsonProperty("store") Boolean store; + /** + * Developer-defined tags and values used for filtering completions in the dashboard. + */ + private @JsonProperty("metadata") Map metadata = new HashMap<>(); /** * OpenAI Tool Function Callbacks to register with the ChatModel. * For Prompt Options the functionCallbacks are automatically enabled for the duration of the prompt execution. @@ -246,6 +254,8 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) { .httpHeaders(fromOptions.getHttpHeaders()) .proxyToolCalls(fromOptions.getProxyToolCalls()) .toolContext(fromOptions.getToolContext()) + .store(fromOptions.getStore()) + .metadata(fromOptions.getMetadata()) .build(); } @@ -494,6 +504,22 @@ public void setToolContext(Map toolContext) { this.toolContext = toolContext; } + public Boolean getStore() { + return this.store; + } + + public void setStore(Boolean store) { + this.store = store; + } + + public Map getMetadata() { + return this.metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + @Override public OpenAiChatOptions copy() { return OpenAiChatOptions.fromOptions(this); @@ -505,7 +531,8 @@ public int hashCode() { this.maxTokens, this.maxCompletionTokens, this.n, this.presencePenalty, this.responseFormat, 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.proxyToolCalls, this.toolContext, this.outputModalities, this.outputAudio, this.store, + this.metadata); } @Override @@ -535,7 +562,8 @@ public boolean equals(Object o) { && Objects.equals(this.toolContext, other.toolContext) && Objects.equals(this.proxyToolCalls, other.proxyToolCalls) && Objects.equals(this.outputModalities, other.outputModalities) - && Objects.equals(this.outputAudio, other.outputAudio); + && Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store) + && Objects.equals(this.metadata, other.metadata); } @Override @@ -702,6 +730,16 @@ public Builder toolContext(Map toolContext) { return this; } + public Builder store(Boolean store) { + this.options.store = store; + return this; + } + + public Builder metadata(Map metadata) { + this.options.metadata = metadata; + return this; + } + public OpenAiChatOptions build() { return this.options; } diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java index c9e54ceee66..a29ebcd8fcd 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 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. @@ -804,7 +804,7 @@ public record ChatCompletionRequest(// @formatter:off @JsonProperty("messages") List messages, @JsonProperty("model") String model, @JsonProperty("store") Boolean store, - @JsonProperty("metadata") Object metadata, + @JsonProperty("metadata") Map metadata, @JsonProperty("frequency_penalty") Double frequencyPenalty, @JsonProperty("logit_bias") Map logitBias, @JsonProperty("logprobs") Boolean logprobs, diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java index db0baf2cc7e..ab45ac0a3b8 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 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. @@ -599,6 +599,15 @@ void validateCallResponseMetadata() { assertThat(response.getMetadata().getUsage().getTotalTokens()).isPositive(); } + @Test + void validateStoreAndMetadata() { + OpenAiChatOptions options = OpenAiChatOptions.builder().store(true).metadata(Map.of("type", "dev")).build(); + + ChatResponse response = this.openAiChatModel.call(new Prompt("Tell me a joke", options)); + + assertThat(response).isNotNull(); + } + record ActorsFilmsRecord(String actor, List movies) { }