@arizeai/openinference-instrumentation-openai 2.2.0 → 2.3.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 +11 -0
- package/dist/esm/instrumentation.d.ts.map +1 -1
- package/dist/esm/instrumentation.js +80 -1
- package/dist/esm/instrumentation.js.map +1 -1
- package/dist/esm/responsesAttributes.d.ts +8 -0
- package/dist/esm/responsesAttributes.d.ts.map +1 -0
- package/dist/esm/responsesAttributes.js +222 -0
- package/dist/esm/responsesAttributes.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esnext/instrumentation.d.ts.map +1 -1
- package/dist/esnext/instrumentation.js +65 -1
- package/dist/esnext/instrumentation.js.map +1 -1
- package/dist/esnext/responsesAttributes.d.ts +8 -0
- package/dist/esnext/responsesAttributes.d.ts.map +1 -0
- package/dist/esnext/responsesAttributes.js +243 -0
- package/dist/esnext/responsesAttributes.js.map +1 -0
- package/dist/esnext/tsconfig.esnext.tsbuildinfo +1 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/src/instrumentation.d.ts.map +1 -1
- package/dist/src/instrumentation.js +65 -1
- package/dist/src/instrumentation.js.map +1 -1
- package/dist/src/responsesAttributes.d.ts +8 -0
- package/dist/src/responsesAttributes.d.ts.map +1 -0
- package/dist/src/responsesAttributes.js +261 -0
- package/dist/src/responsesAttributes.js.map +1 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/instrumentation.ts +116 -2
- package/src/responsesAttributes.ts +286 -0
- package/src/version.ts +1 -1
package/src/instrumentation.ts
CHANGED
|
@@ -45,6 +45,18 @@ import {
|
|
|
45
45
|
safelyJSONStringify,
|
|
46
46
|
TraceConfigOptions,
|
|
47
47
|
} from "@arizeai/openinference-core";
|
|
48
|
+
import {
|
|
49
|
+
ResponseCreateParamsBase,
|
|
50
|
+
ResponseStreamEvent,
|
|
51
|
+
Response as ResponseType,
|
|
52
|
+
} from "openai/resources/responses/responses";
|
|
53
|
+
|
|
54
|
+
import {
|
|
55
|
+
consumeResponseStreamEvents,
|
|
56
|
+
getResponsesInputMessagesAttributes,
|
|
57
|
+
getResponsesOutputMessagesAttributes,
|
|
58
|
+
getResponsesUsageAttributes,
|
|
59
|
+
} from "./responsesAttributes";
|
|
48
60
|
|
|
49
61
|
const MODULE_NAME = "openai";
|
|
50
62
|
|
|
@@ -141,10 +153,10 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
|
|
|
141
153
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
142
154
|
const instrumentation: OpenAIInstrumentation = this;
|
|
143
155
|
|
|
156
|
+
// Patch create chat completions
|
|
144
157
|
type ChatCompletionCreateType =
|
|
145
158
|
typeof module.OpenAI.Chat.Completions.prototype.create;
|
|
146
159
|
|
|
147
|
-
// Patch create chat completions
|
|
148
160
|
this._wrap(
|
|
149
161
|
module.OpenAI.Chat.Completions.prototype,
|
|
150
162
|
"create",
|
|
@@ -367,6 +379,102 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
|
|
|
367
379
|
},
|
|
368
380
|
);
|
|
369
381
|
|
|
382
|
+
// Patch responses (if the patched module contains the Responses interface)
|
|
383
|
+
if (module.OpenAI.Responses) {
|
|
384
|
+
type ResponsesCreateType =
|
|
385
|
+
typeof module.OpenAI.Responses.prototype.create;
|
|
386
|
+
|
|
387
|
+
this._wrap(
|
|
388
|
+
module.OpenAI.Responses.prototype,
|
|
389
|
+
"create",
|
|
390
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
391
|
+
(original: ResponsesCreateType): any => {
|
|
392
|
+
return function patchedCreate(
|
|
393
|
+
this: unknown,
|
|
394
|
+
...args: Parameters<ResponsesCreateType>
|
|
395
|
+
) {
|
|
396
|
+
const body = args[0];
|
|
397
|
+
const { input: _messages, ...invocationParameters } = body;
|
|
398
|
+
const span = instrumentation.oiTracer.startSpan(
|
|
399
|
+
`OpenAI Responses`,
|
|
400
|
+
{
|
|
401
|
+
kind: SpanKind.INTERNAL,
|
|
402
|
+
attributes: {
|
|
403
|
+
[SemanticConventions.OPENINFERENCE_SPAN_KIND]:
|
|
404
|
+
OpenInferenceSpanKind.LLM,
|
|
405
|
+
[SemanticConventions.LLM_MODEL_NAME]: body.model,
|
|
406
|
+
[SemanticConventions.INPUT_VALUE]: JSON.stringify(body),
|
|
407
|
+
[SemanticConventions.INPUT_MIME_TYPE]: MimeType.JSON,
|
|
408
|
+
[SemanticConventions.LLM_INVOCATION_PARAMETERS]:
|
|
409
|
+
JSON.stringify(invocationParameters),
|
|
410
|
+
[SemanticConventions.LLM_SYSTEM]: LLMSystem.OPENAI,
|
|
411
|
+
[SemanticConventions.LLM_PROVIDER]: LLMProvider.OPENAI,
|
|
412
|
+
...getResponsesInputMessagesAttributes(body),
|
|
413
|
+
...getLLMToolsJSONSchema(body),
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
const execContext = getExecContext(span);
|
|
418
|
+
const execPromise = safeExecuteInTheMiddle<
|
|
419
|
+
ReturnType<ResponsesCreateType>
|
|
420
|
+
>(
|
|
421
|
+
() => {
|
|
422
|
+
return context.with(trace.setSpan(execContext, span), () => {
|
|
423
|
+
return original.apply(this, args);
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
(error) => {
|
|
427
|
+
// Push the error to the span
|
|
428
|
+
if (error) {
|
|
429
|
+
span.recordException(error);
|
|
430
|
+
span.setStatus({
|
|
431
|
+
code: SpanStatusCode.ERROR,
|
|
432
|
+
message: error.message,
|
|
433
|
+
});
|
|
434
|
+
span.end();
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
);
|
|
438
|
+
const wrappedPromise = execPromise.then((result) => {
|
|
439
|
+
const recordSpan = (result?: ResponseType) => {
|
|
440
|
+
if (!result) {
|
|
441
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
442
|
+
span.end();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
span.setAttributes({
|
|
446
|
+
[SemanticConventions.OUTPUT_VALUE]: JSON.stringify(result),
|
|
447
|
+
[SemanticConventions.OUTPUT_MIME_TYPE]: MimeType.JSON,
|
|
448
|
+
// Override the model from the value sent by the server
|
|
449
|
+
[SemanticConventions.LLM_MODEL_NAME]: result.model,
|
|
450
|
+
...getResponsesOutputMessagesAttributes(result),
|
|
451
|
+
...getResponsesUsageAttributes(result),
|
|
452
|
+
});
|
|
453
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
454
|
+
span.end();
|
|
455
|
+
};
|
|
456
|
+
if (isResponseCreateResponse(result)) {
|
|
457
|
+
// Record the results, as we have the final result
|
|
458
|
+
recordSpan(result);
|
|
459
|
+
} else {
|
|
460
|
+
// This is a streaming response
|
|
461
|
+
// First split the stream via tee
|
|
462
|
+
const [leftStream, rightStream] = result.tee();
|
|
463
|
+
// take the right stream, consuming it and then recording the final chunk
|
|
464
|
+
// into the span
|
|
465
|
+
consumeResponseStreamEvents(rightStream).then(recordSpan);
|
|
466
|
+
// give the left stream back to the caller
|
|
467
|
+
result = leftStream;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return result;
|
|
471
|
+
});
|
|
472
|
+
return context.bind(execContext, wrappedPromise);
|
|
473
|
+
};
|
|
474
|
+
},
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
370
478
|
_isOpenInferencePatched = true;
|
|
371
479
|
try {
|
|
372
480
|
// This can fail if the module is made immutable via the runtime or bundler
|
|
@@ -399,6 +507,12 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
|
|
|
399
507
|
}
|
|
400
508
|
}
|
|
401
509
|
|
|
510
|
+
function isResponseCreateResponse(
|
|
511
|
+
response: Stream<ResponseStreamEvent> | ResponseType,
|
|
512
|
+
): response is ResponseType {
|
|
513
|
+
return "object" in response && response.object === "response";
|
|
514
|
+
}
|
|
515
|
+
|
|
402
516
|
/**
|
|
403
517
|
* type-guard that checks if the response is a chat completion response
|
|
404
518
|
*/
|
|
@@ -449,7 +563,7 @@ function getLLMInputMessagesAttributes(
|
|
|
449
563
|
* Converts each tool definition into a json schema
|
|
450
564
|
*/
|
|
451
565
|
function getLLMToolsJSONSchema(
|
|
452
|
-
body: ChatCompletionCreateParamsBase,
|
|
566
|
+
body: ChatCompletionCreateParamsBase | ResponseCreateParamsBase,
|
|
453
567
|
): Attributes {
|
|
454
568
|
if (!body.tools) {
|
|
455
569
|
// If tools is undefined, return an empty object
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { SemanticConventions } from "@arizeai/openinference-semantic-conventions";
|
|
2
|
+
import { Attributes } from "@opentelemetry/api";
|
|
3
|
+
import {
|
|
4
|
+
ResponseCreateParamsBase,
|
|
5
|
+
ResponseInputItem,
|
|
6
|
+
ResponseOutputItem,
|
|
7
|
+
ResponseStreamEvent,
|
|
8
|
+
Response as ResponseType,
|
|
9
|
+
} from "openai/resources/responses/responses";
|
|
10
|
+
import { Stream } from "openai/streaming";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get attributes for responses api Items that are not typical messages with role
|
|
14
|
+
* @param item - The item to get attributes for
|
|
15
|
+
* @param prefix - The prefix to use for the attributes
|
|
16
|
+
* @returns The attributes for the item
|
|
17
|
+
*/
|
|
18
|
+
function getResponseItemAttributes(
|
|
19
|
+
item: Exclude<ResponseInputItem | ResponseOutputItem, { role: string }>,
|
|
20
|
+
prefix = "",
|
|
21
|
+
): Attributes {
|
|
22
|
+
const attributes: Attributes = {};
|
|
23
|
+
// all items that are not typical messages with role
|
|
24
|
+
// things like images, files, etc.
|
|
25
|
+
const toolCallPrefix = `${prefix}${SemanticConventions.MESSAGE_TOOL_CALLS}.0.`;
|
|
26
|
+
|
|
27
|
+
switch (item.type) {
|
|
28
|
+
case "function_call": {
|
|
29
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "assistant";
|
|
30
|
+
// now using tool call prefix to simulate multiple tool calls per message
|
|
31
|
+
attributes[`${toolCallPrefix}${SemanticConventions.TOOL_CALL_ID}`] =
|
|
32
|
+
item.call_id;
|
|
33
|
+
attributes[
|
|
34
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_NAME}`
|
|
35
|
+
] = item.name;
|
|
36
|
+
attributes[
|
|
37
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_ARGUMENTS_JSON}`
|
|
38
|
+
] = item.arguments;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case "function_call_output": {
|
|
42
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "tool";
|
|
43
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_TOOL_CALL_ID}`] =
|
|
44
|
+
item.call_id;
|
|
45
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_CONTENT}`] =
|
|
46
|
+
item.output;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "reasoning": {
|
|
50
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "assistant";
|
|
51
|
+
item.summary.forEach((summaryItem, index) => {
|
|
52
|
+
const summaryItemPrefix = `${prefix}${SemanticConventions.MESSAGE_CONTENTS}.${index}.`;
|
|
53
|
+
if (summaryItem.type === "summary_text") {
|
|
54
|
+
attributes[
|
|
55
|
+
`${summaryItemPrefix}${SemanticConventions.MESSAGE_CONTENT_TYPE}`
|
|
56
|
+
] = "summary_text";
|
|
57
|
+
attributes[
|
|
58
|
+
`${summaryItemPrefix}${SemanticConventions.MESSAGE_CONTENT_TEXT}`
|
|
59
|
+
] = summaryItem.text;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "item_reference": {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "file_search_call": {
|
|
68
|
+
if (!item.results) {
|
|
69
|
+
// its a tool call
|
|
70
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] =
|
|
71
|
+
"assistant";
|
|
72
|
+
// now using tool call prefix to simulate multiple tool calls per message
|
|
73
|
+
attributes[`${toolCallPrefix}${SemanticConventions.TOOL_CALL_ID}`] =
|
|
74
|
+
item.id;
|
|
75
|
+
attributes[
|
|
76
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_NAME}`
|
|
77
|
+
] = item.type;
|
|
78
|
+
attributes[
|
|
79
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_ARGUMENTS_JSON}`
|
|
80
|
+
] = JSON.stringify(item.queries);
|
|
81
|
+
} else {
|
|
82
|
+
// its a tool call output
|
|
83
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "tool";
|
|
84
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_TOOL_CALL_ID}`] =
|
|
85
|
+
item.id;
|
|
86
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_CONTENT}`] =
|
|
87
|
+
JSON.stringify(item.results);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "computer_call": {
|
|
92
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "assistant";
|
|
93
|
+
// now using tool call prefix to simulate multiple tool calls per message
|
|
94
|
+
attributes[`${toolCallPrefix}${SemanticConventions.TOOL_CALL_ID}`] =
|
|
95
|
+
item.id;
|
|
96
|
+
attributes[
|
|
97
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_NAME}`
|
|
98
|
+
] = item.type;
|
|
99
|
+
attributes[
|
|
100
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_ARGUMENTS_JSON}`
|
|
101
|
+
] = JSON.stringify(item.action);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "computer_call_output": {
|
|
105
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "tool";
|
|
106
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_TOOL_CALL_ID}`] =
|
|
107
|
+
item.call_id;
|
|
108
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_CONTENT}`] =
|
|
109
|
+
JSON.stringify(item.output);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "web_search_call": {
|
|
113
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_ROLE}`] = "assistant";
|
|
114
|
+
// now using tool call prefix to simulate multiple tool calls per message
|
|
115
|
+
attributes[`${toolCallPrefix}${SemanticConventions.TOOL_CALL_ID}`] =
|
|
116
|
+
item.id;
|
|
117
|
+
attributes[
|
|
118
|
+
`${toolCallPrefix}${SemanticConventions.TOOL_CALL_FUNCTION_NAME}`
|
|
119
|
+
] = item.type;
|
|
120
|
+
// web search call does not share its arguments with the caller
|
|
121
|
+
// it will show "undefined" in the arguments when traced
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return attributes;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get attributes for responses api Item, input or output
|
|
130
|
+
* Non message items are detected and handled by {@link getResponseItemAttributes}
|
|
131
|
+
* @param itemMessage - The message item to get attributes for
|
|
132
|
+
* @param prefix - The prefix to use for the attributes
|
|
133
|
+
* @returns The attributes for the message item
|
|
134
|
+
*/
|
|
135
|
+
function getResponseItemMessageAttributes(
|
|
136
|
+
itemMessage: ResponseInputItem | ResponseOutputItem,
|
|
137
|
+
prefix = "",
|
|
138
|
+
): Attributes {
|
|
139
|
+
const message =
|
|
140
|
+
typeof itemMessage === "string"
|
|
141
|
+
? ({ content: itemMessage, role: "user" } satisfies ResponseInputItem)
|
|
142
|
+
: itemMessage;
|
|
143
|
+
if (!("role" in message)) {
|
|
144
|
+
return getResponseItemAttributes(message, prefix);
|
|
145
|
+
}
|
|
146
|
+
const role = message.role;
|
|
147
|
+
const attributes: Attributes = {
|
|
148
|
+
[`${prefix}${SemanticConventions.MESSAGE_ROLE}`]: role,
|
|
149
|
+
};
|
|
150
|
+
// add contents from message
|
|
151
|
+
if (typeof message.content === "string") {
|
|
152
|
+
attributes[`${prefix}${SemanticConventions.MESSAGE_CONTENT}`] =
|
|
153
|
+
message.content;
|
|
154
|
+
} else if (Array.isArray(message.content)) {
|
|
155
|
+
message.content.forEach((part, index) => {
|
|
156
|
+
const contentsIndexPrefix = `${prefix}${SemanticConventions.MESSAGE_CONTENTS}.${index}.`;
|
|
157
|
+
if (part.type === "input_text") {
|
|
158
|
+
attributes[
|
|
159
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TYPE}`
|
|
160
|
+
] = "input_text";
|
|
161
|
+
attributes[
|
|
162
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TEXT}`
|
|
163
|
+
] = part.text;
|
|
164
|
+
} else if (part.type === "output_text") {
|
|
165
|
+
attributes[
|
|
166
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TYPE}`
|
|
167
|
+
] = "output_text";
|
|
168
|
+
attributes[
|
|
169
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TEXT}`
|
|
170
|
+
] = part.text;
|
|
171
|
+
} else if (part.type === "input_image") {
|
|
172
|
+
attributes[
|
|
173
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TYPE}`
|
|
174
|
+
] = "input_image";
|
|
175
|
+
if (part.image_url) {
|
|
176
|
+
attributes[
|
|
177
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_IMAGE}.${SemanticConventions.IMAGE_URL}`
|
|
178
|
+
] = part.image_url;
|
|
179
|
+
}
|
|
180
|
+
} else if (part.type === "input_file") {
|
|
181
|
+
// TODO: Handle input file
|
|
182
|
+
} else if (part.type === "refusal") {
|
|
183
|
+
attributes[
|
|
184
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TYPE}`
|
|
185
|
+
] = "refusal";
|
|
186
|
+
attributes[
|
|
187
|
+
`${contentsIndexPrefix}${SemanticConventions.MESSAGE_CONTENT_TEXT}`
|
|
188
|
+
] = part.refusal;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// add role based fields to attributes
|
|
193
|
+
// as of now, there are no role based fields as they are handled by alternatively typed
|
|
194
|
+
// items
|
|
195
|
+
switch (message.role) {
|
|
196
|
+
case "user": {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "assistant": {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "system": {
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "developer": {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return attributes;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getResponsesInputMessagesAttributes(
|
|
213
|
+
body: ResponseCreateParamsBase,
|
|
214
|
+
): Attributes {
|
|
215
|
+
const attributes: Attributes = {};
|
|
216
|
+
const items: ResponseInputItem[] = [];
|
|
217
|
+
if (body.instructions) {
|
|
218
|
+
items.push({ content: body.instructions, role: "system" });
|
|
219
|
+
}
|
|
220
|
+
if (typeof body.input === "string") {
|
|
221
|
+
items.push({ content: body.input, role: "user" });
|
|
222
|
+
} else {
|
|
223
|
+
items.push(...body.input);
|
|
224
|
+
}
|
|
225
|
+
items.forEach((item, index) => {
|
|
226
|
+
const indexPrefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.${index}.`;
|
|
227
|
+
const itemAttributes = getResponseItemMessageAttributes(item, indexPrefix);
|
|
228
|
+
Object.entries(itemAttributes).forEach(([key, value]) => {
|
|
229
|
+
attributes[key] = value;
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return attributes;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function getResponsesUsageAttributes(
|
|
237
|
+
response: ResponseType,
|
|
238
|
+
): Attributes {
|
|
239
|
+
if (response.usage) {
|
|
240
|
+
return {
|
|
241
|
+
[SemanticConventions.LLM_TOKEN_COUNT_COMPLETION]:
|
|
242
|
+
response.usage.output_tokens,
|
|
243
|
+
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT]: response.usage.input_tokens,
|
|
244
|
+
[SemanticConventions.LLM_TOKEN_COUNT_TOTAL]: response.usage.total_tokens,
|
|
245
|
+
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ]:
|
|
246
|
+
response.usage.input_tokens_details?.cached_tokens,
|
|
247
|
+
[SemanticConventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING]:
|
|
248
|
+
response.usage.output_tokens_details?.reasoning_tokens,
|
|
249
|
+
// no audio tokens for response inputs or outputs
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return {};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function getResponsesOutputMessagesAttributes(
|
|
256
|
+
response: ResponseType,
|
|
257
|
+
): Attributes {
|
|
258
|
+
const attributes: Attributes = {};
|
|
259
|
+
const items = response.output;
|
|
260
|
+
items.forEach((item, index) => {
|
|
261
|
+
const indexPrefix = `${SemanticConventions.LLM_OUTPUT_MESSAGES}.${index}.`;
|
|
262
|
+
const itemAttributes = getResponseItemMessageAttributes(item, indexPrefix);
|
|
263
|
+
Object.entries(itemAttributes).forEach(([key, value]) => {
|
|
264
|
+
attributes[key] = value;
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return attributes;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function consumeResponseStreamEvents(
|
|
272
|
+
stream: Stream<ResponseStreamEvent>,
|
|
273
|
+
): Promise<ResponseType | undefined> {
|
|
274
|
+
let response: ResponseType | undefined;
|
|
275
|
+
|
|
276
|
+
for await (const event of stream) {
|
|
277
|
+
switch (event.type) {
|
|
278
|
+
case "response.completed": {
|
|
279
|
+
response = event.response;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return response;
|
|
286
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// this is an auto-generated file. see js/scripts/version-update.js
|
|
2
|
-
export const VERSION = "2.
|
|
2
|
+
export const VERSION = "2.3.0";
|