Skip to content

Commit 94d3f8d

Browse files
committed
make this 2n faster
1 parent 0ce72f0 commit 94d3f8d

File tree

1 file changed

+51
-28
lines changed

1 file changed

+51
-28
lines changed

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

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
6368
function 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

Comments
 (0)