Skip to content

Commit 43ce2ac

Browse files
authored
feat(core): Automatically disable truncation when span streaming is enabled in Vercel AI integration (#20232)
When span streaming is enabled, the `enableTruncation` option now defaults to `false` unless the user has explicitly set it. Should be merged after: #20195 Closes: #20226
1 parent 88a4c3e commit 43ce2ac

File tree

6 files changed

+116
-1
lines changed

6 files changed

+116
-1
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
transport: loggingTransport,
10+
traceLifecycle: 'stream',
11+
integrations: [
12+
Sentry.vercelAIIntegration({
13+
enableTruncation: true,
14+
}),
15+
],
16+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
transport: loggingTransport,
10+
traceLifecycle: 'stream',
11+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as Sentry from '@sentry/node';
2+
import { generateText } from 'ai';
3+
import { MockLanguageModelV1 } from 'ai/test';
4+
5+
async function run() {
6+
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
7+
const longContent = 'A'.repeat(50_000);
8+
await generateText({
9+
experimental_telemetry: { isEnabled: true },
10+
model: new MockLanguageModelV1({
11+
doGenerate: async () => ({
12+
rawCall: { rawPrompt: null, rawSettings: {} },
13+
finishReason: 'stop',
14+
usage: { promptTokens: 10, completionTokens: 5 },
15+
text: 'Response',
16+
}),
17+
}),
18+
messages: [
19+
{ role: 'user', content: longContent },
20+
{ role: 'assistant', content: 'Some reply' },
21+
{ role: 'user', content: 'Follow-up question' },
22+
],
23+
});
24+
});
25+
26+
// Flush is required when span streaming is enabled to ensure streamed spans are sent before the process exits
27+
await Sentry.flush(2000);
28+
}
29+
30+
run();

dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,4 +983,50 @@ describe('Vercel AI integration', () => {
983983
});
984984
},
985985
);
986+
987+
const streamingLongContent = 'A'.repeat(50_000);
988+
989+
createEsmAndCjsTests(__dirname, 'scenario-streaming.mjs', 'instrument-streaming.mjs', (createRunner, test) => {
990+
test('automatically disables truncation when span streaming is enabled', async () => {
991+
await createRunner()
992+
.expect({
993+
span: container => {
994+
const spans = container.items;
995+
996+
const chatSpan = spans.find(s =>
997+
s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.includes(streamingLongContent),
998+
);
999+
expect(chatSpan).toBeDefined();
1000+
},
1001+
})
1002+
.start()
1003+
.completed();
1004+
});
1005+
});
1006+
1007+
createEsmAndCjsTests(
1008+
__dirname,
1009+
'scenario-streaming.mjs',
1010+
'instrument-streaming-with-truncation.mjs',
1011+
(createRunner, test) => {
1012+
test('respects explicit enableTruncation: true even when span streaming is enabled', async () => {
1013+
await createRunner()
1014+
.expect({
1015+
span: container => {
1016+
const spans = container.items;
1017+
1018+
// With explicit enableTruncation: true, truncation keeps only the last message
1019+
// and drops the long content. The result should NOT contain the full 50k 'A' string.
1020+
const chatSpan = spans.find(s =>
1021+
s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.includes('Follow-up question'),
1022+
);
1023+
expect(chatSpan).toBeDefined();
1024+
expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).not.toContain(streamingLongContent);
1025+
},
1026+
})
1027+
.start()
1028+
.completed();
1029+
});
1030+
},
1031+
);
9861032
});

packages/core/src/tracing/ai/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import { captureException } from '../../exports';
55
import { getClient } from '../../currentScopes';
6+
import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled';
67
import type { Span } from '../../types-hoist/span';
78
import { isThenable } from '../../utils/is';
89
import {
@@ -56,6 +57,16 @@ export function resolveAIRecordingOptions<T extends AIRecordingOptions>(options?
5657
} as T & Required<AIRecordingOptions>;
5758
}
5859

60+
/**
61+
* Resolves whether truncation should be enabled.
62+
* If the user explicitly set `enableTruncation`, that value is used.
63+
* Otherwise, truncation is disabled when span streaming is active.
64+
*/
65+
export function shouldEnableTruncation(enableTruncation: boolean | undefined): boolean {
66+
const client = getClient();
67+
return enableTruncation ?? !(client && hasSpanStreamingEnabled(client));
68+
}
69+
5970
/**
6071
* Build method path from current traversal
6172
*/

packages/core/src/tracing/vercel-ai/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { Client } from '../../client';
33
import { getClient } from '../../currentScopes';
44
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
5+
import { shouldEnableTruncation } from '../ai/utils';
56
import type { Event } from '../../types-hoist/event';
67
import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON } from '../../types-hoist/span';
78
import { spanToJSON } from '../../utils/spanUtils';
@@ -119,7 +120,7 @@ function onVercelAiSpanStart(span: Span): void {
119120
const integration = client?.getIntegrationByName('VercelAI') as
120121
| { options?: { enableTruncation?: boolean } }
121122
| undefined;
122-
const enableTruncation = integration?.options?.enableTruncation ?? true;
123+
const enableTruncation = shouldEnableTruncation(integration?.options?.enableTruncation);
123124

124125
processGenerateSpan(span, name, attributes, enableTruncation);
125126
}

0 commit comments

Comments
 (0)