Skip to content

Commit 8535978

Browse files
committed
quick refactor to record inputs option
1 parent 247bf9f commit 8535978

File tree

2 files changed

+32
-59
lines changed
  • dev-packages/node-integration-tests/suites/tracing/openai
  • packages/core/src/utils

2 files changed

+32
-59
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@ describe('OpenAI integration', () => {
107107
origin: 'manual',
108108
status: 'ok',
109109
}),
110-
// Second span - responses API with PII (no messages attribute for this API)
110+
// Second span - responses API with PII
111111
expect.objectContaining({
112112
data: {
113113
'gen_ai.operation.name': 'chat',
114114
'sentry.op': 'gen_ai.chat',
115115
'sentry.origin': 'manual',
116116
'gen_ai.system': 'openai',
117117
'gen_ai.request.model': 'gpt-3.5-turbo',
118+
'gen_ai.request.messages': '"Translate this to French: Hello"',
118119
'gen_ai.response.text': 'Response to: Translate this to French: Hello',
119120
'gen_ai.response.model': 'gpt-3.5-turbo',
120121
'gen_ai.response.id': 'resp_mock456',

packages/core/src/utils/openai.ts

Lines changed: 30 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -44,34 +44,23 @@ import {
4444

4545
/**
4646
* Extract request attributes from method arguments
47-
* Following Sentry's AI Agents conventions
4847
*/
4948
function extractRequestAttributes(args: unknown[], methodPath: string): Record<string, unknown> {
5049
const attributes: Record<string, unknown> = {
5150
[GEN_AI_SYSTEM_ATTRIBUTE]: 'openai',
5251
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: getOperationName(methodPath),
5352
};
5453

55-
if (args.length > 0 && args[0] && typeof args[0] === 'object') {
54+
if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) {
5655
const params = args[0] as Record<string, unknown>;
5756

58-
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = params.model || 'unknown';
59-
60-
if (params.temperature !== undefined) {
61-
attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE] = params.temperature;
62-
}
63-
if (params.top_p !== undefined) {
64-
attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE] = params.top_p;
65-
}
66-
67-
if (params.frequency_penalty !== undefined) {
57+
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = params.model ?? 'unknown';
58+
if ('temperature' in params) attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE] = params.temperature;
59+
if ('top_p' in params) attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE] = params.top_p;
60+
if ('frequency_penalty' in params)
6861
attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE] = params.frequency_penalty;
69-
}
70-
if (params.presence_penalty !== undefined) {
71-
attributes[GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE] = params.presence_penalty;
72-
}
62+
if ('presence_penalty' in params) attributes[GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE] = params.presence_penalty;
7363
} else {
74-
// REQUIRED: Ensure model is always set even when no params provided
7564
attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = 'unknown';
7665
}
7766

@@ -134,7 +123,6 @@ function setCommonResponseAttributes(span: Span, id?: string, model?: string, ti
134123
*/
135124
function addChatCompletionAttributes(span: Span, response: OpenAiChatCompletionObject): void {
136125
setCommonResponseAttributes(span, response.id, response.model, response.created);
137-
138126
if (response.usage) {
139127
setTokenUsageAttributes(
140128
span,
@@ -143,11 +131,10 @@ function addChatCompletionAttributes(span: Span, response: OpenAiChatCompletionO
143131
response.usage.total_tokens,
144132
);
145133
}
146-
147-
// Finish reasons - must be stringified array
148-
if (response.choices && Array.isArray(response.choices)) {
149-
const finishReasons = response.choices.map(choice => choice.finish_reason).filter(reason => reason !== null);
150-
134+
if (Array.isArray(response.choices)) {
135+
const finishReasons = response.choices
136+
.map(choice => choice.finish_reason)
137+
.filter((reason): reason is string => reason !== null);
151138
if (finishReasons.length > 0) {
152139
span.setAttributes({
153140
[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify(finishReasons),
@@ -161,14 +148,11 @@ function addChatCompletionAttributes(span: Span, response: OpenAiChatCompletionO
161148
*/
162149
function addResponsesApiAttributes(span: Span, response: OpenAIResponseObject): void {
163150
setCommonResponseAttributes(span, response.id, response.model, response.created_at);
164-
165151
if (response.status) {
166152
span.setAttributes({
167153
[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify([response.status]),
168154
});
169155
}
170-
171-
// Token usage for responses API
172156
if (response.usage) {
173157
setTokenUsageAttributes(
174158
span,
@@ -190,27 +174,28 @@ function addResponseAttributes(span: Span, result: unknown, recordOutputs?: bool
190174

191175
if (isChatCompletionResponse(response)) {
192176
addChatCompletionAttributes(span, response);
193-
194-
if (recordOutputs && response.choices && response.choices.length > 0) {
177+
if (recordOutputs && response.choices?.length) {
195178
const responseTexts = response.choices.map(choice => choice.message?.content || '');
196-
span.setAttributes({
197-
[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: JSON.stringify(responseTexts),
198-
});
179+
span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: JSON.stringify(responseTexts) });
199180
}
200181
} else if (isResponsesApiResponse(response)) {
201182
addResponsesApiAttributes(span, response);
202-
203183
if (recordOutputs && response.output_text) {
204-
span.setAttributes({
205-
[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.output_text,
206-
});
184+
span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.output_text });
207185
}
208186
}
209187
}
210188

211-
/**
212-
* Get options from integration configuration
213-
*/
189+
// Extract and record AI request inputs, if present. This is intentionally separate from response attributes.
190+
function addRequestAttributes(span: Span, params: Record<string, unknown>): void {
191+
if ('messages' in params) {
192+
span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.messages) });
193+
}
194+
if ('input' in params) {
195+
span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.input) });
196+
}
197+
}
198+
214199
function getOptionsFromIntegration(): OpenAiOptions {
215200
const scope = getCurrentScope();
216201
const client = scope.getClient();
@@ -237,33 +222,24 @@ function instrumentMethod<T extends unknown[], R>(
237222
return async function instrumentedMethod(...args: T): Promise<R> {
238223
const finalOptions = options || getOptionsFromIntegration();
239224
const requestAttributes = extractRequestAttributes(args, methodPath);
240-
const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] || 'unknown';
225+
const model = (requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] as string) || 'unknown';
241226
const operationName = getOperationName(methodPath);
242227

243228
return startSpan(
244229
{
245-
// Span name follows Sentry convention: "{operation_name} {model}"
246-
// e.g., "chat gpt-4", "chat o3-mini", "embeddings text-embedding-3-small"
247230
name: `${operationName} ${model}`,
248231
op: getSpanOperation(methodPath),
249232
attributes: requestAttributes as Record<string, SpanAttributeValue>,
250233
},
251234
async (span: Span) => {
252235
try {
253-
// Record inputs if enabled - must be stringified JSON
254236
if (finalOptions.recordInputs && args[0] && typeof args[0] === 'object') {
255-
const params = args[0] as Record<string, unknown>;
256-
if (params.messages) {
257-
span.setAttributes({
258-
[GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.messages),
259-
});
260-
}
237+
addRequestAttributes(span, args[0] as Record<string, unknown>);
261238
}
262239

263240
const result = await originalMethod.apply(context, args);
264241
// TODO: Add streaming support
265242
addResponseAttributes(span, result, finalOptions.recordOutputs);
266-
267243
return result;
268244
} catch (error) {
269245
captureException(error);
@@ -279,20 +255,16 @@ function instrumentMethod<T extends unknown[], R>(
279255
*/
280256
function createDeepProxy(target: object, currentPath = '', options?: OpenAiOptions): OpenAiClient {
281257
return new Proxy(target, {
282-
get(obj: Record<string | symbol, unknown>, prop: string | symbol): unknown {
283-
if (typeof prop === 'symbol') {
284-
return obj[prop];
285-
}
286-
287-
const value = obj[prop];
288-
const methodPath = buildMethodPath(currentPath, prop);
258+
get(obj: object, prop: string): unknown {
259+
const value = (obj as Record<string, unknown>)[prop];
260+
const methodPath = buildMethodPath(currentPath, String(prop));
289261

290262
if (typeof value === 'function' && shouldInstrument(methodPath)) {
291263
return instrumentMethod(value as (...args: unknown[]) => Promise<unknown>, methodPath, obj, options);
292264
}
293265

294-
if (typeof value === 'object' && value !== null) {
295-
return createDeepProxy(value, methodPath, options);
266+
if (value && typeof value === 'object') {
267+
return createDeepProxy(value as object, methodPath, options);
296268
}
297269

298270
return value;

0 commit comments

Comments
 (0)