@overmind-lab/trace-sdk 0.0.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 +124 -0
- package/biome.json +74 -0
- package/package.json +28 -0
- package/src/instrumentation-openai/image-wrappers.ts +546 -0
- package/src/instrumentation-openai/index.ts +757 -0
- package/src/instrumentation-openai/types.ts +34 -0
- package/src/overmind-client.ts +95 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { type Attributes, type Span, SpanKind, type trace } from "@opentelemetry/api";
|
|
3
|
+
import {
|
|
4
|
+
ATTR_GEN_AI_COMPLETION,
|
|
5
|
+
ATTR_GEN_AI_PROMPT,
|
|
6
|
+
ATTR_GEN_AI_REQUEST_MODEL,
|
|
7
|
+
ATTR_GEN_AI_SYSTEM,
|
|
8
|
+
ATTR_GEN_AI_USAGE_COMPLETION_TOKENS,
|
|
9
|
+
ATTR_GEN_AI_USAGE_PROMPT_TOKENS,
|
|
10
|
+
} from "@opentelemetry/semantic-conventions/incubating";
|
|
11
|
+
import { SpanAttributes } from "@traceloop/ai-semantic-conventions";
|
|
12
|
+
import type {
|
|
13
|
+
ImageCreateVariationParams,
|
|
14
|
+
ImageEditParams,
|
|
15
|
+
ImageGenerateParams,
|
|
16
|
+
ImagesResponse,
|
|
17
|
+
} from "openai/resources/images";
|
|
18
|
+
import type { ImageUploadCallback } from "./types";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calculate completion tokens for image generation based on OpenAI's actual token costs
|
|
22
|
+
*
|
|
23
|
+
* Token costs based on OpenAI documentation:
|
|
24
|
+
* For gpt-image-1: Square (1024×1024) Portrait (1024×1536) Landscape (1536×1024)
|
|
25
|
+
* Low 272 tokens 408 tokens 400 tokens
|
|
26
|
+
* Medium 1056 tokens 1584 tokens 1568 tokens
|
|
27
|
+
* High 4160 tokens 6240 tokens 6208 tokens
|
|
28
|
+
*
|
|
29
|
+
* For DALL-E 3:
|
|
30
|
+
* Standard 1056 tokens 1584 tokens 1568 tokens
|
|
31
|
+
* HD 4160 tokens 6240 tokens 6208 tokens
|
|
32
|
+
*/
|
|
33
|
+
function calculateImageGenerationTokens(params: any, imageCount: number): number {
|
|
34
|
+
const size = params?.size || "1024x1024";
|
|
35
|
+
const model = params?.model || "dall-e-2";
|
|
36
|
+
const quality = params?.quality || "standard";
|
|
37
|
+
|
|
38
|
+
// Token costs for different models and sizes
|
|
39
|
+
let tokensPerImage: number;
|
|
40
|
+
|
|
41
|
+
if (model === "dall-e-2") {
|
|
42
|
+
// DALL-E 2 has fixed costs regardless of quality
|
|
43
|
+
const dalle2Costs: Record<string, number> = {
|
|
44
|
+
"256x256": 68,
|
|
45
|
+
"512x512": 272,
|
|
46
|
+
"1024x1024": 1056,
|
|
47
|
+
};
|
|
48
|
+
tokensPerImage = dalle2Costs[size] || 1056;
|
|
49
|
+
} else if (model === "dall-e-3") {
|
|
50
|
+
// DALL-E 3 costs depend on quality and size
|
|
51
|
+
const dalle3Costs: Record<string, Record<string, number>> = {
|
|
52
|
+
standard: {
|
|
53
|
+
"1024x1024": 1056,
|
|
54
|
+
"1024x1792": 1584,
|
|
55
|
+
"1792x1024": 1568,
|
|
56
|
+
},
|
|
57
|
+
hd: {
|
|
58
|
+
"1024x1024": 4160,
|
|
59
|
+
"1024x1792": 6240,
|
|
60
|
+
"1792x1024": 6208,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
tokensPerImage = dalle3Costs[quality]?.[size] || dalle3Costs["standard"]["1024x1024"];
|
|
64
|
+
} else {
|
|
65
|
+
// Default fallback for unknown models
|
|
66
|
+
tokensPerImage = 1056;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return tokensPerImage * imageCount;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function processImageInRequest(
|
|
73
|
+
image: any,
|
|
74
|
+
traceId: string,
|
|
75
|
+
spanId: string,
|
|
76
|
+
uploadCallback: ImageUploadCallback,
|
|
77
|
+
index = 0
|
|
78
|
+
): Promise<string | null> {
|
|
79
|
+
try {
|
|
80
|
+
let base64Data: string;
|
|
81
|
+
let filename: string;
|
|
82
|
+
|
|
83
|
+
if (typeof image === "string") {
|
|
84
|
+
// Could be a file path, base64 string, or URL
|
|
85
|
+
if (image.startsWith("data:image/")) {
|
|
86
|
+
const commaIndex = image.indexOf(",");
|
|
87
|
+
base64Data = image.substring(commaIndex + 1);
|
|
88
|
+
filename = `input_image_${index}.png`;
|
|
89
|
+
} else if (image.startsWith("http")) {
|
|
90
|
+
return null;
|
|
91
|
+
} else {
|
|
92
|
+
base64Data = image;
|
|
93
|
+
filename = `input_image_${index}.png`;
|
|
94
|
+
}
|
|
95
|
+
} else if (image && typeof image === "object") {
|
|
96
|
+
// Handle Node.js Buffer objects and ReadStream
|
|
97
|
+
if (Buffer.isBuffer(image)) {
|
|
98
|
+
base64Data = image.toString("base64");
|
|
99
|
+
filename = `input_image_${index}.png`;
|
|
100
|
+
} else if (image.read && typeof image.read === "function") {
|
|
101
|
+
const chunks: Buffer[] = [];
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
image.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
104
|
+
image.on("end", async () => {
|
|
105
|
+
try {
|
|
106
|
+
const buffer = Buffer.concat(chunks);
|
|
107
|
+
const base64Data = buffer.toString("base64");
|
|
108
|
+
const filename = image.path || `input_image_${index}.png`;
|
|
109
|
+
const url = await uploadCallback(traceId, spanId, filename, base64Data);
|
|
110
|
+
resolve(url);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("Error processing stream image:", error);
|
|
113
|
+
resolve(null);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
image.on("error", (error: Error) => {
|
|
117
|
+
console.error("Error reading image stream:", error);
|
|
118
|
+
resolve(null);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const url = await uploadCallback(traceId, spanId, filename, base64Data);
|
|
129
|
+
return url;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Error processing image in request:", error);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function setImageGenerationRequestAttributes(span: Span, params: ImageGenerateParams): void {
|
|
137
|
+
const attributes: Attributes = {};
|
|
138
|
+
|
|
139
|
+
if (params.model) {
|
|
140
|
+
attributes[ATTR_GEN_AI_REQUEST_MODEL] = params.model;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (params.size) {
|
|
144
|
+
attributes["gen_ai.request.image.size"] = params.size;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (params.quality) {
|
|
148
|
+
attributes["gen_ai.request.image.quality"] = params.quality;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (params.style) {
|
|
152
|
+
attributes["gen_ai.request.image.style"] = params.style;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (params.n) {
|
|
156
|
+
attributes["gen_ai.request.image.count"] = params.n;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (params.prompt) {
|
|
160
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.0.content`] = params.prompt;
|
|
161
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.0.role`] = "user";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
165
|
+
if (value !== undefined) {
|
|
166
|
+
span.setAttribute(key, value);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function setImageEditRequestAttributes(
|
|
172
|
+
span: Span,
|
|
173
|
+
params: ImageEditParams,
|
|
174
|
+
uploadCallback?: ImageUploadCallback
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
const attributes: Attributes = {};
|
|
177
|
+
|
|
178
|
+
if (params.model) {
|
|
179
|
+
attributes[ATTR_GEN_AI_REQUEST_MODEL] = params.model;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (params.size) {
|
|
183
|
+
attributes["gen_ai.request.image.size"] = params.size;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (params.n) {
|
|
187
|
+
attributes["gen_ai.request.image.count"] = params.n;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (params.prompt) {
|
|
191
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.0.content`] = params.prompt;
|
|
192
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.0.role`] = "user";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Process input image if upload callback is available
|
|
196
|
+
if (params.image && uploadCallback && span.spanContext().traceId && span.spanContext().spanId) {
|
|
197
|
+
const traceId = span.spanContext().traceId;
|
|
198
|
+
const spanId = span.spanContext().spanId;
|
|
199
|
+
|
|
200
|
+
const imageUrl = await processImageInRequest(params.image, traceId, spanId, uploadCallback, 0);
|
|
201
|
+
|
|
202
|
+
if (imageUrl) {
|
|
203
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.1.content`] = JSON.stringify([
|
|
204
|
+
{ type: "image_url", image_url: { url: imageUrl } },
|
|
205
|
+
]);
|
|
206
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.1.role`] = "user";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
211
|
+
if (value !== undefined) {
|
|
212
|
+
span.setAttribute(key, value);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function setImageVariationRequestAttributes(
|
|
218
|
+
span: Span,
|
|
219
|
+
params: ImageCreateVariationParams,
|
|
220
|
+
uploadCallback?: ImageUploadCallback
|
|
221
|
+
): Promise<void> {
|
|
222
|
+
const attributes: Attributes = {};
|
|
223
|
+
|
|
224
|
+
if (params.model) {
|
|
225
|
+
attributes[ATTR_GEN_AI_REQUEST_MODEL] = params.model;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (params.size) {
|
|
229
|
+
attributes["gen_ai.request.image.size"] = params.size;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (params.n) {
|
|
233
|
+
attributes["gen_ai.request.image.count"] = params.n;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Process input image if upload callback is available
|
|
237
|
+
if (params.image && uploadCallback && span.spanContext().traceId && span.spanContext().spanId) {
|
|
238
|
+
const traceId = span.spanContext().traceId;
|
|
239
|
+
const spanId = span.spanContext().spanId;
|
|
240
|
+
|
|
241
|
+
const imageUrl = await processImageInRequest(params.image, traceId, spanId, uploadCallback, 0);
|
|
242
|
+
|
|
243
|
+
if (imageUrl) {
|
|
244
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.0.content`] = JSON.stringify([
|
|
245
|
+
{ type: "image_url", image_url: { url: imageUrl } },
|
|
246
|
+
]);
|
|
247
|
+
attributes[`${ATTR_GEN_AI_PROMPT}.0.role`] = "user";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
252
|
+
if (value !== undefined) {
|
|
253
|
+
span.setAttribute(key, value);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function setImageGenerationResponseAttributes(
|
|
259
|
+
span: Span,
|
|
260
|
+
response: ImagesResponse,
|
|
261
|
+
uploadCallback?: ImageUploadCallback,
|
|
262
|
+
instrumentationConfig?: { enrichTokens?: boolean },
|
|
263
|
+
params?: any
|
|
264
|
+
): Promise<void> {
|
|
265
|
+
const attributes: Attributes = {};
|
|
266
|
+
|
|
267
|
+
if (response.data && response.data.length > 0) {
|
|
268
|
+
const completionTokens = calculateImageGenerationTokens(params, response.data.length);
|
|
269
|
+
attributes[ATTR_GEN_AI_USAGE_COMPLETION_TOKENS] = completionTokens;
|
|
270
|
+
|
|
271
|
+
// Calculate prompt tokens if enrichTokens is enabled
|
|
272
|
+
if (instrumentationConfig?.enrichTokens) {
|
|
273
|
+
try {
|
|
274
|
+
let estimatedPromptTokens = 0;
|
|
275
|
+
|
|
276
|
+
if (params?.prompt) {
|
|
277
|
+
estimatedPromptTokens += Math.ceil(params.prompt.length / 4);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (params?.image) {
|
|
281
|
+
estimatedPromptTokens += 272;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (estimatedPromptTokens > 0) {
|
|
285
|
+
attributes[ATTR_GEN_AI_USAGE_PROMPT_TOKENS] = estimatedPromptTokens;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
attributes[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] =
|
|
289
|
+
estimatedPromptTokens + completionTokens;
|
|
290
|
+
} catch {
|
|
291
|
+
attributes[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] = completionTokens;
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
attributes[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] = completionTokens;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (response.data && response.data.length > 0) {
|
|
299
|
+
const firstImage = response.data[0];
|
|
300
|
+
|
|
301
|
+
if (firstImage.b64_json && uploadCallback) {
|
|
302
|
+
try {
|
|
303
|
+
const traceId = span.spanContext().traceId;
|
|
304
|
+
const spanId = span.spanContext().spanId;
|
|
305
|
+
|
|
306
|
+
const imageUrl = await uploadCallback(
|
|
307
|
+
traceId,
|
|
308
|
+
spanId,
|
|
309
|
+
"generated_image.png",
|
|
310
|
+
firstImage.b64_json
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.content`] = JSON.stringify([
|
|
314
|
+
{ type: "image_url", image_url: { url: imageUrl } },
|
|
315
|
+
]);
|
|
316
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.role`] = "assistant";
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error("Failed to upload generated image:", error);
|
|
319
|
+
}
|
|
320
|
+
} else if (firstImage.url && uploadCallback) {
|
|
321
|
+
try {
|
|
322
|
+
const traceId = span.spanContext().traceId;
|
|
323
|
+
const spanId = span.spanContext().spanId;
|
|
324
|
+
|
|
325
|
+
const response = await fetch(firstImage.url);
|
|
326
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
327
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
328
|
+
const base64Data = buffer.toString("base64");
|
|
329
|
+
|
|
330
|
+
const uploadedUrl = await uploadCallback(
|
|
331
|
+
traceId,
|
|
332
|
+
spanId,
|
|
333
|
+
"generated_image.png",
|
|
334
|
+
base64Data
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.content`] = JSON.stringify([
|
|
338
|
+
{ type: "image_url", image_url: { url: uploadedUrl } },
|
|
339
|
+
]);
|
|
340
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.role`] = "assistant";
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error("Failed to fetch and upload generated image:", error);
|
|
343
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.content`] = JSON.stringify([
|
|
344
|
+
{ type: "image_url", image_url: { url: firstImage.url } },
|
|
345
|
+
]);
|
|
346
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.role`] = "assistant";
|
|
347
|
+
}
|
|
348
|
+
} else if (firstImage.url) {
|
|
349
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.content`] = JSON.stringify([
|
|
350
|
+
{ type: "image_url", image_url: { url: firstImage.url } },
|
|
351
|
+
]);
|
|
352
|
+
attributes[`${ATTR_GEN_AI_COMPLETION}.0.role`] = "assistant";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (firstImage.revised_prompt) {
|
|
356
|
+
attributes["gen_ai.response.revised_prompt"] = firstImage.revised_prompt;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
361
|
+
if (value !== undefined) {
|
|
362
|
+
span.setAttribute(key, value);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function wrapImageGeneration(
|
|
368
|
+
tracer: ReturnType<typeof trace.getTracer>,
|
|
369
|
+
uploadCallback?: ImageUploadCallback,
|
|
370
|
+
instrumentationConfig?: { enrichTokens?: boolean }
|
|
371
|
+
) {
|
|
372
|
+
return (original: (...args: any[]) => any) =>
|
|
373
|
+
function (this: any, ...args: any[]) {
|
|
374
|
+
const params = args[0] as ImageGenerateParams;
|
|
375
|
+
|
|
376
|
+
const span = tracer.startSpan("openai.images.generate", {
|
|
377
|
+
kind: SpanKind.CLIENT,
|
|
378
|
+
attributes: {
|
|
379
|
+
[ATTR_GEN_AI_SYSTEM]: "OpenAI",
|
|
380
|
+
"gen_ai.request.type": "image_generation",
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const response = original.apply(this, args);
|
|
385
|
+
|
|
386
|
+
if (response && typeof response.then === "function") {
|
|
387
|
+
return response
|
|
388
|
+
.then(async (result: any) => {
|
|
389
|
+
try {
|
|
390
|
+
setImageGenerationRequestAttributes(span, params);
|
|
391
|
+
await setImageGenerationResponseAttributes(
|
|
392
|
+
span,
|
|
393
|
+
result,
|
|
394
|
+
uploadCallback,
|
|
395
|
+
instrumentationConfig,
|
|
396
|
+
params
|
|
397
|
+
);
|
|
398
|
+
return result;
|
|
399
|
+
} catch (error) {
|
|
400
|
+
span.recordException(error as Error);
|
|
401
|
+
throw error;
|
|
402
|
+
} finally {
|
|
403
|
+
span.end();
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
.catch((error: Error) => {
|
|
407
|
+
span.recordException(error);
|
|
408
|
+
span.end();
|
|
409
|
+
throw error;
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
try {
|
|
413
|
+
setImageGenerationRequestAttributes(span, params);
|
|
414
|
+
return response;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
span.recordException(error as Error);
|
|
417
|
+
throw error;
|
|
418
|
+
} finally {
|
|
419
|
+
span.end();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function wrapImageEdit(
|
|
426
|
+
tracer: ReturnType<typeof trace.getTracer>,
|
|
427
|
+
uploadCallback?: ImageUploadCallback,
|
|
428
|
+
instrumentationConfig?: { enrichTokens?: boolean }
|
|
429
|
+
) {
|
|
430
|
+
return (original: (...args: any[]) => any) =>
|
|
431
|
+
function (this: any, ...args: any[]) {
|
|
432
|
+
const params = args[0] as ImageEditParams;
|
|
433
|
+
|
|
434
|
+
const span = tracer.startSpan("openai.images.edit", {
|
|
435
|
+
kind: SpanKind.CLIENT,
|
|
436
|
+
attributes: {
|
|
437
|
+
[ATTR_GEN_AI_SYSTEM]: "OpenAI",
|
|
438
|
+
"gen_ai.request.type": "image_edit",
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const setRequestAttributesPromise = setImageEditRequestAttributes(
|
|
443
|
+
span,
|
|
444
|
+
params,
|
|
445
|
+
uploadCallback
|
|
446
|
+
).catch((error) => {
|
|
447
|
+
console.error("Error setting image edit request attributes:", error);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const response = original.apply(this, args);
|
|
451
|
+
|
|
452
|
+
if (response && typeof response.then === "function") {
|
|
453
|
+
return response
|
|
454
|
+
.then(async (result: any) => {
|
|
455
|
+
try {
|
|
456
|
+
await setRequestAttributesPromise;
|
|
457
|
+
await setImageGenerationResponseAttributes(
|
|
458
|
+
span,
|
|
459
|
+
result,
|
|
460
|
+
uploadCallback,
|
|
461
|
+
instrumentationConfig,
|
|
462
|
+
params
|
|
463
|
+
);
|
|
464
|
+
return result;
|
|
465
|
+
} catch (error) {
|
|
466
|
+
span.recordException(error as Error);
|
|
467
|
+
throw error;
|
|
468
|
+
} finally {
|
|
469
|
+
span.end();
|
|
470
|
+
}
|
|
471
|
+
})
|
|
472
|
+
.catch(async (error: Error) => {
|
|
473
|
+
await setRequestAttributesPromise;
|
|
474
|
+
span.recordException(error);
|
|
475
|
+
span.end();
|
|
476
|
+
throw error;
|
|
477
|
+
});
|
|
478
|
+
} else {
|
|
479
|
+
try {
|
|
480
|
+
return response;
|
|
481
|
+
} catch (error) {
|
|
482
|
+
span.recordException(error as Error);
|
|
483
|
+
throw error;
|
|
484
|
+
} finally {
|
|
485
|
+
span.end();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function wrapImageVariation(
|
|
492
|
+
tracer: ReturnType<typeof trace.getTracer>,
|
|
493
|
+
uploadCallback?: ImageUploadCallback,
|
|
494
|
+
instrumentationConfig?: { enrichTokens?: boolean }
|
|
495
|
+
) {
|
|
496
|
+
return (original: (...args: any[]) => any) =>
|
|
497
|
+
function (this: any, ...args: any[]) {
|
|
498
|
+
const params = args[0] as ImageCreateVariationParams;
|
|
499
|
+
|
|
500
|
+
const span = tracer.startSpan("openai.images.createVariation", {
|
|
501
|
+
kind: SpanKind.CLIENT,
|
|
502
|
+
attributes: {
|
|
503
|
+
[ATTR_GEN_AI_SYSTEM]: "OpenAI",
|
|
504
|
+
"gen_ai.request.type": "image_variation",
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const response = original.apply(this, args);
|
|
509
|
+
|
|
510
|
+
if (response && typeof response.then === "function") {
|
|
511
|
+
return response
|
|
512
|
+
.then(async (result: any) => {
|
|
513
|
+
try {
|
|
514
|
+
await setImageVariationRequestAttributes(span, params, uploadCallback);
|
|
515
|
+
await setImageGenerationResponseAttributes(
|
|
516
|
+
span,
|
|
517
|
+
result,
|
|
518
|
+
uploadCallback,
|
|
519
|
+
instrumentationConfig,
|
|
520
|
+
params
|
|
521
|
+
);
|
|
522
|
+
return result;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
span.recordException(error as Error);
|
|
525
|
+
throw error;
|
|
526
|
+
} finally {
|
|
527
|
+
span.end();
|
|
528
|
+
}
|
|
529
|
+
})
|
|
530
|
+
.catch((error: Error) => {
|
|
531
|
+
span.recordException(error);
|
|
532
|
+
span.end();
|
|
533
|
+
throw error;
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
try {
|
|
537
|
+
return response;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
span.recordException(error as Error);
|
|
540
|
+
throw error;
|
|
541
|
+
} finally {
|
|
542
|
+
span.end();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|