Skip to content

Commit d1dab91

Browse files
committed
fix(core): Instrument invoke_agent root span, and move vercel ai logic to a folder
1 parent f307a22 commit d1dab91

File tree

5 files changed

+75
-61
lines changed

5 files changed

+75
-61
lines changed

packages/core/src/utils/vercel-ai.ts renamed to packages/core/src/utils/vercel-ai/index.ts

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import type { Client } from '../client';
2-
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
3-
import type { Event } from '../types-hoist/event';
4-
import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, SpanOrigin } from '../types-hoist/span';
5-
import { spanToJSON } from './spanUtils';
1+
import type { Client } from '../../client';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
3+
import type { Event } from '../../types-hoist/event';
4+
import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, SpanOrigin } from '../../types-hoist/span';
5+
import { spanToJSON } from '../spanUtils';
6+
import type { TokenSummary } from './types';
7+
import { accumulateTokensForParent, applyAccumulatedTokens } from './utils';
68
import type { ProviderMetadata } from './vercel-ai-attributes';
79
import {
810
AI_MODEL_ID_ATTRIBUTE,
@@ -60,11 +62,6 @@ function onVercelAiSpanStart(span: Span): void {
6062
processGenerateSpan(span, name, attributes);
6163
}
6264

63-
interface TokenSummary {
64-
inputTokens: number;
65-
outputTokens: number;
66-
}
67-
6865
function vercelAiEventProcessor(event: Event): Event {
6966
if (event.type === 'transaction' && event.spans) {
7067
// Map to accumulate token data by parent span ID
@@ -86,6 +83,12 @@ function vercelAiEventProcessor(event: Event): Event {
8683

8784
applyAccumulatedTokens(span, tokenAccumulator);
8885
}
86+
87+
// Also apply to root when it is the invoke_agent pipeline
88+
const trace = event.contexts?.trace;
89+
if (trace && trace.op === 'gen_ai.invoke_agent') {
90+
applyAccumulatedTokens(trace, tokenAccumulator);
91+
}
8992
}
9093

9194
return event;
@@ -262,56 +265,6 @@ export function addVercelAiProcessors(client: Client): void {
262265
client.addEventProcessor(Object.assign(vercelAiEventProcessor, { id: 'VercelAiEventProcessor' }));
263266
}
264267

265-
/**
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.
269-
*/
270-
function accumulateTokensForParent(span: SpanJSON, tokenAccumulator: Map<string, TokenSummary>): void {
271-
const parentSpanId = span.parent_span_id;
272-
if (!parentSpanId) {
273-
return;
274-
}
275-
276-
const inputTokens = span.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE];
277-
const outputTokens = span.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE];
278-
279-
if (typeof inputTokens === 'number' || typeof outputTokens === 'number') {
280-
const existing = tokenAccumulator.get(parentSpanId) || { inputTokens: 0, outputTokens: 0 };
281-
282-
if (typeof inputTokens === 'number') {
283-
existing.inputTokens += inputTokens;
284-
}
285-
if (typeof outputTokens === 'number') {
286-
existing.outputTokens += outputTokens;
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;
302-
}
303-
304-
if (accumulated.inputTokens > 0) {
305-
span.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = accumulated.inputTokens;
306-
}
307-
if (accumulated.outputTokens > 0) {
308-
span.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = accumulated.outputTokens;
309-
}
310-
if (accumulated.inputTokens > 0 || accumulated.outputTokens > 0) {
311-
span.data['gen_ai.usage.total_tokens'] = accumulated.inputTokens + accumulated.outputTokens;
312-
}
313-
}
314-
315268
function addProviderMetadataToAttributes(attributes: SpanAttributes): void {
316269
const providerMetadata = attributes[AI_RESPONSE_PROVIDER_METADATA_ATTRIBUTE] as string | undefined;
317270
if (providerMetadata) {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface TokenSummary {
2+
inputTokens: number;
3+
outputTokens: number;
4+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { TraceContext } from '../../types-hoist/context';
2+
import type { SpanJSON } from '../../types-hoist/span';
3+
import { GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE } from '../gen-ai-attributes';
4+
import type { TokenSummary } from './types';
5+
6+
/**
7+
* Accumulates token data from a span to its parent in the token accumulator map.
8+
* This function extracts token usage from the current span and adds it to the
9+
* accumulated totals for its parent span.
10+
*/
11+
export function accumulateTokensForParent(span: SpanJSON, tokenAccumulator: Map<string, TokenSummary>): void {
12+
const parentSpanId = span.parent_span_id;
13+
if (!parentSpanId) {
14+
return;
15+
}
16+
17+
const inputTokens = span.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE];
18+
const outputTokens = span.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE];
19+
20+
if (typeof inputTokens === 'number' || typeof outputTokens === 'number') {
21+
const existing = tokenAccumulator.get(parentSpanId) || { inputTokens: 0, outputTokens: 0 };
22+
23+
if (typeof inputTokens === 'number') {
24+
existing.inputTokens += inputTokens;
25+
}
26+
if (typeof outputTokens === 'number') {
27+
existing.outputTokens += outputTokens;
28+
}
29+
30+
tokenAccumulator.set(parentSpanId, existing);
31+
}
32+
}
33+
34+
/**
35+
* Applies accumulated token data to the `gen_ai.invoke_agent` span.
36+
* Only immediate children of the `gen_ai.invoke_agent` span are considered,
37+
* since aggregation will automatically occur for each parent span.
38+
*/
39+
export function applyAccumulatedTokens(
40+
spanOrTrace: SpanJSON | TraceContext,
41+
tokenAccumulator: Map<string, TokenSummary>,
42+
): void {
43+
const accumulated = tokenAccumulator.get(spanOrTrace.span_id);
44+
if (!accumulated || !spanOrTrace.data) {
45+
return;
46+
}
47+
48+
if (accumulated.inputTokens > 0) {
49+
spanOrTrace.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = accumulated.inputTokens;
50+
}
51+
if (accumulated.outputTokens > 0) {
52+
spanOrTrace.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = accumulated.outputTokens;
53+
}
54+
if (accumulated.inputTokens > 0 || accumulated.outputTokens > 0) {
55+
spanOrTrace.data['gen_ai.usage.total_tokens'] = accumulated.inputTokens + accumulated.outputTokens;
56+
}
57+
}

packages/node/src/integrations/tracing/vercelai/instrumentation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase {
8989
* Initializes the instrumentation by defining the modules to be patched.
9090
*/
9191
public init(): InstrumentationModuleDefinition {
92-
const module = new InstrumentationNodeModuleDefinition('ai', ['>=3.0.0 <5'], this._patch.bind(this));
92+
const module = new InstrumentationNodeModuleDefinition('ai', ['>=3.0.0 <6'], this._patch.bind(this));
9393
return module;
9494
}
9595

0 commit comments

Comments
 (0)