Skip to content

Commit 85668cd

Browse files
committed
fix(core): avoid double-counting reasoning output tokens
1 parent 846ccf4 commit 85668cd

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/core/src/core/geminiChat.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,56 @@ describe('GeminiChat', async () => {
26082608
).toBeGreaterThanOrEqual(177_000);
26092609
});
26102610

2611+
it('does not double-count OpenAI-compatible reasoning tokens already included in candidates', async () => {
2612+
const compressSpy = vi.spyOn(
2613+
ChatCompressionService.prototype,
2614+
'compress',
2615+
);
2616+
compressSpy.mockResolvedValue({
2617+
newHistory: null,
2618+
info: {
2619+
originalTokenCount: 176_400,
2620+
newTokenCount: 176_400,
2621+
compressionStatus: CompressionStatus.NOOP,
2622+
},
2623+
});
2624+
vi.mocked(mockContentGenerator.generateContentStream)
2625+
.mockResolvedValueOnce(
2626+
makeStreamResponse('first', {
2627+
promptTokenCount: 175_400,
2628+
candidatesTokenCount: 1_000,
2629+
thoughtsTokenCount: 500,
2630+
totalTokenCount: 176_400,
2631+
}),
2632+
)
2633+
.mockResolvedValueOnce(makeStreamResponse('second'));
2634+
2635+
chat.setLastPromptTokenCount(50_000);
2636+
const firstStream = await chat.sendMessageStream(
2637+
'test-model',
2638+
{ message: 'prime OpenAI-compatible reasoning token counters' },
2639+
'prompt-prime-openai-reasoning-tokens',
2640+
);
2641+
for await (const _ of firstStream) {
2642+
/* consume */
2643+
}
2644+
2645+
const secondStream = await chat.sendMessageStream(
2646+
'test-model',
2647+
{ message: 'small follow-up' },
2648+
'prompt-openai-reasoning-follow-up',
2649+
);
2650+
for await (const _ of secondStream) {
2651+
/* consume */
2652+
}
2653+
2654+
expect(compressSpy).toHaveBeenCalledTimes(2);
2655+
expect(compressSpy.mock.calls[1][1].force).toBe(false);
2656+
expect(
2657+
compressSpy.mock.calls[1][1].precomputedEffectiveTokens,
2658+
).toBeLessThan(177_000);
2659+
});
2660+
26112661
it('resets previous response candidate tokens when seeding last prompt tokens externally', async () => {
26122662
const compressSpy = vi
26132663
.spyOn(ChatCompressionService.prototype, 'compress')

packages/core/src/core/geminiChat.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,8 +2541,14 @@ export class GeminiChat {
25412541
usageMetadata.promptTokenCount ?? usageMetadata.totalTokenCount;
25422542
this.lastCandidatesTokenCount =
25432543
usageMetadata.promptTokenCount !== undefined
2544-
? (usageMetadata.candidatesTokenCount ?? 0) +
2545-
(usageMetadata.thoughtsTokenCount ?? 0)
2544+
? usageMetadata.totalTokenCount !== undefined
2545+
? Math.max(
2546+
0,
2547+
usageMetadata.totalTokenCount -
2548+
usageMetadata.promptTokenCount,
2549+
)
2550+
: (usageMetadata.candidatesTokenCount ?? 0) +
2551+
(usageMetadata.thoughtsTokenCount ?? 0)
25462552
: 0;
25472553
if (lastPromptTokenCount) {
25482554
// Always update the per-chat counter so this chat (including

0 commit comments

Comments
 (0)