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.
- package/README.md +15 -11
- package/dist/helpers.d.ts +10 -0
- package/dist/helpers.js +114 -7
- package/dist/helpers.js.map +1 -1
- package/dist/instrumentation/__tests__/anthropic-wrapper.test.js +1 -1
- package/dist/instrumentation/__tests__/anthropic-wrapper.test.js.map +1 -1
- package/dist/instrumentation/__tests__/base-wrapper.test.js +1 -1
- package/dist/instrumentation/__tests__/base-wrapper.test.js.map +1 -1
- package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.d.ts +4 -0
- package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.js +99 -0
- package/dist/instrumentation/__tests__/google-ai-trace-comparison.test.js.map +1 -0
- package/dist/instrumentation/__tests__/groq-trace-comparison.test.d.ts +7 -0
- package/dist/instrumentation/__tests__/groq-trace-comparison.test.js +180 -0
- package/dist/instrumentation/__tests__/groq-trace-comparison.test.js.map +1 -0
- package/dist/instrumentation/__tests__/mistral-trace-comparison.test.d.ts +4 -0
- package/dist/instrumentation/__tests__/mistral-trace-comparison.test.js +127 -0
- package/dist/instrumentation/__tests__/mistral-trace-comparison.test.js.map +1 -0
- package/dist/instrumentation/__tests__/openai-wrapper.test.js +81 -18
- package/dist/instrumentation/__tests__/openai-wrapper.test.js.map +1 -1
- package/dist/instrumentation/__tests__/together-trace-comparison.test.d.ts +4 -0
- package/dist/instrumentation/__tests__/together-trace-comparison.test.js +98 -0
- package/dist/instrumentation/__tests__/together-trace-comparison.test.js.map +1 -0
- package/dist/instrumentation/__tests__/trace-comparison-utils.d.ts +66 -0
- package/dist/instrumentation/__tests__/trace-comparison-utils.js +245 -0
- package/dist/instrumentation/__tests__/trace-comparison-utils.js.map +1 -0
- package/dist/instrumentation/anthropic/index.js +6 -4
- package/dist/instrumentation/anthropic/index.js.map +1 -1
- package/dist/instrumentation/anthropic/wrapper.js +12 -30
- package/dist/instrumentation/anthropic/wrapper.js.map +1 -1
- package/dist/instrumentation/base-wrapper.js +2 -2
- package/dist/instrumentation/base-wrapper.js.map +1 -1
- package/dist/instrumentation/cohere/wrapper.js +8 -10
- package/dist/instrumentation/cohere/wrapper.js.map +1 -1
- package/dist/instrumentation/google-ai/index.d.ts +11 -0
- package/dist/instrumentation/google-ai/index.js +48 -0
- package/dist/instrumentation/google-ai/index.js.map +1 -0
- package/dist/instrumentation/google-ai/wrapper.d.ts +34 -0
- package/dist/instrumentation/google-ai/wrapper.js +241 -0
- package/dist/instrumentation/google-ai/wrapper.js.map +1 -0
- package/dist/instrumentation/groq/index.d.ts +11 -0
- package/dist/instrumentation/groq/index.js +43 -0
- package/dist/instrumentation/groq/index.js.map +1 -0
- package/dist/instrumentation/groq/wrapper.d.ts +33 -0
- package/dist/instrumentation/groq/wrapper.js +289 -0
- package/dist/instrumentation/groq/wrapper.js.map +1 -0
- package/dist/instrumentation/index.js +10 -0
- package/dist/instrumentation/index.js.map +1 -1
- package/dist/instrumentation/mistral/index.d.ts +11 -0
- package/dist/instrumentation/mistral/index.js +66 -0
- package/dist/instrumentation/mistral/index.js.map +1 -0
- package/dist/instrumentation/mistral/wrapper.d.ts +34 -0
- package/dist/instrumentation/mistral/wrapper.js +340 -0
- package/dist/instrumentation/mistral/wrapper.js.map +1 -0
- package/dist/instrumentation/ollama/wrapper.d.ts +11 -3
- package/dist/instrumentation/ollama/wrapper.js +60 -95
- 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 +30 -1
- package/dist/instrumentation/openai/wrapper.js +419 -61
- package/dist/instrumentation/openai/wrapper.js.map +1 -1
- package/dist/instrumentation/together/index.d.ts +11 -0
- package/dist/instrumentation/together/index.js +43 -0
- package/dist/instrumentation/together/index.js.map +1 -0
- package/dist/instrumentation/together/wrapper.d.ts +33 -0
- package/dist/instrumentation/together/wrapper.js +271 -0
- package/dist/instrumentation/together/wrapper.js.map +1 -0
- package/dist/otel/__tests__/metrics.test.js +5 -5
- package/dist/otel/__tests__/metrics.test.js.map +1 -1
- package/dist/semantic-convention.d.ts +34 -3
- package/dist/semantic-convention.js +50 -14
- package/dist/semantic-convention.js.map +1 -1
- package/dist/types.d.ts +1 -1
- 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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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 (
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|