Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fcb3206

Browse files
authored
fix(telemetry): use max instead of sum for streaming token usage aggregation (agentscope-ai#1098)
1 parent 5c826e3 commit fcb3206

2 files changed

Lines changed: 88 additions & 8 deletions

File tree

agentscope-extensions/agentscope-extensions-studio/src/main/java/io/agentscope/core/tracing/telemetry/StreamChatResponseAggregator.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import io.agentscope.core.model.ChatUsage;
2828
import java.util.ArrayList;
2929
import java.util.List;
30-
import java.util.concurrent.atomic.AtomicInteger;
3130

3231
/**
3332
* An aggregator for streaming {@link ChatResponse}.
@@ -41,9 +40,9 @@ final class StreamChatResponseAggregator {
4140
private final ThinkingAccumulator thinkingAcc = new ThinkingAccumulator();
4241
private final ToolCallsAccumulator toolCallsAcc = new ToolCallsAccumulator();
4342

44-
// Usage
45-
private final AtomicInteger inputTokens = new AtomicInteger(0);
46-
private final AtomicInteger outputTokens = new AtomicInteger(0);
43+
// Usage: take the max value from all chunks, since providers report cumulative totals
44+
private int inputTokens;
45+
private int outputTokens;
4746
private double time;
4847

4948
private String finishReason;
@@ -73,8 +72,8 @@ public void append(ChatResponse chunk) {
7372

7473
ChatUsage usage = chunk.getUsage();
7574
if (usage != null) {
76-
inputTokens.addAndGet(usage.getInputTokens());
77-
outputTokens.addAndGet(usage.getOutputTokens());
75+
inputTokens = Math.max(inputTokens, usage.getInputTokens());
76+
outputTokens = Math.max(outputTokens, usage.getOutputTokens());
7877
time = usage.getTime();
7978
}
8079

@@ -95,8 +94,8 @@ public ChatResponse getResponse() {
9594
.content(contentBlocks)
9695
.usage(
9796
ChatUsage.builder()
98-
.inputTokens(inputTokens.get())
99-
.outputTokens(outputTokens.get())
97+
.inputTokens(inputTokens)
98+
.outputTokens(outputTokens)
10099
.time(time)
101100
.build())
102101
.finishReason(finishReason)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.agentscope.core.tracing.telemetry;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
20+
import io.agentscope.core.message.TextBlock;
21+
import io.agentscope.core.model.ChatResponse;
22+
import io.agentscope.core.model.ChatUsage;
23+
import java.util.List;
24+
import org.junit.jupiter.api.DisplayName;
25+
import org.junit.jupiter.api.Test;
26+
27+
@DisplayName("StreamChatResponseAggregator Tests")
28+
class StreamChatResponseAggregatorTest {
29+
30+
@Test
31+
@DisplayName("Cumulative usage should take max, not sum")
32+
void testCumulativeUsageTakesMax() {
33+
StreamChatResponseAggregator agg = StreamChatResponseAggregator.create();
34+
35+
for (int i = 1; i <= 5; i++) {
36+
agg.append(
37+
ChatResponse.builder()
38+
.id("test-id")
39+
.content(List.of(TextBlock.builder().text("chunk" + i).build()))
40+
.usage(
41+
ChatUsage.builder()
42+
.inputTokens(100)
43+
.outputTokens(i * 20)
44+
.time(i * 0.5)
45+
.build())
46+
.finishReason(i == 5 ? "stop" : null)
47+
.build());
48+
}
49+
50+
ChatResponse response = agg.getResponse();
51+
assertEquals("test-id", response.getId());
52+
assertEquals(100, response.getUsage().getInputTokens());
53+
assertEquals(100, response.getUsage().getOutputTokens());
54+
assertEquals("stop", response.getFinishReason());
55+
}
56+
57+
@Test
58+
@DisplayName("Only last chunk carries usage (OpenAI style)")
59+
void testOnlyLastChunkHasUsage() {
60+
StreamChatResponseAggregator agg = StreamChatResponseAggregator.create();
61+
62+
for (int i = 0; i < 3; i++) {
63+
agg.append(
64+
ChatResponse.builder()
65+
.id("openai-id")
66+
.content(List.of(TextBlock.builder().text("part" + i).build()))
67+
.build());
68+
}
69+
70+
agg.append(
71+
ChatResponse.builder()
72+
.id("openai-id")
73+
.usage(ChatUsage.builder().inputTokens(200).outputTokens(150).build())
74+
.finishReason("stop")
75+
.build());
76+
77+
ChatResponse response = agg.getResponse();
78+
assertEquals(200, response.getUsage().getInputTokens());
79+
assertEquals(150, response.getUsage().getOutputTokens());
80+
}
81+
}

0 commit comments

Comments
 (0)