@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arizeai/openinference-instrumentation-openai",
3
- "version": "0.0.2",
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.11"
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
  }
@@ -1,209 +1,206 @@
1
1
  import * as openai from "openai";
2
2
  import {
3
- InstrumentationBase,
4
- InstrumentationConfig,
5
- InstrumentationModuleDefinition,
6
- InstrumentationNodeModuleDefinition,
7
- safeExecuteInTheMiddle,
3
+ InstrumentationBase,
4
+ InstrumentationConfig,
5
+ InstrumentationModuleDefinition,
6
+ InstrumentationNodeModuleDefinition,
7
+ safeExecuteInTheMiddle,
8
8
  } from "@opentelemetry/instrumentation";
9
9
  import {
10
- diag,
11
- context,
12
- trace,
13
- SpanKind,
14
- Attributes,
15
- SpanStatusCode,
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
- SemanticConventions,
20
- OpenInferenceSpanKind,
21
- MimeType,
19
+ SemanticConventions,
20
+ OpenInferenceSpanKind,
21
+ MimeType,
22
22
  } from "@arizeai/openinference-semantic-conventions";
23
23
  import {
24
- ChatCompletion,
25
- ChatCompletionChunk,
26
- ChatCompletionCreateParamsBase,
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
- constructor(config?: InstrumentationConfig) {
34
- super(
35
- "@arizeai/openinference-instrumentation-openai",
36
- VERSION,
37
- Object.assign({}, config)
38
- );
39
- }
33
+ constructor(config?: InstrumentationConfig) {
34
+ super(
35
+ "@arizeai/openinference-instrumentation-openai",
36
+ VERSION,
37
+ Object.assign({}, config),
38
+ );
39
+ }
40
40
 
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;
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
- * 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;
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
- // Patch create chat completions
66
- this._wrap(
67
- module.OpenAI.Chat.Completions.prototype,
68
- "create",
69
- (original: CompletionCreateType): any => {
70
- return function patchedCreate(
71
- this: unknown,
72
- ...args: Parameters<
73
- typeof module.OpenAI.Chat.Completions.prototype.create
74
- >
75
- ) {
76
- const body = args[0];
77
- const { messages, ...invocationParameters } = body;
78
- const span = instrumentation.tracer.startSpan(
79
- `OpenAI Chat Completions`,
80
- {
81
- kind: SpanKind.CLIENT,
82
- attributes: {
83
- [SemanticConventions.OPENINFERENCE_SPAN_KIND]:
84
- OpenInferenceSpanKind.LLM,
85
- [SemanticConventions.LLM_MODEL_NAME]:
86
- body.model,
87
- [SemanticConventions.INPUT_VALUE]:
88
- JSON.stringify(body),
89
- [SemanticConventions.INPUT_MIME_TYPE]:
90
- MimeType.JSON,
91
- [SemanticConventions.LLM_INVOCATION_PARAMETERS]:
92
- JSON.stringify(invocationParameters),
93
- ...getLLMInputMessagesAttributes(body),
94
- },
95
- }
96
- );
97
- const execContext = trace.setSpan(context.active(), span);
98
- const execPromise = safeExecuteInTheMiddle<
99
- ReturnType<CompletionCreateType>
100
- >(
101
- () => {
102
- return context.with(execContext, () => {
103
- return original.apply(this, args);
104
- });
105
- },
106
- (error) => {
107
- // Push the error to the span
108
- if (error) {
109
- span.recordException(error);
110
- }
111
- }
112
- );
113
- const wrappedPromise = execPromise.then((result) => {
114
- if (result) {
115
- // Record the results
116
- span.setAttributes({
117
- [SemanticConventions.OUTPUT_VALUE]:
118
- JSON.stringify(result),
119
- [SemanticConventions.OUTPUT_MIME_TYPE]:
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
- module.openInferencePatched = true;
134
- return module;
135
- }
136
- /**
137
- * Un-patches the OpenAI module's chat completions API
138
- */
139
- private unpatch(moduleExports: typeof openai, moduleVersion?: string) {
140
- diag.debug(`Removing patch for ${MODULE_NAME}@${moduleVersion}`);
141
- this._unwrap(moduleExports.OpenAI.Chat.Completions.prototype, "create");
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
- body: ChatCompletionCreateParamsBase
146
+ body: ChatCompletionCreateParamsBase,
150
147
  ): Attributes {
151
- return body.messages.reduce((acc, message, index) => {
152
- const index_prefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.${index}`;
153
- acc[`${index_prefix}.${SemanticConventions.MESSAGE_CONTENT}`] = String(
154
- message.content
155
- );
156
- acc[`${index_prefix}.${SemanticConventions.MESSAGE_ROLE}`] = String(
157
- message.role
158
- );
159
- return acc;
160
- }, {} as 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);
161
158
  }
162
159
 
163
160
  /**
164
161
  * Get Usage attributes
165
162
  */
166
163
  function getUsageAttributes(
167
- response: Stream<ChatCompletionChunk> | ChatCompletion
164
+ response: Stream<ChatCompletionChunk> | ChatCompletion,
168
165
  ) {
169
- if (response.hasOwnProperty("usage")) {
170
- const completion = response as ChatCompletion;
171
- if (completion.usage) {
172
- return {
173
- [SemanticConventions.LLM_TOKEN_COUNT_COMPLETION]:
174
- completion.usage.completion_tokens,
175
- [SemanticConventions.LLM_TOKEN_COUNT_PROMPT]:
176
- completion.usage.prompt_tokens,
177
- [SemanticConventions.LLM_TOKEN_COUNT_TOTAL]:
178
- completion.usage.total_tokens,
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
- return {};
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
- response: Stream<ChatCompletionChunk> | ChatCompletion
186
+ response: Stream<ChatCompletionChunk> | ChatCompletion,
190
187
  ): Attributes {
191
- // Handle chat completion
192
- if (response.hasOwnProperty("choices")) {
193
- const completion = response as ChatCompletion;
194
- // Right now support just the first choice
195
- const choice = completion.choices[0];
196
- if (!choice) {
197
- return {};
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
  }