Skip to content

Commit f1acf51

Browse files
committed
feat(core): Automatically disable truncation when span streaming is enabled in OpenAI
integration When span streaming is enabled, the `enableTruncation` option now defaults to `false` unless the user has explicitly set it. Closes: #20221
1 parent 2af59be commit f1acf51

5 files changed

Lines changed: 92 additions & 2 deletions

File tree

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.openAIIntegration({
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+
});

dev-packages/node-integration-tests/suites/tracing/openai/scenario-no-truncation.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ async function run() {
7575
});
7676
});
7777

78+
// Flush is required when span streaming is enabled to ensure streamed spans are sent before the process exits
79+
await Sentry.flush();
7880
server.close();
7981
}
8082

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,4 +1019,59 @@ describe('OpenAI integration', () => {
10191019
.completed();
10201020
});
10211021
});
1022+
1023+
const streamingLongContent = 'A'.repeat(50_000);
1024+
const streamingLongString = 'B'.repeat(50_000);
1025+
1026+
createEsmAndCjsTests(__dirname, 'scenario-no-truncation.mjs', 'instrument-streaming.mjs', (createRunner, test) => {
1027+
test('automatically disables truncation when span streaming is enabled', async () => {
1028+
await createRunner()
1029+
.expect({
1030+
span: container => {
1031+
const spans = container.items;
1032+
1033+
const expectedChatMessages = JSON.stringify([{ role: 'user', content: streamingLongContent }]);
1034+
const chatSpan = spans.find(
1035+
s => s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === expectedChatMessages,
1036+
);
1037+
expect(chatSpan).toBeDefined();
1038+
1039+
const responsesSpan = spans.find(
1040+
s => s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === streamingLongString,
1041+
);
1042+
expect(responsesSpan).toBeDefined();
1043+
},
1044+
})
1045+
.start()
1046+
.completed();
1047+
});
1048+
});
1049+
1050+
createEsmAndCjsTests(
1051+
__dirname,
1052+
'scenario-no-truncation.mjs',
1053+
'instrument-streaming-with-truncation.mjs',
1054+
(createRunner, test) => {
1055+
test('respects explicit enableTruncation: true even when span streaming is enabled', async () => {
1056+
await createRunner()
1057+
.expect({
1058+
span: container => {
1059+
const spans = container.items;
1060+
1061+
// With explicit enableTruncation: true, content should be truncated despite streaming.
1062+
// Find the chat span by matching the start of the truncated content (the 'A' repeated messages).
1063+
const chatSpan = spans.find(s =>
1064+
s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.startsWith('[{"role":"user","content":"AAAA'),
1065+
);
1066+
expect(chatSpan).toBeDefined();
1067+
expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value.length).toBeLessThan(
1068+
streamingLongContent.length,
1069+
);
1070+
},
1071+
})
1072+
.start()
1073+
.completed();
1074+
});
1075+
},
1076+
);
10221077
});

packages/core/src/tracing/openai/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getClient } from '../../currentScopes';
12
import { DEBUG_BUILD } from '../../debug-build';
23
import { captureException } from '../../exports';
34
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
@@ -24,6 +25,7 @@ import {
2425
resolveAIRecordingOptions,
2526
wrapPromiseWithMethods,
2627
} from '../ai/utils';
28+
import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled';
2729
import { OPENAI_METHOD_REGISTRY } from './constants';
2830
import { instrumentStream } from './streaming';
2931
import type { ChatCompletionChunk, OpenAiOptions, OpenAIStream, ResponseStreamingEvent } from './types';
@@ -170,7 +172,9 @@ function instrumentMethod<T extends unknown[], R>(
170172
originalResult = originalMethod.apply(context, args);
171173

172174
if (options.recordInputs && params) {
173-
addRequestAttributes(span, params, operationName, options.enableTruncation ?? true);
175+
const client = getClient();
176+
const enableTruncation = options.enableTruncation ?? !(client && hasSpanStreamingEnabled(client));
177+
addRequestAttributes(span, params, operationName, enableTruncation);
174178
}
175179

176180
// Return async processing
@@ -208,7 +212,9 @@ function instrumentMethod<T extends unknown[], R>(
208212
originalResult = originalMethod.apply(context, args);
209213

210214
if (options.recordInputs && params) {
211-
addRequestAttributes(span, params, operationName, options.enableTruncation ?? true);
215+
const client = getClient();
216+
const enableTruncation = options.enableTruncation ?? !(client && hasSpanStreamingEnabled(client));
217+
addRequestAttributes(span, params, operationName, enableTruncation);
212218
}
213219

214220
return originalResult.then(

0 commit comments

Comments
 (0)