@arizeai/openinference-instrumentation-openai 0.0.1-rc9 → 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 +30 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/instrumentation.d.ts +19 -7
- package/dist/esm/instrumentation.d.ts.map +1 -1
- package/dist/esm/instrumentation.js +77 -9
- package/dist/esm/instrumentation.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/version.d.ts +2 -2
- package/dist/esm/version.d.ts.map +1 -1
- package/dist/esm/version.js +2 -2
- package/dist/esm/version.js.map +1 -1
- package/dist/esnext/index.d.ts +1 -1
- package/dist/esnext/index.js +1 -1
- package/dist/esnext/instrumentation.d.ts +19 -7
- package/dist/esnext/instrumentation.d.ts.map +1 -1
- package/dist/esnext/instrumentation.js +75 -9
- package/dist/esnext/instrumentation.js.map +1 -1
- package/dist/esnext/tsconfig.esnext.tsbuildinfo +1 -1
- package/dist/esnext/version.d.ts +2 -2
- package/dist/esnext/version.d.ts.map +1 -1
- package/dist/esnext/version.js +2 -2
- package/dist/esnext/version.js.map +1 -1
- package/dist/src/instrumentation.d.ts +6 -0
- package/dist/src/instrumentation.d.ts.map +1 -1
- package/dist/src/instrumentation.js +73 -7
- package/dist/src/instrumentation.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/test/openai.test.js +55 -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 +183 -102
- 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": "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,125 +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
|
-
|
|
16
|
-
Attributes,
|
|
10
|
+
diag,
|
|
11
|
+
context,
|
|
12
|
+
trace,
|
|
13
|
+
SpanKind,
|
|
14
|
+
Attributes,
|
|
15
|
+
SpanStatusCode,
|
|
17
16
|
} from "@opentelemetry/api";
|
|
18
17
|
import { VERSION } from "./version";
|
|
19
18
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
SemanticConventions,
|
|
20
|
+
OpenInferenceSpanKind,
|
|
21
|
+
MimeType,
|
|
22
22
|
} from "@arizeai/openinference-semantic-conventions";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
ChatCompletion,
|
|
25
|
+
ChatCompletionChunk,
|
|
26
|
+
ChatCompletionCreateParamsBase,
|
|
27
|
+
} from "openai/resources/chat/completions";
|
|
28
|
+
import { Stream } from "openai/streaming";
|
|
24
29
|
|
|
25
30
|
const MODULE_NAME = "openai";
|
|
26
31
|
|
|
27
32
|
export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
constructor(config?: InstrumentationConfig) {
|
|
34
|
+
super(
|
|
35
|
+
"@arizeai/openinference-instrumentation-openai",
|
|
36
|
+
VERSION,
|
|
37
|
+
Object.assign({}, config),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
44
60
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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;
|
|
65
|
+
|
|
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
|
+
});
|
|
99
121
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
}
|
|
107
140
|
}
|
|
108
141
|
|
|
109
142
|
/**
|
|
110
|
-
* Converts the body input
|
|
143
|
+
* Converts the body of the request to LLM input messages
|
|
111
144
|
*/
|
|
112
145
|
function getLLMInputMessagesAttributes(
|
|
113
|
-
|
|
146
|
+
body: ChatCompletionCreateParamsBase,
|
|
147
|
+
): Attributes {
|
|
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);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get Usage attributes
|
|
162
|
+
*/
|
|
163
|
+
function getUsageAttributes(
|
|
164
|
+
response: Stream<ChatCompletionChunk> | ChatCompletion,
|
|
165
|
+
) {
|
|
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
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Converts the result to LLM output attributes
|
|
184
|
+
*/
|
|
185
|
+
function getLLMOutputMessagesAttributes(
|
|
186
|
+
response: Stream<ChatCompletionChunk> | ChatCompletion,
|
|
114
187
|
): Attributes {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 {};
|
|
195
|
+
}
|
|
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;
|
|
124
203
|
}, {} as Attributes);
|
|
204
|
+
}
|
|
205
|
+
return {};
|
|
125
206
|
}
|
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 = "0.0.
|
|
2
|
+
export const VERSION = "0.0.2";
|