@arizeai/openinference-instrumentation-openai 0.0.3 → 0.0.4

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,16 +1,20 @@
1
1
  {
2
2
  "name": "@arizeai/openinference-instrumentation-openai",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "OpenInference instrumentation for OpenAI",
5
5
  "private": false,
6
6
  "main": "dist/src/index.js",
7
7
  "module": "dist/esm/index.js",
8
8
  "esnext": "dist/esnext/index.js",
9
9
  "types": "dist/src/index.d.ts",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/Arize-ai/openinference.git"
13
+ },
10
14
  "dependencies": {
11
15
  "@opentelemetry/api": "^1.7.0",
12
16
  "@opentelemetry/instrumentation": "^0.46.0",
13
- "@arizeai/openinference-semantic-conventions": "0.0.12"
17
+ "@arizeai/openinference-semantic-conventions": "0.0.13"
14
18
  },
15
19
  "keywords": [],
16
20
  "files": [
@@ -26,6 +26,10 @@ import {
26
26
  ChatCompletionCreateParamsBase,
27
27
  } from "openai/resources/chat/completions";
28
28
  import { Stream } from "openai/streaming";
29
+ import {
30
+ CreateEmbeddingResponse,
31
+ EmbeddingCreateParams,
32
+ } from "openai/resources";
29
33
 
30
34
  const MODULE_NAME = "openai";
31
35
 
@@ -80,7 +84,7 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
80
84
  const span = instrumentation.tracer.startSpan(
81
85
  `OpenAI Chat Completions`,
82
86
  {
83
- kind: SpanKind.CLIENT,
87
+ kind: SpanKind.INTERNAL,
84
88
  attributes: {
85
89
  [SemanticConventions.OPENINFERENCE_SPAN_KIND]:
86
90
  OpenInferenceSpanKind.LLM,
@@ -106,6 +110,11 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
106
110
  // Push the error to the span
107
111
  if (error) {
108
112
  span.recordException(error);
113
+ span.setStatus({
114
+ code: SpanStatusCode.ERROR,
115
+ message: error.message,
116
+ });
117
+ span.end();
109
118
  }
110
119
  },
111
120
  );
@@ -115,6 +124,12 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
115
124
  span.setAttributes({
116
125
  [SemanticConventions.OUTPUT_VALUE]: JSON.stringify(result),
117
126
  [SemanticConventions.OUTPUT_MIME_TYPE]: MimeType.JSON,
127
+ // Override the model from the value sent by the server
128
+ [SemanticConventions.LLM_MODEL_NAME]: isChatCompletionResponse(
129
+ result,
130
+ )
131
+ ? result.model
132
+ : body.model,
118
133
  ...getLLMOutputMessagesAttributes(result),
119
134
  ...getUsageAttributes(result),
120
135
  });
@@ -127,6 +142,75 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
127
142
  };
128
143
  },
129
144
  );
145
+
146
+ // Patch embeddings
147
+ type EmbeddingsCreateType =
148
+ typeof module.OpenAI.Embeddings.prototype.create;
149
+ this._wrap(
150
+ module.OpenAI.Embeddings.prototype,
151
+ "create",
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ (original: EmbeddingsCreateType): any => {
154
+ return function patchedEmbeddingCreate(
155
+ this: unknown,
156
+ ...args: Parameters<typeof module.OpenAI.Embeddings.prototype.create>
157
+ ) {
158
+ const body = args[0];
159
+ const { input } = body;
160
+ const isStringInput = typeof input == "string";
161
+ const span = instrumentation.tracer.startSpan(`OpenAI Embeddings`, {
162
+ kind: SpanKind.INTERNAL,
163
+ attributes: {
164
+ [SemanticConventions.OPENINFERENCE_SPAN_KIND]:
165
+ OpenInferenceSpanKind.EMBEDDING,
166
+ [SemanticConventions.EMBEDDING_MODEL_NAME]: body.model,
167
+ [SemanticConventions.INPUT_VALUE]: isStringInput
168
+ ? input
169
+ : JSON.stringify(input),
170
+ [SemanticConventions.INPUT_MIME_TYPE]: isStringInput
171
+ ? MimeType.TEXT
172
+ : MimeType.JSON,
173
+ ...getEmbeddingTextAttributes(body),
174
+ },
175
+ });
176
+ const execContext = trace.setSpan(context.active(), span);
177
+ const execPromise = safeExecuteInTheMiddle<
178
+ ReturnType<EmbeddingsCreateType>
179
+ >(
180
+ () => {
181
+ return context.with(execContext, () => {
182
+ return original.apply(this, args);
183
+ });
184
+ },
185
+ (error) => {
186
+ // Push the error to the span
187
+ if (error) {
188
+ span.recordException(error);
189
+ span.setStatus({
190
+ code: SpanStatusCode.ERROR,
191
+ message: error.message,
192
+ });
193
+ span.end();
194
+ }
195
+ },
196
+ );
197
+ const wrappedPromise = execPromise.then((result) => {
198
+ if (result) {
199
+ // Record the results
200
+ span.setAttributes({
201
+ // Do not record the output data as it can be large
202
+ ...getEmbeddingEmbeddingsAttributes(result),
203
+ });
204
+ }
205
+ span.setStatus({ code: SpanStatusCode.OK });
206
+ span.end();
207
+ return result;
208
+ });
209
+ return context.bind(execContext, wrappedPromise);
210
+ };
211
+ },
212
+ );
213
+
130
214
  module.openInferencePatched = true;
131
215
  return module;
132
216
  }
@@ -136,9 +220,19 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
136
220
  private unpatch(moduleExports: typeof openai, moduleVersion?: string) {
137
221
  diag.debug(`Removing patch for ${MODULE_NAME}@${moduleVersion}`);
138
222
  this._unwrap(moduleExports.OpenAI.Chat.Completions.prototype, "create");
223
+ this._unwrap(moduleExports.OpenAI.Embeddings.prototype, "create");
139
224
  }
140
225
  }
141
226
 
227
+ /**
228
+ * type-guard that checks if the response is a chat completion response
229
+ */
230
+ function isChatCompletionResponse(
231
+ response: Stream<ChatCompletionChunk> | ChatCompletion,
232
+ ): response is ChatCompletion {
233
+ return "choices" in response;
234
+ }
235
+
142
236
  /**
143
237
  * Converts the body of the request to LLM input messages
144
238
  */
@@ -204,3 +298,43 @@ function getLLMOutputMessagesAttributes(
204
298
  }
205
299
  return {};
206
300
  }
301
+
302
+ /**
303
+ * Converts the embedding result payload to embedding attributes
304
+ */
305
+ function getEmbeddingTextAttributes(
306
+ request: EmbeddingCreateParams,
307
+ ): Attributes {
308
+ if (typeof request.input == "string") {
309
+ return {
310
+ [`${SemanticConventions.EMBEDDING_EMBEDDINGS}.0.${SemanticConventions.EMBEDDING_TEXT}`]:
311
+ request.input,
312
+ };
313
+ } else if (
314
+ Array.isArray(request.input) &&
315
+ request.input.length > 0 &&
316
+ typeof request.input[0] == "string"
317
+ ) {
318
+ return request.input.reduce((acc, input, index) => {
319
+ const index_prefix = `${SemanticConventions.EMBEDDING_EMBEDDINGS}.${index}`;
320
+ acc[`${index_prefix}.${SemanticConventions.EMBEDDING_TEXT}`] = input;
321
+ return acc;
322
+ }, {} as Attributes);
323
+ }
324
+ // Ignore other cases where input is a number or an array of numbers
325
+ return {};
326
+ }
327
+
328
+ /**
329
+ * Converts the embedding result payload to embedding attributes
330
+ */
331
+ function getEmbeddingEmbeddingsAttributes(
332
+ response: CreateEmbeddingResponse,
333
+ ): Attributes {
334
+ return response.data.reduce((acc, embedding, index) => {
335
+ const index_prefix = `${SemanticConventions.EMBEDDING_EMBEDDINGS}.${index}`;
336
+ acc[`${index_prefix}.${SemanticConventions.EMBEDDING_VECTOR}`] =
337
+ embedding.embedding;
338
+ return acc;
339
+ }, {} as Attributes);
340
+ }
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";
2
+ export const VERSION = "0.0.4";