mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-07-09 17:28:45 +00:00
feat(ai): add reasoning token counts to Usage
Add optional reasoning?: number to Usage as a subset of output. Populate for Anthropic (output_tokens_details.thinking_tokens), OpenAI Responses/Codex/Azure (output_tokens_details.reasoning_tokens), OpenAI Completions (completion_tokens_details.reasoning_tokens), and Google Generative AI / Vertex (thoughtsTokenCount). Bedrock Converse and Mistral do not return a reasoning breakdown, so they stay unset. closes #6057
This commit is contained in:
parent
5c76ae407d
commit
d7868b0998
7 changed files with 23 additions and 0 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added an optional `reasoning` field to `Usage` reporting reasoning/thinking token counts as a subset of `output`. Populated for Anthropic (`output_tokens_details.thinking_tokens`), OpenAI Responses/Codex/Azure (`output_tokens_details.reasoning_tokens`), OpenAI Completions (`completion_tokens_details.reasoning_tokens`), and Google Generative AI / Vertex (`thoughtsTokenCount`). Bedrock Converse and Mistral are not populated because those APIs do not return a reasoning token breakdown ([#6057](https://github.com/earendil-works/pi/issues/6057)).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed retry classification for provider errors that explicitly tell callers to retry the request ([#6019](https://github.com/earendil-works/pi/issues/6019)).
|
||||
|
|
|
|||
|
|
@ -699,6 +699,14 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
|
|||
if (event.usage.cache_creation_input_tokens != null) {
|
||||
output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
||||
}
|
||||
// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the
|
||||
// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from
|
||||
// its Usage type, so read it through a narrow cast. Verified against the live API.
|
||||
const thinkingTokens = (event.usage as { output_tokens_details?: { thinking_tokens?: number } })
|
||||
.output_tokens_details?.thinking_tokens;
|
||||
if (thinkingTokens != null) {
|
||||
output.usage.reasoning = thinkingTokens;
|
||||
}
|
||||
// Anthropic doesn't provide total_tokens, compute from components
|
||||
output.usage.totalTokens =
|
||||
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@ export const stream: StreamFunction<"google-generative-ai", GoogleOptions> = (
|
|||
(chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
||||
cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
|
||||
cacheWrite: 0,
|
||||
reasoning: chunk.usageMetadata.thoughtsTokenCount || 0,
|
||||
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
||||
cost: {
|
||||
input: 0,
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ export const stream: StreamFunction<"google-vertex", GoogleVertexOptions> = (
|
|||
(chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
||||
cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
|
||||
cacheWrite: 0,
|
||||
reasoning: chunk.usageMetadata.thoughtsTokenCount || 0,
|
||||
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
||||
cost: {
|
||||
input: 0,
|
||||
|
|
|
|||
|
|
@ -1107,6 +1107,7 @@ function parseChunkUsage(
|
|||
completion_tokens?: number;
|
||||
prompt_cache_hit_tokens?: number;
|
||||
prompt_tokens_details?: { cached_tokens?: number; cache_write_tokens?: number };
|
||||
completion_tokens_details?: { reasoning_tokens?: number };
|
||||
},
|
||||
model: Model<"openai-completions">,
|
||||
): AssistantMessage["usage"] {
|
||||
|
|
@ -1130,6 +1131,7 @@ function parseChunkUsage(
|
|||
output: outputTokens,
|
||||
cacheRead: cacheReadTokens,
|
||||
cacheWrite: cacheWriteTokens,
|
||||
reasoning: rawUsage.completion_tokens_details?.reasoning_tokens || 0,
|
||||
totalTokens: input + outputTokens + cacheReadTokens + cacheWriteTokens,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|||
output: response.usage.output_tokens || 0,
|
||||
cacheRead: cachedTokens,
|
||||
cacheWrite: 0,
|
||||
reasoning: response.usage.output_tokens_details?.reasoning_tokens || 0,
|
||||
totalTokens: response.usage.total_tokens || 0,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -356,6 +356,12 @@ export interface Usage {
|
|||
cacheWrite: number;
|
||||
/** Subset of `cacheWrite` written with 1h retention. Only Anthropic reports this split. */
|
||||
cacheWrite1h?: number;
|
||||
/**
|
||||
* Reasoning/thinking tokens, when the provider reports them. This is a subset of
|
||||
* `output`: `output` already includes these tokens. Set to a number (possibly 0) by
|
||||
* providers that expose a reasoning breakdown; left undefined by providers that don't.
|
||||
*/
|
||||
reasoning?: number;
|
||||
totalTokens: number;
|
||||
cost: {
|
||||
input: number;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue