@@ -60,20 +60,34 @@ function onVercelAiSpanStart(span: Span): void {
6060 processGenerateSpan ( span , name , attributes ) ;
6161}
6262
63+ interface TokenSummary {
64+ inputTokens : number ;
65+ outputTokens : number ;
66+ }
67+
6368function vercelAiEventProcessor ( event : Event ) : Event {
6469 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
6674 for ( const span of event . spans ) {
67- // this mutates spans in-place
6875 processEndedVercelAiSpan ( span ) ;
76+
77+ // Accumulate token data for parent spans
78+ accumulateTokensForParent ( span , tokenAccumulator ) ;
6979 }
7080
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
7382 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 ) ;
7588 }
7689 }
90+
7791 return event ;
7892}
7993/**
@@ -249,43 +263,52 @@ export function addVercelAiProcessors(client: Client): void {
249263}
250264
251265/**
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.
259269 */
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 ) {
262273 return ;
263274 }
264- const childSpans = allSpans . filter ( childSpan => childSpan . parent_span_id === spanJSON . span_id ) ;
265275
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 ] ;
268278
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 } ;
272281
273282 if ( typeof inputTokens === 'number' ) {
274- totalInputTokens += inputTokens ;
283+ existing . inputTokens += inputTokens ;
275284 }
276285 if ( typeof outputTokens === 'number' ) {
277- totalOutputTokens += outputTokens ;
286+ existing . outputTokens += outputTokens ;
278287 }
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 ;
279302 }
280303
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 ;
283306 }
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 ;
286309 }
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 ;
289312 }
290313}
291314
0 commit comments