@arizeai/openinference-instrumentation-openai 4.0.6 → 4.1.0

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": "4.0.6",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "description": "OpenInference instrumentation for OpenAI",
6
6
  "keywords": [],
@@ -28,8 +28,8 @@
28
28
  "@opentelemetry/api": "^1.9.0",
29
29
  "@opentelemetry/core": "^1.25.1",
30
30
  "@opentelemetry/instrumentation": "^0.46.0",
31
- "@arizeai/openinference-core": "2.0.6",
32
- "@arizeai/openinference-semantic-conventions": "2.2.0"
31
+ "@arizeai/openinference-core": "2.0.8",
32
+ "@arizeai/openinference-semantic-conventions": "2.3.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@opentelemetry/exporter-trace-otlp-proto": "^0.50.0",
@@ -51,6 +51,45 @@ const MODULE_NAME = "openai";
51
51
 
52
52
  const INSTRUMENTATION_NAME = "@arizeai/openinference-instrumentation-openai";
53
53
 
54
+ /**
55
+ * Maps hostname suffixes to their corresponding LLM provider value.
56
+ */
57
+ export const HOST_SUFFIX_TO_PROVIDER: Record<string, LLMProvider> = {
58
+ "api.openai.com": LLMProvider.OPENAI,
59
+ "openai.azure.com": LLMProvider.AZURE,
60
+ "services.ai.azure.com": LLMProvider.AZURE,
61
+ "cognitiveservices.azure.com": LLMProvider.AZURE,
62
+ "api.anthropic.com": LLMProvider.ANTHROPIC,
63
+ "api.cohere.com": LLMProvider.COHERE,
64
+ "api.cohere.ai": LLMProvider.COHERE,
65
+ "api.mistral.ai": LLMProvider.MISTRALAI,
66
+ "generativelanguage.googleapis.com": LLMProvider.GOOGLE,
67
+ "aiplatform.googleapis.com": LLMProvider.GOOGLE,
68
+ "amazonaws.com": LLMProvider.AWS,
69
+ "api.x.ai": LLMProvider.XAI,
70
+ "api.deepseek.com": LLMProvider.DEEPSEEK,
71
+ "api.groq.com": LLMProvider.GROQ,
72
+ "api.fireworks.ai": LLMProvider.FIREWORKS,
73
+ "api.moonshot.cn": LLMProvider.MOONSHOT,
74
+ "api.cerebras.ai": LLMProvider.CEREBRAS,
75
+ "api.perplexity.ai": LLMProvider.PERPLEXITY,
76
+ "api.together.ai": LLMProvider.TOGETHER,
77
+ "api.together.xyz": LLMProvider.TOGETHER,
78
+ };
79
+
80
+ /**
81
+ * Return the LLM provider name for the given API hostname.
82
+ */
83
+ export function getProviderFromHost(host: string): LLMProvider | undefined {
84
+ const normalised = host.toLowerCase().trim();
85
+ for (const [suffix, provider] of Object.entries(HOST_SUFFIX_TO_PROVIDER)) {
86
+ if (normalised.endsWith(suffix)) {
87
+ return provider;
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+
54
93
  /**
55
94
  * Flag to check if the openai module has been patched
56
95
  * Note: This is a fallback in case the module is made immutable (e.x. Deno, webpack, etc.)
@@ -84,9 +123,9 @@ function getExecContext(span: Span) {
84
123
  * Gets the appropriate LLM provider based on the OpenAI client instance
85
124
  * Follows the same logic as the Python implementation by checking the baseURL host
86
125
  * @param clientInstance The OpenAI client instance
87
- * @returns LLMProvider.AZURE for Azure OpenAI, LLMProvider.OPENAI for regular OpenAI
126
+ * @returns LLM provider based on the API hostname
88
127
  */
89
- function getLLMProvider(clientInstance: unknown): LLMProvider {
128
+ function getLLMProvider(clientInstance: unknown): LLMProvider | undefined {
90
129
  try {
91
130
  // The clientInstance might be a sub-object (like Completions) that has a _client property
92
131
  // pointing to the actual OpenAI/AzureOpenAI client
@@ -124,24 +163,13 @@ function getLLMProvider(clientInstance: unknown): LLMProvider {
124
163
  }
125
164
 
126
165
  if (host && typeof host === "string") {
127
- // Follow the same pattern as Python implementation
128
- if (host.includes("api.openai.com")) {
129
- return LLMProvider.OPENAI;
130
- } else if (host.includes("openai.azure.com")) {
131
- return LLMProvider.AZURE;
132
- } else if (host.includes("api.microsoft.com")) {
133
- // Additional Azure endpoint pattern
134
- return LLMProvider.AZURE;
135
- }
166
+ return getProviderFromHost(host);
136
167
  }
137
168
  } catch (error) {
138
- // If we can't determine, default to regular OpenAI
139
169
  diag.debug("Failed to determine LLM provider from instance", error);
140
170
  }
141
-
142
- // Default to OpenAI if we can't determine
143
- return LLMProvider.OPENAI;
144
171
  }
172
+
145
173
  /**
146
174
  * An auto instrumentation class for OpenAI that creates {@link https://github.com/Arize-ai/openinference/blob/main/spec/semantic_conventions.md|OpenInference} Compliant spans for the OpenAI API
147
175
  * @param instrumentationConfig The config for the instrumentation @see {@link InstrumentationConfig}
@@ -294,6 +322,7 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
294
322
  // Override the model from the value sent by the server
295
323
  [SemanticConventions.LLM_MODEL_NAME]: result.model,
296
324
  ...getChatCompletionLLMOutputMessagesAttributes(result),
325
+ ...getChatCompletionFinishReasonAttributes(result),
297
326
  ...getUsageAttributes(result),
298
327
  });
299
328
  span.setStatus({ code: SpanStatusCode.OK });
@@ -400,6 +429,7 @@ export class OpenAIInstrumentation extends InstrumentationBase<typeof openai> {
400
429
  [SemanticConventions.EMBEDDING_MODEL_NAME]: body.model,
401
430
  [SemanticConventions.INPUT_VALUE]: isStringInput ? input : JSON.stringify(input),
402
431
  [SemanticConventions.INPUT_MIME_TYPE]: isStringInput ? MimeType.TEXT : MimeType.JSON,
432
+ [SemanticConventions.LLM_SYSTEM]: LLMSystem.OPENAI,
403
433
  [SemanticConventions.LLM_PROVIDER]: getLLMProvider(this),
404
434
  ...getEmbeddingTextAttributes(body),
405
435
  },
@@ -762,6 +792,19 @@ function getChatCompletionLLMOutputMessagesAttributes(chatCompletion: ChatComple
762
792
  }, {} as Attributes);
763
793
  }
764
794
 
795
+ /**
796
+ * Extracts the llm.finish_reason attribute from a chat completion response.
797
+ */
798
+ function getChatCompletionFinishReasonAttributes(chatCompletion: ChatCompletion): Attributes {
799
+ const choice = chatCompletion.choices[0];
800
+ if (!choice?.finish_reason) {
801
+ return {};
802
+ }
803
+ return {
804
+ [SemanticConventions.LLM_FINISH_REASON]: choice.finish_reason,
805
+ };
806
+ }
807
+
765
808
  function getChatCompletionOutputMessageAttributes(message: ChatCompletionMessage): Attributes {
766
809
  const role = message.role;
767
810
  const attributes: Attributes = {
@@ -856,6 +899,7 @@ async function consumeChatCompletionStreamChunks(stream: Stream<ChatCompletionCh
856
899
  // So the final tool and function calls need to be aggregated
857
900
  // across chunks
858
901
  const toolAndFunctionCallAttributes: Attributes = {};
902
+ let finishReason: string | undefined;
859
903
  // The first message is for the assistant response so we start at 1
860
904
  for await (const chunk of stream) {
861
905
  if (chunk.choices.length <= 0) {
@@ -865,6 +909,9 @@ async function consumeChatCompletionStreamChunks(stream: Stream<ChatCompletionCh
865
909
  if (choice.delta.content) {
866
910
  streamResponse += choice.delta.content;
867
911
  }
912
+ if (choice.finish_reason) {
913
+ finishReason = choice.finish_reason;
914
+ }
868
915
  // Accumulate the tool and function call attributes
869
916
  const toolAndFunctionCallAttributesDiff =
870
917
  getToolAndFunctionCallAttributesFromStreamChunk(chunk);
@@ -885,6 +932,9 @@ async function consumeChatCompletionStreamChunks(stream: Stream<ChatCompletionCh
885
932
  [`${messageIndexPrefix}${SemanticConventions.MESSAGE_CONTENT}`]: streamResponse,
886
933
  [`${messageIndexPrefix}${SemanticConventions.MESSAGE_ROLE}`]: "assistant",
887
934
  };
935
+ if (finishReason) {
936
+ attributes[SemanticConventions.LLM_FINISH_REASON] = finishReason;
937
+ }
888
938
  // Add the tool and function call attributes
889
939
  for (const [key, value] of Object.entries(toolAndFunctionCallAttributes)) {
890
940
  attributes[`${messageIndexPrefix}${key}`] = value;
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 = "4.0.6";
2
+ export const VERSION = "4.1.0";