openlit 1.7.0 → 1.7.1

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.
@@ -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,17 +197,23 @@ 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
218
  // Format 'messages' into a single string
148
219
  const messagePrompt = messages || [];
@@ -174,6 +245,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
174
245
  span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
175
246
  span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
176
247
  const model = result.model || 'gpt-3.5-turbo';
248
+ const responseModel = result.model || model;
177
249
  const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json);
178
250
  // Calculate cost of the operation
179
251
  const cost = helpers_1.default.getChatModelCost(model, pricingInfo, result.usage.prompt_tokens, result.usage.completion_tokens);
@@ -184,29 +256,93 @@ class OpenAIWrapper extends base_wrapper_1.default {
184
256
  cost,
185
257
  aiSystem: OpenAIWrapper.aiSystem,
186
258
  });
259
+ // Response model
260
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
261
+ // OpenAI-specific attributes
262
+ if (result.system_fingerprint) {
263
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT, result.system_fingerprint);
264
+ }
265
+ if (result.service_tier) {
266
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SERVICE_TIER, result.service_tier);
267
+ }
268
+ // Token usage
187
269
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, result.usage.prompt_tokens);
188
270
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, result.usage.completion_tokens);
189
271
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, result.usage.total_tokens);
272
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, result.usage.total_tokens);
273
+ // Enhanced token details
274
+ if (result.usage.completion_tokens_details) {
275
+ if (result.usage.completion_tokens_details.reasoning_tokens) {
276
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_REASONING_TOKENS, result.usage.completion_tokens_details.reasoning_tokens);
277
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COMPLETION_TOKENS_DETAILS_REASONING, result.usage.completion_tokens_details.reasoning_tokens);
278
+ }
279
+ if (result.usage.completion_tokens_details.audio_tokens) {
280
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COMPLETION_TOKENS_DETAILS_AUDIO, result.usage.completion_tokens_details.audio_tokens);
281
+ }
282
+ }
283
+ if (result.usage.prompt_tokens_details) {
284
+ if (result.usage.prompt_tokens_details.cached_tokens) {
285
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_PROMPT_TOKENS_DETAILS_CACHE_READ, result.usage.prompt_tokens_details.cached_tokens);
286
+ }
287
+ if (result.usage.prompt_tokens_details.audio_tokens) {
288
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_PROMPT_TOKENS_DETAILS_CACHE_WRITE, result.usage.prompt_tokens_details.audio_tokens);
289
+ }
290
+ }
291
+ // TTFT and TBT metrics
292
+ if (ttft > 0) {
293
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
294
+ }
295
+ if (tbt > 0) {
296
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
297
+ }
298
+ // Finish reason
190
299
  if (result.choices[0].finish_reason) {
191
- span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, result.choices[0].finish_reason);
300
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [result.choices[0].finish_reason]);
192
301
  }
193
- if (tools) {
194
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, 'Function called with tools');
302
+ // Output type
303
+ const outputType = typeof result.choices[0].message.content === 'string'
304
+ ? semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT
305
+ : semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_JSON;
306
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, outputType);
307
+ // Tool calls handling
308
+ if (result.choices[0].message.tool_calls) {
309
+ const toolCalls = result.choices[0].message.tool_calls;
310
+ const toolNames = toolCalls.map((t) => t.function?.name || '').filter(Boolean);
311
+ const toolIds = toolCalls.map((t) => t.id || '').filter(Boolean);
312
+ const toolArgs = toolCalls.map((t) => t.function?.arguments || '').filter(Boolean);
313
+ const toolTypes = toolCalls.map((t) => t.type || '').filter(Boolean);
314
+ if (toolNames.length > 0) {
315
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, toolNames.join(', '));
316
+ }
317
+ if (toolIds.length > 0) {
318
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, toolIds.join(', '));
319
+ }
320
+ if (toolArgs.length > 0) {
321
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, toolArgs);
322
+ }
323
+ if (toolTypes.length > 0) {
324
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_TYPE, toolTypes.join(', '));
325
+ }
195
326
  }
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
- }
327
+ // Content
328
+ if (traceContent) {
329
+ // Format completion content - use actual content or empty string if only tool calls
330
+ const completionContent = result.choices[0].message.content || '';
331
+ if (n === 1) {
332
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, completionContent);
333
+ }
334
+ else {
335
+ let i = 0;
336
+ while (i < n) {
337
+ const attribute_name = `${semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION}.${i}`;
338
+ span.setAttribute(attribute_name, result.choices[i].message.content || '');
339
+ i += 1;
208
340
  }
209
341
  }
342
+ // Add events for backward compatibility
343
+ span.addEvent(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION_EVENT, {
344
+ [semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION]: completionContent,
345
+ });
210
346
  }
211
347
  return {
212
348
  genAIEndpoint,
@@ -223,13 +359,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
223
359
  return async function (...args) {
224
360
  const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
225
361
  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
- };
362
+ let metricParams;
233
363
  try {
234
364
  const response = await originalMethod.apply(this, args);
235
365
  const model = response.model || 'text-embedding-ada-002';
@@ -237,7 +367,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
237
367
  const cost = helpers_1.default.getEmbedModelCost(model, pricingInfo, response.usage.prompt_tokens);
238
368
  span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_EMBEDDING);
239
369
  const { dimensions, encoding_format = 'float', input, user } = args[0];
240
- // Set base span attribues
370
+ // Set base span attributes
241
371
  OpenAIWrapper.setBaseSpanAttributes(span, {
242
372
  genAIEndpoint,
243
373
  model,
@@ -245,15 +375,29 @@ class OpenAIWrapper extends base_wrapper_1.default {
245
375
  cost,
246
376
  aiSystem: OpenAIWrapper.aiSystem,
247
377
  });
378
+ // Set missing critical attributes to match Python SDK
379
+ span.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, 'api.openai.com');
380
+ span.setAttribute(semantic_convention_1.default.SERVER_PORT, 443);
381
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, false);
382
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, 0);
383
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, 0);
384
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, '1.7.0');
248
385
  // 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);
386
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_ENCODING_FORMATS, [encoding_format]);
387
+ if (dimensions) {
388
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_EMBEDDING_DIMENSION, dimensions);
389
+ }
390
+ if (user) {
391
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_USER, user);
392
+ }
251
393
  if (traceContent) {
252
- span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, input);
394
+ const formattedInput = typeof input === 'string' ? input : JSON.stringify(input);
395
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, formattedInput);
253
396
  }
254
397
  // Request Params attributes : End
255
398
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens);
256
399
  span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, response.usage.total_tokens);
400
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, response.usage.prompt_tokens);
257
401
  metricParams = {
258
402
  genAIEndpoint,
259
403
  model,
@@ -265,10 +409,13 @@ class OpenAIWrapper extends base_wrapper_1.default {
265
409
  }
266
410
  catch (e) {
267
411
  helpers_1.default.handleException(span, e);
412
+ throw e;
268
413
  }
269
414
  finally {
270
415
  span.end();
271
- base_wrapper_1.default.recordMetrics(span, metricParams);
416
+ if (metricParams) {
417
+ base_wrapper_1.default.recordMetrics(span, metricParams);
418
+ }
272
419
  }
273
420
  });
274
421
  };
@@ -513,6 +660,287 @@ class OpenAIWrapper extends base_wrapper_1.default {
513
660
  };
514
661
  };
515
662
  }
663
+ static _patchResponsesCreate(tracer) {
664
+ const genAIEndpoint = 'openai.resources.responses';
665
+ return (originalMethod) => {
666
+ return async function (...args) {
667
+ const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
668
+ return api_1.context
669
+ .with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
670
+ return originalMethod.apply(this, args);
671
+ })
672
+ .then((response) => {
673
+ const { stream = false } = args[0];
674
+ if (stream) {
675
+ return helpers_1.default.createStreamProxy(response, OpenAIWrapper._responsesGenerator({
676
+ args,
677
+ genAIEndpoint,
678
+ response,
679
+ span,
680
+ }));
681
+ }
682
+ return OpenAIWrapper._responsesComplete({ args, genAIEndpoint, response, span });
683
+ })
684
+ .catch((e) => {
685
+ helpers_1.default.handleException(span, e);
686
+ span.end();
687
+ });
688
+ };
689
+ };
690
+ }
691
+ static async _responsesComplete({ args, genAIEndpoint, response, span, }) {
692
+ let metricParams;
693
+ try {
694
+ metricParams = await OpenAIWrapper._responsesCommonSetter({
695
+ args,
696
+ genAIEndpoint,
697
+ result: response,
698
+ span,
699
+ });
700
+ return response;
701
+ }
702
+ catch (e) {
703
+ helpers_1.default.handleException(span, e);
704
+ }
705
+ finally {
706
+ span.end();
707
+ if (metricParams) {
708
+ base_wrapper_1.default.recordMetrics(span, metricParams);
709
+ }
710
+ }
711
+ }
712
+ static async *_responsesGenerator({ args, genAIEndpoint, response, span, }) {
713
+ let metricParams;
714
+ const timestamps = [];
715
+ const startTime = Date.now();
716
+ try {
717
+ const { input } = args[0];
718
+ const result = {
719
+ id: '',
720
+ model: '',
721
+ service_tier: 'default',
722
+ status: 'completed',
723
+ output: [],
724
+ usage: {
725
+ input_tokens: 0,
726
+ output_tokens: 0,
727
+ output_tokens_details: {
728
+ reasoning_tokens: 0,
729
+ },
730
+ },
731
+ };
732
+ let llmResponse = '';
733
+ let responseTools = [];
734
+ for await (const chunk of response) {
735
+ timestamps.push(Date.now());
736
+ if (chunk.type === 'response.output_text.delta') {
737
+ llmResponse += chunk.delta || '';
738
+ }
739
+ else if (chunk.type === 'response.output_item.added') {
740
+ const item = chunk.item;
741
+ if (item?.type === 'function_call') {
742
+ responseTools.push({
743
+ id: item.id,
744
+ call_id: item.call_id,
745
+ name: item.name,
746
+ type: item.type,
747
+ arguments: item.arguments || '',
748
+ status: item.status,
749
+ });
750
+ }
751
+ }
752
+ else if (chunk.type === 'response.function_call_arguments.delta') {
753
+ const itemId = chunk.item_id;
754
+ const delta = chunk.delta || '';
755
+ const tool = responseTools.find(t => t.id === itemId);
756
+ if (tool) {
757
+ tool.arguments += delta;
758
+ }
759
+ }
760
+ else if (chunk.type === 'response.completed') {
761
+ const responseData = chunk.response;
762
+ result.id = responseData.id;
763
+ result.model = responseData.model;
764
+ result.status = responseData.status;
765
+ const usage = responseData.usage || {};
766
+ result.usage.input_tokens = usage.input_tokens || 0;
767
+ result.usage.output_tokens = usage.output_tokens || 0;
768
+ result.usage.output_tokens_details.reasoning_tokens =
769
+ usage.output_tokens_details?.reasoning_tokens || 0;
770
+ }
771
+ yield chunk;
772
+ }
773
+ // Construct output array
774
+ if (llmResponse) {
775
+ result.output.push({
776
+ type: 'message',
777
+ content: [{ type: 'text', text: llmResponse }],
778
+ });
779
+ }
780
+ if (responseTools.length > 0) {
781
+ result.output.push(...responseTools);
782
+ }
783
+ // Calculate TTFT and TBT
784
+ const ttft = timestamps.length > 0 ? (timestamps[0] - startTime) / 1000 : 0;
785
+ let tbt = 0;
786
+ if (timestamps.length > 1) {
787
+ const timeDiffs = timestamps.slice(1).map((t, i) => t - timestamps[i]);
788
+ tbt = timeDiffs.reduce((a, b) => a + b, 0) / timeDiffs.length / 1000;
789
+ }
790
+ metricParams = await OpenAIWrapper._responsesCommonSetter({
791
+ args,
792
+ genAIEndpoint,
793
+ result,
794
+ span,
795
+ ttft,
796
+ tbt,
797
+ });
798
+ return result;
799
+ }
800
+ catch (e) {
801
+ helpers_1.default.handleException(span, e);
802
+ }
803
+ finally {
804
+ span.end();
805
+ if (metricParams) {
806
+ base_wrapper_1.default.recordMetrics(span, metricParams);
807
+ }
808
+ }
809
+ }
810
+ static async _responsesCommonSetter({ args, genAIEndpoint, result, span, ttft = 0, tbt = 0, }) {
811
+ const traceContent = config_1.default.traceContent;
812
+ const { input, temperature = 1.0, top_p = 1.0, max_output_tokens, reasoning, stream = false, } = args[0];
813
+ // Format input for prompt
814
+ let prompt = '';
815
+ if (typeof input === 'string') {
816
+ prompt = input;
817
+ }
818
+ else if (Array.isArray(input)) {
819
+ const formattedMessages = [];
820
+ for (const item of input) {
821
+ const role = item.role || 'user';
822
+ const content = item.content;
823
+ if (typeof content === 'string') {
824
+ formattedMessages.push(`${role}: ${content}`);
825
+ }
826
+ else if (Array.isArray(content)) {
827
+ const contentParts = content
828
+ .map((part) => {
829
+ if (part.type === 'input_text') {
830
+ return `text: ${part.text || ''}`;
831
+ }
832
+ else if (part.type === 'input_image' && part.image_url && !part.image_url.startsWith('data:')) {
833
+ return `image_url: ${part.image_url}`;
834
+ }
835
+ return '';
836
+ })
837
+ .filter(Boolean)
838
+ .join(', ');
839
+ formattedMessages.push(`${role}: ${contentParts}`);
840
+ }
841
+ }
842
+ prompt = formattedMessages.join('\n');
843
+ }
844
+ // Request Params attributes
845
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature);
846
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p);
847
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MAX_TOKENS, max_output_tokens || -1);
848
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_IS_STREAM, stream);
849
+ if (reasoning?.effort) {
850
+ span.setAttribute('gen_ai.request.reasoning_effort', reasoning.effort);
851
+ }
852
+ if (traceContent) {
853
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, prompt);
854
+ }
855
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
856
+ const model = result.model || 'gpt-4o';
857
+ const responseModel = result.model || model;
858
+ const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json);
859
+ // Calculate cost
860
+ const inputTokens = result.usage?.input_tokens || 0;
861
+ const outputTokens = result.usage?.output_tokens || 0;
862
+ const cost = helpers_1.default.getChatModelCost(model, pricingInfo, inputTokens, outputTokens);
863
+ OpenAIWrapper.setBaseSpanAttributes(span, {
864
+ genAIEndpoint,
865
+ model,
866
+ user: '',
867
+ cost,
868
+ aiSystem: OpenAIWrapper.aiSystem,
869
+ });
870
+ // Response attributes
871
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
872
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, responseModel);
873
+ span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [result.status || 'completed']);
874
+ span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_TYPE, semantic_convention_1.default.GEN_AI_OUTPUT_TYPE_TEXT);
875
+ if (result.service_tier) {
876
+ span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_SERVICE_TIER, result.service_tier);
877
+ }
878
+ // Token usage
879
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
880
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
881
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_TOTAL_TOKENS, inputTokens + outputTokens);
882
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_TOKEN_USAGE, inputTokens + outputTokens);
883
+ // Reasoning tokens
884
+ if (result.usage?.output_tokens_details?.reasoning_tokens) {
885
+ span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_REASONING_TOKENS, result.usage.output_tokens_details.reasoning_tokens);
886
+ }
887
+ // TTFT and TBT metrics
888
+ if (ttft > 0) {
889
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TTFT, ttft);
890
+ }
891
+ if (tbt > 0) {
892
+ span.setAttribute(semantic_convention_1.default.GEN_AI_SERVER_TBT, tbt);
893
+ }
894
+ // Extract completion text from output
895
+ let completionText = '';
896
+ if (result.output && Array.isArray(result.output)) {
897
+ for (const item of result.output) {
898
+ if (item.type === 'message' && item.content) {
899
+ for (const content of item.content) {
900
+ if (content.type === 'text' || content.type === 'output_text') {
901
+ completionText += content.text || '';
902
+ }
903
+ }
904
+ }
905
+ }
906
+ }
907
+ // Tool calls handling for Responses API
908
+ const toolCalls = result.tools || [];
909
+ if (toolCalls.length > 0) {
910
+ const toolNames = toolCalls.map((t) => t.name || '').filter(Boolean);
911
+ const toolIds = toolCalls.map((t) => t.call_id || '').filter(Boolean);
912
+ const toolArgs = toolCalls.map((t) => t.arguments || '').filter(Boolean);
913
+ const toolTypes = toolCalls.map((t) => t.type || '').filter(Boolean);
914
+ if (toolNames.length > 0) {
915
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, toolNames.join(', '));
916
+ }
917
+ if (toolIds.length > 0) {
918
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, toolIds.join(', '));
919
+ }
920
+ if (toolArgs.length > 0) {
921
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, toolArgs.join(', '));
922
+ }
923
+ if (toolTypes.length > 0) {
924
+ span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_TYPE, toolTypes.join(', '));
925
+ }
926
+ }
927
+ // Content
928
+ if (traceContent) {
929
+ // Set completion content - use actual text or empty string if only tool calls
930
+ span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION, completionText);
931
+ // Add events for backward compatibility
932
+ span.addEvent(semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION_EVENT, {
933
+ [semantic_convention_1.default.GEN_AI_CONTENT_COMPLETION]: completionText,
934
+ });
935
+ }
936
+ return {
937
+ genAIEndpoint,
938
+ model,
939
+ user: '',
940
+ cost,
941
+ aiSystem: OpenAIWrapper.aiSystem,
942
+ };
943
+ }
516
944
  }
517
945
  OpenAIWrapper.aiSystem = semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI;
518
946
  exports.default = OpenAIWrapper;