Skip to content

Commit dbebf0c

Browse files
committed
Implement embeddings api support
1 parent 14d9264 commit dbebf0c

File tree

6 files changed

+111
-3
lines changed

6 files changed

+111
-3
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ class MockOpenAI {
7979
create: async params => {
8080
await new Promise(resolve => setTimeout(resolve, 10));
8181

82+
if (params.model === 'error-model') {
83+
const error = new Error('Model not found');
84+
error.status = 404;
85+
error.headers = { 'x-request-id': 'mock-request-123' };
86+
throw error;
87+
}
88+
8289
return {
8390
object: 'list',
8491
data: [

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,18 @@ describe('OpenAI integration', () => {
152152
'sentry.origin': 'auto.ai.openai',
153153
'gen_ai.system': 'openai',
154154
'gen_ai.request.model': 'text-embedding-3-small',
155+
'gen_ai.request.encoding_format': 'float',
156+
'gen_ai.request.dimensions': 1536,
157+
'gen_ai.response.model': 'text-embedding-3-small',
158+
'gen_ai.usage.input_tokens': 10,
159+
'gen_ai.usage.total_tokens': 10,
160+
'openai.response.model': 'text-embedding-3-small',
161+
'openai.usage.prompt_tokens': 10,
155162
},
163+
description: 'embeddings text-embedding-3-small',
164+
op: 'gen_ai.embeddings',
165+
origin: 'auto.ai.openai',
166+
status: 'ok',
156167
}),
157168
// Eighth span - embeddings API error model
158169
expect.objectContaining({
@@ -163,6 +174,10 @@ describe('OpenAI integration', () => {
163174
'gen_ai.system': 'openai',
164175
'gen_ai.request.model': 'error-model',
165176
},
177+
description: 'embeddings error-model',
178+
op: 'gen_ai.embeddings',
179+
origin: 'auto.ai.openai',
180+
status: 'internal_error',
166181
}),
167182
]),
168183
};
@@ -317,25 +332,42 @@ describe('OpenAI integration', () => {
317332
origin: 'auto.ai.openai',
318333
status: 'internal_error',
319334
}),
320-
// Seventh span - embeddings API
335+
// Seventh span - embeddings API with PII
321336
expect.objectContaining({
322337
data: {
323338
'gen_ai.operation.name': 'embeddings',
324339
'sentry.op': 'gen_ai.embeddings',
325340
'sentry.origin': 'auto.ai.openai',
326341
'gen_ai.system': 'openai',
327342
'gen_ai.request.model': 'text-embedding-3-small',
343+
'gen_ai.request.encoding_format': 'float',
344+
'gen_ai.request.dimensions': 1536,
345+
'gen_ai.request.messages': 'Embedding test!',
346+
'gen_ai.response.model': 'text-embedding-3-small',
347+
'gen_ai.usage.input_tokens': 10,
348+
'gen_ai.usage.total_tokens': 10,
349+
'openai.response.model': 'text-embedding-3-small',
350+
'openai.usage.prompt_tokens': 10,
328351
},
352+
description: 'embeddings text-embedding-3-small',
353+
op: 'gen_ai.embeddings',
354+
origin: 'auto.ai.openai',
355+
status: 'ok',
329356
}),
330-
// Eighth span - embeddings API error model
357+
// Eighth span - embeddings API error model with PII
331358
expect.objectContaining({
332359
data: {
333360
'gen_ai.operation.name': 'embeddings',
334361
'sentry.op': 'gen_ai.embeddings',
335362
'sentry.origin': 'auto.ai.openai',
336363
'gen_ai.system': 'openai',
337364
'gen_ai.request.model': 'error-model',
365+
'gen_ai.request.messages': 'Error embedding test!',
338366
},
367+
description: 'embeddings error-model',
368+
op: 'gen_ai.embeddings',
369+
origin: 'auto.ai.openai',
370+
status: 'internal_error',
339371
}),
340372
]),
341373
};

packages/core/src/tracing/ai/gen-ai-attributes.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ export const GEN_AI_REQUEST_TOP_K_ATTRIBUTE = 'gen_ai.request.top_k';
6565
*/
6666
export const GEN_AI_REQUEST_STOP_SEQUENCES_ATTRIBUTE = 'gen_ai.request.stop_sequences';
6767

68+
/**
69+
* The encoding format for the model request
70+
*/
71+
export const GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE = 'gen_ai.request.encoding_format';
72+
73+
/**
74+
* The dimensions for the model request
75+
*/
76+
export const GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE = 'gen_ai.request.dimensions';
77+
6878
/**
6979
* Array of reasons why the model stopped generating tokens
7080
*/

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type { Span, SpanAttributeValue } from '../../types-hoist/span';
77
import {
88
GEN_AI_OPERATION_NAME_ATTRIBUTE,
99
GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE,
10+
GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE,
11+
GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE,
1012
GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE,
1113
GEN_AI_REQUEST_MESSAGES_ATTRIBUTE,
1214
GEN_AI_REQUEST_MODEL_ATTRIBUTE,
@@ -15,9 +17,11 @@ import {
1517
GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE,
1618
GEN_AI_REQUEST_TOP_P_ATTRIBUTE,
1719
GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE,
20+
GEN_AI_RESPONSE_MODEL_ATTRIBUTE,
1821
GEN_AI_RESPONSE_TEXT_ATTRIBUTE,
1922
GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE,
2023
GEN_AI_SYSTEM_ATTRIBUTE,
24+
OPENAI_RESPONSE_MODEL_ATTRIBUTE,
2125
} from '../ai/gen-ai-attributes';
2226
import { getTruncatedJsonString } from '../ai/utils';
2327
import { OPENAI_INTEGRATION_NAME } from './constants';
@@ -26,6 +30,7 @@ import type {
2630
ChatCompletionChunk,
2731
InstrumentedMethod,
2832
OpenAiChatCompletionObject,
33+
OpenAICreateEmbeddingsObject,
2934
OpenAiIntegration,
3035
OpenAiOptions,
3136
OpenAiResponse,
@@ -38,6 +43,7 @@ import {
3843
getOperationName,
3944
getSpanOperation,
4045
isChatCompletionResponse,
46+
isEmbeddingsResponse,
4147
isResponsesApiResponse,
4248
setCommonResponseAttributes,
4349
setTokenUsageAttributes,
@@ -82,6 +88,8 @@ function extractRequestAttributes(args: unknown[], methodPath: string): Record<s
8288
attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE] = params.frequency_penalty;
8389
if ('presence_penalty' in params) attributes[GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE] = params.presence_penalty;
8490
if ('stream' in params) attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE] = params.stream;
91+
if ('encoding_format' in params) attributes[GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE] = params.encoding_format;
92+
if ('dimensions' in params) attributes[GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE] = params.dimensions;
8593
} else {
8694
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = 'unknown';
8795
}
@@ -166,6 +174,20 @@ function addResponsesApiAttributes(span: Span, response: OpenAIResponseObject, r
166174
}
167175
}
168176

177+
/**
178+
* Add attributes for Embeddings API responses
179+
*/
180+
function addEmbeddingsAttributes(span: Span, response: OpenAICreateEmbeddingsObject): void {
181+
span.setAttributes({
182+
[OPENAI_RESPONSE_MODEL_ATTRIBUTE]: response.model,
183+
[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: response.model,
184+
});
185+
186+
if (response.usage) {
187+
setTokenUsageAttributes(span, response.usage.prompt_tokens, undefined, response.usage.total_tokens);
188+
}
189+
}
190+
169191
/**
170192
* Add response attributes to spans
171193
* This currently supports both Chat Completion and Responses API responses
@@ -186,6 +208,8 @@ function addResponseAttributes(span: Span, result: unknown, recordOutputs?: bool
186208
if (recordOutputs && response.output_text) {
187209
span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.output_text });
188210
}
211+
} else if (isEmbeddingsResponse(response)) {
212+
addEmbeddingsAttributes(span, response);
189213
}
190214
}
191215

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,29 @@ export interface OpenAIResponseObject {
131131
metadata: Record<string, unknown>;
132132
}
133133

134-
export type OpenAiResponse = OpenAiChatCompletionObject | OpenAIResponseObject;
134+
/**
135+
* @see https://platform.openai.com/docs/api-reference/embeddings/object
136+
*/
137+
export interface OpenAIEmbeddingsObject {
138+
object: 'embedding';
139+
embedding: number[];
140+
index: number;
141+
}
142+
143+
/**
144+
* @see https://platform.openai.com/docs/api-reference/embeddings/create
145+
*/
146+
export interface OpenAICreateEmbeddingsObject {
147+
object: 'list';
148+
data: OpenAIEmbeddingsObject[];
149+
model: string;
150+
usage: {
151+
prompt_tokens: number;
152+
total_tokens: number;
153+
};
154+
}
155+
156+
export type OpenAiResponse = OpenAiChatCompletionObject | OpenAIResponseObject | OpenAICreateEmbeddingsObject;
135157

136158
/**
137159
* Streaming event types for the Responses API

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
ChatCompletionChunk,
1818
InstrumentedMethod,
1919
OpenAiChatCompletionObject,
20+
OpenAICreateEmbeddingsObject,
2021
OpenAIResponseObject,
2122
ResponseStreamingEvent,
2223
} from './types';
@@ -83,6 +84,18 @@ export function isResponsesApiResponse(response: unknown): response is OpenAIRes
8384
);
8485
}
8586

87+
/**
88+
* Check if response is an Embeddings API object
89+
*/
90+
export function isEmbeddingsResponse(response: unknown): response is OpenAICreateEmbeddingsObject {
91+
return (
92+
response !== null &&
93+
typeof response === 'object' &&
94+
'object' in response &&
95+
(response as Record<string, unknown>).object === 'list'
96+
);
97+
}
98+
8699
/**
87100
* Check if streaming event is from the Responses API
88101
*/

0 commit comments

Comments
 (0)