Skip to content

Commit 1673907

Browse files
dafrizmarkpollack
authored andcommitted
Add support for reasoning tokens in OpenAI usage data
This change introduces a new field for tracking reasoning tokens in the OpenAI API response. It extends the Usage record to include CompletionTokenDetails, allowing for more granular token usage reporting. The OpenAiUsage class is updated to expose this new data, and corresponding unit tests are added to verify the behavior. This enhancement provides more detailed insights into token usage, particularly for advanced AI models that separate reasoning from other generation processes.
1 parent 8354507 commit 1673907

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,12 +936,28 @@ public record TopLogProbs(// @formatter:off
936936
* @param promptTokens Number of tokens in the prompt.
937937
* @param totalTokens Total number of tokens used in the request (prompt +
938938
* completion).
939+
* @param completionTokenDetails Breakdown of tokens used in a completion
939940
*/
940941
@JsonInclude(Include.NON_NULL)
941942
public record Usage(// @formatter:off
942943
@JsonProperty("completion_tokens") Integer completionTokens,
943944
@JsonProperty("prompt_tokens") Integer promptTokens,
944-
@JsonProperty("total_tokens") Integer totalTokens) {// @formatter:on
945+
@JsonProperty("total_tokens") Integer totalTokens,
946+
@JsonProperty("completion_tokens_details") CompletionTokenDetails completionTokenDetails) {// @formatter:on
947+
948+
public Usage(Integer completionTokens, Integer promptTokens, Integer totalTokens) {
949+
this(completionTokens, promptTokens, totalTokens, null);
950+
}
951+
952+
/**
953+
* Breakdown of tokens used in a completion
954+
*
955+
* @param reasoningTokens Number of tokens generated by the model for reasoning.
956+
*/
957+
@JsonInclude(Include.NON_NULL)
958+
public record CompletionTokenDetails(// @formatter:off
959+
@JsonProperty("reasoning_tokens") Integer reasoningTokens) {// @formatter:on
960+
}
945961

946962
}
947963

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public Long getGenerationTokens() {
5858
return generationTokens != null ? generationTokens.longValue() : 0;
5959
}
6060

61+
public Long getReasoningTokens() {
62+
OpenAiApi.Usage.CompletionTokenDetails completionTokenDetails = getUsage().completionTokenDetails();
63+
Integer reasoningTokens = completionTokenDetails != null ? completionTokenDetails.reasoningTokens() : null;
64+
return reasoningTokens != null ? reasoningTokens.longValue() : 0;
65+
}
66+
6167
@Override
6268
public Long getTotalTokens() {
6369
Integer totalTokens = getUsage().totalTokens();

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,28 @@ void whenTotalTokensIsNull() {
5454
assertThat(usage.getTotalTokens()).isEqualTo(300);
5555
}
5656

57+
@Test
58+
void whenCompletionTokenDetailsIsNull() {
59+
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300, null);
60+
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
61+
assertThat(usage.getTotalTokens()).isEqualTo(300);
62+
assertThat(usage.getReasoningTokens()).isEqualTo(0);
63+
}
64+
65+
@Test
66+
void whenReasoningTokensIsNull() {
67+
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300,
68+
new OpenAiApi.Usage.CompletionTokenDetails(null));
69+
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
70+
assertThat(usage.getReasoningTokens()).isEqualTo(0);
71+
}
72+
73+
@Test
74+
void whenCompletionTokenDetailsIsPresent() {
75+
OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(100, 200, 300,
76+
new OpenAiApi.Usage.CompletionTokenDetails(50));
77+
OpenAiUsage usage = OpenAiUsage.from(openAiUsage);
78+
assertThat(usage.getReasoningTokens()).isEqualTo(50);
79+
}
80+
5781
}

0 commit comments

Comments
 (0)