Skip to content

Commit 20abb58

Browse files
committed
feat(core): Accumulate tokens for gen_ai.invoke_agent spans from child LLM calls
1 parent 254a86f commit 20abb58

File tree

2 files changed

+55
-0
lines changed
  • dev-packages/node-integration-tests/suites/tracing/vercelai
  • packages/core/src/utils

2 files changed

+55
-0
lines changed

dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,9 @@ describe('Vercel AI integration', () => {
432432
'vercel.ai.settings.maxSteps': 1,
433433
'vercel.ai.streaming': false,
434434
'gen_ai.response.model': 'mock-model-id',
435+
'gen_ai.usage.input_tokens': 15,
436+
'gen_ai.usage.output_tokens': 25,
437+
'gen_ai.usage.total_tokens': 40,
435438
'operation.name': 'ai.generateText',
436439
'sentry.op': 'gen_ai.invoke_agent',
437440
'sentry.origin': 'auto.vercelai.otel',
@@ -550,6 +553,9 @@ describe('Vercel AI integration', () => {
550553
'vercel.ai.settings.maxSteps': 1,
551554
'vercel.ai.streaming': false,
552555
'gen_ai.response.model': 'mock-model-id',
556+
'gen_ai.usage.input_tokens': 15,
557+
'gen_ai.usage.output_tokens': 25,
558+
'gen_ai.usage.total_tokens': 40,
553559
'operation.name': 'ai.generateText',
554560
'sentry.op': 'gen_ai.invoke_agent',
555561
'sentry.origin': 'auto.vercelai.otel',

packages/core/src/utils/vercel-ai.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,17 @@ function onVercelAiSpanStart(span: Span): void {
6262

6363
function vercelAiEventProcessor(event: Event): Event {
6464
if (event.type === 'transaction' && event.spans) {
65+
// First pass: process all spans normally
6566
for (const span of event.spans) {
6667
// this mutates spans in-place
6768
processEndedVercelAiSpan(span);
6869
}
70+
71+
// Second pass: accumulate tokens for gen_ai.invoke_agent spans
72+
// TODO: Determine how to handle token aggregation for tool call spans.
73+
for (const span of event.spans) {
74+
accumulateTokensFromChildSpans(span, event.spans);
75+
}
6976
}
7077
return event;
7178
}
@@ -241,6 +248,48 @@ export function addVercelAiProcessors(client: Client): void {
241248
client.addEventProcessor(Object.assign(vercelAiEventProcessor, { id: 'VercelAiEventProcessor' }));
242249
}
243250

251+
/**
252+
* For the gen_ai.invoke_agent span, correctly iterate over child spans and aggregate:
253+
* - Input tokens from client LLM child spans that include this attribute.
254+
* - Output tokens from client LLM child spans that include this attribute.
255+
* - Total tokens from client LLM child spans that include this attribute.
256+
*
257+
* Only immediate children of the invoke_agent span need to be considered,
258+
* since aggregation will automatically occur for each parent span.
259+
*
260+
*/
261+
function accumulateTokensFromChildSpans(spanJSON: SpanJSON, allSpans: SpanJSON[]): void {
262+
if (spanJSON.op !== 'gen_ai.invoke_agent') {
263+
return;
264+
}
265+
const childSpans = allSpans.filter(childSpan => childSpan.parent_span_id === spanJSON.span_id);
266+
267+
let totalInputTokens = 0;
268+
let totalOutputTokens = 0;
269+
270+
for (const childSpan of childSpans) {
271+
const inputTokens = childSpan.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE];
272+
const outputTokens = childSpan.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE];
273+
274+
if (typeof inputTokens === 'number') {
275+
totalInputTokens += inputTokens;
276+
}
277+
if (typeof outputTokens === 'number') {
278+
totalOutputTokens += outputTokens;
279+
}
280+
}
281+
282+
if (totalInputTokens > 0) {
283+
spanJSON.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = totalInputTokens;
284+
}
285+
if (totalOutputTokens > 0) {
286+
spanJSON.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = totalOutputTokens;
287+
}
288+
if (totalInputTokens > 0 || totalOutputTokens > 0) {
289+
spanJSON.data['gen_ai.usage.total_tokens'] = totalInputTokens + totalOutputTokens;
290+
}
291+
}
292+
244293
function addProviderMetadataToAttributes(attributes: SpanAttributes): void {
245294
const providerMetadata = attributes[AI_RESPONSE_PROVIDER_METADATA_ATTRIBUTE] as string | undefined;
246295
if (providerMetadata) {

0 commit comments

Comments
 (0)