openlit 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +15 -11
  2. package/dist/helpers.d.ts +10 -0
  3. package/dist/helpers.js +114 -7
  4. package/dist/helpers.js.map +1 -1
  5. package/dist/instrumentation/__tests__/anthropic-wrapper.test.js +1 -1
  6. package/dist/instrumentation/__tests__/anthropic-wrapper.test.js.map +1 -1
  7. package/dist/instrumentation/__tests__/base-wrapper.test.js +1 -1
  8. package/dist/instrumentation/__tests__/base-wrapper.test.js.map +1 -1
  9. package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.d.ts +4 -0
  10. package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.js +99 -0
  11. package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.js.map +1 -0
  12. package/dist/instrumentation/__tests__/groq-trace-comparison.test.d.ts +7 -0
  13. package/dist/instrumentation/__tests__/groq-trace-comparison.test.js +180 -0
  14. package/dist/instrumentation/__tests__/groq-trace-comparison.test.js.map +1 -0
  15. package/dist/instrumentation/__tests__/mistral-trace-comparison.test.d.ts +4 -0
  16. package/dist/instrumentation/__tests__/mistral-trace-comparison.test.js +127 -0
  17. package/dist/instrumentation/__tests__/mistral-trace-comparison.test.js.map +1 -0
  18. package/dist/instrumentation/__tests__/openai-wrapper.test.js +81 -18
  19. package/dist/instrumentation/__tests__/openai-wrapper.test.js.map +1 -1
  20. package/dist/instrumentation/__tests__/together-trace-comparison.test.d.ts +4 -0
  21. package/dist/instrumentation/__tests__/together-trace-comparison.test.js +98 -0
  22. package/dist/instrumentation/__tests__/together-trace-comparison.test.js.map +1 -0
  23. package/dist/instrumentation/__tests__/trace-comparison-utils.d.ts +66 -0
  24. package/dist/instrumentation/__tests__/trace-comparison-utils.js +245 -0
  25. package/dist/instrumentation/__tests__/trace-comparison-utils.js.map +1 -0
  26. package/dist/instrumentation/anthropic/index.js +6 -4
  27. package/dist/instrumentation/anthropic/index.js.map +1 -1
  28. package/dist/instrumentation/anthropic/wrapper.js +12 -30
  29. package/dist/instrumentation/anthropic/wrapper.js.map +1 -1
  30. package/dist/instrumentation/base-wrapper.js +2 -2
  31. package/dist/instrumentation/base-wrapper.js.map +1 -1
  32. package/dist/instrumentation/cohere/wrapper.js +8 -10
  33. package/dist/instrumentation/cohere/wrapper.js.map +1 -1
  34. package/dist/instrumentation/google-ai/index.d.ts +11 -0
  35. package/dist/instrumentation/google-ai/index.js +48 -0
  36. package/dist/instrumentation/google-ai/index.js.map +1 -0
  37. package/dist/instrumentation/google-ai/wrapper.d.ts +34 -0
  38. package/dist/instrumentation/google-ai/wrapper.js +241 -0
  39. package/dist/instrumentation/google-ai/wrapper.js.map +1 -0
  40. package/dist/instrumentation/groq/index.d.ts +11 -0
  41. package/dist/instrumentation/groq/index.js +43 -0
  42. package/dist/instrumentation/groq/index.js.map +1 -0
  43. package/dist/instrumentation/groq/wrapper.d.ts +33 -0
  44. package/dist/instrumentation/groq/wrapper.js +289 -0
  45. package/dist/instrumentation/groq/wrapper.js.map +1 -0
  46. package/dist/instrumentation/index.js +10 -0
  47. package/dist/instrumentation/index.js.map +1 -1
  48. package/dist/instrumentation/mistral/index.d.ts +11 -0
  49. package/dist/instrumentation/mistral/index.js +66 -0
  50. package/dist/instrumentation/mistral/index.js.map +1 -0
  51. package/dist/instrumentation/mistral/wrapper.d.ts +34 -0
  52. package/dist/instrumentation/mistral/wrapper.js +340 -0
  53. package/dist/instrumentation/mistral/wrapper.js.map +1 -0
  54. package/dist/instrumentation/ollama/wrapper.d.ts +11 -3
  55. package/dist/instrumentation/ollama/wrapper.js +60 -95
  56. package/dist/instrumentation/ollama/wrapper.js.map +1 -1
  57. package/dist/instrumentation/openai/index.js +11 -0
  58. package/dist/instrumentation/openai/index.js.map +1 -1
  59. package/dist/instrumentation/openai/wrapper.d.ts +30 -1
  60. package/dist/instrumentation/openai/wrapper.js +419 -61
  61. package/dist/instrumentation/openai/wrapper.js.map +1 -1
  62. package/dist/instrumentation/together/index.d.ts +11 -0
  63. package/dist/instrumentation/together/index.js +43 -0
  64. package/dist/instrumentation/together/index.js.map +1 -0
  65. package/dist/instrumentation/together/wrapper.d.ts +33 -0
  66. package/dist/instrumentation/together/wrapper.js +271 -0
  67. package/dist/instrumentation/together/wrapper.js.map +1 -0
  68. package/dist/otel/__tests__/metrics.test.js +5 -5
  69. package/dist/otel/__tests__/metrics.test.js.map +1 -1
  70. package/dist/semantic-convention.d.ts +34 -3
  71. package/dist/semantic-convention.js +50 -14
  72. package/dist/semantic-convention.js.map +1 -1
  73. package/dist/types.d.ts +1 -1
  74. package/package.json +1 -1
@@ -61,6 +61,8 @@ class OpenAIWrapper extends base_wrapper_1.default {
61
61
  }
62
62
  static async *_chatCompletionGenerator({ args, genAIEndpoint, response, span, }) {
63
63
  let metricParams;
64
+ const timestamps = [];
65
+ const startTime = Date.now();
64
66
  try {
65
67
  const { messages } = args[0];
66
68
  let { tools } = args[0];
@@ -68,6 +70,8 @@ class OpenAIWrapper extends base_wrapper_1.default {
68
70
  id: '0',
69
71
  created: -1,
70
72
  model: '',
73
+ system_fingerprint: '',
74
+ service_tier: 'auto',
71
75
  choices: [
72
76
  {
73
77
  index: 0,
@@ -80,12 +84,28 @@ class OpenAIWrapper extends base_wrapper_1.default {
80
84
  prompt_tokens: 0,
81
85
  completion_tokens: 0,
82
86
  total_tokens: 0,
87
+ completion_tokens_details: {
88
+ reasoning_tokens: 0,
89
+ audio_tokens: 0,
90
+ },
91
+ prompt_tokens_details: {
92
+ cached_tokens: 0,
93
+ audio_tokens: 0,
94
+ },
83
95
  },
84
96
  };
97
+ let toolCalls = [];
85
98
  for await (const chunk of response) {
99
+ timestamps.push(Date.now());
86
100
  result.id = chunk.id;
87
101
  result.created = chunk.created;
88
102
  result.model = chunk.model;
103
+ if (chunk.system_fingerprint) {
104
+ result.system_fingerprint = chunk.system_fingerprint;
105
+ }
106
+ if (chunk.service_tier) {
107
+ result.service_tier = chunk.service_tier;
108
+ }
89
109
  if (chunk.choices[0]?.finish_reason) {
90
110
  result.choices[0].finish_reason = chunk.choices[0].finish_reason;
91
111
  }
@@ -95,11 +115,45 @@ class OpenAIWrapper extends base_wrapper_1.default {
95
115
  if (chunk.choices[0]?.delta.content) {
96
116
  result.choices[0].message.content += chunk.choices[0].delta.content;
97
117
  }
118
+ // Improved tool calls handling for streaming
98
119
  if (chunk.choices[0]?.delta.tool_calls) {
120
+ const deltaTools = chunk.choices[0].delta.tool_calls;
121
+ for (const tool of deltaTools) {
122
+ const idx = tool.index || 0;
123
+ // Extend array if needed
124
+ while (toolCalls.length <= idx) {
125
+ toolCalls.push({
126
+ id: '',
127
+ type: 'function',
128
+ function: { name: '', arguments: '' }
129
+ });
130
+ }
131
+ if (tool.id) {
132
+ // New tool call
133
+ toolCalls[idx].id = tool.id;
134
+ toolCalls[idx].type = tool.type || 'function';
135
+ if (tool.function?.name) {
136
+ toolCalls[idx].function.name = tool.function.name;
137
+ }
138
+ if (tool.function?.arguments) {
139
+ toolCalls[idx].function.arguments = tool.function.arguments;
140
+ }
141
+ }
142
+ else if (tool.function?.arguments) {
143
+ // Append arguments to existing tool call
144
+ toolCalls[idx].function.arguments += tool.function.arguments;
145
+ }
146
+ }
99
147
  tools = true;
100
148
  }
101
149
  yield chunk;
102
150
  }
151
+ if (toolCalls.length > 0) {
152
+ result.choices[0].message = {
153
+ ...result.choices[0].message,
154
+ tool_calls: toolCalls
155
+ };
156
+ }
103
157
  let promptTokens = 0;
104
158
  for (const message of messages || []) {
105
159
  promptTokens += helpers_1.default.openaiTokens(message.content, result.model) ?? 0;
@@ -110,14 +164,25 @@ class OpenAIWrapper extends base_wrapper_1.default {
110
164
  prompt_tokens: promptTokens,
111
165
  completion_tokens: completionTokens,
112
166
  total_tokens: promptTokens + completionTokens,
167
+ completion_tokens_details: result.usage.completion_tokens_details,
168
+ prompt_tokens_details: result.usage.prompt_tokens_details,
113
169
  };
114
170
  }
115
171
  args[0].tools = tools;
172
+ // Calculate TTFT and TBT
173
+ const ttft = timestamps.length > 0 ? (timestamps[0] - startTime) / 1000 : 0;
174
+ let tbt = 0;
175
+ if (timestamps.length > 1) {
176
+ const timeDiffs = timestamps.slice(1).map((t, i) => t - timestamps[i]);
177
+ tbt = timeDiffs.reduce((a, b) => a + b, 0) / timeDiffs.length / 1000;
178
+ }
116
179
  metricParams = await OpenAIWrapper._chatCompletionCommonSetter({
117
180
  args,
118
181
  genAIEndpoint,
119
182
  result,
120
183
  span,
184
+ ttft,
185
+ tbt,
121
186
  });
122
187
  return result;
123
188
  }
@@ -132,48 +197,31 @@ class OpenAIWrapper extends base_wrapper_1.default {
132
197
  }
133
198
  }
134
199
  }
135
- static async _chatCompletionCommonSetter({ args, genAIEndpoint, result, span, }) {
200
+ static async _chatCompletionCommonSetter({ args, genAIEndpoint, result, span, ttft = 0, tbt = 0, }) {
136
201
  const traceContent = config_1.default.traceContent;
137
- const { messages, frequency_penalty = 0, max_tokens = null, n = 1, presence_penalty = 0, seed = null, temperature = 1, top_p, user, stream = false, tools, } = args[0];
202
+ const { messages, frequency_penalty = 0, max_tokens = null, n = 1, presence_penalty = 0, seed = null, stop = null, temperature = 1, top_p, user, stream = false, tools, } = args[0];
138
203
  // Request Params attributes : Start
139
204
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p || 1);
140
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_tokens);
205
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_tokens || -1);
141
206
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature);
142
207
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty);
143
208
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty);
144
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SEED, seed);
209
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SEED, seed ? String(seed) : '');
145
210
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, stream);
211
+ if (stop) {
212
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_STOP_SEQUENCES, Array.isArray(stop) ? stop : [stop]);
213
+ }
214
+ if (user) {
215
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_USER, user);
216
+ }
146
217
  if (traceContent) {
147
- // Format 'messages' into a single string
148
- const messagePrompt = messages || [];
149
- const formattedMessages = [];
150
- for (const message of messagePrompt) {
151
- const role = message.role;
152
- const content = message.content;
153
- if (Array.isArray(content)) {
154
- const contentStr = content
155
- .map((item) => {
156
- if ('type' in item) {
157
- return `${item.type}: ${item.text ? item.text : item.image_url}`;
158
- }
159
- else {
160
- return `text: ${item.text}`;
161
- }
162
- })
163
- .join(', ');
164
- formattedMessages.push(`${role}: ${contentStr}`);
165
- }
166
- else {
167
- formattedMessages.push(`${role}: ${content}`);
168
- }
169
- }
170
- const prompt = formattedMessages.join('\n');
171
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt);
218
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, helpers_1.default.buildInputMessages(messages || []));
172
219
  }
173
220
  // Request Params attributes : End
174
221
  span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
175
222
  span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
176
223
  const model = result.model || 'gpt-3.5-turbo';
224
+ const responseModel = result.model || model;
177
225
  const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json);
178
226
  // Calculate cost of the operation
179
227
  const cost = helpers_1.default.getChatModelCost(model, pricingInfo, result.usage.prompt_tokens, result.usage.completion_tokens);
@@ -184,30 +232,80 @@ class OpenAIWrapper extends base_wrapper_1.default {
184
232
  cost,
185
233
  aiSystem: OpenAIWrapper.aiSystem,
186
234
  });
235
+ // Response model
236
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
237
+ // OpenAI-specific attributes
238
+ if (result.system_fingerprint) {
239
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT, result.system_fingerprint);
240
+ }
241
+ if (result.service_tier) {
242
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SERVICE_TIER, result.service_tier);
243
+ }
244
+ // Token usage
187
245
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, result.usage.prompt_tokens);
188
246
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, result.usage.completion_tokens);
189
247
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, result.usage.total_tokens);
190
- if (result.choices[0].finish_reason) {
191
- span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, result.choices[0].finish_reason);
248
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, result.usage.total_tokens);
249
+ // Enhanced token details
250
+ if (result.usage.completion_tokens_details) {
251
+ if (result.usage.completion_tokens_details.reasoning_tokens) {
252
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_REASONING_TOKENS, result.usage.completion_tokens_details.reasoning_tokens);
253
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COMPLETION_TOKENS_DETAILS_REASONING, result.usage.completion_tokens_details.reasoning_tokens);
254
+ }
255
+ if (result.usage.completion_tokens_details.audio_tokens) {
256
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COMPLETION_TOKENS_DETAILS_AUDIO, result.usage.completion_tokens_details.audio_tokens);
257
+ }
192
258
  }
193
- if (tools) {
194
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, 'Function called with tools');
259
+ if (result.usage.prompt_tokens_details) {
260
+ if (result.usage.prompt_tokens_details.cached_tokens) {
261
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_PROMPT_TOKENS_DETAILS_CACHE_READ, result.usage.prompt_tokens_details.cached_tokens);
262
+ }
263
+ if (result.usage.prompt_tokens_details.audio_tokens) {
264
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_PROMPT_TOKENS_DETAILS_CACHE_WRITE, result.usage.prompt_tokens_details.audio_tokens);
265
+ }
195
266
  }
196
- else {
197
- if (traceContent) {
198
- if (n === 1) {
199
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, result.choices[0].message.content);
200
- }
201
- else {
202
- let i = 0;
203
- while (i < n) {
204
- const attribute_name = `${semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION}.[i]`;
205
- span.setAttribute(attribute_name, result.choices[i].message.content);
206
- i += 1;
207
- }
208
- }
267
+ // TTFT and TBT metrics
268
+ if (ttft > 0) {
269
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
270
+ }
271
+ if (tbt > 0) {
272
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
273
+ }
274
+ // Finish reason
275
+ if (result.choices[0].finish_reason) {
276
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [result.choices[0].finish_reason]);
277
+ }
278
+ // Output type
279
+ const outputType = typeof result.choices[0].message.content === 'string'
280
+ ? semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT
281
+ : semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_JSON;
282
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, outputType);
283
+ // Tool calls handling
284
+ if (result.choices[0].message.tool_calls) {
285
+ const toolCalls = result.choices[0].message.tool_calls;
286
+ const toolNames = toolCalls.map((t) => t.function?.name || '').filter(Boolean);
287
+ const toolIds = toolCalls.map((t) => t.id || '').filter(Boolean);
288
+ const toolArgs = toolCalls.map((t) => t.function?.arguments || '').filter(Boolean);
289
+ const toolTypes = toolCalls.map((t) => t.type || '').filter(Boolean);
290
+ if (toolNames.length > 0) {
291
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, toolNames.join(', '));
292
+ }
293
+ if (toolIds.length > 0) {
294
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, toolIds.join(', '));
295
+ }
296
+ if (toolArgs.length > 0) {
297
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, toolArgs);
298
+ }
299
+ if (toolTypes.length > 0) {
300
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_TYPE, toolTypes.join(', '));
209
301
  }
210
302
  }
303
+ // Content
304
+ if (traceContent) {
305
+ const toolCalls = result.choices[0].message.tool_calls;
306
+ const outputJson = helpers_1.default.buildOutputMessages(result.choices[0].message.content || '', result.choices[0].finish_reason || 'stop', toolCalls);
307
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, outputJson);
308
+ }
211
309
  return {
212
310
  genAIEndpoint,
213
311
  model,
@@ -223,13 +321,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
223
321
  return async function (...args) {
224
322
  const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
225
323
  return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
226
- let metricParams = {
227
- genAIEndpoint,
228
- model: '',
229
- user: '',
230
- cost: 0,
231
- aiSystem: OpenAIWrapper.aiSystem,
232
- };
324
+ let metricParams;
233
325
  try {
234
326
  const response = await originalMethod.apply(this, args);
235
327
  const model = response.model || 'text-embedding-ada-002';
@@ -237,7 +329,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
237
329
  const cost = helpers_1.default.getEmbedModelCost(model, pricingInfo, response.usage.prompt_tokens);
238
330
  span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_EMBEDDING);
239
331
  const { dimensions, encoding_format = 'float', input, user } = args[0];
240
- // Set base span attribues
332
+ // Set base span attributes
241
333
  OpenAIWrapper.setBaseSpanAttributes(span, {
242
334
  genAIEndpoint,
243
335
  model,
@@ -245,15 +337,29 @@ class OpenAIWrapper extends base_wrapper_1.default {
245
337
  cost,
246
338
  aiSystem: OpenAIWrapper.aiSystem,
247
339
  });
340
+ // Set missing critical attributes to match Python SDK
341
+ span.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, 'api.openai.com');
342
+ span.setAttribute(semantic_convention_1.default.SERVER_PORT, 443);
343
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, false);
344
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, 0);
345
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, 0);
346
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, '1.7.0');
248
347
  // Request Params attributes : Start
249
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_ENCODING_FORMATS, encoding_format);
250
- span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_EMBEDDING_DIMENSION, dimensions);
348
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_ENCODING_FORMATS, [encoding_format]);
349
+ if (dimensions) {
350
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_EMBEDDING_DIMENSION, dimensions);
351
+ }
352
+ if (user) {
353
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_USER, user);
354
+ }
251
355
  if (traceContent) {
252
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, input);
356
+ const formattedInput = typeof input === 'string' ? input : JSON.stringify(input);
357
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, formattedInput);
253
358
  }
254
359
  // Request Params attributes : End
255
360
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens);
256
361
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, response.usage.total_tokens);
362
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, response.usage.prompt_tokens);
257
363
  metricParams = {
258
364
  genAIEndpoint,
259
365
  model,
@@ -265,10 +371,13 @@ class OpenAIWrapper extends base_wrapper_1.default {
265
371
  }
266
372
  catch (e) {
267
373
  helpers_1.default.handleException(span, e);
374
+ throw e;
268
375
  }
269
376
  finally {
270
377
  span.end();
271
- base_wrapper_1.default.recordMetrics(span, metricParams);
378
+ if (metricParams) {
379
+ base_wrapper_1.default.recordMetrics(span, metricParams);
380
+ }
272
381
  }
273
382
  });
274
383
  };
@@ -356,7 +465,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
356
465
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_QUALITY, quality);
357
466
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_STYLE, style);
358
467
  if (traceContent) {
359
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt);
468
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, prompt);
360
469
  }
361
470
  // Request Params attributes : End
362
471
  let imagesCount = 0;
@@ -422,7 +531,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
422
531
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_QUALITY, quality);
423
532
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IMAGE_STYLE, style);
424
533
  if (traceContent) {
425
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt);
534
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, prompt);
426
535
  }
427
536
  // Request Params attributes : End
428
537
  let imagesCount = 0;
@@ -486,7 +595,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
486
595
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_AUDIO_RESPONSE_FORMAT, response_format);
487
596
  span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_AUDIO_SPEED, speed);
488
597
  if (traceContent) {
489
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, input);
598
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, input);
490
599
  }
491
600
  // Request Params attributes : End
492
601
  // Store metric parameters for use after span ends
@@ -513,6 +622,255 @@ class OpenAIWrapper extends base_wrapper_1.default {
513
622
  };
514
623
  };
515
624
  }
625
+ static _patchResponsesCreate(tracer) {
626
+ const genAIEndpoint = 'openai.resources.responses';
627
+ return (originalMethod) => {
628
+ return async function (...args) {
629
+ const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
630
+ return api_1.context
631
+ .with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
632
+ return originalMethod.apply(this, args);
633
+ })
634
+ .then((response) => {
635
+ const { stream = false } = args[0];
636
+ if (stream) {
637
+ return helpers_1.default.createStreamProxy(response, OpenAIWrapper._responsesGenerator({
638
+ args,
639
+ genAIEndpoint,
640
+ response,
641
+ span,
642
+ }));
643
+ }
644
+ return OpenAIWrapper._responsesComplete({ args, genAIEndpoint, response, span });
645
+ })
646
+ .catch((e) => {
647
+ helpers_1.default.handleException(span, e);
648
+ span.end();
649
+ });
650
+ };
651
+ };
652
+ }
653
+ static async _responsesComplete({ args, genAIEndpoint, response, span, }) {
654
+ let metricParams;
655
+ try {
656
+ metricParams = await OpenAIWrapper._responsesCommonSetter({
657
+ args,
658
+ genAIEndpoint,
659
+ result: response,
660
+ span,
661
+ });
662
+ return response;
663
+ }
664
+ catch (e) {
665
+ helpers_1.default.handleException(span, e);
666
+ }
667
+ finally {
668
+ span.end();
669
+ if (metricParams) {
670
+ base_wrapper_1.default.recordMetrics(span, metricParams);
671
+ }
672
+ }
673
+ }
674
+ static async *_responsesGenerator({ args, genAIEndpoint, response, span, }) {
675
+ let metricParams;
676
+ const timestamps = [];
677
+ const startTime = Date.now();
678
+ try {
679
+ const { input } = args[0];
680
+ const result = {
681
+ id: '',
682
+ model: '',
683
+ service_tier: 'default',
684
+ status: 'completed',
685
+ output: [],
686
+ usage: {
687
+ input_tokens: 0,
688
+ output_tokens: 0,
689
+ output_tokens_details: {
690
+ reasoning_tokens: 0,
691
+ },
692
+ },
693
+ };
694
+ let llmResponse = '';
695
+ let responseTools = [];
696
+ for await (const chunk of response) {
697
+ timestamps.push(Date.now());
698
+ if (chunk.type === 'response.output_text.delta') {
699
+ llmResponse += chunk.delta || '';
700
+ }
701
+ else if (chunk.type === 'response.output_item.added') {
702
+ const item = chunk.item;
703
+ if (item?.type === 'function_call') {
704
+ responseTools.push({
705
+ id: item.id,
706
+ call_id: item.call_id,
707
+ name: item.name,
708
+ type: item.type,
709
+ arguments: item.arguments || '',
710
+ status: item.status,
711
+ });
712
+ }
713
+ }
714
+ else if (chunk.type === 'response.function_call_arguments.delta') {
715
+ const itemId = chunk.item_id;
716
+ const delta = chunk.delta || '';
717
+ const tool = responseTools.find(t => t.id === itemId);
718
+ if (tool) {
719
+ tool.arguments += delta;
720
+ }
721
+ }
722
+ else if (chunk.type === 'response.completed') {
723
+ const responseData = chunk.response;
724
+ result.id = responseData.id;
725
+ result.model = responseData.model;
726
+ result.status = responseData.status;
727
+ const usage = responseData.usage || {};
728
+ result.usage.input_tokens = usage.input_tokens || 0;
729
+ result.usage.output_tokens = usage.output_tokens || 0;
730
+ result.usage.output_tokens_details.reasoning_tokens =
731
+ usage.output_tokens_details?.reasoning_tokens || 0;
732
+ }
733
+ yield chunk;
734
+ }
735
+ // Construct output array
736
+ if (llmResponse) {
737
+ result.output.push({
738
+ type: 'message',
739
+ content: [{ type: 'text', text: llmResponse }],
740
+ });
741
+ }
742
+ if (responseTools.length > 0) {
743
+ result.output.push(...responseTools);
744
+ }
745
+ // Calculate TTFT and TBT
746
+ const ttft = timestamps.length > 0 ? (timestamps[0] - startTime) / 1000 : 0;
747
+ let tbt = 0;
748
+ if (timestamps.length > 1) {
749
+ const timeDiffs = timestamps.slice(1).map((t, i) => t - timestamps[i]);
750
+ tbt = timeDiffs.reduce((a, b) => a + b, 0) / timeDiffs.length / 1000;
751
+ }
752
+ metricParams = await OpenAIWrapper._responsesCommonSetter({
753
+ args,
754
+ genAIEndpoint,
755
+ result,
756
+ span,
757
+ ttft,
758
+ tbt,
759
+ });
760
+ return result;
761
+ }
762
+ catch (e) {
763
+ helpers_1.default.handleException(span, e);
764
+ }
765
+ finally {
766
+ span.end();
767
+ if (metricParams) {
768
+ base_wrapper_1.default.recordMetrics(span, metricParams);
769
+ }
770
+ }
771
+ }
772
+ static async _responsesCommonSetter({ args, genAIEndpoint, result, span, ttft = 0, tbt = 0, }) {
773
+ const traceContent = config_1.default.traceContent;
774
+ const { input, temperature = 1.0, top_p = 1.0, max_output_tokens, reasoning, stream = false, } = args[0];
775
+ // Normalize Responses API input to messages array for buildInputMessages
776
+ const responsesMessages = typeof input === 'string'
777
+ ? [{ role: 'user', content: input }]
778
+ : (Array.isArray(input) ? input : []);
779
+ // Request Params attributes
780
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature);
781
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p);
782
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_output_tokens || -1);
783
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, stream);
784
+ if (reasoning?.effort) {
785
+ span.setAttribute('gen_ai.request.reasoning_effort', reasoning.effort);
786
+ }
787
+ if (traceContent) {
788
+ span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, helpers_1.default.buildInputMessages(responsesMessages));
789
+ }
790
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
791
+ const model = result.model || 'gpt-4o';
792
+ const responseModel = result.model || model;
793
+ const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json);
794
+ // Calculate cost
795
+ const inputTokens = result.usage?.input_tokens || 0;
796
+ const outputTokens = result.usage?.output_tokens || 0;
797
+ const cost = helpers_1.default.getChatModelCost(model, pricingInfo, inputTokens, outputTokens);
798
+ OpenAIWrapper.setBaseSpanAttributes(span, {
799
+ genAIEndpoint,
800
+ model,
801
+ user: '',
802
+ cost,
803
+ aiSystem: OpenAIWrapper.aiSystem,
804
+ });
805
+ // Response attributes
806
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
807
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
808
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [result.status || 'completed']);
809
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT);
810
+ if (result.service_tier) {
811
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SERVICE_TIER, result.service_tier);
812
+ }
813
+ // Token usage
814
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
815
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
816
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, inputTokens + outputTokens);
817
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, inputTokens + outputTokens);
818
+ // Reasoning tokens
819
+ if (result.usage?.output_tokens_details?.reasoning_tokens) {
820
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_REASONING_TOKENS, result.usage.output_tokens_details.reasoning_tokens);
821
+ }
822
+ // TTFT and TBT metrics
823
+ if (ttft > 0) {
824
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
825
+ }
826
+ if (tbt > 0) {
827
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
828
+ }
829
+ // Extract completion text from output
830
+ let completionText = '';
831
+ if (result.output && Array.isArray(result.output)) {
832
+ for (const item of result.output) {
833
+ if (item.type === 'message' && item.content) {
834
+ for (const content of item.content) {
835
+ if (content.type === 'text' || content.type === 'output_text') {
836
+ completionText += content.text || '';
837
+ }
838
+ }
839
+ }
840
+ }
841
+ }
842
+ // Tool calls handling for Responses API
843
+ const toolCalls = result.tools || [];
844
+ if (toolCalls.length > 0) {
845
+ const toolNames = toolCalls.map((t) => t.name || '').filter(Boolean);
846
+ const toolIds = toolCalls.map((t) => t.call_id || '').filter(Boolean);
847
+ const toolArgs = toolCalls.map((t) => t.arguments || '').filter(Boolean);
848
+ const toolTypes = toolCalls.map((t) => t.type || '').filter(Boolean);
849
+ if (toolNames.length > 0) {
850
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, toolNames.join(', '));
851
+ }
852
+ if (toolIds.length > 0) {
853
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, toolIds.join(', '));
854
+ }
855
+ if (toolArgs.length > 0) {
856
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, toolArgs.join(', '));
857
+ }
858
+ if (toolTypes.length > 0) {
859
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_TYPE, toolTypes.join(', '));
860
+ }
861
+ }
862
+ // Content
863
+ if (traceContent) {
864
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, helpers_1.default.buildOutputMessages(completionText, result.status || 'stop'));
865
+ }
866
+ return {
867
+ genAIEndpoint,
868
+ model,
869
+ user: '',
870
+ cost,
871
+ aiSystem: OpenAIWrapper.aiSystem,
872
+ };
873
+ }
516
874
  }
517
875
  OpenAIWrapper.aiSystem = semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI;
518
876
  exports.default = OpenAIWrapper;