@arizeai/openinference-instrumentation-openai 0.0.2 → 0.0.3
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 +2 -2
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/instrumentation.d.ts +19 -13
- package/dist/esm/instrumentation.d.ts.map +1 -1
- package/dist/esm/instrumentation.js +9 -8
- 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/index.d.ts +1 -1
- package/dist/esnext/index.js +1 -1
- package/dist/esnext/instrumentation.d.ts +19 -13
- package/dist/esnext/instrumentation.d.ts.map +1 -1
- package/dist/esnext/instrumentation.js +9 -8
- 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 +9 -8
- package/dist/src/instrumentation.js.map +1 -1
- package/dist/test/openai.test.js +3 -10
- package/dist/test/openai.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/instrumentation.ts +163 -166
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arizeai/openinference-instrumentation-openai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "OpenInference instrumentation for OpenAI",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@opentelemetry/api": "^1.7.0",
|
|
12
12
|
"@opentelemetry/instrumentation": "^0.46.0",
|
|
13
|
-
"@arizeai/openinference-semantic-conventions": "0.0.
|
|
13
|
+
"@arizeai/openinference-semantic-conventions": "0.0.12"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [],
|
|
16
16
|
"files": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"prebuild": "rimraf dist & pnpm run version:update",
|
|
31
31
|
"build": "tsc --build tsconfig.json tsconfig.esm.json tsconfig.esnext.json",
|
|
32
32
|
"version:update": "../../scripts/version-update.js",
|
|
33
|
+
"type:check": "tsc --noEmit",
|
|
33
34
|
"test": "jest ."
|
|
34
35
|
}
|
|
35
36
|
}
|
package/src/instrumentation.ts
CHANGED
|
@@ -1,209 +1,206 @@
|
|
|
1
1
|
import * as openai from "openai";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
InstrumentationBase,
|
|
4
|
+
InstrumentationConfig,
|
|
5
|
+
InstrumentationModuleDefinition,
|
|
6
|
+
InstrumentationNodeModuleDefinition,
|
|
7
|
+
safeExecuteInTheMiddle,
|
|
8
8
|
} from "@opentelemetry/instrumentation";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
diag,
|
|
11
|
+
context,
|
|
12
|
+
trace,
|
|
13
|
+
SpanKind,
|
|
14
|
+
Attributes,
|
|
15
|
+
SpanStatusCode,
|
|
16
16
|
} from "@opentelemetry/api";
|
|
17
17
|
import { VERSION } from "./version";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
SemanticConventions,
|
|
20
|
+
OpenInferenceSpanKind,
|
|
21
|
+
MimeType,
|
|
22
22
|
} from "@arizeai/openinference-semantic-conventions";
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
ChatCompletion,
|
|
25
|
+
ChatCompletionChunk,
|
|
26
|
+
ChatCompletionCreateParamsBase,
|
|
27
27
|
} from "openai/resources/chat/completions";
|
|
28
28
|
import { Stream } from "openai/streaming";
|
|
29
29
|
|
|
30
30
|
const MODULE_NAME = "openai";
|
|
31
31
|
|
|
32
32
|
export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
constructor(config?: InstrumentationConfig) {
|
|
34
|
+
super(
|
|
35
|
+
"@arizeai/openinference-instrumentation-openai",
|
|
36
|
+
VERSION,
|
|
37
|
+
Object.assign({}, config),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
protected init(): InstrumentationModuleDefinition<typeof openai> {
|
|
42
|
+
const module = new InstrumentationNodeModuleDefinition<typeof openai>(
|
|
43
|
+
"openai",
|
|
44
|
+
["^4.0.0"],
|
|
45
|
+
this.patch.bind(this),
|
|
46
|
+
this.unpatch.bind(this),
|
|
47
|
+
);
|
|
48
|
+
return module;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Patches the OpenAI module
|
|
52
|
+
*/
|
|
53
|
+
private patch(
|
|
54
|
+
module: typeof openai & { openInferencePatched?: boolean },
|
|
55
|
+
moduleVersion?: string,
|
|
56
|
+
) {
|
|
57
|
+
diag.debug(`Applying patch for ${MODULE_NAME}@${moduleVersion}`);
|
|
58
|
+
if (module?.openInferencePatched) {
|
|
59
|
+
return module;
|
|
49
60
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
module: typeof openai & { openInferencePatched?: boolean },
|
|
55
|
-
moduleVersion?: string
|
|
56
|
-
) {
|
|
57
|
-
diag.debug(`Applying patch for ${MODULE_NAME}@${moduleVersion}`);
|
|
58
|
-
if (module?.openInferencePatched) {
|
|
59
|
-
return module;
|
|
60
|
-
}
|
|
61
|
-
const instrumentation: OpenAIInstrumentation = this;
|
|
62
|
-
type CompletionCreateType =
|
|
63
|
-
typeof module.OpenAI.Chat.Completions.prototype.create;
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
62
|
+
const instrumentation: OpenAIInstrumentation = this;
|
|
63
|
+
type CompletionCreateType =
|
|
64
|
+
typeof module.OpenAI.Chat.Completions.prototype.create;
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
MimeType.JSON,
|
|
121
|
-
...getLLMOutputMessagesAttributes(result),
|
|
122
|
-
...getUsageAttributes(result),
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
126
|
-
span.end();
|
|
127
|
-
return result;
|
|
128
|
-
});
|
|
129
|
-
return context.bind(execContext, wrappedPromise);
|
|
130
|
-
};
|
|
66
|
+
// Patch create chat completions
|
|
67
|
+
this._wrap(
|
|
68
|
+
module.OpenAI.Chat.Completions.prototype,
|
|
69
|
+
"create",
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
(original: CompletionCreateType): any => {
|
|
72
|
+
return function patchedCreate(
|
|
73
|
+
this: unknown,
|
|
74
|
+
...args: Parameters<
|
|
75
|
+
typeof module.OpenAI.Chat.Completions.prototype.create
|
|
76
|
+
>
|
|
77
|
+
) {
|
|
78
|
+
const body = args[0];
|
|
79
|
+
const { messages: _messages, ...invocationParameters } = body;
|
|
80
|
+
const span = instrumentation.tracer.startSpan(
|
|
81
|
+
`OpenAI Chat Completions`,
|
|
82
|
+
{
|
|
83
|
+
kind: SpanKind.CLIENT,
|
|
84
|
+
attributes: {
|
|
85
|
+
[SemanticConventions.OPENINFERENCE_SPAN_KIND]:
|
|
86
|
+
OpenInferenceSpanKind.LLM,
|
|
87
|
+
[SemanticConventions.LLM_MODEL_NAME]: body.model,
|
|
88
|
+
[SemanticConventions.INPUT_VALUE]: JSON.stringify(body),
|
|
89
|
+
[SemanticConventions.INPUT_MIME_TYPE]: MimeType.JSON,
|
|
90
|
+
[SemanticConventions.LLM_INVOCATION_PARAMETERS]:
|
|
91
|
+
JSON.stringify(invocationParameters),
|
|
92
|
+
...getLLMInputMessagesAttributes(body),
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
const execContext = trace.setSpan(context.active(), span);
|
|
97
|
+
const execPromise = safeExecuteInTheMiddle<
|
|
98
|
+
ReturnType<CompletionCreateType>
|
|
99
|
+
>(
|
|
100
|
+
() => {
|
|
101
|
+
return context.with(execContext, () => {
|
|
102
|
+
return original.apply(this, args);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
(error) => {
|
|
106
|
+
// Push the error to the span
|
|
107
|
+
if (error) {
|
|
108
|
+
span.recordException(error);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
const wrappedPromise = execPromise.then((result) => {
|
|
113
|
+
if (result) {
|
|
114
|
+
// Record the results
|
|
115
|
+
span.setAttributes({
|
|
116
|
+
[SemanticConventions.OUTPUT_VALUE]: JSON.stringify(result),
|
|
117
|
+
[SemanticConventions.OUTPUT_MIME_TYPE]: MimeType.JSON,
|
|
118
|
+
...getLLMOutputMessagesAttributes(result),
|
|
119
|
+
...getUsageAttributes(result),
|
|
120
|
+
});
|
|
131
121
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
122
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
123
|
+
span.end();
|
|
124
|
+
return result;
|
|
125
|
+
});
|
|
126
|
+
return context.bind(execContext, wrappedPromise);
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
module.openInferencePatched = true;
|
|
131
|
+
return module;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Un-patches the OpenAI module's chat completions API
|
|
135
|
+
*/
|
|
136
|
+
private unpatch(moduleExports: typeof openai, moduleVersion?: string) {
|
|
137
|
+
diag.debug(`Removing patch for ${MODULE_NAME}@${moduleVersion}`);
|
|
138
|
+
this._unwrap(moduleExports.OpenAI.Chat.Completions.prototype, "create");
|
|
139
|
+
}
|
|
143
140
|
}
|
|
144
141
|
|
|
145
142
|
/**
|
|
146
143
|
* Converts the body of the request to LLM input messages
|
|
147
144
|
*/
|
|
148
145
|
function getLLMInputMessagesAttributes(
|
|
149
|
-
|
|
146
|
+
body: ChatCompletionCreateParamsBase,
|
|
150
147
|
): Attributes {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
148
|
+
return body.messages.reduce((acc, message, index) => {
|
|
149
|
+
const index_prefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.${index}`;
|
|
150
|
+
acc[`${index_prefix}.${SemanticConventions.MESSAGE_CONTENT}`] = String(
|
|
151
|
+
message.content,
|
|
152
|
+
);
|
|
153
|
+
acc[`${index_prefix}.${SemanticConventions.MESSAGE_ROLE}`] = String(
|
|
154
|
+
message.role,
|
|
155
|
+
);
|
|
156
|
+
return acc;
|
|
157
|
+
}, {} as Attributes);
|
|
161
158
|
}
|
|
162
159
|
|
|
163
160
|
/**
|
|
164
161
|
* Get Usage attributes
|
|
165
162
|
*/
|
|
166
163
|
function getUsageAttributes(
|
|
167
|
-
|
|
164
|
+
response: Stream<ChatCompletionChunk> | ChatCompletion,
|
|
168
165
|
) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
166
|
+
if (Object.prototype.hasOwnProperty.call(response, "usage")) {
|
|
167
|
+
const completion = response as ChatCompletion;
|
|
168
|
+
if (completion.usage) {
|
|
169
|
+
return {
|
|
170
|
+
[SemanticConventions.LLM_TOKEN_COUNT_COMPLETION]:
|
|
171
|
+
completion.usage.completion_tokens,
|
|
172
|
+
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT]:
|
|
173
|
+
completion.usage.prompt_tokens,
|
|
174
|
+
[SemanticConventions.LLM_TOKEN_COUNT_TOTAL]:
|
|
175
|
+
completion.usage.total_tokens,
|
|
176
|
+
};
|
|
181
177
|
}
|
|
182
|
-
|
|
178
|
+
}
|
|
179
|
+
return {};
|
|
183
180
|
}
|
|
184
181
|
|
|
185
182
|
/**
|
|
186
183
|
* Converts the result to LLM output attributes
|
|
187
184
|
*/
|
|
188
185
|
function getLLMOutputMessagesAttributes(
|
|
189
|
-
|
|
186
|
+
response: Stream<ChatCompletionChunk> | ChatCompletion,
|
|
190
187
|
): Attributes {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
return [choice.message].reduce((acc, message, index) => {
|
|
200
|
-
const index_prefix = `${SemanticConventions.LLM_OUTPUT_MESSAGES}.${index}`;
|
|
201
|
-
acc[`${index_prefix}.${SemanticConventions.MESSAGE_CONTENT}`] =
|
|
202
|
-
String(message.content);
|
|
203
|
-
acc[`${index_prefix}.${SemanticConventions.MESSAGE_ROLE}`] =
|
|
204
|
-
message.role;
|
|
205
|
-
return acc;
|
|
206
|
-
}, {} as Attributes);
|
|
188
|
+
// Handle chat completion
|
|
189
|
+
if (Object.prototype.hasOwnProperty.call(response, "choices")) {
|
|
190
|
+
const completion = response as ChatCompletion;
|
|
191
|
+
// Right now support just the first choice
|
|
192
|
+
const choice = completion.choices[0];
|
|
193
|
+
if (!choice) {
|
|
194
|
+
return {};
|
|
207
195
|
}
|
|
208
|
-
return {
|
|
196
|
+
return [choice.message].reduce((acc, message, index) => {
|
|
197
|
+
const index_prefix = `${SemanticConventions.LLM_OUTPUT_MESSAGES}.${index}`;
|
|
198
|
+
acc[`${index_prefix}.${SemanticConventions.MESSAGE_CONTENT}`] = String(
|
|
199
|
+
message.content,
|
|
200
|
+
);
|
|
201
|
+
acc[`${index_prefix}.${SemanticConventions.MESSAGE_ROLE}`] = message.role;
|
|
202
|
+
return acc;
|
|
203
|
+
}, {} as Attributes);
|
|
204
|
+
}
|
|
205
|
+
return {};
|
|
209
206
|
}
|