Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -73,6 +73,33 @@ private long getSafeCompletionTokens(OpenAIUsage usage) {
: 0;
}

/**
* Safely parse usage information into AgentScope ChatUsage.
*
* @param openAIUsage the OpenAI usage object (may be null)
* @param startTime Request start time for calculating duration
* @return AgentScope ChatUsage or null if the input is null
*/
private ChatUsage parseUsage(OpenAIUsage openAIUsage, Instant startTime) {
if (openAIUsage == null) {
return null;
}

return 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();
}

public OpenAIResponseParser() {}

/**
Expand Down Expand Up @@ -104,17 +131,7 @@ protected ChatResponse parseCompletionResponse(OpenAIResponse response, Instant

try {
// 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)
.build();
}
usage = parseUsage(response.getUsage(), startTime);

// Parse response content
OpenAIChoice choice = response.getFirstChoice();
Expand Down Expand Up @@ -331,23 +348,7 @@ protected ChatResponse parseChunkResponse(OpenAIResponse response, Instant start

try {
// Parse usage information (usually only in the last chunk)
if (response.getUsage() != null) {
OpenAIUsage openAIUsage = response.getUsage();
usage =
ChatUsage.builder()
.inputTokens(
openAIUsage.getPromptTokens() != null
? openAIUsage.getPromptTokens()
: 0)
.outputTokens(
openAIUsage.getCompletionTokens() != null
? openAIUsage.getCompletionTokens()
: 0)
.time(
Duration.between(startTime, Instant.now()).toMillis()
/ 1000.0)
.build();
}
usage = parseUsage(response.getUsage(), startTime);

// Parse chunk content
OpenAIChoice choice = response.getFirstChoice();
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
Loading
Loading