From 7826136b892cf0a9e758e4038ab917801c963f85 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 10 Nov 2025 13:29:18 +0100 Subject: [PATCH 1/4] Add failing test to confirm responses api string inputs do not get truncated --- ...enario-message-truncation-completions.mjs} | 2 +- .../scenario-message-truncation-responses.mjs | 93 +++++++++++++++++++ .../suites/tracing/openai/test.ts | 38 +++++++- 3 files changed, 131 insertions(+), 2 deletions(-) rename dev-packages/node-integration-tests/suites/tracing/openai/{scenario-message-truncation.mjs => scenario-message-truncation-completions.mjs} (97%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs similarity index 97% rename from dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs rename to dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs index 5623d3763657..96684ed9ec4f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs @@ -12,7 +12,7 @@ class MockOpenAI { await new Promise(resolve => setTimeout(resolve, 10)); return { - id: 'chatcmpl-truncation-test', + id: 'chatcmpl-completions-truncation-test', object: 'chat.completion', created: 1677652288, model: params.model, diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs new file mode 100644 index 000000000000..dae4f03aa740 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs @@ -0,0 +1,93 @@ +import { instrumentOpenAiClient } from '@sentry/core'; +import * as Sentry from '@sentry/node'; + +class MockOpenAI { + constructor(config) { + this.apiKey = config.apiKey; + + this.responses = { + create: async params => { + // Simulate processing time + await new Promise(resolve => setTimeout(resolve, 10)); + + return { + id: 'chatcmpl-responses-truncation-test', + object: 'response', + created_at: 1677652288, + status: 'completed', + error: null, + incomplete_details: null, + instructions: null, + max_output_tokens: null, + model: params.model, + output: [ + { + type: 'message', + id: 'message-123', + status: 'completed', + role: 'assistant', + content: [ + { + type: 'output_text', + text: 'Response to truncated messages', + annotations: [], + }, + ], + }, + ], + parallel_tool_calls: true, + previous_response_id: null, + reasoning: { + effort: null, + summary: null, + }, + store: true, + temperature: params.temperature, + text: { + format: { + type: 'text', + }, + }, + tool_choice: 'auto', + tools: [], + top_p: 1.0, + truncation: 'disabled', + usage: { + input_tokens: 10, + input_tokens_details: { + cached_tokens: 0, + }, + output_tokens: 15, + output_tokens_details: { + reasoning_tokens: 0, + }, + total_tokens: 25, + }, + user: null, + metadata: {}, + }; + }, + }; + } +} + +async function run() { + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + const mockClient = new MockOpenAI({ + apiKey: 'mock-api-key', + }); + + const client = instrumentOpenAiClient(mockClient); + + // Create 1 large message that should get truncated to fit within the 20KB limit + const largeContent = 'A'.repeat(25000) + 'B'.repeat(25000); // ~50KB should be truncated to include only As + + await client.responses.create({ + model: 'gpt-3.5-turbo', + input: largeContent, + temperature: 0.7, + }); + }); +} + +run(); 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 218e3c7ee61f..01d0ccc172cc 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -400,7 +400,7 @@ describe('OpenAI integration', () => { createEsmAndCjsTests( __dirname, - 'scenario-message-truncation.mjs', + 'scenario-message-truncation-completions.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { test('truncates messages when they exceed byte limit - keeps only last message and crops it', async () => { @@ -433,4 +433,40 @@ describe('OpenAI integration', () => { }); }, ); + + createEsmAndCjsTests( + __dirname, + 'scenario-message-truncation-responses.mjs', + 'instrument-with-pii.mjs', + (createRunner, test) => { + test('should truncate input strings that exceed byte limit', async () => { + await createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'main', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.operation.name': 'responses', + 'sentry.op': 'gen_ai.responses', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'gpt-3.5-turbo', + // Message should be present but truncated to include only As + 'gen_ai.request.input': expect.stringMatching(/^A+$/), + }), + description: 'responses gpt-3.5-turbo', + op: 'gen_ai.responses', + origin: 'auto.ai.openai', + status: 'ok', + }), + ]), + }, + }) + .start() + .completed(); + }); + }, + ); }); From 26197a39ccc9f35975810fcd0a7d8d377c0ae575 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 10 Nov 2025 14:07:03 +0100 Subject: [PATCH 2/4] Fix test --- .../node-integration-tests/suites/tracing/openai/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 01d0ccc172cc..a78b73377916 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -454,7 +454,7 @@ describe('OpenAI integration', () => { 'gen_ai.system': 'openai', 'gen_ai.request.model': 'gpt-3.5-turbo', // Message should be present but truncated to include only As - 'gen_ai.request.input': expect.stringMatching(/^A+$/), + 'gen_ai.request.messages': expect.stringMatching(/^A+$/), }), description: 'responses gpt-3.5-turbo', op: 'gen_ai.responses', From 1b80539e2c1a63dc0e8356309a354f4747dc58ab Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 10 Nov 2025 14:12:07 +0100 Subject: [PATCH 3/4] Fix string input truncation --- .../suites/tracing/openai/test.ts | 4 ++-- packages/core/src/utils/ai/messageTruncation.ts | 10 ++++++++++ packages/core/src/utils/ai/utils.ts | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) 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 a78b73377916..5cbb27df73bf 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -439,7 +439,7 @@ describe('OpenAI integration', () => { 'scenario-message-truncation-responses.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { - test('should truncate input strings that exceed byte limit', async () => { + test('truncates string inputs when they exceed byte limit', async () => { await createRunner() .ignore('event') .expect({ @@ -453,7 +453,7 @@ describe('OpenAI integration', () => { 'sentry.origin': 'auto.ai.openai', 'gen_ai.system': 'openai', 'gen_ai.request.model': 'gpt-3.5-turbo', - // Message should be present but truncated to include only As + // Messages should be present and should include truncated string input (contains only As) 'gen_ai.request.messages': expect.stringMatching(/^A+$/), }), description: 'responses gpt-3.5-turbo', diff --git a/packages/core/src/utils/ai/messageTruncation.ts b/packages/core/src/utils/ai/messageTruncation.ts index 64d186f927b8..945761f6220c 100644 --- a/packages/core/src/utils/ai/messageTruncation.ts +++ b/packages/core/src/utils/ai/messageTruncation.ts @@ -294,3 +294,13 @@ export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): export function truncateGenAiMessages(messages: unknown[]): unknown[] { return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); } + +/** + * Truncate GenAI string input using the default byte limit. + * + * @param input - The string to truncate + * @returns Truncated string + */ +export function truncateGenAiStringInput(input: string): string { + return truncateTextByBytes(input, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); +} diff --git a/packages/core/src/utils/ai/utils.ts b/packages/core/src/utils/ai/utils.ts index 00e147a16e5f..4a7a14eea554 100644 --- a/packages/core/src/utils/ai/utils.ts +++ b/packages/core/src/utils/ai/utils.ts @@ -7,7 +7,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from './gen-ai-attributes'; -import { truncateGenAiMessages } from './messageTruncation'; +import { truncateGenAiMessages, truncateGenAiStringInput } from './messageTruncation'; /** * Maps AI method paths to Sentry operation name */ @@ -95,7 +95,7 @@ export function setTokenUsageAttributes( export function getTruncatedJsonString(value: T | T[]): string { if (typeof value === 'string') { // Some values are already JSON strings, so we don't need to duplicate the JSON parsing - return value; + return truncateGenAiStringInput(value); } if (Array.isArray(value)) { // truncateGenAiMessages returns an array of strings, so we need to stringify it From 5a0af4f7ce15ca75e991d07bc6005fb6099769dc Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 10 Nov 2025 14:14:25 +0100 Subject: [PATCH 4/4] Update comments --- .../tracing/openai/scenario-message-truncation-responses.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs index dae4f03aa740..aebd3341eb33 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs @@ -79,8 +79,8 @@ async function run() { const client = instrumentOpenAiClient(mockClient); - // Create 1 large message that should get truncated to fit within the 20KB limit - const largeContent = 'A'.repeat(25000) + 'B'.repeat(25000); // ~50KB should be truncated to include only As + // Create 1 large message that gets truncated to fit within the 20KB limit + const largeContent = 'A'.repeat(25000) + 'B'.repeat(25000); // ~50KB gets truncated to include only As await client.responses.create({ model: 'gpt-3.5-turbo',