From d2fccc5ed5bf73a3259c656009bdbbff0641ec13 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 17 Nov 2025 10:27:31 +0100 Subject: [PATCH 1/9] add failing tests for embeddings api support --- .../suites/tracing/openai/scenario.mjs | 40 +++++++++++ .../suites/tracing/openai/test.ts | 70 ++++++++++++++++++- ...cenario-message-truncation-completions.mjs | 0 ...scenario-message-truncation-embeddings.mjs | 66 +++++++++++++++++ .../scenario-message-truncation-responses.mjs | 0 5 files changed, 174 insertions(+), 2 deletions(-) rename dev-packages/node-integration-tests/suites/tracing/openai/{ => truncation}/scenario-message-truncation-completions.mjs (100%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs rename dev-packages/node-integration-tests/suites/tracing/openai/{ => truncation}/scenario-message-truncation-responses.mjs (100%) diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs index fde651c3c1ff..310d5636f8af 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs @@ -74,6 +74,28 @@ class MockOpenAI { }; }, }; + + this.embeddings = { + create: async params => { + await new Promise(resolve => setTimeout(resolve, 10)); + + return { + object: 'list', + data: [ + { + object: 'embedding', + embedding: [0.1, 0.2, 0.3], + index: 0, + }, + ], + model: params.model, + usage: { + prompt_tokens: 10, + total_tokens: 10, + }, + }; + }, + }; } // Create a mock streaming response for chat completions @@ -312,6 +334,24 @@ async function run() { } catch { // Error is expected and handled } + + // Seventh test: embeddings API + await client.embeddings.create({ + input: 'Embedding test!', + model: 'text-embedding-3-small', + dimensions: 1536, + encoding_format: 'float', + }); + + // Eighth test: embeddings API error model + try { + await client.embeddings.create({ + input: 'Error embedding test!', + model: 'error-model', + }); + } catch { + // Error is expected and handled + } }); } 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 5cbb27df73bf..34d4919859c0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -144,6 +144,26 @@ describe('OpenAI integration', () => { origin: 'auto.ai.openai', status: 'internal_error', }), + // Seventh span - embeddings API + expect.objectContaining({ + data: { + 'gen_ai.operation.name': 'embeddings', + 'sentry.op': 'gen_ai.embeddings', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'text-embedding-3-small', + }, + }), + // Eighth span - embeddings API error model + expect.objectContaining({ + data: { + 'gen_ai.operation.name': 'embeddings', + 'sentry.op': 'gen_ai.embeddings', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'error-model', + }, + }), ]), }; @@ -297,6 +317,26 @@ describe('OpenAI integration', () => { origin: 'auto.ai.openai', status: 'internal_error', }), + // Seventh span - embeddings API + expect.objectContaining({ + data: { + 'gen_ai.operation.name': 'embeddings', + 'sentry.op': 'gen_ai.embeddings', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'text-embedding-3-small', + }, + }), + // Eighth span - embeddings API error model + expect.objectContaining({ + data: { + 'gen_ai.operation.name': 'embeddings', + 'sentry.op': 'gen_ai.embeddings', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'error-model', + }, + }), ]), }; @@ -400,7 +440,7 @@ describe('OpenAI integration', () => { createEsmAndCjsTests( __dirname, - 'scenario-message-truncation-completions.mjs', + 'truncation/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 () => { @@ -436,7 +476,7 @@ describe('OpenAI integration', () => { createEsmAndCjsTests( __dirname, - 'scenario-message-truncation-responses.mjs', + 'truncation/scenario-message-truncation-responses.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { test('truncates string inputs when they exceed byte limit', async () => { @@ -469,4 +509,30 @@ describe('OpenAI integration', () => { }); }, ); + + createEsmAndCjsTests( + __dirname, + 'truncation/scenario-message-truncation-embeddings.mjs', + 'instrument-with-pii.mjs', + (createRunner, test) => { + test('truncates messages when they exceed byte limit - keeps only last message and crops it', async () => { + await createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'main', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.operation.name': 'embeddings', + }), + }), + ]), + }, + }) + .start() + .completed(); + }); + }, + ); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-completions.mjs similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs rename to dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-completions.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs new file mode 100644 index 000000000000..005254fbadcb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs @@ -0,0 +1,66 @@ +import { instrumentOpenAiClient } from '@sentry/core'; +import * as Sentry from '@sentry/node'; + +class MockOpenAI { + constructor(config) { + this.apiKey = config.apiKey; + + this.embeddings = { + create: async params => { + await new Promise(resolve => setTimeout(resolve, 10)); + + return { + object: 'list', + data: [ + { + object: 'embedding', + embedding: [0.1, 0.2, 0.3], + index: 0, + }, + ], + model: params.model, + usage: { + prompt_tokens: 10, + total_tokens: 10, + }, + }; + }, + }; + } +} + +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 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.embeddings.create({ + input: largeContent, + model: 'text-embedding-3-small', + dimensions: 1536, + encoding_format: 'float', + }); + + // Create 3 large messages where: + // - First 2 messages are very large (will be dropped) + // - Last message is large but will be truncated to fit within the 20KB limit + const largeContent1 = 'A'.repeat(15000); // ~15KB + const largeContent2 = 'B'.repeat(15000); // ~15KB + const largeContent3 = 'C'.repeat(25000); // ~25KB (will be truncated) + + await client.embeddings.create({ + input: [largeContent1, largeContent2, largeContent3], + model: 'text-embedding-3-small', + dimensions: 1536, + encoding_format: 'float', + }); + }); +} + +run(); 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/truncation/scenario-message-truncation-responses.mjs similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-responses.mjs rename to dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-responses.mjs From 14d9264447267f21a4adce83574443c626edf967 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 17 Nov 2025 10:49:51 +0100 Subject: [PATCH 2/9] progres --- packages/core/src/tracing/ai/gen-ai-attributes.ts | 1 + packages/core/src/tracing/openai/constants.ts | 2 +- packages/core/src/tracing/openai/utils.ts | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/tracing/ai/gen-ai-attributes.ts b/packages/core/src/tracing/ai/gen-ai-attributes.ts index 84efb21c1822..61b3280c2832 100644 --- a/packages/core/src/tracing/ai/gen-ai-attributes.ts +++ b/packages/core/src/tracing/ai/gen-ai-attributes.ts @@ -193,6 +193,7 @@ export const OPENAI_USAGE_PROMPT_TOKENS_ATTRIBUTE = 'openai.usage.prompt_tokens' export const OPENAI_OPERATIONS = { CHAT: 'chat', RESPONSES: 'responses', + EMBEDDINGS: 'embeddings', } as const; // ============================================================================= diff --git a/packages/core/src/tracing/openai/constants.ts b/packages/core/src/tracing/openai/constants.ts index c4952b123b0f..e8b5c6ddc87f 100644 --- a/packages/core/src/tracing/openai/constants.ts +++ b/packages/core/src/tracing/openai/constants.ts @@ -2,7 +2,7 @@ export const OPENAI_INTEGRATION_NAME = 'OpenAI'; // https://platform.openai.com/docs/quickstart?api-mode=responses // https://platform.openai.com/docs/quickstart?api-mode=chat -export const INSTRUMENTED_METHODS = ['responses.create', 'chat.completions.create'] as const; +export const INSTRUMENTED_METHODS = ['responses.create', 'chat.completions.create', 'embeddings.create'] as const; export const RESPONSES_TOOL_CALL_EVENT_TYPES = [ 'response.output_item.added', 'response.function_call_arguments.delta', diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index 17007693e739..27d6a04b1ade 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -31,6 +31,9 @@ export function getOperationName(methodPath: string): string { if (methodPath.includes('responses')) { return OPENAI_OPERATIONS.RESPONSES; } + if (methodPath.includes('embeddings')) { + return OPENAI_OPERATIONS.EMBEDDINGS; + } return methodPath.split('.').pop() || 'unknown'; } From dbebf0cd5b87fbaabea4bdffa5e325288e70d703 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 17 Nov 2025 13:34:43 +0100 Subject: [PATCH 3/9] Implement embeddings api support --- .../suites/tracing/openai/scenario.mjs | 7 ++++ .../suites/tracing/openai/test.ts | 36 +++++++++++++++++-- .../core/src/tracing/ai/gen-ai-attributes.ts | 10 ++++++ packages/core/src/tracing/openai/index.ts | 24 +++++++++++++ packages/core/src/tracing/openai/types.ts | 24 ++++++++++++- packages/core/src/tracing/openai/utils.ts | 13 +++++++ 6 files changed, 111 insertions(+), 3 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs index 310d5636f8af..0c572e0da457 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs @@ -79,6 +79,13 @@ class MockOpenAI { create: async params => { await new Promise(resolve => setTimeout(resolve, 10)); + if (params.model === 'error-model') { + const error = new Error('Model not found'); + error.status = 404; + error.headers = { 'x-request-id': 'mock-request-123' }; + throw error; + } + return { object: 'list', data: [ 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 34d4919859c0..8d0af271bb56 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -152,7 +152,18 @@ describe('OpenAI integration', () => { 'sentry.origin': 'auto.ai.openai', 'gen_ai.system': 'openai', 'gen_ai.request.model': 'text-embedding-3-small', + 'gen_ai.request.encoding_format': 'float', + 'gen_ai.request.dimensions': 1536, + 'gen_ai.response.model': 'text-embedding-3-small', + 'gen_ai.usage.input_tokens': 10, + 'gen_ai.usage.total_tokens': 10, + 'openai.response.model': 'text-embedding-3-small', + 'openai.usage.prompt_tokens': 10, }, + description: 'embeddings text-embedding-3-small', + op: 'gen_ai.embeddings', + origin: 'auto.ai.openai', + status: 'ok', }), // Eighth span - embeddings API error model expect.objectContaining({ @@ -163,6 +174,10 @@ describe('OpenAI integration', () => { 'gen_ai.system': 'openai', 'gen_ai.request.model': 'error-model', }, + description: 'embeddings error-model', + op: 'gen_ai.embeddings', + origin: 'auto.ai.openai', + status: 'internal_error', }), ]), }; @@ -317,7 +332,7 @@ describe('OpenAI integration', () => { origin: 'auto.ai.openai', status: 'internal_error', }), - // Seventh span - embeddings API + // Seventh span - embeddings API with PII expect.objectContaining({ data: { 'gen_ai.operation.name': 'embeddings', @@ -325,9 +340,21 @@ describe('OpenAI integration', () => { 'sentry.origin': 'auto.ai.openai', 'gen_ai.system': 'openai', 'gen_ai.request.model': 'text-embedding-3-small', + 'gen_ai.request.encoding_format': 'float', + 'gen_ai.request.dimensions': 1536, + 'gen_ai.request.messages': 'Embedding test!', + 'gen_ai.response.model': 'text-embedding-3-small', + 'gen_ai.usage.input_tokens': 10, + 'gen_ai.usage.total_tokens': 10, + 'openai.response.model': 'text-embedding-3-small', + 'openai.usage.prompt_tokens': 10, }, + description: 'embeddings text-embedding-3-small', + op: 'gen_ai.embeddings', + origin: 'auto.ai.openai', + status: 'ok', }), - // Eighth span - embeddings API error model + // Eighth span - embeddings API error model with PII expect.objectContaining({ data: { 'gen_ai.operation.name': 'embeddings', @@ -335,7 +362,12 @@ describe('OpenAI integration', () => { 'sentry.origin': 'auto.ai.openai', 'gen_ai.system': 'openai', 'gen_ai.request.model': 'error-model', + 'gen_ai.request.messages': 'Error embedding test!', }, + description: 'embeddings error-model', + op: 'gen_ai.embeddings', + origin: 'auto.ai.openai', + status: 'internal_error', }), ]), }; diff --git a/packages/core/src/tracing/ai/gen-ai-attributes.ts b/packages/core/src/tracing/ai/gen-ai-attributes.ts index 61b3280c2832..f4263ebf4dda 100644 --- a/packages/core/src/tracing/ai/gen-ai-attributes.ts +++ b/packages/core/src/tracing/ai/gen-ai-attributes.ts @@ -65,6 +65,16 @@ export const GEN_AI_REQUEST_TOP_K_ATTRIBUTE = 'gen_ai.request.top_k'; */ export const GEN_AI_REQUEST_STOP_SEQUENCES_ATTRIBUTE = 'gen_ai.request.stop_sequences'; +/** + * The encoding format for the model request + */ +export const GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE = 'gen_ai.request.encoding_format'; + +/** + * The dimensions for the model request + */ +export const GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE = 'gen_ai.request.dimensions'; + /** * Array of reasons why the model stopped generating tokens */ diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index bb099199772c..45c5b866bc5c 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -7,6 +7,8 @@ import type { Span, SpanAttributeValue } from '../../types-hoist/span'; import { GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, + GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, + GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE, GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, GEN_AI_REQUEST_MESSAGES_ATTRIBUTE, GEN_AI_REQUEST_MODEL_ATTRIBUTE, @@ -15,9 +17,11 @@ import { GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, GEN_AI_REQUEST_TOP_P_ATTRIBUTE, GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, + GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_RESPONSE_TEXT_ATTRIBUTE, GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, GEN_AI_SYSTEM_ATTRIBUTE, + OPENAI_RESPONSE_MODEL_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { getTruncatedJsonString } from '../ai/utils'; import { OPENAI_INTEGRATION_NAME } from './constants'; @@ -26,6 +30,7 @@ import type { ChatCompletionChunk, InstrumentedMethod, OpenAiChatCompletionObject, + OpenAICreateEmbeddingsObject, OpenAiIntegration, OpenAiOptions, OpenAiResponse, @@ -38,6 +43,7 @@ import { getOperationName, getSpanOperation, isChatCompletionResponse, + isEmbeddingsResponse, isResponsesApiResponse, setCommonResponseAttributes, setTokenUsageAttributes, @@ -82,6 +88,8 @@ function extractRequestAttributes(args: unknown[], methodPath: string): Record; } -export type OpenAiResponse = OpenAiChatCompletionObject | OpenAIResponseObject; +/** + * @see https://platform.openai.com/docs/api-reference/embeddings/object + */ +export interface OpenAIEmbeddingsObject { + object: 'embedding'; + embedding: number[]; + index: number; +} + +/** + * @see https://platform.openai.com/docs/api-reference/embeddings/create + */ +export interface OpenAICreateEmbeddingsObject { + object: 'list'; + data: OpenAIEmbeddingsObject[]; + model: string; + usage: { + prompt_tokens: number; + total_tokens: number; + }; +} + +export type OpenAiResponse = OpenAiChatCompletionObject | OpenAIResponseObject | OpenAICreateEmbeddingsObject; /** * Streaming event types for the Responses API diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index 27d6a04b1ade..b8db9e0558cc 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -17,6 +17,7 @@ import type { ChatCompletionChunk, InstrumentedMethod, OpenAiChatCompletionObject, + OpenAICreateEmbeddingsObject, OpenAIResponseObject, ResponseStreamingEvent, } from './types'; @@ -83,6 +84,18 @@ export function isResponsesApiResponse(response: unknown): response is OpenAIRes ); } +/** + * Check if response is an Embeddings API object + */ +export function isEmbeddingsResponse(response: unknown): response is OpenAICreateEmbeddingsObject { + return ( + response !== null && + typeof response === 'object' && + 'object' in response && + (response as Record).object === 'list' + ); +} + /** * Check if streaming event is from the Responses API */ From fd4f990b0f153ba9bae67c1303e71384d802fb99 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 17 Nov 2025 15:00:10 +0100 Subject: [PATCH 4/9] Split embeddings test into separate file --- .../{scenario.mjs => scenario-chat.mjs} | 47 ----- .../tracing/openai/scenario-embeddings.mjs | 67 +++++++ .../suites/tracing/openai/test.ts | 167 +++++++++++------- 3 files changed, 166 insertions(+), 115 deletions(-) rename dev-packages/node-integration-tests/suites/tracing/openai/{scenario.mjs => scenario-chat.mjs} (87%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/openai/scenario-embeddings.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-chat.mjs similarity index 87% rename from dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs rename to dev-packages/node-integration-tests/suites/tracing/openai/scenario-chat.mjs index 0c572e0da457..fde651c3c1ff 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-chat.mjs @@ -74,35 +74,6 @@ class MockOpenAI { }; }, }; - - this.embeddings = { - create: async params => { - await new Promise(resolve => setTimeout(resolve, 10)); - - if (params.model === 'error-model') { - const error = new Error('Model not found'); - error.status = 404; - error.headers = { 'x-request-id': 'mock-request-123' }; - throw error; - } - - return { - object: 'list', - data: [ - { - object: 'embedding', - embedding: [0.1, 0.2, 0.3], - index: 0, - }, - ], - model: params.model, - usage: { - prompt_tokens: 10, - total_tokens: 10, - }, - }; - }, - }; } // Create a mock streaming response for chat completions @@ -341,24 +312,6 @@ async function run() { } catch { // Error is expected and handled } - - // Seventh test: embeddings API - await client.embeddings.create({ - input: 'Embedding test!', - model: 'text-embedding-3-small', - dimensions: 1536, - encoding_format: 'float', - }); - - // Eighth test: embeddings API error model - try { - await client.embeddings.create({ - input: 'Error embedding test!', - model: 'error-model', - }); - } catch { - // Error is expected and handled - } }); } diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/scenario-embeddings.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-embeddings.mjs new file mode 100644 index 000000000000..9cdb24a42da9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/openai/scenario-embeddings.mjs @@ -0,0 +1,67 @@ +import { instrumentOpenAiClient } from '@sentry/core'; +import * as Sentry from '@sentry/node'; + +class MockOpenAI { + constructor(config) { + this.apiKey = config.apiKey; + + this.embeddings = { + create: async params => { + await new Promise(resolve => setTimeout(resolve, 10)); + + if (params.model === 'error-model') { + const error = new Error('Model not found'); + error.status = 404; + error.headers = { 'x-request-id': 'mock-request-123' }; + throw error; + } + + return { + object: 'list', + data: [ + { + object: 'embedding', + embedding: [0.1, 0.2, 0.3], + index: 0, + }, + ], + model: params.model, + usage: { + prompt_tokens: 10, + total_tokens: 10, + }, + }; + }, + }; + } +} + +async function run() { + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + const mockClient = new MockOpenAI({ + apiKey: 'mock-api-key', + }); + + const client = instrumentOpenAiClient(mockClient); + + // First test: embeddings API + await client.embeddings.create({ + input: 'Embedding test!', + model: 'text-embedding-3-small', + dimensions: 1536, + encoding_format: 'float', + }); + + // Second test: embeddings API error model + try { + await client.embeddings.create({ + input: 'Error embedding test!', + model: 'error-model', + }); + } catch { + // Error is expected and handled + } + }); +} + +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 8d0af271bb56..116c3a6208fa 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -6,7 +6,7 @@ describe('OpenAI integration', () => { cleanupChildProcesses(); }); - const EXPECTED_TRANSACTION_DEFAULT_PII_FALSE = { + const EXPECTED_TRANSACTION_DEFAULT_PII_FALSE_CHAT = { transaction: 'main', spans: expect.arrayContaining([ // First span - basic chat completion without PII @@ -144,45 +144,10 @@ describe('OpenAI integration', () => { origin: 'auto.ai.openai', status: 'internal_error', }), - // Seventh span - embeddings API - expect.objectContaining({ - data: { - 'gen_ai.operation.name': 'embeddings', - 'sentry.op': 'gen_ai.embeddings', - 'sentry.origin': 'auto.ai.openai', - 'gen_ai.system': 'openai', - 'gen_ai.request.model': 'text-embedding-3-small', - 'gen_ai.request.encoding_format': 'float', - 'gen_ai.request.dimensions': 1536, - 'gen_ai.response.model': 'text-embedding-3-small', - 'gen_ai.usage.input_tokens': 10, - 'gen_ai.usage.total_tokens': 10, - 'openai.response.model': 'text-embedding-3-small', - 'openai.usage.prompt_tokens': 10, - }, - description: 'embeddings text-embedding-3-small', - op: 'gen_ai.embeddings', - origin: 'auto.ai.openai', - status: 'ok', - }), - // Eighth span - embeddings API error model - expect.objectContaining({ - data: { - 'gen_ai.operation.name': 'embeddings', - 'sentry.op': 'gen_ai.embeddings', - 'sentry.origin': 'auto.ai.openai', - 'gen_ai.system': 'openai', - 'gen_ai.request.model': 'error-model', - }, - description: 'embeddings error-model', - op: 'gen_ai.embeddings', - origin: 'auto.ai.openai', - status: 'internal_error', - }), ]), }; - const EXPECTED_TRANSACTION_DEFAULT_PII_TRUE = { + const EXPECTED_TRANSACTION_DEFAULT_PII_TRUE_CHAT = { transaction: 'main', spans: expect.arrayContaining([ // First span - basic chat completion with PII @@ -332,7 +297,64 @@ describe('OpenAI integration', () => { origin: 'auto.ai.openai', status: 'internal_error', }), - // Seventh span - embeddings API with PII + ]), + }; + + const EXPECTED_TRANSACTION_WITH_OPTIONS = { + transaction: 'main', + spans: expect.arrayContaining([ + // Check that custom options are respected + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.request.messages': expect.any(String), // Should include messages when recordInputs: true + 'gen_ai.response.text': expect.any(String), // Should include response text when recordOutputs: true + }), + }), + // Check that custom options are respected for streaming + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.request.messages': expect.any(String), // Should include messages when recordInputs: true + 'gen_ai.response.text': expect.any(String), // Should include response text when recordOutputs: true + 'gen_ai.request.stream': true, // Should be marked as stream + }), + }), + ]), + }; + + createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument.mjs', (createRunner, test) => { + test('creates openai related spans with sendDefaultPii: false', async () => { + await createRunner() + .ignore('event') + .expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_FALSE_CHAT }) + .start() + .completed(); + }); + }); + + createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { + test('creates openai related spans with sendDefaultPii: true', async () => { + await createRunner() + .ignore('event') + .expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_TRUE_CHAT }) + .start() + .completed(); + }); + }); + + createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-with-options.mjs', (createRunner, test) => { + test('creates openai related spans with custom options', async () => { + await createRunner() + .ignore('event') + .expect({ transaction: EXPECTED_TRANSACTION_WITH_OPTIONS }) + .start() + .completed(); + }); + }); + + const EXPECTED_TRANSACTION_DEFAULT_PII_FALSE_EMBEDDINGS = { + transaction: 'main', + spans: expect.arrayContaining([ + // First span - embeddings API expect.objectContaining({ data: { 'gen_ai.operation.name': 'embeddings', @@ -342,7 +364,6 @@ describe('OpenAI integration', () => { 'gen_ai.request.model': 'text-embedding-3-small', 'gen_ai.request.encoding_format': 'float', 'gen_ai.request.dimensions': 1536, - 'gen_ai.request.messages': 'Embedding test!', 'gen_ai.response.model': 'text-embedding-3-small', 'gen_ai.usage.input_tokens': 10, 'gen_ai.usage.total_tokens': 10, @@ -354,7 +375,7 @@ describe('OpenAI integration', () => { origin: 'auto.ai.openai', status: 'ok', }), - // Eighth span - embeddings API error model with PII + // Second span - embeddings API error model expect.objectContaining({ data: { 'gen_ai.operation.name': 'embeddings', @@ -362,7 +383,6 @@ describe('OpenAI integration', () => { 'sentry.origin': 'auto.ai.openai', 'gen_ai.system': 'openai', 'gen_ai.request.model': 'error-model', - 'gen_ai.request.messages': 'Error embedding test!', }, description: 'embeddings error-model', op: 'gen_ai.embeddings', @@ -372,52 +392,63 @@ describe('OpenAI integration', () => { ]), }; - const EXPECTED_TRANSACTION_WITH_OPTIONS = { + const EXPECTED_TRANSACTION_DEFAULT_PII_TRUE_EMBEDDINGS = { transaction: 'main', spans: expect.arrayContaining([ - // Check that custom options are respected + // First span - embeddings API with PII expect.objectContaining({ - data: expect.objectContaining({ - 'gen_ai.request.messages': expect.any(String), // Should include messages when recordInputs: true - 'gen_ai.response.text': expect.any(String), // Should include response text when recordOutputs: true - }), + data: { + 'gen_ai.operation.name': 'embeddings', + 'sentry.op': 'gen_ai.embeddings', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'text-embedding-3-small', + 'gen_ai.request.encoding_format': 'float', + 'gen_ai.request.dimensions': 1536, + 'gen_ai.request.messages': 'Embedding test!', + 'gen_ai.response.model': 'text-embedding-3-small', + 'gen_ai.usage.input_tokens': 10, + 'gen_ai.usage.total_tokens': 10, + 'openai.response.model': 'text-embedding-3-small', + 'openai.usage.prompt_tokens': 10, + }, + description: 'embeddings text-embedding-3-small', + op: 'gen_ai.embeddings', + origin: 'auto.ai.openai', + status: 'ok', }), - // Check that custom options are respected for streaming + // Second span - embeddings API error model with PII expect.objectContaining({ - data: expect.objectContaining({ - 'gen_ai.request.messages': expect.any(String), // Should include messages when recordInputs: true - 'gen_ai.response.text': expect.any(String), // Should include response text when recordOutputs: true - 'gen_ai.request.stream': true, // Should be marked as stream - }), + data: { + 'gen_ai.operation.name': 'embeddings', + 'sentry.op': 'gen_ai.embeddings', + 'sentry.origin': 'auto.ai.openai', + 'gen_ai.system': 'openai', + 'gen_ai.request.model': 'error-model', + 'gen_ai.request.messages': 'Error embedding test!', + }, + description: 'embeddings error-model', + op: 'gen_ai.embeddings', + origin: 'auto.ai.openai', + status: 'internal_error', }), ]), }; - - createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument.mjs', (createRunner, test) => { test('creates openai related spans with sendDefaultPii: false', async () => { await createRunner() .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_FALSE }) + .expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_FALSE_EMBEDDINGS }) .start() .completed(); }); }); - createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { + createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { test('creates openai related spans with sendDefaultPii: true', async () => { await createRunner() .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_TRUE }) - .start() - .completed(); - }); - }); - - createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-options.mjs', (createRunner, test) => { - test('creates openai related spans with custom options', async () => { - await createRunner() - .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION_WITH_OPTIONS }) + .expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_TRUE_EMBEDDINGS }) .start() .completed(); }); From 1fd3d67d27d8e343be0e00e92f9b360a134ed7e3 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 17 Nov 2025 16:55:52 +0100 Subject: [PATCH 5/9] . --- .../truncation/scenario-message-truncation-embeddings.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs index 005254fbadcb..b2e5cf3206fe 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/truncation/scenario-message-truncation-embeddings.mjs @@ -37,7 +37,7 @@ async function run() { const client = instrumentOpenAiClient(mockClient); - // Create 1 large message that gets truncated to fit within the 20KB limit + // Create 1 large input 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.embeddings.create({ @@ -47,9 +47,9 @@ async function run() { encoding_format: 'float', }); - // Create 3 large messages where: - // - First 2 messages are very large (will be dropped) - // - Last message is large but will be truncated to fit within the 20KB limit + // Create 3 large inputs where: + // - First 2 inputs are very large (will be dropped) + // - Last input is large but will be truncated to fit within the 20KB limit const largeContent1 = 'A'.repeat(15000); // ~15KB const largeContent2 = 'B'.repeat(15000); // ~15KB const largeContent3 = 'C'.repeat(25000); // ~25KB (will be truncated) From 90359e444d210c0a9cf4df5fe7d69b45207d7cd7 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 18 Nov 2025 13:23:22 +0100 Subject: [PATCH 6/9] Move addXAttributes to utils --- packages/core/src/tracing/openai/index.ts | 103 +--------------------- packages/core/src/tracing/openai/utils.ts | 97 ++++++++++++++++++++ 2 files changed, 100 insertions(+), 100 deletions(-) diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index 45c5b866bc5c..d448a4aa1b64 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -16,12 +16,8 @@ import { GEN_AI_REQUEST_STREAM_ATTRIBUTE, GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, GEN_AI_SYSTEM_ATTRIBUTE, - OPENAI_RESPONSE_MODEL_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { getTruncatedJsonString } from '../ai/utils'; import { OPENAI_INTEGRATION_NAME } from './constants'; @@ -29,12 +25,9 @@ import { instrumentStream } from './streaming'; import type { ChatCompletionChunk, InstrumentedMethod, - OpenAiChatCompletionObject, - OpenAICreateEmbeddingsObject, OpenAiIntegration, OpenAiOptions, OpenAiResponse, - OpenAIResponseObject, OpenAIStream, ResponseStreamingEvent, } from './types'; @@ -45,8 +38,9 @@ import { isChatCompletionResponse, isEmbeddingsResponse, isResponsesApiResponse, - setCommonResponseAttributes, - setTokenUsageAttributes, + addChatCompletionAttributes, + addResponsesApiAttributes, + addEmbeddingsAttributes, shouldInstrument, } from './utils'; @@ -97,97 +91,6 @@ function extractRequestAttributes(args: unknown[], methodPath: string): Record choice.finish_reason) - .filter((reason): reason is string => reason !== null); - if (finishReasons.length > 0) { - span.setAttributes({ - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify(finishReasons), - }); - } - - // Extract tool calls from all choices (only if recordOutputs is true) - if (recordOutputs) { - const toolCalls = response.choices - .map(choice => choice.message?.tool_calls) - .filter(calls => Array.isArray(calls) && calls.length > 0) - .flat(); - - if (toolCalls.length > 0) { - span.setAttributes({ - [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(toolCalls), - }); - } - } - } -} - -/** - * Add attributes for Responses API responses - */ -function addResponsesApiAttributes(span: Span, response: OpenAIResponseObject, recordOutputs?: boolean): void { - setCommonResponseAttributes(span, response.id, response.model, response.created_at); - if (response.status) { - span.setAttributes({ - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify([response.status]), - }); - } - if (response.usage) { - setTokenUsageAttributes( - span, - response.usage.input_tokens, - response.usage.output_tokens, - response.usage.total_tokens, - ); - } - - // Extract function calls from output (only if recordOutputs is true) - if (recordOutputs) { - const responseWithOutput = response as OpenAIResponseObject & { output?: unknown[] }; - if (Array.isArray(responseWithOutput.output) && responseWithOutput.output.length > 0) { - // Filter for function_call type objects in the output array - const functionCalls = responseWithOutput.output.filter( - (item): unknown => - typeof item === 'object' && item !== null && (item as Record).type === 'function_call', - ); - - if (functionCalls.length > 0) { - span.setAttributes({ - [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(functionCalls), - }); - } - } - } -} - -/** - * Add attributes for Embeddings API responses - */ -function addEmbeddingsAttributes(span: Span, response: OpenAICreateEmbeddingsObject): void { - span.setAttributes({ - [OPENAI_RESPONSE_MODEL_ATTRIBUTE]: response.model, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: response.model, - }); - - if (response.usage) { - setTokenUsageAttributes(span, response.usage.prompt_tokens, undefined, response.usage.total_tokens); - } -} - /** * Add response attributes to spans * This currently supports both Chat Completion and Responses API responses diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index b8db9e0558cc..12e3025e1686 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -5,6 +5,8 @@ import { GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, + GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, + GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, OPENAI_OPERATIONS, OPENAI_RESPONSE_ID_ATTRIBUTE, OPENAI_RESPONSE_MODEL_ATTRIBUTE, @@ -121,6 +123,101 @@ export function isChatCompletionChunk(event: unknown): event is ChatCompletionCh ); } +/** + * Add attributes for Chat Completion responses + */ +export function addChatCompletionAttributes( + span: Span, + response: OpenAiChatCompletionObject, + recordOutputs?: boolean, +): void { + setCommonResponseAttributes(span, response.id, response.model, response.created); + if (response.usage) { + setTokenUsageAttributes( + span, + response.usage.prompt_tokens, + response.usage.completion_tokens, + response.usage.total_tokens, + ); + } + if (Array.isArray(response.choices)) { + const finishReasons = response.choices + .map(choice => choice.finish_reason) + .filter((reason): reason is string => reason !== null); + if (finishReasons.length > 0) { + span.setAttributes({ + [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify(finishReasons), + }); + } + + // Extract tool calls from all choices (only if recordOutputs is true) + if (recordOutputs) { + const toolCalls = response.choices + .map(choice => choice.message?.tool_calls) + .filter(calls => Array.isArray(calls) && calls.length > 0) + .flat(); + + if (toolCalls.length > 0) { + span.setAttributes({ + [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(toolCalls), + }); + } + } + } +} + +/** + * Add attributes for Responses API responses + */ +export function addResponsesApiAttributes(span: Span, response: OpenAIResponseObject, recordOutputs?: boolean): void { + setCommonResponseAttributes(span, response.id, response.model, response.created_at); + if (response.status) { + span.setAttributes({ + [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify([response.status]), + }); + } + if (response.usage) { + setTokenUsageAttributes( + span, + response.usage.input_tokens, + response.usage.output_tokens, + response.usage.total_tokens, + ); + } + + // Extract function calls from output (only if recordOutputs is true) + if (recordOutputs) { + const responseWithOutput = response as OpenAIResponseObject & { output?: unknown[] }; + if (Array.isArray(responseWithOutput.output) && responseWithOutput.output.length > 0) { + // Filter for function_call type objects in the output array + const functionCalls = responseWithOutput.output.filter( + (item): unknown => + typeof item === 'object' && item !== null && (item as Record).type === 'function_call', + ); + + if (functionCalls.length > 0) { + span.setAttributes({ + [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(functionCalls), + }); + } + } + } +} + +/** + * Add attributes for Embeddings API responses + */ +export function addEmbeddingsAttributes(span: Span, response: OpenAICreateEmbeddingsObject): void { + span.setAttributes({ + [OPENAI_RESPONSE_MODEL_ATTRIBUTE]: response.model, + [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: response.model, + }); + + if (response.usage) { + setTokenUsageAttributes(span, response.usage.prompt_tokens, undefined, response.usage.total_tokens); + } +} + /** * Set token usage attributes * @param span - The span to add attributes to From 02fe01f331be5757dbee9caaeb611a2428db06d8 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 18 Nov 2025 13:44:26 +0100 Subject: [PATCH 7/9] Improve detection of embedding requests --- packages/core/src/tracing/openai/utils.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index 12e3025e1686..43eb178c6572 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -90,11 +90,14 @@ export function isResponsesApiResponse(response: unknown): response is OpenAIRes * Check if response is an Embeddings API object */ export function isEmbeddingsResponse(response: unknown): response is OpenAICreateEmbeddingsObject { + if (response === null || typeof response !== 'object' || !('object' in response)) { + return false; + } + const responseObject = response as Record; return ( - response !== null && - typeof response === 'object' && - 'object' in response && - (response as Record).object === 'list' + responseObject.object === 'list' && + typeof responseObject.model === 'string' && + (responseObject.model as string).toLowerCase().includes('embedding') ); } From db9114c3a15ff5b0dd0cd54275865a0cc0891c9a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 18 Nov 2025 14:02:52 +0100 Subject: [PATCH 8/9] formatting --- packages/core/src/tracing/openai/index.ts | 6 +++--- packages/core/src/tracing/openai/utils.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index d448a4aa1b64..bba2ee0f5afd 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -32,15 +32,15 @@ import type { ResponseStreamingEvent, } from './types'; import { + addChatCompletionAttributes, + addEmbeddingsAttributes, + addResponsesApiAttributes, buildMethodPath, getOperationName, getSpanOperation, isChatCompletionResponse, isEmbeddingsResponse, isResponsesApiResponse, - addChatCompletionAttributes, - addResponsesApiAttributes, - addEmbeddingsAttributes, shouldInstrument, } from './utils'; diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index 43eb178c6572..e7ad95862785 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -1,12 +1,12 @@ import type { Span } from '../../types-hoist/span'; import { + GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, GEN_AI_RESPONSE_ID_ATTRIBUTE, GEN_AI_RESPONSE_MODEL_ATTRIBUTE, + GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, OPENAI_OPERATIONS, OPENAI_RESPONSE_ID_ATTRIBUTE, OPENAI_RESPONSE_MODEL_ATTRIBUTE, @@ -97,7 +97,7 @@ export function isEmbeddingsResponse(response: unknown): response is OpenAICreat return ( responseObject.object === 'list' && typeof responseObject.model === 'string' && - (responseObject.model as string).toLowerCase().includes('embedding') + (responseObject.model ).toLowerCase().includes('embedding') ); } From b3ae30b49c6dce065b31629a3739ee618d5ad688 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 18 Nov 2025 14:15:04 +0100 Subject: [PATCH 9/9] . --- packages/core/src/tracing/openai/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index e7ad95862785..4dff5b4fdbb8 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -97,7 +97,7 @@ export function isEmbeddingsResponse(response: unknown): response is OpenAICreat return ( responseObject.object === 'list' && typeof responseObject.model === 'string' && - (responseObject.model ).toLowerCase().includes('embedding') + responseObject.model.toLowerCase().includes('embedding') ); }