openlit 1.6.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.
- package/README.md +19 -1
- package/dist/helpers.js +8 -4
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/instrumentation/__tests__/anthropic-wrapper.test.d.ts +1 -0
- package/dist/instrumentation/__tests__/anthropic-wrapper.test.js +92 -0
- package/dist/instrumentation/__tests__/anthropic-wrapper.test.js.map +1 -0
- package/dist/instrumentation/__tests__/base-wrapper.test.d.ts +1 -0
- package/dist/instrumentation/__tests__/base-wrapper.test.js +175 -0
- package/dist/instrumentation/__tests__/base-wrapper.test.js.map +1 -0
- package/dist/instrumentation/__tests__/cohere-wrapper.test.d.ts +1 -0
- package/dist/instrumentation/__tests__/cohere-wrapper.test.js +131 -0
- package/dist/instrumentation/__tests__/cohere-wrapper.test.js.map +1 -0
- package/dist/instrumentation/__tests__/openai-wrapper.test.d.ts +1 -0
- package/dist/instrumentation/__tests__/openai-wrapper.test.js +181 -0
- package/dist/instrumentation/__tests__/openai-wrapper.test.js.map +1 -0
- package/dist/instrumentation/anthropic/wrapper.d.ts +7 -1
- package/dist/instrumentation/anthropic/wrapper.js +16 -1
- package/dist/instrumentation/anthropic/wrapper.js.map +1 -1
- package/dist/instrumentation/base-wrapper.d.ts +3 -2
- package/dist/instrumentation/base-wrapper.js +81 -1
- package/dist/instrumentation/base-wrapper.js.map +1 -1
- package/dist/instrumentation/cohere/wrapper.d.ts +7 -1
- package/dist/instrumentation/cohere/wrapper.js +19 -2
- package/dist/instrumentation/cohere/wrapper.js.map +1 -1
- package/dist/instrumentation/ollama/wrapper.d.ts +2 -1
- package/dist/instrumentation/ollama/wrapper.js +2 -2
- package/dist/instrumentation/ollama/wrapper.js.map +1 -1
- package/dist/instrumentation/openai/index.js +11 -0
- package/dist/instrumentation/openai/index.js.map +1 -1
- package/dist/instrumentation/openai/wrapper.d.ts +39 -3
- package/dist/instrumentation/openai/wrapper.js +537 -26
- package/dist/instrumentation/openai/wrapper.js.map +1 -1
- package/dist/otel/__tests__/metrics.test.d.ts +1 -0
- package/dist/otel/__tests__/metrics.test.js +51 -0
- package/dist/otel/__tests__/metrics.test.js.map +1 -0
- package/dist/otel/metrics.d.ts +22 -0
- package/dist/otel/metrics.js +132 -0
- package/dist/otel/metrics.js.map +1 -0
- package/dist/{tracing.d.ts → otel/tracing.d.ts} +1 -1
- package/dist/{tracing.js → otel/tracing.js} +17 -15
- package/dist/otel/tracing.js.map +1 -0
- package/dist/semantic-convention.d.ts +36 -0
- package/dist/semantic-convention.js +53 -11
- package/dist/semantic-convention.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/package.json +10 -11
- package/dist/tracing.js.map +0 -1
|
@@ -20,7 +20,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
20
20
|
})
|
|
21
21
|
.then((response) => {
|
|
22
22
|
const { stream = false } = args[0];
|
|
23
|
-
if (
|
|
23
|
+
if (stream) {
|
|
24
24
|
return helpers_1.default.createStreamProxy(response, OpenAIWrapper._chatCompletionGenerator({
|
|
25
25
|
args,
|
|
26
26
|
genAIEndpoint,
|
|
@@ -38,8 +38,9 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
static async _chatCompletion({ args, genAIEndpoint, response, span, }) {
|
|
41
|
+
let metricParams;
|
|
41
42
|
try {
|
|
42
|
-
await OpenAIWrapper._chatCompletionCommonSetter({
|
|
43
|
+
metricParams = await OpenAIWrapper._chatCompletionCommonSetter({
|
|
43
44
|
args,
|
|
44
45
|
genAIEndpoint,
|
|
45
46
|
result: response,
|
|
@@ -52,9 +53,16 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
52
53
|
}
|
|
53
54
|
finally {
|
|
54
55
|
span.end();
|
|
56
|
+
// Record metrics after span has ended if parameters are available
|
|
57
|
+
if (metricParams) {
|
|
58
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
static async *_chatCompletionGenerator({ args, genAIEndpoint, response, span, }) {
|
|
63
|
+
let metricParams;
|
|
64
|
+
const timestamps = [];
|
|
65
|
+
const startTime = Date.now();
|
|
58
66
|
try {
|
|
59
67
|
const { messages } = args[0];
|
|
60
68
|
let { tools } = args[0];
|
|
@@ -62,6 +70,8 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
62
70
|
id: '0',
|
|
63
71
|
created: -1,
|
|
64
72
|
model: '',
|
|
73
|
+
system_fingerprint: '',
|
|
74
|
+
service_tier: 'auto',
|
|
65
75
|
choices: [
|
|
66
76
|
{
|
|
67
77
|
index: 0,
|
|
@@ -74,12 +84,28 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
74
84
|
prompt_tokens: 0,
|
|
75
85
|
completion_tokens: 0,
|
|
76
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
|
+
},
|
|
77
95
|
},
|
|
78
96
|
};
|
|
97
|
+
let toolCalls = [];
|
|
79
98
|
for await (const chunk of response) {
|
|
99
|
+
timestamps.push(Date.now());
|
|
80
100
|
result.id = chunk.id;
|
|
81
101
|
result.created = chunk.created;
|
|
82
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
|
+
}
|
|
83
109
|
if (chunk.choices[0]?.finish_reason) {
|
|
84
110
|
result.choices[0].finish_reason = chunk.choices[0].finish_reason;
|
|
85
111
|
}
|
|
@@ -89,11 +115,45 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
89
115
|
if (chunk.choices[0]?.delta.content) {
|
|
90
116
|
result.choices[0].message.content += chunk.choices[0].delta.content;
|
|
91
117
|
}
|
|
118
|
+
// Improved tool calls handling for streaming
|
|
92
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
|
+
}
|
|
93
147
|
tools = true;
|
|
94
148
|
}
|
|
95
149
|
yield chunk;
|
|
96
150
|
}
|
|
151
|
+
if (toolCalls.length > 0) {
|
|
152
|
+
result.choices[0].message = {
|
|
153
|
+
...result.choices[0].message,
|
|
154
|
+
tool_calls: toolCalls
|
|
155
|
+
};
|
|
156
|
+
}
|
|
97
157
|
let promptTokens = 0;
|
|
98
158
|
for (const message of messages || []) {
|
|
99
159
|
promptTokens += helpers_1.default.openaiTokens(message.content, result.model) ?? 0;
|
|
@@ -104,14 +164,25 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
104
164
|
prompt_tokens: promptTokens,
|
|
105
165
|
completion_tokens: completionTokens,
|
|
106
166
|
total_tokens: promptTokens + completionTokens,
|
|
167
|
+
completion_tokens_details: result.usage.completion_tokens_details,
|
|
168
|
+
prompt_tokens_details: result.usage.prompt_tokens_details,
|
|
107
169
|
};
|
|
108
170
|
}
|
|
109
171
|
args[0].tools = tools;
|
|
110
|
-
|
|
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
|
+
}
|
|
179
|
+
metricParams = await OpenAIWrapper._chatCompletionCommonSetter({
|
|
111
180
|
args,
|
|
112
181
|
genAIEndpoint,
|
|
113
182
|
result,
|
|
114
183
|
span,
|
|
184
|
+
ttft,
|
|
185
|
+
tbt,
|
|
115
186
|
});
|
|
116
187
|
return result;
|
|
117
188
|
}
|
|
@@ -120,19 +191,29 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
120
191
|
}
|
|
121
192
|
finally {
|
|
122
193
|
span.end();
|
|
194
|
+
// Record metrics after span has ended if parameters are available
|
|
195
|
+
if (metricParams) {
|
|
196
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
197
|
+
}
|
|
123
198
|
}
|
|
124
199
|
}
|
|
125
|
-
static async _chatCompletionCommonSetter({ args, genAIEndpoint, result, span, }) {
|
|
200
|
+
static async _chatCompletionCommonSetter({ args, genAIEndpoint, result, span, ttft = 0, tbt = 0, }) {
|
|
126
201
|
const traceContent = config_1.default.traceContent;
|
|
127
|
-
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];
|
|
128
203
|
// Request Params attributes : Start
|
|
129
204
|
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TOP_P, top_p || 1);
|
|
130
|
-
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);
|
|
131
206
|
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_TEMPERATURE, temperature);
|
|
132
207
|
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty);
|
|
133
208
|
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty);
|
|
134
|
-
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) : '');
|
|
135
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
|
+
}
|
|
136
217
|
if (traceContent) {
|
|
137
218
|
// Format 'messages' into a single string
|
|
138
219
|
const messagePrompt = messages || [];
|
|
@@ -164,6 +245,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
164
245
|
span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT);
|
|
165
246
|
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, result.id);
|
|
166
247
|
const model = result.model || 'gpt-3.5-turbo';
|
|
248
|
+
const responseModel = result.model || model;
|
|
167
249
|
const pricingInfo = await config_1.default.updatePricingJson(config_1.default.pricing_json);
|
|
168
250
|
// Calculate cost of the operation
|
|
169
251
|
const cost = helpers_1.default.getChatModelCost(model, pricingInfo, result.usage.prompt_tokens, result.usage.completion_tokens);
|
|
@@ -174,30 +256,101 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
174
256
|
cost,
|
|
175
257
|
aiSystem: OpenAIWrapper.aiSystem,
|
|
176
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
|
|
177
269
|
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, result.usage.prompt_tokens);
|
|
178
270
|
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS, result.usage.completion_tokens);
|
|
179
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
|
|
180
299
|
if (result.choices[0].finish_reason) {
|
|
181
|
-
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]);
|
|
182
301
|
}
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
}
|
|
185
326
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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;
|
|
198
340
|
}
|
|
199
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
|
+
});
|
|
200
346
|
}
|
|
347
|
+
return {
|
|
348
|
+
genAIEndpoint,
|
|
349
|
+
model,
|
|
350
|
+
user,
|
|
351
|
+
cost,
|
|
352
|
+
aiSystem: OpenAIWrapper.aiSystem,
|
|
353
|
+
};
|
|
201
354
|
}
|
|
202
355
|
static _patchEmbedding(tracer) {
|
|
203
356
|
const genAIEndpoint = 'openai.resources.embeddings';
|
|
@@ -206,6 +359,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
206
359
|
return async function (...args) {
|
|
207
360
|
const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
|
|
208
361
|
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
|
362
|
+
let metricParams;
|
|
209
363
|
try {
|
|
210
364
|
const response = await originalMethod.apply(this, args);
|
|
211
365
|
const model = response.model || 'text-embedding-ada-002';
|
|
@@ -213,7 +367,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
213
367
|
const cost = helpers_1.default.getEmbedModelCost(model, pricingInfo, response.usage.prompt_tokens);
|
|
214
368
|
span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_EMBEDDING);
|
|
215
369
|
const { dimensions, encoding_format = 'float', input, user } = args[0];
|
|
216
|
-
// Set base span
|
|
370
|
+
// Set base span attributes
|
|
217
371
|
OpenAIWrapper.setBaseSpanAttributes(span, {
|
|
218
372
|
genAIEndpoint,
|
|
219
373
|
model,
|
|
@@ -221,22 +375,47 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
221
375
|
cost,
|
|
222
376
|
aiSystem: OpenAIWrapper.aiSystem,
|
|
223
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');
|
|
224
385
|
// Request Params attributes : Start
|
|
225
|
-
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_ENCODING_FORMATS, encoding_format);
|
|
226
|
-
|
|
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
|
+
}
|
|
227
393
|
if (traceContent) {
|
|
228
|
-
|
|
394
|
+
const formattedInput = typeof input === 'string' ? input : JSON.stringify(input);
|
|
395
|
+
span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, formattedInput);
|
|
229
396
|
}
|
|
230
397
|
// Request Params attributes : End
|
|
231
398
|
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens);
|
|
232
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);
|
|
401
|
+
metricParams = {
|
|
402
|
+
genAIEndpoint,
|
|
403
|
+
model,
|
|
404
|
+
user,
|
|
405
|
+
cost,
|
|
406
|
+
aiSystem: OpenAIWrapper.aiSystem,
|
|
407
|
+
};
|
|
233
408
|
return response;
|
|
234
409
|
}
|
|
235
410
|
catch (e) {
|
|
236
411
|
helpers_1.default.handleException(span, e);
|
|
412
|
+
throw e;
|
|
237
413
|
}
|
|
238
414
|
finally {
|
|
239
415
|
span.end();
|
|
416
|
+
if (metricParams) {
|
|
417
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
418
|
+
}
|
|
240
419
|
}
|
|
241
420
|
});
|
|
242
421
|
};
|
|
@@ -248,6 +427,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
248
427
|
return async function (...args) {
|
|
249
428
|
const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
|
|
250
429
|
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
|
430
|
+
let metricParams;
|
|
251
431
|
try {
|
|
252
432
|
const response = await originalMethod.apply(this, args);
|
|
253
433
|
const model = response.model || 'gpt-3.5-turbo';
|
|
@@ -271,6 +451,13 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
271
451
|
span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, response.id);
|
|
272
452
|
span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, response.usage.prompt_tokens);
|
|
273
453
|
span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_FINETUNE_STATUS, response.status);
|
|
454
|
+
// Store metric parameters for use after span ends
|
|
455
|
+
metricParams = {
|
|
456
|
+
genAIEndpoint,
|
|
457
|
+
model,
|
|
458
|
+
user,
|
|
459
|
+
aiSystem: OpenAIWrapper.aiSystem,
|
|
460
|
+
};
|
|
274
461
|
return response;
|
|
275
462
|
}
|
|
276
463
|
catch (e) {
|
|
@@ -278,6 +465,10 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
278
465
|
}
|
|
279
466
|
finally {
|
|
280
467
|
span.end();
|
|
468
|
+
// Record metrics after span has ended if parameters are available
|
|
469
|
+
if (metricParams) {
|
|
470
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
471
|
+
}
|
|
281
472
|
}
|
|
282
473
|
});
|
|
283
474
|
};
|
|
@@ -290,6 +481,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
290
481
|
return async function (...args) {
|
|
291
482
|
const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
|
|
292
483
|
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
|
484
|
+
let metricParams;
|
|
293
485
|
try {
|
|
294
486
|
const response = await originalMethod.apply(this, args);
|
|
295
487
|
const { prompt, quality = 'standard', response_format = 'url', size = '1024x1024', style = 'vivid', user, } = args[0];
|
|
@@ -323,6 +515,14 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
323
515
|
imagesCount++;
|
|
324
516
|
}
|
|
325
517
|
}
|
|
518
|
+
// Store metric parameters for use after span ends
|
|
519
|
+
metricParams = {
|
|
520
|
+
genAIEndpoint,
|
|
521
|
+
model,
|
|
522
|
+
user,
|
|
523
|
+
cost,
|
|
524
|
+
aiSystem: OpenAIWrapper.aiSystem,
|
|
525
|
+
};
|
|
326
526
|
return response;
|
|
327
527
|
}
|
|
328
528
|
catch (e) {
|
|
@@ -330,6 +530,10 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
330
530
|
}
|
|
331
531
|
finally {
|
|
332
532
|
span.end();
|
|
533
|
+
// Record metrics after span has ended if parameters are available
|
|
534
|
+
if (metricParams) {
|
|
535
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
536
|
+
}
|
|
333
537
|
}
|
|
334
538
|
});
|
|
335
539
|
};
|
|
@@ -342,6 +546,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
342
546
|
return async function (...args) {
|
|
343
547
|
const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
|
|
344
548
|
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
|
549
|
+
let metricParams;
|
|
345
550
|
try {
|
|
346
551
|
const response = await originalMethod.apply(this, args);
|
|
347
552
|
const { prompt, quality = 'standard', response_format = 'url', size = '1024x1024', style = 'vivid', user, } = args[0];
|
|
@@ -376,6 +581,14 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
376
581
|
imagesCount++;
|
|
377
582
|
}
|
|
378
583
|
}
|
|
584
|
+
// Store metric parameters for use after span ends
|
|
585
|
+
metricParams = {
|
|
586
|
+
genAIEndpoint,
|
|
587
|
+
model,
|
|
588
|
+
user,
|
|
589
|
+
cost,
|
|
590
|
+
aiSystem: OpenAIWrapper.aiSystem,
|
|
591
|
+
};
|
|
379
592
|
return response;
|
|
380
593
|
}
|
|
381
594
|
catch (e) {
|
|
@@ -383,6 +596,10 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
383
596
|
}
|
|
384
597
|
finally {
|
|
385
598
|
span.end();
|
|
599
|
+
// Record metrics after span has ended if parameters are available
|
|
600
|
+
if (metricParams) {
|
|
601
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
602
|
+
}
|
|
386
603
|
}
|
|
387
604
|
});
|
|
388
605
|
};
|
|
@@ -395,6 +612,7 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
395
612
|
return async function (...args) {
|
|
396
613
|
const span = tracer.startSpan(genAIEndpoint, { kind: api_1.SpanKind.CLIENT });
|
|
397
614
|
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
|
|
615
|
+
let metricParams;
|
|
398
616
|
try {
|
|
399
617
|
const response = await originalMethod.apply(this, args);
|
|
400
618
|
const { input, user, voice, response_format = 'mp3', speed = 1 } = args[0];
|
|
@@ -418,6 +636,14 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
418
636
|
span.setAttribute(semantic_convention_1.default.GEN_AI_CONTENT_PROMPT, input);
|
|
419
637
|
}
|
|
420
638
|
// Request Params attributes : End
|
|
639
|
+
// Store metric parameters for use after span ends
|
|
640
|
+
metricParams = {
|
|
641
|
+
genAIEndpoint,
|
|
642
|
+
model,
|
|
643
|
+
user,
|
|
644
|
+
cost,
|
|
645
|
+
aiSystem: OpenAIWrapper.aiSystem,
|
|
646
|
+
};
|
|
421
647
|
return response;
|
|
422
648
|
}
|
|
423
649
|
catch (e) {
|
|
@@ -425,11 +651,296 @@ class OpenAIWrapper extends base_wrapper_1.default {
|
|
|
425
651
|
}
|
|
426
652
|
finally {
|
|
427
653
|
span.end();
|
|
654
|
+
// Record metrics after span has ended if parameters are available
|
|
655
|
+
if (metricParams) {
|
|
656
|
+
base_wrapper_1.default.recordMetrics(span, metricParams);
|
|
657
|
+
}
|
|
428
658
|
}
|
|
429
659
|
});
|
|
430
660
|
};
|
|
431
661
|
};
|
|
432
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
|
+
}
|
|
433
944
|
}
|
|
434
945
|
OpenAIWrapper.aiSystem = semantic_convention_1.default.GEN_AI_SYSTEM_OPENAI;
|
|
435
946
|
exports.default = OpenAIWrapper;
|