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:
@@ -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 },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user