@arizeai/openinference-genai 0.1.9 → 0.2.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/src/attributes.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  ATTR_GEN_AI_AGENT_NAME,
6
6
  ATTR_GEN_AI_COMPLETION,
7
7
  ATTR_GEN_AI_INPUT_MESSAGES,
8
+ ATTR_GEN_AI_OPERATION_NAME,
8
9
  ATTR_GEN_AI_OUTPUT_MESSAGES,
9
10
  ATTR_GEN_AI_PROMPT,
10
11
  ATTR_GEN_AI_PROVIDER_NAME,
@@ -24,6 +25,15 @@ import {
24
25
  ATTR_GEN_AI_TOOL_TYPE,
25
26
  ATTR_GEN_AI_USAGE_INPUT_TOKENS,
26
27
  ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
28
+ GEN_AI_OPERATION_NAME_VALUE_CHAT,
29
+ GEN_AI_OPERATION_NAME_VALUE_CREATE_AGENT,
30
+ GEN_AI_OPERATION_NAME_VALUE_EMBEDDINGS,
31
+ GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL,
32
+ GEN_AI_OPERATION_NAME_VALUE_GENERATE_CONTENT,
33
+ GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT,
34
+ GEN_AI_OPERATION_NAME_VALUE_INVOKE_WORKFLOW,
35
+ GEN_AI_OPERATION_NAME_VALUE_RETRIEVAL,
36
+ GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION,
27
37
  } from "@opentelemetry/semantic-conventions/incubating";
28
38
 
29
39
  import {
@@ -59,6 +69,43 @@ const AGENT_KIND_PREFIXES = [
59
69
  ATTR_GEN_AI_AGENT_DESCRIPTION,
60
70
  ] as const;
61
71
 
72
+ // "plan" was added to the OTel GenAI semantic conventions
73
+ // (open-telemetry/semantic-conventions-genai#97, merged 2026-05-11) but is not yet released,
74
+ // so @opentelemetry/semantic-conventions exports no constant for it — hence the literal.
75
+ const GEN_AI_OPERATION_NAME_VALUE_PLAN = "plan";
76
+
77
+ // gen_ai.operation.name values that classify a span by OpenInference span kind. Mirrors
78
+ // AGENT_KIND_PREFIXES / TOOL_EXECUTION_PREFIXES. These operations are recognized by value
79
+ // because the conversion target — OpenInferenceSpanKind — is a closed enum with no per-
80
+ // operation member, so each operation must be mapped into our vocabulary; the original
81
+ // operation name is not preserved on the OI span.
82
+ const AGENT_OPERATIONS: ReadonlySet<string> = new Set([
83
+ GEN_AI_OPERATION_NAME_VALUE_CREATE_AGENT,
84
+ GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT,
85
+ GEN_AI_OPERATION_NAME_VALUE_PLAN,
86
+ ]);
87
+
88
+ // "invoke_workflow" maps to CHAIN rather than AGENT: a workflow is a fixed orchestration of
89
+ // steps (a chain), whereas AGENT is reserved for autonomous, LLM-driven control flow. An
90
+ // explicit agent identity (agent.* attribute) still takes precedence and yields AGENT; see
91
+ // mapSpanKind.
92
+ const CHAIN_OPERATIONS: ReadonlySet<string> = new Set([
93
+ GEN_AI_OPERATION_NAME_VALUE_INVOKE_WORKFLOW,
94
+ ]);
95
+
96
+ // Terminal operations describe a single concrete model/data call (a leaf in the span tree),
97
+ // each with a direct OpenInference span kind. operation.name is authoritative for these — an
98
+ // agent.* identity on them is context (which agent owns the call), not a signal to upgrade
99
+ // the span to AGENT (see mapSpanKind).
100
+ const TERMINAL_OPERATION_SPAN_KINDS: ReadonlyMap<string, OpenInferenceSpanKind> = new Map([
101
+ [GEN_AI_OPERATION_NAME_VALUE_CHAT, OpenInferenceSpanKind.LLM],
102
+ [GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, OpenInferenceSpanKind.LLM],
103
+ [GEN_AI_OPERATION_NAME_VALUE_GENERATE_CONTENT, OpenInferenceSpanKind.LLM],
104
+ [GEN_AI_OPERATION_NAME_VALUE_EMBEDDINGS, OpenInferenceSpanKind.EMBEDDING],
105
+ [GEN_AI_OPERATION_NAME_VALUE_RETRIEVAL, OpenInferenceSpanKind.RETRIEVER],
106
+ [GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL, OpenInferenceSpanKind.TOOL],
107
+ ]);
108
+
62
109
  const TOOL_EXECUTION_PREFIXES = [
63
110
  ATTR_GEN_AI_TOOL_NAME,
64
111
  ATTR_GEN_AI_TOOL_DESCRIPTION,
@@ -172,6 +219,7 @@ export const convertGenAISpanAttributesToOpenInferenceSpanAttributes = (
172
219
  ): Attributes => {
173
220
  return merge(
174
221
  mapProviderAndSystem(spanAttributes),
222
+ mapAgentAttributes(spanAttributes),
175
223
  mapModels(spanAttributes),
176
224
  mapSpanKind(spanAttributes),
177
225
  mapInvocationParameters(spanAttributes),
@@ -197,6 +245,23 @@ export const mapProviderAndSystem = (spanAttributes: Attributes): Attributes =>
197
245
  return attrs;
198
246
  };
199
247
 
248
+ /**
249
+ * Map GenAI agent attributes to OpenInference agent attributes.
250
+ *
251
+ * Currently only gen_ai.agent.name -> agent.name. gen_ai.agent.id and
252
+ * gen_ai.agent.description (which also drive AGENT span-kind detection in mapSpanKind) are
253
+ * intentionally not mapped, as OpenInference has no corresponding agent attributes for them.
254
+ *
255
+ * @param spanAttributes - The GenAI span attributes to read agent attributes from
256
+ * @returns The mapped OpenInference agent attributes
257
+ */
258
+ export const mapAgentAttributes = (spanAttributes: Attributes): Attributes => {
259
+ const attrs: Attributes = {};
260
+ const agentName = getString(spanAttributes[ATTR_GEN_AI_AGENT_NAME]);
261
+ set(attrs, SemanticConventions.AGENT_NAME, agentName);
262
+ return attrs;
263
+ };
264
+
200
265
  /**
201
266
  * Map model name to openinference attributes
202
267
  * @param spanAttributes - The span attributes containing model name to map
@@ -220,11 +285,28 @@ export const mapSpanKind = (spanAttributes: Attributes): Attributes => {
220
285
  const attrs: Attributes = {};
221
286
  // default to LLM for now
222
287
  let spanKind = OpenInferenceSpanKind.LLM;
223
- // detect agent kind
224
- if (AGENT_KIND_PREFIXES.some((prefix) => spanAttributes[prefix])) {
288
+ const operationName = getString(spanAttributes[ATTR_GEN_AI_OPERATION_NAME]);
289
+ const terminalSpanKind =
290
+ operationName !== undefined ? TERMINAL_OPERATION_SPAN_KINDS.get(operationName) : undefined;
291
+ const hasAgentAttributes = AGENT_KIND_PREFIXES.some((prefix) => spanAttributes[prefix]);
292
+ if (terminalSpanKind !== undefined) {
293
+ // Concrete leaf operation (chat/text_completion/generate_content/embeddings/retrieval/
294
+ // execute_tool): authoritative. An agent.* identity here is just context (which agent
295
+ // owns the call) and does not upgrade it to AGENT.
296
+ spanKind = terminalSpanKind;
297
+ } else if (
298
+ hasAgentAttributes ||
299
+ (operationName !== undefined && AGENT_OPERATIONS.has(operationName))
300
+ ) {
301
+ // Control-flow span: an agent.* identity, or an agent-class operation, makes it AGENT —
302
+ // even over a CHAIN-class operation like invoke_workflow.
225
303
  spanKind = OpenInferenceSpanKind.AGENT;
304
+ } else if (operationName !== undefined && CHAIN_OPERATIONS.has(operationName)) {
305
+ // CHAIN-class operation, only reached when no agent identity is present.
306
+ spanKind = OpenInferenceSpanKind.CHAIN;
226
307
  }
227
- // detect tool execution kind
308
+ // Tool attributes force TOOL even when the operation says otherwise — covers tool-execution
309
+ // spans not labeled with operation.name = execute_tool.
228
310
  if (TOOL_EXECUTION_PREFIXES.some((prefix) => spanAttributes[prefix])) {
229
311
  spanKind = OpenInferenceSpanKind.TOOL;
230
312
  }