@arizeai/openinference-instrumentation-openai 4.4.3 → 4.4.5
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/dist/esm/instrumentation.d.ts.map +1 -1
- package/dist/esm/instrumentation.js +85 -29
- package/dist/esm/instrumentation.js.map +1 -1
- 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 +95 -38
- package/dist/esnext/instrumentation.js.map +1 -1
- 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 +95 -38
- package/dist/src/instrumentation.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/instrumentation.ts +92 -30
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arizeai/openinference-instrumentation-openai",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OpenInference instrumentation for OpenAI",
|
|
6
6
|
"keywords": [],
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"@opentelemetry/api": "^1.9.0",
|
|
29
29
|
"@opentelemetry/core": "^2.8.0",
|
|
30
30
|
"@opentelemetry/instrumentation": "^0.46.0",
|
|
31
|
-
"@arizeai/openinference-core": "2.8.
|
|
32
|
-
"@arizeai/openinference-semantic-conventions": "2.
|
|
31
|
+
"@arizeai/openinference-core": "2.8.2",
|
|
32
|
+
"@arizeai/openinference-semantic-conventions": "2.16.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.50.0",
|
package/src/instrumentation.ts
CHANGED
|
@@ -87,6 +87,7 @@ export const HOST_SUFFIX_TO_PROVIDER: Record<string, LLMProvider> = {
|
|
|
87
87
|
"api.minimaxi.com": LLMProvider.MINIMAX,
|
|
88
88
|
"api.minimax.chat": LLMProvider.MINIMAX,
|
|
89
89
|
"oci.oraclecloud.com": LLMProvider.ORACLE,
|
|
90
|
+
"ml.cloud.ibm.com": LLMProvider.IBM,
|
|
90
91
|
};
|
|
91
92
|
|
|
92
93
|
/**
|
|
@@ -571,8 +572,17 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
|
|
|
571
572
|
// First split the stream via tee
|
|
572
573
|
const [leftStream, rightStream] = result.tee();
|
|
573
574
|
// take the right stream, consuming it and then recording the final chunk
|
|
574
|
-
// into the span
|
|
575
|
-
|
|
575
|
+
// into the span. If reading it fails, the caller gets the same error from
|
|
576
|
+
// the left stream and the span ends with status ERROR.
|
|
577
|
+
void consumeResponseStreamEvents(rightStream).then((response) => {
|
|
578
|
+
// An aborted request ends the stream without an error or a final
|
|
579
|
+
// response, so leave the status unset
|
|
580
|
+
if (!response && rightStream.controller.signal.aborted) {
|
|
581
|
+
span.end();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
recordSpan(response);
|
|
585
|
+
}, recordError);
|
|
576
586
|
// give the left stream back to the caller
|
|
577
587
|
result = leftStream;
|
|
578
588
|
}
|
|
@@ -1000,7 +1010,10 @@ function getEmbeddingUsageAttributes(response: CreateEmbeddingResponse): Attribu
|
|
|
1000
1010
|
}
|
|
1001
1011
|
|
|
1002
1012
|
/**
|
|
1003
|
-
* Consumes the stream chunks and adds them to the span
|
|
1013
|
+
* Consumes the stream chunks and adds them to the span.
|
|
1014
|
+
* The span ends with status OK when the stream is read to the end, with status ERROR when
|
|
1015
|
+
* reading it fails, and with the status unset when the request is aborted. In every case it
|
|
1016
|
+
* keeps what was streamed so far.
|
|
1004
1017
|
*/
|
|
1005
1018
|
async function consumeChatCompletionStreamChunks(stream: Stream<ChatCompletionChunk>, span: Span) {
|
|
1006
1019
|
let streamResponse = "";
|
|
@@ -1009,34 +1022,44 @@ async function consumeChatCompletionStreamChunks(stream: Stream<ChatCompletionCh
|
|
|
1009
1022
|
// So the final tool and function calls need to be aggregated
|
|
1010
1023
|
// across chunks
|
|
1011
1024
|
const toolAndFunctionCallAttributes: Attributes = {};
|
|
1025
|
+
const getToolCallIndex = createStreamToolCallIndexResolver();
|
|
1012
1026
|
let finishReason: string | undefined;
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1027
|
+
let streamFailed = false;
|
|
1028
|
+
let streamError: unknown;
|
|
1029
|
+
try {
|
|
1030
|
+
for await (const chunk of stream) {
|
|
1031
|
+
// With `stream_options.include_usage`, the final chunk carries the request's
|
|
1032
|
+
// usage and an empty `choices` array, so read it before the guard below.
|
|
1033
|
+
if (chunk.usage) {
|
|
1034
|
+
span.setAttributes(getUsageAttributes(chunk));
|
|
1035
|
+
}
|
|
1036
|
+
if (chunk.choices.length <= 0) {
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
const choice = chunk.choices[0];
|
|
1040
|
+
if (choice.delta.content) {
|
|
1041
|
+
streamResponse += choice.delta.content;
|
|
1042
|
+
}
|
|
1043
|
+
if (choice.finish_reason) {
|
|
1044
|
+
finishReason = choice.finish_reason;
|
|
1045
|
+
}
|
|
1046
|
+
// Accumulate the tool and function call attributes
|
|
1047
|
+
const toolAndFunctionCallAttributesDiff = getToolAndFunctionCallAttributesFromStreamChunk(
|
|
1048
|
+
chunk,
|
|
1049
|
+
getToolCallIndex,
|
|
1050
|
+
);
|
|
1051
|
+
for (const [key, value] of Object.entries(toolAndFunctionCallAttributesDiff)) {
|
|
1052
|
+
if (isString(toolAndFunctionCallAttributes[key]) && isString(value)) {
|
|
1053
|
+
toolAndFunctionCallAttributes[key] += value;
|
|
1054
|
+
} else if (isString(value)) {
|
|
1055
|
+
toolAndFunctionCallAttributes[key] = value;
|
|
1056
|
+
}
|
|
1038
1057
|
}
|
|
1039
1058
|
}
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
// The caller reads the other branch of the tee and gets the same error
|
|
1061
|
+
streamFailed = true;
|
|
1062
|
+
streamError = error;
|
|
1040
1063
|
}
|
|
1041
1064
|
const messageIndexPrefix = `${SemanticConventions.LLM_OUTPUT_MESSAGES}.0.`;
|
|
1042
1065
|
|
|
@@ -1055,20 +1078,59 @@ async function consumeChatCompletionStreamChunks(stream: Stream<ChatCompletionCh
|
|
|
1055
1078
|
attributes[`${messageIndexPrefix}${key}`] = value;
|
|
1056
1079
|
}
|
|
1057
1080
|
span.setAttributes(attributes);
|
|
1081
|
+
if (streamFailed) {
|
|
1082
|
+
endSpanWithError(span, streamError);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
// An aborted request ends the stream without an error, but did not run to completion
|
|
1086
|
+
if (!stream.controller.signal.aborted) {
|
|
1087
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
1088
|
+
}
|
|
1058
1089
|
span.end();
|
|
1059
1090
|
}
|
|
1060
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* Returns a function that gives the position of a streamed tool call delta within the message.
|
|
1094
|
+
* OpenAI sends an index with every delta. Some OpenAI-compatible providers (such as Gemini)
|
|
1095
|
+
* leave it out, so a delta with a new id, or a function name without an id, starts the next
|
|
1096
|
+
* tool call and any other delta continues the latest one.
|
|
1097
|
+
*/
|
|
1098
|
+
function createStreamToolCallIndexResolver() {
|
|
1099
|
+
let latestIndex = -1;
|
|
1100
|
+
let latestId: string | undefined;
|
|
1101
|
+
let nextIndex = 0;
|
|
1102
|
+
return (toolCall: ChatCompletionChunk.Choice.Delta.ToolCall): number => {
|
|
1103
|
+
// The SDK types the index as required, but some providers omit it
|
|
1104
|
+
if (typeof toolCall.index === "number") {
|
|
1105
|
+
latestIndex = toolCall.index;
|
|
1106
|
+
nextIndex = Math.max(nextIndex, toolCall.index + 1);
|
|
1107
|
+
return toolCall.index;
|
|
1108
|
+
}
|
|
1109
|
+
const startsNewToolCall =
|
|
1110
|
+
latestIndex < 0 || (toolCall.id ? toolCall.id !== latestId : toolCall.function?.name != null);
|
|
1111
|
+
if (startsNewToolCall) {
|
|
1112
|
+
latestIndex = nextIndex++;
|
|
1113
|
+
latestId = toolCall.id;
|
|
1114
|
+
}
|
|
1115
|
+
return latestIndex;
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1061
1119
|
/**
|
|
1062
1120
|
* Extracts the semantic attributes from the stream chunk for tool_calls and function_calls
|
|
1063
1121
|
*/
|
|
1064
|
-
function getToolAndFunctionCallAttributesFromStreamChunk(
|
|
1122
|
+
function getToolAndFunctionCallAttributesFromStreamChunk(
|
|
1123
|
+
chunk: ChatCompletionChunk,
|
|
1124
|
+
getToolCallIndex: (toolCall: ChatCompletionChunk.Choice.Delta.ToolCall) => number,
|
|
1125
|
+
): Attributes {
|
|
1065
1126
|
if (chunk.choices.length <= 0) {
|
|
1066
1127
|
return {};
|
|
1067
1128
|
}
|
|
1068
1129
|
const choice = chunk.choices[0];
|
|
1069
1130
|
const attributes: Attributes = {};
|
|
1070
1131
|
if (choice.delta.tool_calls) {
|
|
1071
|
-
choice.delta.tool_calls.forEach((toolCall
|
|
1132
|
+
choice.delta.tool_calls.forEach((toolCall) => {
|
|
1133
|
+
const index = getToolCallIndex(toolCall);
|
|
1072
1134
|
const toolCallIndexPrefix = `${SemanticConventions.MESSAGE_TOOL_CALLS}.${index}.`;
|
|
1073
1135
|
// Add the tool call id if it exists
|
|
1074
1136
|
if (toolCall.id) {
|
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 = "4.4.
|
|
2
|
+
export const VERSION = "4.4.5";
|