Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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 @@ -221,6 +221,8 @@ private void collectStructuredOutputMetadata(List<Msg> messages) {
int totalInput = 0;
int totalOutput = 0;
double totalTime = 0;
Integer totalReasoning = null;
Integer totalCached = null;
boolean hasUsage = false;

for (Msg msg : messages) {
Expand All @@ -232,6 +234,15 @@ private void collectStructuredOutputMetadata(List<Msg> messages) {
totalInput += usage.getInputTokens();
totalOutput += usage.getOutputTokens();
totalTime += usage.getTime();
if (usage.getReasoningTokens() != null) {
totalReasoning =
(totalReasoning == null ? 0 : totalReasoning)
+ usage.getReasoningTokens();
}
if (usage.getCachedTokens() != null) {
totalCached =
(totalCached == null ? 0 : totalCached) + usage.getCachedTokens();
}
}

// Collect ThinkingBlock (keep the last one)
Expand All @@ -247,6 +258,8 @@ private void collectStructuredOutputMetadata(List<Msg> messages) {
? ChatUsage.builder()
.inputTokens(totalInput)
.outputTokens(totalOutput)
.reasoningTokens(totalReasoning)
.cachedTokens(totalCached)
.time(totalTime)
.build()
: null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class ReasoningContext {
private int inputTokens = 0;
private int outputTokens = 0;
private double time = 0;
private Integer reasoningTokens = null;
private Integer cachedTokens = null;

public ReasoningContext(String agentName) {
this.agentName = agentName;
Expand Down Expand Up @@ -84,6 +86,12 @@ public List<Msg> processChunk(ChatResponse chunk) {
inputTokens = usage.getInputTokens();
outputTokens = usage.getOutputTokens();
time = usage.getTime();
if (usage.getReasoningTokens() != null) {
reasoningTokens = usage.getReasoningTokens();
}
if (usage.getCachedTokens() != null) {
cachedTokens = usage.getCachedTokens();
}
}

List<Msg> streamingMsgs = new ArrayList<>();
Expand Down Expand Up @@ -166,12 +174,18 @@ public Msg buildFinalMessage() {

// Build metadata with accumulated ChatUsage
Map<String, Object> metadata = new HashMap<>();
if (inputTokens > 0 || outputTokens > 0 || time > 0) {
if (inputTokens > 0
|| outputTokens > 0
|| time > 0
|| reasoningTokens != null
|| cachedTokens != null) {
ChatUsage chatUsage =
ChatUsage.builder()
.inputTokens(inputTokens)
.outputTokens(outputTokens)
.time(time)
.reasoningTokens(reasoningTokens)
.cachedTokens(cachedTokens)
.build();
metadata.put(MessageMetadataKeys.CHAT_USAGE, chatUsage);
}
Expand Down Expand Up @@ -278,11 +292,17 @@ public List<ToolUseBlock> getAllAccumulatedToolCalls() {
* @return ChatUsage with accumulated tokens, or null if no usage data
*/
public ChatUsage getChatUsage() {
if (inputTokens > 0 || outputTokens > 0 || time > 0) {
if (inputTokens > 0
|| outputTokens > 0
|| time > 0
|| reasoningTokens != null
|| cachedTokens != null) {
return ChatUsage.builder()
.inputTokens(inputTokens)
.outputTokens(outputTokens)
.time(time)
.reasoningTokens(reasoningTokens)
.cachedTokens(cachedTokens)
.build();
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ public static ChatResponse parseMessage(Message message, Instant startTime) {
}

// Parse usage
Integer cachedTokens =
message.usage().cacheReadInputTokens().map(Long::intValue).orElse(null);

ChatUsage usage =
ChatUsage.builder()
.inputTokens((int) message.usage().inputTokens())
.outputTokens((int) message.usage().outputTokens())
.time(Duration.between(startTime, Instant.now()).toMillis() / 1000.0)
.cachedTokens(cachedTokens)
.build();

return ChatResponse.builder().id(message.id()).content(contentBlocks).usage(usage).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ public ChatResponse parseResponse(DashScopeResponse response, Instant startTime)
.time(
Duration.between(startTime, Instant.now()).toMillis()
/ 1000.0)
.reasoningTokens(u.getReasoningTokens())
.cachedTokens(u.getCachedTokens())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ public class DashScopeUsage {
@JsonProperty("total_tokens")
private Integer totalTokens;

/** Reasoning tokens used by advanced models. */
@JsonProperty("reasoning_tokens")
private Integer reasoningTokens;

/** Prompt tokens saved by caching mechanism. */
@JsonProperty("cached_tokens")
private Integer cachedTokens;

/** Image tokens (for multimodal). */
@JsonProperty("image_tokens")
private Integer imageTokens;
Expand Down Expand Up @@ -85,6 +93,22 @@ public void setTotalTokens(Integer totalTokens) {
this.totalTokens = totalTokens;
}

public Integer getReasoningTokens() {
return reasoningTokens;
}

public void setReasoningTokens(Integer reasoningTokens) {
this.reasoningTokens = reasoningTokens;
}

public Integer getCachedTokens() {
return cachedTokens;
}

public void setCachedTokens(Integer cachedTokens) {
this.cachedTokens = cachedTokens;
}

public Integer getImageTokens() {
return imageTokens;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ public ChatResponse parseResponse(GenerateContentResponse response, Instant star

int inputTokens = metadata.promptTokenCount().orElse(0);
int totalOutputTokens = metadata.candidatesTokenCount().orElse(0);
int thinkingTokens = metadata.thoughtsTokenCount().orElse(0);
Integer reasoningTokens = metadata.thoughtsTokenCount().orElse(null);
Integer cachedTokens = metadata.cachedContentTokenCount().orElse(null);
int thinkingTokens = reasoningTokens != null ? reasoningTokens : 0;

// Output tokens exclude thinking tokens (following DashScope behavior)
// In Gemini, candidatesTokenCount includes thinking, so we subtract it
Expand All @@ -109,6 +111,8 @@ public ChatResponse parseResponse(GenerateContentResponse response, Instant star
.time(
Duration.between(startTime, Instant.now()).toMillis()
/ 1000.0)
.reasoningTokens(reasoningTokens)
.cachedTokens(cachedTokens)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,26 @@ protected ChatResponse parseCompletionResponse(OpenAIResponse response, Instant
// Parse usage information
if (response.getUsage() != null) {
OpenAIUsage openAIUsage = response.getUsage();

usage =
ChatUsage.builder()
.inputTokens((int) getSafePromptTokens(openAIUsage))
.outputTokens((int) getSafeCompletionTokens(openAIUsage))
.time(
Duration.between(startTime, Instant.now()).toMillis()
/ 1000.0)
.reasoningTokens(
openAIUsage.getCompletionTokensDetails() != null
? openAIUsage
.getCompletionTokensDetails()
.getReasoningTokens()
: null)
.cachedTokens(
openAIUsage.getPromptTokensDetails() != null
? openAIUsage
.getPromptTokensDetails()
.getCachedTokens()
: null)
.build();
}

Expand Down Expand Up @@ -333,6 +346,7 @@ protected ChatResponse parseChunkResponse(OpenAIResponse response, Instant start
// Parse usage information (usually only in the last chunk)
if (response.getUsage() != null) {
OpenAIUsage openAIUsage = response.getUsage();

usage =
ChatUsage.builder()
.inputTokens(
Expand All @@ -346,6 +360,18 @@ protected ChatResponse parseChunkResponse(OpenAIResponse response, Instant start
.time(
Duration.between(startTime, Instant.now()).toMillis()
/ 1000.0)
.reasoningTokens(
openAIUsage.getCompletionTokensDetails() != null
? openAIUsage
.getCompletionTokensDetails()
.getReasoningTokens()
: null)
.cachedTokens(
openAIUsage.getPromptTokensDetails() != null
? openAIUsage
.getPromptTokensDetails()
.getCachedTokens()
: null)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ private MessageMetadataKeys() {
/**
* Metadata key for chat usage statistics.
*
* <p>Contains token usage information (input tokens, output tokens, and time)
* accumulated during model generation. This allows users to track token consumption
* for cost estimation and usage monitoring.
* <p>Contains token usage information (input tokens, output tokens, time, and optional
* reasoning/cached tokens) accumulated during model generation. This allows users
* to track token consumption for cost estimation and usage monitoring.
*
* <p><b>Type:</b> {@link io.agentscope.core.model.ChatUsage}
* <p><b>Example:</b>
Expand All @@ -86,6 +86,9 @@ private MessageMetadataKeys() {
* System.out.println("Input tokens: " + usage.getInputTokens());
* System.out.println("Output tokens: " + usage.getOutputTokens());
* System.out.println("Total tokens: " + usage.getTotalTokens());
* // Advanced metrics (may be null if not supported by the model)
* System.out.println("Reasoning tokens: " + usage.getReasoningTokens());
* System.out.println("Cached tokens: " + usage.getCachedTokens());
* }
* }</pre>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,9 @@ public String getTextContent() {
* System.out.println("Output tokens: " + usage.getOutputTokens());
* System.out.println("Total tokens: " + usage.getTotalTokens());
* System.out.println("Time: " + usage.getTime() + "s");
* // Advanced metrics (may be null if not supported by the model)
* System.out.println("Reasoning tokens: " + usage.getReasoningTokens());
* System.out.println("Cached tokens: " + usage.getCachedTokens());
* }
* }</pre>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
*
* <p>This immutable data class tracks the number of tokens used during a chat completion,
* including input tokens (prompt), output tokens (generated response), and execution time.
* It also supports optional tracking of reasoning and cached tokens for advanced models.
*/
public class ChatUsage {

private final int inputTokens;
private final int outputTokens;
private final double time;
private final Integer reasoningTokens;
private final Integer cachedTokens;

/**
* Creates a new ChatUsage instance.
Expand All @@ -35,9 +38,29 @@ public class ChatUsage {
* @param time the execution time in seconds
*/
public ChatUsage(int inputTokens, int outputTokens, double time) {
this(inputTokens, outputTokens, time, null, null);
}

/**
* Creates a new ChatUsage instance with advanced token metrics.
*
* @param inputTokens the number of tokens used for the input/prompt
* @param outputTokens the number of tokens used for the output/generated response
* @param time the execution time in seconds
* @param reasoningTokens the number of tokens used for reasoning/thinking (can be null)
* @param cachedTokens the number of tokens saved by prompt caching (can be null)
*/
public ChatUsage(
int inputTokens,
int outputTokens,
double time,
Integer reasoningTokens,
Integer cachedTokens) {
this.inputTokens = inputTokens;
this.outputTokens = outputTokens;
this.time = time;
this.reasoningTokens = reasoningTokens;
this.cachedTokens = cachedTokens;
}

/**
Expand Down Expand Up @@ -76,6 +99,24 @@ public double getTime() {
return time;
}

/**
* Gets the number of reasoning tokens used.
*
* @return the number of reasoning tokens, or null if the model does not support this metric
*/
public Integer getReasoningTokens() {
return reasoningTokens;
}

/**
* Gets the number of cached tokens used.
*
* @return the number of cached tokens, or null if the model does not support this metric
*/
public Integer getCachedTokens() {
return cachedTokens;
}

/**
* Creates a new builder for ChatUsage.
*
Expand All @@ -92,6 +133,8 @@ public static class Builder {
private int inputTokens;
private int outputTokens;
private double time;
private Integer reasoningTokens;
private Integer cachedTokens;

/**
* Sets the number of input tokens.
Expand Down Expand Up @@ -126,13 +169,35 @@ public Builder time(double time) {
return this;
}

/**
* Sets the number of reasoning tokens.
*
* @param reasoningTokens the number of tokens used for reasoning
* @return this builder instance
*/
public Builder reasoningTokens(Integer reasoningTokens) {
this.reasoningTokens = reasoningTokens;
return this;
}

/**
* Sets the number of cached tokens.
*
* @param cachedTokens the number of tokens saved by caching
* @return this builder instance
*/
public Builder cachedTokens(Integer cachedTokens) {
this.cachedTokens = cachedTokens;
return this;
}

/**
* Builds a new ChatUsage instance with the set values.
*
* @return a new ChatUsage instance
*/
public ChatUsage build() {
return new ChatUsage(inputTokens, outputTokens, time);
return new ChatUsage(inputTokens, outputTokens, time, reasoningTokens, cachedTokens);
}
}
}
Loading
Loading