From 7c7a496668dade5233276e72799f23f40cfe9c57 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Sun, 12 Apr 2026 00:41:23 +0900 Subject: [PATCH 1/3] 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 --- .../instrument-streaming-with-truncation.mjs | 16 ++++++ .../tracing/openai/instrument-streaming.mjs | 11 ++++ .../tracing/openai/scenario-no-truncation.mjs | 2 + .../suites/tracing/openai/test.ts | 54 +++++++++++++++++++ packages/core/src/tracing/openai/index.ts | 10 +++- 5 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs new file mode 100644 index 000000000000..097c7adcf087 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + sendDefaultPii: true, + transport: loggingTransport, + traceLifecycle: 'stream', + integrations: [ + Sentry.openAIIntegration({ + enableTruncation: true, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs new file mode 100644 index 000000000000..48a860c510c5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + sendDefaultPii: true, + transport: loggingTransport, + traceLifecycle: 'stream', +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-no-truncation.mjs index f19345653c07..838f7e35d285 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-no-truncation.mjs @@ -75,6 +75,8 @@ async function run() { }); }); + // Flush is required when span streaming is enabled to ensure streamed spans are sent before the process exits + await Sentry.flush(); server.close(); } diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts index d3bdc0a6a80c..8e2554c7762c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -1019,4 +1019,58 @@ describe('OpenAI integration', () => { .completed(); }); }); + + const streamingLongContent = 'A'.repeat(50_000); + const streamingLongString = 'B'.repeat(50_000); + + createEsmAndCjsTests(__dirname, 'scenario-no-truncation.mjs', 'instrument-streaming.mjs', (createRunner, test) => { + test('automatically disables truncation when span streaming is enabled', async () => { + await createRunner() + .expect({ + span: container => { + const spans = container.items; + + const chatSpan = spans.find(s => + s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.includes(streamingLongContent), + ); + expect(chatSpan).toBeDefined(); + + const responsesSpan = spans.find(s => + s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.includes(streamingLongString), + ); + expect(responsesSpan).toBeDefined(); + }, + }) + .start() + .completed(); + }); + }); + + createEsmAndCjsTests( + __dirname, + 'scenario-no-truncation.mjs', + 'instrument-streaming-with-truncation.mjs', + (createRunner, test) => { + test('respects explicit enableTruncation: true even when span streaming is enabled', async () => { + await createRunner() + .expect({ + span: container => { + const spans = container.items; + + // With explicit enableTruncation: true, content should be truncated despite streaming. + // Find the chat span by matching the start of the truncated content (the 'A' repeated messages). + const chatSpan = spans.find(s => + s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.startsWith('[{"role":"user","content":"AAAA'), + ); + expect(chatSpan).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value.length).toBeLessThan( + streamingLongContent.length, + ); + }, + }) + .start() + .completed(); + }); + }, + ); }); diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index f1c4d3a06516..23ab5c8d1c28 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -1,3 +1,4 @@ +import { getClient } from '../../currentScopes'; import { DEBUG_BUILD } from '../../debug-build'; import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; @@ -24,6 +25,7 @@ import { resolveAIRecordingOptions, wrapPromiseWithMethods, } from '../ai/utils'; +import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import { OPENAI_METHOD_REGISTRY } from './constants'; import { instrumentStream } from './streaming'; import type { ChatCompletionChunk, OpenAiOptions, OpenAIStream, ResponseStreamingEvent } from './types'; @@ -170,7 +172,9 @@ function instrumentMethod( originalResult = originalMethod.apply(context, args); if (options.recordInputs && params) { - addRequestAttributes(span, params, operationName, options.enableTruncation ?? true); + const client = getClient(); + const enableTruncation = options.enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); + addRequestAttributes(span, params, operationName, enableTruncation); } // Return async processing @@ -208,7 +212,9 @@ function instrumentMethod( originalResult = originalMethod.apply(context, args); if (options.recordInputs && params) { - addRequestAttributes(span, params, operationName, options.enableTruncation ?? true); + const client = getClient(); + const enableTruncation = options.enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); + addRequestAttributes(span, params, operationName, enableTruncation); } return originalResult.then( From 2f1dc981763e9e00c37a2db0ec6b1e868ba18980 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Sun, 12 Apr 2026 01:56:19 +0900 Subject: [PATCH 2/3] Add shouldEnableTruncation helper --- packages/core/src/tracing/ai/utils.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core/src/tracing/ai/utils.ts b/packages/core/src/tracing/ai/utils.ts index d3cce644dbc1..05502b249efb 100644 --- a/packages/core/src/tracing/ai/utils.ts +++ b/packages/core/src/tracing/ai/utils.ts @@ -3,6 +3,7 @@ */ import { captureException } from '../../exports'; import { getClient } from '../../currentScopes'; +import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import type { Span } from '../../types-hoist/span'; import { isThenable } from '../../utils/is'; import { @@ -56,6 +57,16 @@ export function resolveAIRecordingOptions(options? } as T & Required; } +/** + * Resolves whether truncation should be enabled. + * If the user explicitly set `enableTruncation`, that value is used. + * Otherwise, truncation is disabled when span streaming is active. + */ +export function shouldEnableTruncation(enableTruncation: boolean | undefined): boolean { + const client = getClient(); + return enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); +} + /** * Build method path from current traversal */ From 1f60c41bb6f02a0428f2b605a56872bb114a11e9 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Sun, 12 Apr 2026 01:56:24 +0900 Subject: [PATCH 3/3] Use shouldEnableTruncation in OpenAI integration --- packages/core/src/tracing/openai/index.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index 23ab5c8d1c28..820128050b12 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -1,4 +1,3 @@ -import { getClient } from '../../currentScopes'; import { DEBUG_BUILD } from '../../debug-build'; import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; @@ -23,9 +22,9 @@ import { getJsonString, getTruncatedJsonString, resolveAIRecordingOptions, + shouldEnableTruncation, wrapPromiseWithMethods, } from '../ai/utils'; -import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import { OPENAI_METHOD_REGISTRY } from './constants'; import { instrumentStream } from './streaming'; import type { ChatCompletionChunk, OpenAiOptions, OpenAIStream, ResponseStreamingEvent } from './types'; @@ -172,9 +171,7 @@ function instrumentMethod( originalResult = originalMethod.apply(context, args); if (options.recordInputs && params) { - const client = getClient(); - const enableTruncation = options.enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); - addRequestAttributes(span, params, operationName, enableTruncation); + addRequestAttributes(span, params, operationName, shouldEnableTruncation(options.enableTruncation)); } // Return async processing @@ -212,9 +209,7 @@ function instrumentMethod( originalResult = originalMethod.apply(context, args); if (options.recordInputs && params) { - const client = getClient(); - const enableTruncation = options.enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); - addRequestAttributes(span, params, operationName, enableTruncation); + addRequestAttributes(span, params, operationName, shouldEnableTruncation(options.enableTruncation)); } return originalResult.then(