-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(gemini): correct tool-use and thinking token accounting #3035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ac9790c
fa76732
617184b
de1f986
7d3e86a
8601299
54a0be1
1786d1d
47da7d5
6ae69bb
f13f8c1
7f0cff1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,14 +94,32 @@ public ChatResponse parseResponse(GenerateContentResponse response, Instant star | |
| if (response.usageMetadata().isPresent()) { | ||
| GenerateContentResponseUsageMetadata metadata = response.usageMetadata().get(); | ||
|
|
||
| int inputTokens = metadata.promptTokenCount().orElse(0); | ||
| // Server-side tool results are fed back to the model as additional input. | ||
| int inputTokens = | ||
| metadata.promptTokenCount().orElse(0) | ||
| + metadata.toolUsePromptTokenCount().orElse(0); | ||
| int cachedTokens = metadata.cachedContentTokenCount().orElse(0); | ||
| int totalOutputTokens = metadata.candidatesTokenCount().orElse(0); | ||
| int thinkingTokens = metadata.thoughtsTokenCount().orElse(0); | ||
|
|
||
| // Output tokens exclude thinking tokens (following DashScope behavior) | ||
| // In Gemini, candidatesTokenCount includes thinking, so we subtract it | ||
| int outputTokens = totalOutputTokens - thinkingTokens; | ||
| // Gemini reports candidate and thinking tokens separately; both are output. | ||
| // The total already includes thinking, so do not add it again in the fallback. | ||
| int outputTokens; | ||
| if (metadata.candidatesTokenCount().isPresent()) { | ||
| outputTokens = metadata.candidatesTokenCount().get() + thinkingTokens; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flips what
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-review of One residual: in this primary branch |
||
| } else if (metadata.totalTokenCount().isPresent()) { | ||
| int totalTokens = metadata.totalTokenCount().get(); | ||
| int reportedOutputTokens = totalTokens - inputTokens; | ||
| if (reportedOutputTokens < 0) { | ||
| log.debug( | ||
| "Gemini usage totalTokenCount ({}) is smaller than input token" | ||
| + " count ({}); clamping outputTokens to zero", | ||
| totalTokens, | ||
| inputTokens); | ||
| } | ||
| outputTokens = Math.max(0, reportedOutputTokens); | ||
| } else { | ||
| outputTokens = thinkingTokens; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Warning] This initializer is dead on arrival: That is presumably deliberate, but written as an assignment-plus-override it reads like a leftover, and a later edit that adds an Note #3034 solves the same problem with
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch is only reachable when |
||
| } | ||
|
|
||
| usage = | ||
| ChatUsage.builder() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toolUsePromptTokenCountis folded intoinputTokenswhilecachedTokensis passed through unchanged.ChatUsage's contract statescachedTokensis a subset ofinputTokens. That still holds when both come from the prompt, but the newno prompt with tool inputcase in the test matrix (prompt absent, toolUsePrompt 300) is exactly the shape where a cached-token overlap would go unnoticed. Worth one sentence confirming the cached/prompt relationship when only tool-use prompt tokens are reported.