Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ Affected SDKs: All SDKs.
- The `gen_ai.request.available_tools` span attribute was renamed to `gen_ai.tool.definitions` across all AI integrations.
- The `gen_ai.tool.input` span attribute was renamed to `gen_ai.tool.call.arguments` across all AI integrations.
- The `gen_ai.tool.output` span attribute was renamed to `gen_ai.tool.call.result` across all AI integrations.
- The Vercel AI token attributes `gen_ai.usage.input_tokens.cached`, `gen_ai.usage.input_tokens.cache_write`, and `gen_ai.usage.output_tokens.reasoning` were renamed to `gen_ai.usage.cache_read.input_tokens`, `gen_ai.usage.cache_creation.input_tokens`, and `gen_ai.usage.reasoning.output_tokens`.
- Span attributes now use the shared `@sentry/conventions` package under the hood.

If you reference these attributes in custom instrumentation, `beforeSendSpan`, dashboards, or alerts, update them to the new names.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import {
GEN_AI_TOOL_DESCRIPTION,
GEN_AI_TOOL_NAME,
GEN_AI_TOOL_TYPE,
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
GEN_AI_USAGE_INPUT_TOKENS,
GEN_AI_USAGE_INPUT_TOKENS_CACHED,
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
Expand Down Expand Up @@ -793,8 +794,8 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe
// Cache/reasoning token breakdown and conversation id are derived from the model's
// `providerMetadata` — by the OTel processor on v6 and by the channel subscriber on v7,
// both via the shared `getProviderMetadataAttributes` helper, so the shape is identical.
expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED]?.value).toBe(5);
expect(generateContent.attributes['gen_ai.usage.output_tokens.reasoning']?.value).toBe(7);
expect(generateContent.attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS]?.value).toBe(5);
expect(generateContent.attributes[GEN_AI_USAGE_REASONING_OUTPUT_TOKENS]?.value).toBe(7);
expect(generateContent.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe('resp_abc123');

const invokeAgent = container.items.find(
Expand Down
29 changes: 17 additions & 12 deletions packages/core/src/tracing/vercel-ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import {
GEN_AI_TOOL_DESCRIPTION,
GEN_AI_TOOL_NAME,
GEN_AI_TOOL_TYPE,
GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
GEN_AI_USAGE_INPUT_TOKENS,
GEN_AI_USAGE_INPUT_TOKENS_CACHED,
GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE,
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../ai/gen-ai-attributes';
Expand Down Expand Up @@ -256,7 +257,7 @@ function buildOutputMessages(attributes: Record<string, unknown>): void {
export function processVercelAiSpanAttributes(attributes: Record<string, unknown>): void {
renameAttributeKey(attributes, AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS);
renameAttributeKey(attributes, AI_USAGE_PROMPT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS);
renameAttributeKey(attributes, AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_CACHED);
renameAttributeKey(attributes, AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS);

// Parent spans (ai.streamText, ai.streamObject, etc.) use inputTokens/outputTokens instead of promptTokens/completionTokens
renameAttributeKey(attributes, 'ai.usage.inputTokens', GEN_AI_USAGE_INPUT_TOKENS);
Expand All @@ -276,10 +277,10 @@ export function processVercelAiSpanAttributes(attributes: Record<string, unknown
if (
!inputTokensAreCacheInclusive &&
typeof attributes[GEN_AI_USAGE_INPUT_TOKENS] === 'number' &&
typeof attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED] === 'number'
typeof attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] === 'number'
) {
attributes[GEN_AI_USAGE_INPUT_TOKENS] =
attributes[GEN_AI_USAGE_INPUT_TOKENS] + attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED];
attributes[GEN_AI_USAGE_INPUT_TOKENS] + attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];
}

// Compute total tokens from input + output (embeddings may only have input tokens)
Expand Down Expand Up @@ -546,8 +547,8 @@ export function getProviderMetadataAttributes(providerMetadata: unknown): Record
// OpenAI (v5 uses 'openai', v6 Azure Responses API uses 'azure')
const openaiMetadata: OpenAiProviderMetadata | undefined = metadata.openai ?? metadata.azure;
if (openaiMetadata) {
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, openaiMetadata.cachedPromptTokens);
setAttributeIfDefined(attributes, 'gen_ai.usage.output_tokens.reasoning', openaiMetadata.reasoningTokens);
setAttributeIfDefined(attributes, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, openaiMetadata.cachedPromptTokens);
setAttributeIfDefined(attributes, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, openaiMetadata.reasoningTokens);
setAttributeIfDefined(
attributes,
'gen_ai.usage.output_tokens.prediction_accepted',
Expand All @@ -564,24 +565,28 @@ export function getProviderMetadataAttributes(providerMetadata: unknown): Record
if (metadata.anthropic) {
const cachedInputTokens =
metadata.anthropic.usage?.cache_read_input_tokens ?? metadata.anthropic.cacheReadInputTokens;
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, cachedInputTokens);
setAttributeIfDefined(attributes, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, cachedInputTokens);

const cacheWriteInputTokens =
metadata.anthropic.usage?.cache_creation_input_tokens ?? metadata.anthropic.cacheCreationInputTokens;
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE, cacheWriteInputTokens);
setAttributeIfDefined(attributes, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, cacheWriteInputTokens);
}

if (metadata.bedrock?.usage) {
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, metadata.bedrock.usage.cacheReadInputTokens);
setAttributeIfDefined(
attributes,
GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE,
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
metadata.bedrock.usage.cacheReadInputTokens,
);
setAttributeIfDefined(
attributes,
GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
metadata.bedrock.usage.cacheWriteInputTokens,
);
}

if (metadata.deepseek) {
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, metadata.deepseek.promptCacheHitTokens);
setAttributeIfDefined(attributes, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, metadata.deepseek.promptCacheHitTokens);
setAttributeIfDefined(attributes, 'gen_ai.usage.input_tokens.cache_miss', metadata.deepseek.promptCacheMissTokens);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('vercel-ai cached tokens', () => {
});

expect(span.data?.['gen_ai.usage.input_tokens']).toBe(150);
expect(span.data?.['gen_ai.usage.input_tokens.cached']).toBe(50);
expect(span.data?.['gen_ai.usage.cache_read.input_tokens']).toBe(50);
expect(span.data?.['gen_ai.usage.total_tokens']).toBe(150);
});

Expand All @@ -57,7 +57,7 @@ describe('vercel-ai cached tokens', () => {
});

expect(span.data?.['gen_ai.usage.input_tokens']).toBe(9500);
expect(span.data?.['gen_ai.usage.input_tokens.cached']).toBe(8000);
expect(span.data?.['gen_ai.usage.cache_read.input_tokens']).toBe(8000);
expect(span.data?.['gen_ai.usage.output_tokens']).toBe(300);
expect(span.data?.['gen_ai.usage.total_tokens']).toBe(9800);
});
Expand Down
Loading