@arizeai/openinference-genai 0.1.10 → 0.3.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,
@@ -66,6 +113,14 @@ const TOOL_EXECUTION_PREFIXES = [
66
113
  ATTR_GEN_AI_TOOL_TYPE,
67
114
  ] as const;
68
115
 
116
+ const ATTR_GEN_AI_SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions" as const;
117
+ const ATTR_GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions" as const;
118
+ const ATTR_GEN_AI_TOOL_CALL_ARGUMENTS = "gen_ai.tool.call.arguments" as const;
119
+ const ATTR_GEN_AI_TOOL_CALL_RESULT = "gen_ai.tool.call.result" as const;
120
+ const ATTR_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens" as const;
121
+ const ATTR_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS =
122
+ "gen_ai.usage.cache_creation.input_tokens" as const;
123
+
69
124
  // Shared part parsing
70
125
  type AnyPart = GenAIInputMessagePart | GenAIOutputMessagePart;
71
126
 
@@ -82,6 +137,70 @@ const isGenAIChatMessage = (value: unknown): value is ChatMessage => {
82
137
  return true;
83
138
  };
84
139
 
140
+ /**
141
+ * Normalize a GenAI tool definition into the OpenAI-style tool schema shape expected by OpenInference.
142
+ * @param toolDefinition - The tool definition to normalize
143
+ * @returns The normalized tool definition, or the original value when it cannot be normalized
144
+ */
145
+ const normalizeToolDefinition = (toolDefinition: unknown): unknown => {
146
+ if (
147
+ typeof toolDefinition !== "object" ||
148
+ toolDefinition === null ||
149
+ Array.isArray(toolDefinition)
150
+ ) {
151
+ return toolDefinition;
152
+ }
153
+
154
+ const definition = toolDefinition as Record<string, unknown>;
155
+ if (typeof definition.function === "object" && definition.function !== null) {
156
+ return definition;
157
+ }
158
+
159
+ if (typeof definition.name !== "string") {
160
+ return definition;
161
+ }
162
+
163
+ const parameters = definition.parameters ?? definition.inputSchema ?? definition.input_schema;
164
+ const normalizedFunction: Record<string, unknown> = { name: definition.name };
165
+ if (typeof definition.description === "string") {
166
+ normalizedFunction.description = definition.description;
167
+ }
168
+ if (parameters != null) {
169
+ normalizedFunction.parameters = parameters;
170
+ }
171
+
172
+ return {
173
+ type: typeof definition.type === "string" ? definition.type : "function",
174
+ function: normalizedFunction,
175
+ };
176
+ };
177
+
178
+ const getSystemInstructionParts = (spanAttributes: Attributes): GenAIInputMessagePart[] | null => {
179
+ const systemInstructions = getString(spanAttributes[ATTR_GEN_AI_SYSTEM_INSTRUCTIONS]);
180
+ if (systemInstructions == null) {
181
+ return null;
182
+ }
183
+
184
+ const parsedInstructions = safelyParseJSON(systemInstructions);
185
+ if (Array.isArray(parsedInstructions)) {
186
+ const parts = parsedInstructions.flatMap((part): GenAIInputMessagePart[] => {
187
+ if (
188
+ typeof part === "object" &&
189
+ part !== null &&
190
+ "type" in part &&
191
+ part.type === "text" &&
192
+ "content" in part
193
+ ) {
194
+ return [{ type: "text", content: toStringContent(part.content) }];
195
+ }
196
+ return [];
197
+ });
198
+ return parts.length > 0 ? parts : null;
199
+ }
200
+
201
+ return [{ type: "text", content: systemInstructions }];
202
+ };
203
+
85
204
  /**
86
205
  * Process genai message parts into openinference attributes
87
206
  *
@@ -142,10 +261,7 @@ const processMessageParts = ({
142
261
  const response = toStringContent(part.response);
143
262
 
144
263
  set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_TOOL_CALL_ID}`, id);
145
- const contentPrefix = `${msgPrefix}${SemanticConventions.MESSAGE_CONTENTS}.${contentIndex}.`;
146
- set(attrs, `${contentPrefix}${SemanticConventions.MESSAGE_CONTENT_TYPE}`, "text");
147
- set(attrs, `${contentPrefix}${SemanticConventions.MESSAGE_CONTENT_TEXT}`, response);
148
- contentIndex += 1;
264
+ set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_CONTENT}`, response);
149
265
  continue;
150
266
  }
151
267
  default: {
@@ -162,6 +278,30 @@ const processMessageParts = ({
162
278
  }
163
279
  };
164
280
 
281
+ /**
282
+ * Set OpenInference attributes for a single tool response message.
283
+ *
284
+ * GenAI can group multiple tool_call_response parts under one tool-role message;
285
+ * each response needs its own OpenInference message to avoid overwriting siblings.
286
+ * @param params - The tool response message mapping parameters
287
+ */
288
+ const setToolCallResponseMessage = ({
289
+ attrs,
290
+ msgPrefix,
291
+ part,
292
+ }: {
293
+ attrs: Attributes;
294
+ msgPrefix: string;
295
+ part: Extract<AnyPart, { type: "tool_call_response" }>;
296
+ }): void => {
297
+ const id = part.id ?? undefined;
298
+ const response = toStringContent(part.response);
299
+
300
+ set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_ROLE}`, "tool");
301
+ set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_TOOL_CALL_ID}`, id);
302
+ set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_CONTENT}`, response);
303
+ };
304
+
165
305
  /**
166
306
  * Convert GenAI span attributes to OpenInference span attributes
167
307
  * @param spanAttributes - The span attributes containing GenAI span attributes to convert
@@ -172,11 +312,15 @@ export const convertGenAISpanAttributesToOpenInferenceSpanAttributes = (
172
312
  ): Attributes => {
173
313
  return merge(
174
314
  mapProviderAndSystem(spanAttributes),
315
+ mapAgentAttributes(spanAttributes),
175
316
  mapModels(spanAttributes),
176
317
  mapSpanKind(spanAttributes),
177
318
  mapInvocationParameters(spanAttributes),
178
319
  mapInputMessages(spanAttributes),
179
320
  mapOutputMessages(spanAttributes),
321
+ mapAgent(spanAttributes),
322
+ mapSystemInstructions(spanAttributes),
323
+ mapToolDefinitions(spanAttributes),
180
324
  mapTokenCounts(spanAttributes),
181
325
  mapToolExecution(spanAttributes),
182
326
  mapInputValue(spanAttributes),
@@ -193,10 +337,28 @@ export const convertGenAISpanAttributesToOpenInferenceSpanAttributes = (
193
337
  export const mapProviderAndSystem = (spanAttributes: Attributes): Attributes => {
194
338
  const attrs: Attributes = {};
195
339
  const provider = getString(spanAttributes[ATTR_GEN_AI_PROVIDER_NAME]);
340
+ set(attrs, SemanticConventions.LLM_SYSTEM, provider);
196
341
  set(attrs, SemanticConventions.LLM_PROVIDER, provider);
197
342
  return attrs;
198
343
  };
199
344
 
345
+ /**
346
+ * Map GenAI agent attributes to OpenInference agent attributes.
347
+ *
348
+ * Currently only gen_ai.agent.name -> agent.name. gen_ai.agent.id and
349
+ * gen_ai.agent.description (which also drive AGENT span-kind detection in mapSpanKind) are
350
+ * intentionally not mapped, as OpenInference has no corresponding agent attributes for them.
351
+ *
352
+ * @param spanAttributes - The GenAI span attributes to read agent attributes from
353
+ * @returns The mapped OpenInference agent attributes
354
+ */
355
+ export const mapAgentAttributes = (spanAttributes: Attributes): Attributes => {
356
+ const attrs: Attributes = {};
357
+ const agentName = getString(spanAttributes[ATTR_GEN_AI_AGENT_NAME]);
358
+ set(attrs, SemanticConventions.AGENT_NAME, agentName);
359
+ return attrs;
360
+ };
361
+
200
362
  /**
201
363
  * Map model name to openinference attributes
202
364
  * @param spanAttributes - The span attributes containing model name to map
@@ -220,11 +382,28 @@ export const mapSpanKind = (spanAttributes: Attributes): Attributes => {
220
382
  const attrs: Attributes = {};
221
383
  // default to LLM for now
222
384
  let spanKind = OpenInferenceSpanKind.LLM;
223
- // detect agent kind
224
- if (AGENT_KIND_PREFIXES.some((prefix) => spanAttributes[prefix])) {
385
+ const operationName = getString(spanAttributes[ATTR_GEN_AI_OPERATION_NAME]);
386
+ const terminalSpanKind =
387
+ operationName !== undefined ? TERMINAL_OPERATION_SPAN_KINDS.get(operationName) : undefined;
388
+ const hasAgentAttributes = AGENT_KIND_PREFIXES.some((prefix) => spanAttributes[prefix]);
389
+ if (terminalSpanKind !== undefined) {
390
+ // Concrete leaf operation (chat/text_completion/generate_content/embeddings/retrieval/
391
+ // execute_tool): authoritative. An agent.* identity here is just context (which agent
392
+ // owns the call) and does not upgrade it to AGENT.
393
+ spanKind = terminalSpanKind;
394
+ } else if (
395
+ hasAgentAttributes ||
396
+ (operationName !== undefined && AGENT_OPERATIONS.has(operationName))
397
+ ) {
398
+ // Control-flow span: an agent.* identity, or an agent-class operation, makes it AGENT —
399
+ // even over a CHAIN-class operation like invoke_workflow.
225
400
  spanKind = OpenInferenceSpanKind.AGENT;
401
+ } else if (operationName !== undefined && CHAIN_OPERATIONS.has(operationName)) {
402
+ // CHAIN-class operation, only reached when no agent identity is present.
403
+ spanKind = OpenInferenceSpanKind.CHAIN;
226
404
  }
227
- // detect tool execution kind
405
+ // Tool attributes force TOOL even when the operation says otherwise — covers tool-execution
406
+ // spans not labeled with operation.name = execute_tool.
228
407
  if (TOOL_EXECUTION_PREFIXES.some((prefix) => spanAttributes[prefix])) {
229
408
  spanKind = OpenInferenceSpanKind.TOOL;
230
409
  }
@@ -284,6 +463,9 @@ export const mapInputValue = (spanAttributes: Attributes): Attributes => {
284
463
  // fallback to deprecated prompt attribute if input is not present
285
464
  input = getString(spanAttributes[ATTR_GEN_AI_PROMPT]);
286
465
  }
466
+ if (!input) {
467
+ input = getString(spanAttributes[ATTR_GEN_AI_INPUT_MESSAGES]);
468
+ }
287
469
  // only set input value and mime type if input is present
288
470
  if (input) {
289
471
  set(attrs, SemanticConventions.INPUT_VALUE, input);
@@ -304,6 +486,9 @@ export const mapOutputValue = (spanAttributes: Attributes): Attributes => {
304
486
  // fallback to deprecated completion attribute if output is not present
305
487
  output = getString(spanAttributes[ATTR_GEN_AI_COMPLETION]);
306
488
  }
489
+ if (!output) {
490
+ output = getString(spanAttributes[ATTR_GEN_AI_OUTPUT_MESSAGES]);
491
+ }
307
492
  // only set output value and mime type if output is present
308
493
  if (output) {
309
494
  set(attrs, SemanticConventions.OUTPUT_VALUE, output);
@@ -312,6 +497,60 @@ export const mapOutputValue = (spanAttributes: Attributes): Attributes => {
312
497
  return attrs;
313
498
  };
314
499
 
500
+ /**
501
+ * Map GenAI agent attributes to OpenInference attributes.
502
+ * @param spanAttributes - The span attributes containing agent details
503
+ * @returns The mapped agent attributes
504
+ */
505
+ export const mapAgent = (spanAttributes: Attributes): Attributes => {
506
+ const attrs: Attributes = {};
507
+ set(attrs, SemanticConventions.AGENT_NAME, getString(spanAttributes[ATTR_GEN_AI_AGENT_NAME]));
508
+ return attrs;
509
+ };
510
+
511
+ /**
512
+ * Map GenAI system instructions into metadata for preservation.
513
+ * @param spanAttributes - The span attributes containing system instructions
514
+ * @returns The mapped metadata attributes
515
+ */
516
+ export const mapSystemInstructions = (spanAttributes: Attributes): Attributes => {
517
+ const attrs: Attributes = {};
518
+ const systemInstructions = getString(spanAttributes[ATTR_GEN_AI_SYSTEM_INSTRUCTIONS]);
519
+ if (systemInstructions) {
520
+ set(attrs, `${SemanticConventions.METADATA}.gen_ai.system_instructions`, systemInstructions);
521
+
522
+ const msgPrefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.0.`;
523
+ set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_ROLE}`, "system");
524
+ processMessageParts({
525
+ attrs,
526
+ msgPrefix,
527
+ parts: getSystemInstructionParts(spanAttributes) ?? [],
528
+ });
529
+ }
530
+ return attrs;
531
+ };
532
+
533
+ /**
534
+ * Map GenAI tool definitions to OpenInference LLM tool schema attributes.
535
+ * @param spanAttributes - The span attributes containing tool definitions
536
+ * @returns The mapped tool schema attributes
537
+ */
538
+ export const mapToolDefinitions = (spanAttributes: Attributes): Attributes => {
539
+ const attrs: Attributes = {};
540
+ const toolDefinitions = getString(spanAttributes[ATTR_GEN_AI_TOOL_DEFINITIONS]);
541
+ const parsedToolDefinitions = safelyParseJSON(toolDefinitions);
542
+ if (Array.isArray(parsedToolDefinitions)) {
543
+ parsedToolDefinitions.forEach((toolDefinition, index) => {
544
+ set(
545
+ attrs,
546
+ `${SemanticConventions.LLM_TOOLS}.${index}.${SemanticConventions.TOOL_JSON_SCHEMA}`,
547
+ safelyJSONStringify(normalizeToolDefinition(toolDefinition)),
548
+ );
549
+ });
550
+ }
551
+ return attrs;
552
+ };
553
+
315
554
  /**
316
555
  * Map input messages to openinference attributes
317
556
  * @param spanAttributes - The span attributes containing input messages to map
@@ -322,13 +561,30 @@ export const mapInputMessages = (spanAttributes: Attributes): Attributes => {
322
561
  const genAIInputMessages = safelyParseJSON(spanAttributes[ATTR_GEN_AI_INPUT_MESSAGES]);
323
562
 
324
563
  if (Array.isArray(genAIInputMessages)) {
325
- (genAIInputMessages as unknown[]).forEach((msg, msgIndex) => {
564
+ let msgIndex = getSystemInstructionParts(spanAttributes) != null ? 1 : 0;
565
+ (genAIInputMessages as unknown[]).forEach((msg) => {
326
566
  if (!isGenAIChatMessage(msg)) return;
567
+
568
+ const toolCallResponses = msg.parts.filter(
569
+ (part): part is Extract<AnyPart, { type: "tool_call_response" }> =>
570
+ part?.type === "tool_call_response",
571
+ );
572
+
573
+ if (msg.role === "tool" && toolCallResponses.length > 0) {
574
+ toolCallResponses.forEach((part) => {
575
+ const msgPrefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.${msgIndex}.`;
576
+ setToolCallResponseMessage({ attrs, msgPrefix, part });
577
+ msgIndex += 1;
578
+ });
579
+ return;
580
+ }
581
+
327
582
  const msgPrefix = `${SemanticConventions.LLM_INPUT_MESSAGES}.${msgIndex}.`;
328
583
  // set the message role
329
584
  set(attrs, `${msgPrefix}${SemanticConventions.MESSAGE_ROLE}`, msg.role);
330
585
  // process and set the rest of the message parts
331
586
  processMessageParts({ attrs, msgPrefix, parts: msg.parts });
587
+ msgIndex += 1;
332
588
  });
333
589
  }
334
590
 
@@ -368,6 +624,10 @@ export const mapTokenCounts = (spanAttributes: Attributes): Attributes => {
368
624
  const attrs: Attributes = {};
369
625
  const inputTokens = getNumber(spanAttributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS]);
370
626
  const outputTokens = getNumber(spanAttributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]);
627
+ const cacheReadInputTokens = getNumber(spanAttributes[ATTR_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS]);
628
+ const cacheCreationInputTokens = getNumber(
629
+ spanAttributes[ATTR_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS],
630
+ );
371
631
  if (typeof inputTokens === "number") {
372
632
  set(attrs, SemanticConventions.LLM_TOKEN_COUNT_PROMPT, inputTokens);
373
633
  }
@@ -377,6 +637,16 @@ export const mapTokenCounts = (spanAttributes: Attributes): Attributes => {
377
637
  if (typeof inputTokens === "number" && typeof outputTokens === "number") {
378
638
  set(attrs, SemanticConventions.LLM_TOKEN_COUNT_TOTAL, inputTokens + outputTokens);
379
639
  }
640
+ if (typeof cacheReadInputTokens === "number") {
641
+ set(attrs, SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ, cacheReadInputTokens);
642
+ }
643
+ if (typeof cacheCreationInputTokens === "number") {
644
+ set(
645
+ attrs,
646
+ SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,
647
+ cacheCreationInputTokens,
648
+ );
649
+ }
380
650
  return attrs;
381
651
  };
382
652
 
@@ -391,11 +661,21 @@ export const mapToolExecution = (spanAttributes: Attributes): Attributes => {
391
661
  const toolName = getString(spanAttributes[ATTR_GEN_AI_TOOL_NAME]);
392
662
  const toolDescription = getString(spanAttributes[ATTR_GEN_AI_TOOL_DESCRIPTION]);
393
663
  const toolCallId = getString(spanAttributes[ATTR_GEN_AI_TOOL_CALL_ID]);
664
+ const toolCallArguments = getString(spanAttributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS]);
665
+ const toolCallResult = getString(spanAttributes[ATTR_GEN_AI_TOOL_CALL_RESULT]);
394
666
  // parse supported tool details
395
- // note: while openinference can track parameters, gen_ai does not provide this information
396
667
  set(attrs, SemanticConventions.TOOL_NAME, toolName);
397
668
  set(attrs, SemanticConventions.TOOL_DESCRIPTION, toolDescription);
398
669
  set(attrs, SemanticConventions.TOOL_CALL_ID, toolCallId);
670
+ set(attrs, SemanticConventions.TOOL_PARAMETERS, toolCallArguments);
671
+ if (toolCallArguments) {
672
+ set(attrs, SemanticConventions.INPUT_VALUE, toolCallArguments);
673
+ set(attrs, SemanticConventions.INPUT_MIME_TYPE, getMimeType(toolCallArguments));
674
+ }
675
+ if (toolCallResult) {
676
+ set(attrs, SemanticConventions.OUTPUT_VALUE, toolCallResult);
677
+ set(attrs, SemanticConventions.OUTPUT_MIME_TYPE, getMimeType(toolCallResult));
678
+ }
399
679
 
400
680
  return attrs;
401
681
  };