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) {
}