@@ -60,20 +60,34 @@ function onVercelAiSpanStart(span: Span): void {
60
60
processGenerateSpan ( span , name , attributes ) ;
61
61
}
62
62
63
+ interface TokenSummary {
64
+ inputTokens : number ;
65
+ outputTokens : number ;
66
+ }
67
+
63
68
function vercelAiEventProcessor ( event : Event ) : Event {
64
69
if ( event . type === 'transaction' && event . spans ) {
65
- // First pass: process all spans normally
70
+ // Map to accumulate token data by parent span ID
71
+ const tokenAccumulator : Map < string , TokenSummary > = new Map ( ) ;
72
+
73
+ // First pass: process all spans and accumulate token data
66
74
for ( const span of event . spans ) {
67
- // this mutates spans in-place
68
75
processEndedVercelAiSpan ( span ) ;
76
+
77
+ // Accumulate token data for parent spans
78
+ accumulateTokensForParent ( span , tokenAccumulator ) ;
69
79
}
70
80
71
- // Second pass: accumulate tokens for gen_ai.invoke_agent spans
72
- // TODO: Determine how to handle token aggregation for tool call spans.
81
+ // Second pass: apply accumulated token data to parent spans
73
82
for ( const span of event . spans ) {
74
- accumulateTokensFromChildSpans ( span , event . spans ) ;
83
+ if ( span . op !== 'gen_ai.invoke_agent' ) {
84
+ continue ;
85
+ }
86
+
87
+ applyAccumulatedTokens ( span , tokenAccumulator ) ;
75
88
}
76
89
}
90
+
77
91
return event ;
78
92
}
79
93
/**
@@ -249,43 +263,52 @@ export function addVercelAiProcessors(client: Client): void {
249
263
}
250
264
251
265
/**
252
- * For the gen_ai.invoke_agent span, iterate over child spans and aggregate tokens:
253
- * - Input tokens from client LLM child spans that include `gen_ai.usage.input_tokens` attribute.
254
- * - Output tokens from client LLM child spans that include `gen_ai.usage.output_tokens` attribute.
255
- * - Total tokens from client LLM child spans that include `gen_ai.usage.total_tokens` attribute.
256
- *
257
- * Only immediate children of the `gen_ai.invoke_agent` span need to be considered,
258
- * since aggregation will automatically occur for each parent span.
266
+ * Accumulates token data from a span to its parent in the token accumulator map.
267
+ * This function extracts token usage from the current span and adds it to the
268
+ * accumulated totals for its parent span.
259
269
*/
260
- function accumulateTokensFromChildSpans ( spanJSON : SpanJSON , allSpans : SpanJSON [ ] ) : void {
261
- if ( spanJSON . op !== 'gen_ai.invoke_agent' ) {
270
+ function accumulateTokensForParent ( span : SpanJSON , tokenAccumulator : Map < string , TokenSummary > ) : void {
271
+ const parentSpanId = span . parent_span_id ;
272
+ if ( ! parentSpanId ) {
262
273
return ;
263
274
}
264
- const childSpans = allSpans . filter ( childSpan => childSpan . parent_span_id === spanJSON . span_id ) ;
265
275
266
- let totalInputTokens = 0 ;
267
- let totalOutputTokens = 0 ;
276
+ const inputTokens = span . data [ GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ] ;
277
+ const outputTokens = span . data [ GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ] ;
268
278
269
- for ( const childSpan of childSpans ) {
270
- const inputTokens = childSpan . data [ GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ] ;
271
- const outputTokens = childSpan . data [ GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ] ;
279
+ if ( typeof inputTokens === 'number' || typeof outputTokens === 'number' ) {
280
+ const existing = tokenAccumulator . get ( parentSpanId ) || { inputTokens : 0 , outputTokens : 0 } ;
272
281
273
282
if ( typeof inputTokens === 'number' ) {
274
- totalInputTokens += inputTokens ;
283
+ existing . inputTokens += inputTokens ;
275
284
}
276
285
if ( typeof outputTokens === 'number' ) {
277
- totalOutputTokens += outputTokens ;
286
+ existing . outputTokens += outputTokens ;
278
287
}
288
+
289
+ tokenAccumulator . set ( parentSpanId , existing ) ;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Applies accumulated token data to the `gen_ai.invoke_agent` span.
295
+ * Only immediate children of the `gen_ai.invoke_agent` span are considered,
296
+ * since aggregation will automatically occur for each parent span.
297
+ */
298
+ function applyAccumulatedTokens ( span : SpanJSON , tokenAccumulator : Map < string , TokenSummary > ) : void {
299
+ const accumulated = tokenAccumulator . get ( span . span_id ) ;
300
+ if ( ! accumulated ) {
301
+ return ;
279
302
}
280
303
281
- if ( totalInputTokens > 0 ) {
282
- spanJSON . data [ GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ] = totalInputTokens ;
304
+ if ( accumulated . inputTokens > 0 ) {
305
+ span . data [ GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ] = accumulated . inputTokens ;
283
306
}
284
- if ( totalOutputTokens > 0 ) {
285
- spanJSON . data [ GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ] = totalOutputTokens ;
307
+ if ( accumulated . outputTokens > 0 ) {
308
+ span . data [ GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ] = accumulated . outputTokens ;
286
309
}
287
- if ( totalInputTokens > 0 || totalOutputTokens > 0 ) {
288
- spanJSON . data [ 'gen_ai.usage.total_tokens' ] = totalInputTokens + totalOutputTokens ;
310
+ if ( accumulated . inputTokens > 0 || accumulated . outputTokens > 0 ) {
311
+ span . data [ 'gen_ai.usage.total_tokens' ] = accumulated . inputTokens + accumulated . outputTokens ;
289
312
}
290
313
}
291
314
0 commit comments