@@ -62,10 +62,17 @@ function onVercelAiSpanStart(span: Span): void {
62
62
63
63
function vercelAiEventProcessor ( event : Event ) : Event {
64
64
if ( event . type === 'transaction' && event . spans ) {
65
+ // First pass: process all spans normally
65
66
for ( const span of event . spans ) {
66
67
// this mutates spans in-place
67
68
processEndedVercelAiSpan ( span ) ;
68
69
}
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
+ }
69
76
}
70
77
return event ;
71
78
}
@@ -241,6 +248,48 @@ export function addVercelAiProcessors(client: Client): void {
241
248
client . addEventProcessor ( Object . assign ( vercelAiEventProcessor , { id : 'VercelAiEventProcessor' } ) ) ;
242
249
}
243
250
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
+
244
293
function addProviderMetadataToAttributes ( attributes : SpanAttributes ) : void {
245
294
const providerMetadata = attributes [ AI_RESPONSE_PROVIDER_METADATA_ATTRIBUTE ] as string | undefined ;
246
295
if ( providerMetadata ) {
0 commit comments