@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.
Files changed (37) hide show
  1. package/README.md +30 -1
  2. package/dist/esm/index.d.ts +1 -1
  3. package/dist/esm/index.js +1 -1
  4. package/dist/esm/instrumentation.d.ts +19 -7
  5. package/dist/esm/instrumentation.d.ts.map +1 -1
  6. package/dist/esm/instrumentation.js +77 -9
  7. package/dist/esm/instrumentation.js.map +1 -1
  8. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  9. package/dist/esm/version.d.ts +2 -2
  10. package/dist/esm/version.d.ts.map +1 -1
  11. package/dist/esm/version.js +2 -2
  12. package/dist/esm/version.js.map +1 -1
  13. package/dist/esnext/index.d.ts +1 -1
  14. package/dist/esnext/index.js +1 -1
  15. package/dist/esnext/instrumentation.d.ts +19 -7
  16. package/dist/esnext/instrumentation.d.ts.map +1 -1
  17. package/dist/esnext/instrumentation.js +75 -9
  18. package/dist/esnext/instrumentation.js.map +1 -1
  19. package/dist/esnext/tsconfig.esnext.tsbuildinfo +1 -1
  20. package/dist/esnext/version.d.ts +2 -2
  21. package/dist/esnext/version.d.ts.map +1 -1
  22. package/dist/esnext/version.js +2 -2
  23. package/dist/esnext/version.js.map +1 -1
  24. package/dist/src/instrumentation.d.ts +6 -0
  25. package/dist/src/instrumentation.d.ts.map +1 -1
  26. package/dist/src/instrumentation.js +73 -7
  27. package/dist/src/instrumentation.js.map +1 -1
  28. package/dist/src/version.d.ts +1 -1
  29. package/dist/src/version.d.ts.map +1 -1
  30. package/dist/src/version.js +1 -1
  31. package/dist/src/version.js.map +1 -1
  32. package/dist/test/openai.test.js +55 -10
  33. package/dist/test/openai.test.js.map +1 -1
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +3 -2
  36. package/src/instrumentation.ts +183 -102
  37. 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.1-rc9",
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.8"
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,125 +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
- isSpanContextValid,
14
- Span,
15
- SpanKind,
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
- SemanticConventions,
21
- OpenInferenceSpanKind,
19
+ SemanticConventions,
20
+ OpenInferenceSpanKind,
21
+ MimeType,
22
22
  } from "@arizeai/openinference-semantic-conventions";
23
- import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions";
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
- constructor(config?: InstrumentationConfig) {
29
- super(
30
- "@arizeai/openinference-instrumentation-openai",
31
- VERSION,
32
- Object.assign({}, config)
33
- );
34
- }
33
+ constructor(config?: InstrumentationConfig) {
34
+ super(
35
+ "@arizeai/openinference-instrumentation-openai",
36
+ VERSION,
37
+ Object.assign({}, config),
38
+ );
39
+ }
35
40
 
36
- protected init(): InstrumentationModuleDefinition<typeof openai> {
37
- const module = new InstrumentationNodeModuleDefinition<typeof openai>(
38
- "openai",
39
- ["^4.0.0"],
40
- this.patch.bind(this),
41
- this.unpatch.bind(this)
42
- );
43
- 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;
44
60
  }
45
- private patch(
46
- module: typeof openai & { openInferencePatched?: boolean },
47
- moduleVersion?: string
48
- ) {
49
- console.log(`Applying patch for ${MODULE_NAME}@${moduleVersion}`);
50
- const plugin = this;
51
- if (module?.openInferencePatched) {
52
- return module;
53
- }
54
- const instrumentation: OpenAIInstrumentation = this;
55
- type CompletionCreateType =
56
- typeof module.OpenAI.Chat.Completions.prototype.create;
57
- this._wrap(
58
- module.OpenAI.Chat.Completions.prototype,
59
- "create",
60
- (original: CompletionCreateType): any => {
61
- return function patchedCreate(
62
- this: unknown,
63
- ...args: Parameters<
64
- typeof module.OpenAI.Chat.Completions.prototype.create
65
- >
66
- ) {
67
- const body = args[0];
68
- const span = instrumentation.tracer.startSpan(
69
- `OpenAI Chat Completions`,
70
- {
71
- kind: SpanKind.CLIENT,
72
- attributes: {
73
- [SemanticConventions.OPENINFERENCE_SPAN_KIND]:
74
- OpenInferenceSpanKind.LLM,
75
- ...getLLMInputMessagesAttributes(body),
76
- },
77
- }
78
- );
79
- const execContext = trace.setSpan(context.active(), span);
80
- const execPromise = safeExecuteInTheMiddle<
81
- ReturnType<CompletionCreateType>
82
- >(
83
- () => {
84
- return context.with(execContext, () => {
85
- return original.apply(this, args);
86
- });
87
- },
88
- (error) => {
89
- // TODO handler the error
90
- }
91
- );
92
- const wrappedPromise = execPromise.then((result) => {
93
- // TODO set span attributes
94
- span.end();
95
- return result;
96
- });
97
- return context.bind(execContext, wrappedPromise);
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
- module.openInferencePatched = true;
102
- return module;
103
- }
104
- private unpatch(moduleExports: typeof openai, moduleVersion?: string) {
105
- diag.debug(`Removing patch for ${MODULE_NAME}@${moduleVersion}`);
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
- body: ChatCompletionCreateParamsBase
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
- return body.messages.reduce((acc, message, index) => {
116
- const index_prefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.${index}`;
117
- acc[`${index_prefix}.${SemanticConventions.MESSAGE_CONTENT}`] = String(
118
- message.content
119
- );
120
- acc[`${index_prefix}.${SemanticConventions.MESSAGE_ROLE}`] = String(
121
- message.role
122
- );
123
- return acc;
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.1-rc9";
2
+ export const VERSION = "0.0.2";