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..ea42e912828 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
@@ -1323,17 +1323,28 @@ public record TopLogProbs(// @formatter:off
* completion).
* @param promptTokensDetails Breakdown of tokens used in the prompt.
* @param completionTokenDetails Breakdown of tokens used in a completion.
+ * @param promptCacheHitTokens Number of tokens in the prompt that were served from
+ * (util for
+ * DeepSeek
+ * support).
+ * @param promptCacheMissTokens Number of tokens in the prompt that were not served
+ * (util for
+ * DeepSeek
+ * support).
*/
@JsonInclude(Include.NON_NULL)
+ @JsonIgnoreProperties(ignoreUnknown = true)
public record Usage(// @formatter:off
@JsonProperty("completion_tokens") Integer completionTokens,
@JsonProperty("prompt_tokens") Integer promptTokens,
@JsonProperty("total_tokens") Integer totalTokens,
@JsonProperty("prompt_tokens_details") PromptTokensDetails promptTokensDetails,
- @JsonProperty("completion_tokens_details") CompletionTokenDetails completionTokenDetails) { // @formatter:on
+ @JsonProperty("completion_tokens_details") CompletionTokenDetails completionTokenDetails,
+ @JsonProperty("prompt_cache_hit_tokens") Integer promptCacheHitTokens,
+ @JsonProperty("prompt_cache_miss_tokens") Integer promptCacheMissTokens) { // @formatter:on
public Usage(Integer completionTokens, Integer promptTokens, Integer totalTokens) {
- this(completionTokens, promptTokens, totalTokens, null, null);
+ this(completionTokens, promptTokens, totalTokens, null, null, null, null);
}
/**
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java
index 92e8f595b3f..7a177918b42 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java
@@ -74,7 +74,7 @@ void whenTotalTokensIsNull() {
@Test
void whenPromptAndCompletionTokensDetailsIsNull() {
- OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null, null);
+ OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null, null, null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getTotalTokens()).isEqualTo(300);
assertThat(usage.getCompletionTokenDetails().reasoningTokens()).isEqualTo(0);
@@ -85,7 +85,7 @@ void whenPromptAndCompletionTokensDetailsIsNull() {
@Test
void whenCompletionTokenDetailsIsNull() {
- OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null, null);
+ OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null, null, null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getTotalTokens()).isEqualTo(300);
assertThat(usage.getReasoningTokens()).isEqualTo(0);
@@ -94,7 +94,7 @@ void whenCompletionTokenDetailsIsNull() {
@Test
void whenReasoningTokensIsNull() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null,
- new OpenAiApi.Usage.CompletionTokenDetails(null, null, null, null));
+ new OpenAiApi.Usage.CompletionTokenDetails(null, null, null, null), null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getReasoningTokens()).isEqualTo(0);
}
@@ -102,7 +102,7 @@ void whenReasoningTokensIsNull() {
@Test
void whenCompletionTokenDetailsIsPresent() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null,
- new OpenAiApi.Usage.CompletionTokenDetails(50, null, null, null));
+ new OpenAiApi.Usage.CompletionTokenDetails(50, null, null, null), null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getCompletionTokenDetails().reasoningTokens()).isEqualTo(50);
assertThat(usage.getCompletionTokenDetails().acceptedPredictionTokens()).isEqualTo(0);
@@ -113,7 +113,7 @@ void whenCompletionTokenDetailsIsPresent() {
@Test
void whenAcceptedPredictionTokensIsPresent() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null,
- new OpenAiApi.Usage.CompletionTokenDetails(null, 75, null, null));
+ new OpenAiApi.Usage.CompletionTokenDetails(null, 75, null, null), null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getCompletionTokenDetails().reasoningTokens()).isEqualTo(0);
assertThat(usage.getCompletionTokenDetails().acceptedPredictionTokens()).isEqualTo(75);
@@ -124,7 +124,7 @@ void whenAcceptedPredictionTokensIsPresent() {
@Test
void whenAudioTokensIsPresent() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null,
- new OpenAiApi.Usage.CompletionTokenDetails(null, null, 125, null));
+ new OpenAiApi.Usage.CompletionTokenDetails(null, null, 125, null), null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getCompletionTokenDetails().reasoningTokens()).isEqualTo(0);
assertThat(usage.getCompletionTokenDetails().acceptedPredictionTokens()).isEqualTo(0);
@@ -135,7 +135,7 @@ void whenAudioTokensIsPresent() {
@Test
void whenRejectedPredictionTokensIsNull() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null,
- new OpenAiApi.Usage.CompletionTokenDetails(null, null, null, null));
+ new OpenAiApi.Usage.CompletionTokenDetails(null, null, null, null), null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getCompletionTokenDetails().reasoningTokens()).isEqualTo(0);
assertThat(usage.getCompletionTokenDetails().acceptedPredictionTokens()).isEqualTo(0);
@@ -147,7 +147,7 @@ void whenRejectedPredictionTokensIsNull() {
@Test
void whenRejectedPredictionTokensIsPresent() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null,
- new OpenAiApi.Usage.CompletionTokenDetails(null, null, null, 25));
+ new OpenAiApi.Usage.CompletionTokenDetails(null, null, null, 25), null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getCompletionTokenDetails().reasoningTokens()).isEqualTo(0);
assertThat(usage.getCompletionTokenDetails().acceptedPredictionTokens()).isEqualTo(0);
@@ -158,7 +158,7 @@ void whenRejectedPredictionTokensIsPresent() {
@Test
void whenCacheTokensIsNull() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300,
- new OpenAiApi.Usage.PromptTokensDetails(null, null), null);
+ new OpenAiApi.Usage.PromptTokensDetails(null, null), null, null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getPromptTokensDetails().audioTokens()).isEqualTo(0);
assertThat(usage.getPromptTokensDetails().cachedTokens()).isEqualTo(0);
@@ -167,7 +167,7 @@ void whenCacheTokensIsNull() {
@Test
void whenCacheTokensIsPresent() {
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300,
- new OpenAiApi.Usage.PromptTokensDetails(99, 15), null);
+ new OpenAiApi.Usage.PromptTokensDetails(99, 15), null, null, null);
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
assertThat(usage.getPromptTokensDetails().audioTokens()).isEqualTo(99);
assertThat(usage.getPromptTokensDetails().cachedTokens()).isEqualTo(15);