Skip to content

Commit 38cb4d4

Browse files
committed
feat(node): Add @vercel/ai instrumentation
1 parent 3f0926e commit 38cb4d4

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

packages/node/src/integrations/tracing/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { instrumentMysql2, mysql2Integration } from './mysql2';
1818
import { instrumentNest, nestIntegration } from './nest/nest';
1919
import { instrumentPostgres, postgresIntegration } from './postgres';
2020
import { instrumentRedis, redisIntegration } from './redis';
21+
import { vercelAiIntegration } from './vercelai';
2122

2223
/**
2324
* With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required.
@@ -45,6 +46,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
4546
kafkaIntegration(),
4647
amqplibIntegration(),
4748
lruMemoizerIntegration(),
49+
vercelAiIntegration(),
4850
];
4951
}
5052

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, defineIntegration, spanToJSON } from '@sentry/core';
2+
import type { IntegrationFn } from '@sentry/types';
3+
4+
import { addOriginToSpan } from '../../utils/addOriginToSpan';
5+
6+
const INTEGRATION_NAME = 'VercelAi';
7+
8+
const _vercelAiIntegration = (() => {
9+
return {
10+
name: INTEGRATION_NAME,
11+
setupOnce() {
12+
// TODO: Vercel Instrumentation.
13+
},
14+
15+
setup(client) {
16+
// TODO: Only run if @vercel/ai is installed.
17+
client.on('spanStart', span => {
18+
const { data: attributes, description: name } = spanToJSON(span);
19+
20+
if (!attributes || !name) {
21+
return;
22+
}
23+
24+
// https://sdk.vercel.ai/docs/ai-sdk-core/telemetry#basic-llm-span-information
25+
if (attributes['ai.model.id'] && attributes['ai.model.provider']) {
26+
addOriginToSpan(span, 'auto.vercel.ai');
27+
28+
const aiOperation = name.replace('ai.', '');
29+
if (aiOperation.includes('embed')) {
30+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.embeddings');
31+
} else {
32+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.run');
33+
}
34+
35+
if (attributes['ai.prompt']) {
36+
span.setAttribute('ai.input_messages', attributes['ai.prompt']);
37+
}
38+
if (attributes['ai.usage.completionTokens'] != undefined) {
39+
span.setAttribute('ai.completion_tokens.used', attributes['ai.usage.completionTokens']);
40+
}
41+
if (attributes['ai.usage.promptTokens'] != undefined) {
42+
span.setAttribute('ai.prompt_tokens.used', attributes['ai.usage.promptTokens']);
43+
}
44+
if (
45+
attributes['ai.usage.completionTokens'] != undefined &&
46+
attributes['ai.usage.promptTokens'] != undefined
47+
) {
48+
span.setAttribute(
49+
'ai.tokens.used',
50+
attributes['ai.usage.completionTokens'] + attributes['ai.usage.promptTokens'],
51+
);
52+
}
53+
if (attributes['ai.model.id']) {
54+
span.setAttribute('ai.model_id', attributes['ai.model.id']);
55+
}
56+
57+
span.setAttribute('ai.streaming', name.includes('stream'));
58+
}
59+
});
60+
},
61+
};
62+
}) satisfies IntegrationFn;
63+
64+
/**
65+
* Integration for @vercel/ai
66+
*/
67+
export const vercelAiIntegration = defineIntegration(_vercelAiIntegration);

0 commit comments

Comments
 (0)