@introspection-sdk/introspection-node 0.7.0 → 0.7.2

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 (56) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +24 -111
  3. package/dist/converters/claude.js +4 -4
  4. package/dist/converters/claude.js.map +1 -1
  5. package/dist/converters/gemini.d.ts +1 -1
  6. package/dist/converters/gemini.js +6 -6
  7. package/dist/converters/gemini.js.map +1 -1
  8. package/dist/converters/openai.js +4 -4
  9. package/dist/converters/openai.js.map +1 -1
  10. package/dist/converters/openinference.d.ts.map +1 -1
  11. package/dist/converters/openinference.js +4 -4
  12. package/dist/converters/openinference.js.map +1 -1
  13. package/dist/converters/vercel.js +4 -4
  14. package/dist/converters/vercel.js.map +1 -1
  15. package/dist/otel/anthropic.d.ts +3 -4
  16. package/dist/otel/anthropic.d.ts.map +1 -1
  17. package/dist/otel/anthropic.js +3 -4
  18. package/dist/otel/anthropic.js.map +1 -1
  19. package/dist/otel/claude-hooks.d.ts +2 -2
  20. package/dist/otel/claude-hooks.d.ts.map +1 -1
  21. package/dist/otel/claude-hooks.js +12 -10
  22. package/dist/otel/claude-hooks.js.map +1 -1
  23. package/dist/otel/gemini.d.ts +4 -4
  24. package/dist/otel/gemini.d.ts.map +1 -1
  25. package/dist/otel/gemini.js +1 -2
  26. package/dist/otel/gemini.js.map +1 -1
  27. package/dist/otel/langchain-handler.d.ts.map +1 -1
  28. package/dist/otel/langchain-handler.js +7 -4
  29. package/dist/otel/langchain-handler.js.map +1 -1
  30. package/dist/otel/logs.d.ts.map +1 -1
  31. package/dist/otel/logs.js +2 -1
  32. package/dist/otel/logs.js.map +1 -1
  33. package/dist/otel/mastra-exporter.d.ts.map +1 -1
  34. package/dist/otel/mastra-exporter.js +7 -5
  35. package/dist/otel/mastra-exporter.js.map +1 -1
  36. package/dist/otel/span-processor.d.ts.map +1 -1
  37. package/dist/otel/span-processor.js +3 -2
  38. package/dist/otel/span-processor.js.map +1 -1
  39. package/dist/otel/tracing-processor.d.ts +1 -1
  40. package/dist/otel/tracing-processor.d.ts.map +1 -1
  41. package/dist/otel/tracing-processor.js +13 -7
  42. package/dist/otel/tracing-processor.js.map +1 -1
  43. package/package.json +21 -21
  44. package/src/converters/claude.ts +4 -4
  45. package/src/converters/gemini.ts +6 -6
  46. package/src/converters/openai.ts +4 -4
  47. package/src/converters/openinference.ts +4 -9
  48. package/src/converters/vercel.ts +4 -4
  49. package/src/otel/anthropic.ts +9 -15
  50. package/src/otel/claude-hooks.ts +15 -10
  51. package/src/otel/gemini.ts +6 -12
  52. package/src/otel/langchain-handler.ts +11 -4
  53. package/src/otel/logs.ts +2 -1
  54. package/src/otel/mastra-exporter.ts +7 -5
  55. package/src/otel/span-processor.ts +3 -2
  56. package/src/otel/tracing-processor.ts +13 -7
@@ -121,11 +121,6 @@ function extractResponseId(attrs: Attributes): string | undefined {
121
121
  }
122
122
  }
123
123
 
124
- /** Local extended tool definition that includes the optional `type` field from OpenInference. */
125
- interface OIToolDefinition extends ToolDefinition {
126
- type?: string;
127
- }
128
-
129
124
  function extractSystemInstructions(
130
125
  attrs: Attributes,
131
126
  ): SystemInstruction[] | undefined {
@@ -160,7 +155,7 @@ function extractSystemInstructions(
160
155
  function extractToolDefinitions(
161
156
  attrs: Attributes,
162
157
  ): ToolDefinition[] | undefined {
163
- const tools: OIToolDefinition[] = [];
158
+ const tools: ToolDefinition[] = [];
164
159
 
165
160
  for (const [key, value] of Object.entries(attrs)) {
166
161
  const match = key.match(/^llm\.tools\.(\d+)\.tool\.json_schema$/);
@@ -315,7 +310,7 @@ function extractOutputMessages(attrs: Attributes): OutputMessage[] | undefined {
315
310
  const parsed = JSON.parse(toolArgs) as Record<string, unknown>;
316
311
  if (typeof parsed.thinking === "string" && parsed.thinking) {
317
312
  const reasoningPart: ReasoningPart = {
318
- type: "thinking",
313
+ type: "reasoning",
319
314
  content: parsed.thinking,
320
315
  };
321
316
  parts.push(reasoningPart);
@@ -385,8 +380,8 @@ export function convertOpenInferenceToGenAI(
385
380
  const model = extractModel(attrs);
386
381
  if (model) result.requestModel = model;
387
382
 
388
- const system = extractSystem(attrs);
389
- if (system) result.system = system;
383
+ const providerName = extractSystem(attrs);
384
+ if (providerName) result.providerName = providerName;
390
385
 
391
386
  const responseId = extractResponseId(attrs);
392
387
  if (responseId) result.responseId = responseId;
@@ -90,14 +90,14 @@ export function convertVercelAIToGenAI(
90
90
  result["gen_ai.conversation.id"] = metadataConvId;
91
91
  }
92
92
 
93
- // gen_ai.request.model and gen_ai.system from ai.model.*
93
+ // gen_ai.request.model and gen_ai.provider.name from ai.model.*
94
94
  const modelId = attrs["ai.model.id"];
95
95
  if (typeof modelId === "string" && !attrs["gen_ai.request.model"]) {
96
96
  result["gen_ai.request.model"] = modelId;
97
97
  }
98
98
  const modelProvider = attrs["ai.model.provider"];
99
- if (typeof modelProvider === "string" && !attrs["gen_ai.system"]) {
100
- result["gen_ai.system"] = modelProvider;
99
+ if (typeof modelProvider === "string" && !attrs["gen_ai.provider.name"]) {
100
+ result["gen_ai.provider.name"] = modelProvider;
101
101
  }
102
102
 
103
103
  // Vercel AI SDK stores prompt data in two formats:
@@ -141,7 +141,7 @@ export function convertVercelAIToGenAI(
141
141
  if (typeof responseText === "string" && responseText) {
142
142
  const parts: GenAIPart[] = [];
143
143
  if (typeof responseReasoning === "string" && responseReasoning) {
144
- parts.push({ type: "thinking", content: responseReasoning });
144
+ parts.push({ type: "reasoning", content: responseReasoning });
145
145
  }
146
146
  parts.push({ type: "text", content: responseText });
147
147
  result["gen_ai.output.messages"] = JSON.stringify([
@@ -32,8 +32,7 @@ import {
32
32
  SpanKind,
33
33
  SpanStatusCode,
34
34
  } from "@opentelemetry/api";
35
- import type { Tracer, Span } from "@opentelemetry/api";
36
- import type { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
35
+ import type { Tracer, Span, TracerProvider } from "@opentelemetry/api";
37
36
 
38
37
  // ---------------------------------------------------------------------------
39
38
  // Converters
@@ -51,8 +50,8 @@ function blockToParts(block: Record<string, unknown>): MessagePart[] {
51
50
 
52
51
  if (bt === "thinking") {
53
52
  const part: ReasoningPart = {
54
- type: "thinking",
55
- content: (block.thinking as string) || undefined,
53
+ type: "reasoning",
54
+ content: (block.thinking as string) || REDACTED_THINKING_CONTENT,
56
55
  signature: (block.signature as string) || undefined,
57
56
  provider_name: "anthropic",
58
57
  };
@@ -62,11 +61,11 @@ function blockToParts(block: Record<string, unknown>): MessagePart[] {
62
61
  if (bt === "redacted_thinking") {
63
62
  return [
64
63
  {
65
- type: "thinking",
64
+ type: "reasoning",
66
65
  content: REDACTED_THINKING_CONTENT,
67
66
  signature: (block.data as string) || undefined,
68
67
  provider_name: "anthropic",
69
- } as ReasoningPart,
68
+ },
70
69
  ];
71
70
  }
72
71
 
@@ -169,7 +168,6 @@ function startSpan(
169
168
  const span = tracer.startSpan("chat", {
170
169
  kind: SpanKind.CLIENT,
171
170
  attributes: {
172
- "gen_ai.system": "anthropic",
173
171
  "gen_ai.provider.name": "anthropic",
174
172
  "gen_ai.operation.name": "chat",
175
173
  "gen_ai.request.model": model,
@@ -535,7 +533,7 @@ export class AnthropicInstrumentor {
535
533
  private conversationId: string | undefined;
536
534
 
537
535
  instrument(opts: {
538
- tracerProvider?: BasicTracerProvider;
536
+ tracerProvider?: TracerProvider;
539
537
  /** The Anthropic client instance to instrument. */
540
538
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
541
539
  client: any;
@@ -557,9 +555,7 @@ export class AnthropicInstrumentor {
557
555
  this.conversationId = opts.conversationId ?? crypto.randomUUID();
558
556
 
559
557
  const provider = opts.tracerProvider ?? trace.getTracerProvider();
560
- this.tracer = (provider as BasicTracerProvider).getTracer(
561
- "introspection-anthropic",
562
- );
558
+ this.tracer = provider.getTracer("introspection-anthropic");
563
559
 
564
560
  const origCreate = client.messages.create.bind(client.messages);
565
561
  this.patchedClients.push({ client, originalCreate: origCreate });
@@ -582,7 +578,7 @@ export class AnthropicInstrumentor {
582
578
  instrumentClass(opts: {
583
579
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
584
580
  anthropic: any;
585
- tracerProvider?: BasicTracerProvider;
581
+ tracerProvider?: TracerProvider;
586
582
  conversationId?: string;
587
583
  }): void {
588
584
  const Messages = opts.anthropic?.Messages;
@@ -596,9 +592,7 @@ export class AnthropicInstrumentor {
596
592
 
597
593
  this.conversationId = opts.conversationId ?? crypto.randomUUID();
598
594
  const provider = opts.tracerProvider ?? trace.getTracerProvider();
599
- this.tracer = (provider as BasicTracerProvider).getTracer(
600
- "introspection-anthropic",
601
- );
595
+ this.tracer = provider.getTracer("introspection-anthropic");
602
596
 
603
597
  const origCreate = proto.create;
604
598
  const patched = buildTracedCreate(
@@ -295,8 +295,8 @@ export interface IntrospectionClaudeHooksOptions {
295
295
  * Provides instrumented hooks for Claude Agent SDK.
296
296
  *
297
297
  * Creates OpenTelemetry spans with gen_ai.* semantic convention attributes:
298
- * - Session spans: gen_ai.system, gen_ai.request.model
299
- * - Tool spans: gen_ai.tool.name, gen_ai.tool.input, gen_ai.tool.output
298
+ * - Session spans: gen_ai.provider.name, gen_ai.request.model
299
+ * - Tool spans: gen_ai.tool.name, gen_ai.tool.call.arguments, gen_ai.tool.call.result
300
300
  * - Subagent spans: gen_ai.agent.name
301
301
  *
302
302
  * Usage tracking is captured from streamed messages via recordUsage().
@@ -689,7 +689,7 @@ export class IntrospectionClaudeHooks {
689
689
  logger.debug(`Starting session span from init message: ${sessionId}`);
690
690
  const ids = this._resolveIdentity(sessionId);
691
691
  const attrs: Attributes = {
692
- "gen_ai.system": "anthropic",
692
+ "gen_ai.provider.name": "anthropic",
693
693
  "gen_ai.operation.name": "chat",
694
694
  "gen_ai.agent.name": ids.agentName,
695
695
  "gen_ai.conversation.id": ids.conversationId,
@@ -835,7 +835,7 @@ export class IntrospectionClaudeHooks {
835
835
  // when the caller wrapped the query in withAgent() / withConversation().
836
836
  const ids = this._resolveIdentity(sessionInput.session_id);
837
837
  const attrs: Attributes = {
838
- "gen_ai.system": "anthropic",
838
+ "gen_ai.provider.name": "anthropic",
839
839
  "gen_ai.operation.name": "chat",
840
840
  "gen_ai.agent.name": ids.agentName,
841
841
  "gen_ai.conversation.id": ids.conversationId,
@@ -952,11 +952,14 @@ export class IntrospectionClaudeHooks {
952
952
  : undefined;
953
953
 
954
954
  const span = this._tracer.startSpan(
955
- `tool.${toolInput.tool_name}`,
955
+ `execute_tool ${toolInput.tool_name}`,
956
956
  {
957
957
  kind: SpanKind.INTERNAL,
958
958
  attributes: {
959
+ "gen_ai.operation.name": "execute_tool",
959
960
  "gen_ai.tool.name": toolInput.tool_name,
961
+ "gen_ai.tool.type": "function",
962
+ "gen_ai.tool.call.id": toolInput.tool_use_id,
960
963
  "claude.session_id": toolInput.session_id,
961
964
  "claude.tool_use_id": toolInput.tool_use_id,
962
965
  },
@@ -967,11 +970,14 @@ export class IntrospectionClaudeHooks {
967
970
  if (toolInput.tool_input) {
968
971
  try {
969
972
  span.setAttribute(
970
- "gen_ai.tool.input",
973
+ "gen_ai.tool.call.arguments",
971
974
  JSON.stringify(toolInput.tool_input),
972
975
  );
973
976
  } catch {
974
- span.setAttribute("gen_ai.tool.input", String(toolInput.tool_input));
977
+ span.setAttribute(
978
+ "gen_ai.tool.call.arguments",
979
+ String(toolInput.tool_input),
980
+ );
975
981
  }
976
982
  }
977
983
 
@@ -998,17 +1004,16 @@ export class IntrospectionClaudeHooks {
998
1004
  if (toolInput.tool_response !== undefined) {
999
1005
  try {
1000
1006
  span.setAttribute(
1001
- "gen_ai.tool.output",
1007
+ "gen_ai.tool.call.result",
1002
1008
  JSON.stringify(toolInput.tool_response),
1003
1009
  );
1004
1010
  } catch {
1005
1011
  span.setAttribute(
1006
- "gen_ai.tool.output",
1012
+ "gen_ai.tool.call.result",
1007
1013
  String(toolInput.tool_response),
1008
1014
  );
1009
1015
  }
1010
1016
  }
1011
- span.setStatus({ code: SpanStatusCode.OK });
1012
1017
  span.end();
1013
1018
  this._toolSpans.delete(toolInput.tool_use_id);
1014
1019
  }
@@ -2,7 +2,7 @@
2
2
  * Lightweight Google Gemini (`@google/genai`) instrumentor for Introspection SDK.
3
3
  *
4
4
  * Captures the full Gemini response including per-part `thoughtSignature`
5
- * payloads (Gemini 3.x extended thinking) as gen_ai `thinking` parts with
5
+ * payloads (Gemini 3.x extended thinking) as gen_ai `reasoning` parts with
6
6
  * `signature` set — mirroring how the {@link AnthropicInstrumentor} captures
7
7
  * Anthropic thinking blocks.
8
8
  *
@@ -23,8 +23,7 @@
23
23
  */
24
24
 
25
25
  import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
26
- import type { Tracer, Span } from "@opentelemetry/api";
27
- import type { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
26
+ import type { Tracer, Span, TracerProvider } from "@opentelemetry/api";
28
27
 
29
28
  import type { InputMessage, OutputMessage } from "@introspection-sdk/types";
30
29
  import { providerCostAttributes } from "@introspection-sdk/types";
@@ -64,7 +63,6 @@ function startSpan(
64
63
  return tracer.startSpan("chat", {
65
64
  kind: SpanKind.CLIENT,
66
65
  attributes: {
67
- "gen_ai.system": GEMINI_PROVIDER_NAME,
68
66
  "gen_ai.provider.name": GEMINI_PROVIDER_NAME,
69
67
  "gen_ai.operation.name": "chat",
70
68
  "gen_ai.request.model": model,
@@ -374,7 +372,7 @@ export class GeminiInstrumentor {
374
372
  private conversationId: string | undefined;
375
373
 
376
374
  instrument(opts: {
377
- tracerProvider?: BasicTracerProvider;
375
+ tracerProvider?: TracerProvider;
378
376
  /** A `@google/genai` `GoogleGenAI` client instance. */
379
377
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
378
  client: any;
@@ -394,9 +392,7 @@ export class GeminiInstrumentor {
394
392
  this.conversationId = opts.conversationId ?? crypto.randomUUID();
395
393
 
396
394
  const provider = opts.tracerProvider ?? trace.getTracerProvider();
397
- this.tracer = (provider as BasicTracerProvider).getTracer(
398
- "introspection-gemini",
399
- );
395
+ this.tracer = provider.getTracer("introspection-gemini");
400
396
  const tracer = this.tracer;
401
397
  const conversationId = this.conversationId;
402
398
 
@@ -436,7 +432,7 @@ export class GeminiInstrumentor {
436
432
  instrumentClass(opts: {
437
433
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
438
434
  genai: any;
439
- tracerProvider?: BasicTracerProvider;
435
+ tracerProvider?: TracerProvider;
440
436
  conversationId?: string;
441
437
  }): void {
442
438
  const proto = opts.genai?.Models?.prototype;
@@ -449,9 +445,7 @@ export class GeminiInstrumentor {
449
445
 
450
446
  this.conversationId = opts.conversationId ?? crypto.randomUUID();
451
447
  const provider = opts.tracerProvider ?? trace.getTracerProvider();
452
- this.tracer = (provider as BasicTracerProvider).getTracer(
453
- "introspection-gemini",
454
- );
448
+ this.tracer = provider.getTracer("introspection-gemini");
455
449
  const conversationId = this.conversationId;
456
450
 
457
451
  const origGenerate = proto.generateContentInternal;
@@ -317,7 +317,7 @@ export class IntrospectionCallbackHandler extends BaseCallbackHandler {
317
317
 
318
318
  const provider = this._extractProvider(llm);
319
319
  if (provider) {
320
- otelSpan.setAttribute("gen_ai.system", provider);
320
+ otelSpan.setAttribute("gen_ai.provider.name", provider);
321
321
  }
322
322
 
323
323
  const invocationParams = extraParams?.["invocation_params"] as
@@ -571,15 +571,22 @@ export class IntrospectionCallbackHandler extends BaseCallbackHandler {
571
571
  const toolName =
572
572
  runName || tool?.name || tool?.id?.[tool.id.length - 1] || "tool";
573
573
 
574
- const otelSpan = this._createChildSpan(toolName, runId, parentRunId);
574
+ const otelSpan = this._createChildSpan(
575
+ `execute_tool ${toolName}`,
576
+ runId,
577
+ parentRunId,
578
+ );
575
579
  this._spans.set(runId, otelSpan);
576
580
 
577
581
  const conversationId = this._getConversationId(metadata);
578
582
  this._setRootConversationId(conversationId, runId, parentRunId);
579
583
 
584
+ otelSpan.setAttribute("gen_ai.operation.name", "execute_tool");
580
585
  otelSpan.setAttribute("gen_ai.tool.name", toolName);
586
+ otelSpan.setAttribute("gen_ai.tool.type", "function");
587
+ otelSpan.setAttribute("gen_ai.tool.call.id", runId);
581
588
  otelSpan.setAttribute("openinference.span.kind", "TOOL");
582
- otelSpan.setAttribute("gen_ai.tool.input", input);
589
+ otelSpan.setAttribute("gen_ai.tool.call.arguments", input);
583
590
  otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
584
591
  }
585
592
 
@@ -591,7 +598,7 @@ export class IntrospectionCallbackHandler extends BaseCallbackHandler {
591
598
 
592
599
  if (output != null) {
593
600
  otelSpan.setAttribute(
594
- "gen_ai.tool.output",
601
+ "gen_ai.tool.call.result",
595
602
  typeof output === "string" ? output : JSON.stringify(output),
596
603
  );
597
604
  }
package/src/otel/logs.ts CHANGED
@@ -113,7 +113,8 @@ export class IntrospectionLogs {
113
113
  }),
114
114
  );
115
115
 
116
- const processor = new BatchLogRecordProcessor(exporter, {
116
+ const processor = new BatchLogRecordProcessor({
117
+ exporter,
117
118
  maxQueueSize: options.maxBatchSize ?? 100,
118
119
  scheduledDelayMillis: options.flushInterval ?? 5000,
119
120
  });
@@ -358,7 +358,7 @@ export class IntrospectionMastraExporter extends BaseExporter {
358
358
  otelSpan.setAttribute("openinference.span.kind", "LLM");
359
359
 
360
360
  if (model) otelSpan.setAttribute("gen_ai.request.model", model);
361
- if (provider) otelSpan.setAttribute("gen_ai.system", provider);
361
+ if (provider) otelSpan.setAttribute("gen_ai.provider.name", provider);
362
362
  if (responseModel)
363
363
  otelSpan.setAttribute("gen_ai.response.model", responseModel);
364
364
  if (responseId) otelSpan.setAttribute("gen_ai.response.id", responseId);
@@ -481,14 +481,16 @@ export class IntrospectionMastraExporter extends BaseExporter {
481
481
  const conversationId = this._getConversationId(span);
482
482
  const toolName = span.entityName || span.name || "tool";
483
483
 
484
- const otelSpan = this._createSpan(span, toolName);
484
+ const otelSpan = this._createSpan(span, `execute_tool ${toolName}`);
485
+ otelSpan.setAttribute("gen_ai.operation.name", "execute_tool");
485
486
  otelSpan.setAttribute("gen_ai.tool.name", toolName);
487
+ otelSpan.setAttribute("gen_ai.tool.type", "function");
486
488
  otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
487
489
  otelSpan.setAttribute("openinference.span.kind", "TOOL");
488
490
 
489
491
  if (span.input != null) {
490
492
  otelSpan.setAttribute(
491
- "gen_ai.tool.input",
493
+ "gen_ai.tool.call.arguments",
492
494
  typeof span.input === "string"
493
495
  ? span.input
494
496
  : JSON.stringify(span.input),
@@ -496,7 +498,7 @@ export class IntrospectionMastraExporter extends BaseExporter {
496
498
  }
497
499
  if (span.output != null) {
498
500
  otelSpan.setAttribute(
499
- "gen_ai.tool.output",
501
+ "gen_ai.tool.call.result",
500
502
  typeof span.output === "string"
501
503
  ? span.output
502
504
  : JSON.stringify(span.output),
@@ -600,7 +602,7 @@ export class IntrospectionMastraExporter extends BaseExporter {
600
602
  if (Array.isArray(out.reasoning)) {
601
603
  for (const r of out.reasoning as Record<string, unknown>[]) {
602
604
  const text = (r.text || r.content || "") as string;
603
- if (text) parts.push({ type: "thinking", content: text });
605
+ if (text) parts.push({ type: "reasoning", content: text });
604
606
  }
605
607
  }
606
608
 
@@ -308,7 +308,7 @@ export class IntrospectionSpanProcessor implements SpanProcessor {
308
308
  const hasGenAi =
309
309
  isOI ||
310
310
  isVercel ||
311
- span.attributes["gen_ai.system"] != null ||
311
+ span.attributes["gen_ai.provider.name"] != null ||
312
312
  span.attributes["gen_ai.operation.name"] != null ||
313
313
  span.attributes["gen_ai.request.model"] != null ||
314
314
  span.attributes["gen_ai.input.messages"] != null ||
@@ -333,7 +333,7 @@ export class IntrospectionSpanProcessor implements SpanProcessor {
333
333
  if (
334
334
  key === "gen_ai.output.messages" &&
335
335
  typeof value === "string" &&
336
- value.includes('"thinking"')
336
+ value.includes('"reasoning"')
337
337
  ) {
338
338
  attrs[key] = value;
339
339
  } else if (attrs[key] === undefined) {
@@ -351,6 +351,7 @@ export class IntrospectionSpanProcessor implements SpanProcessor {
351
351
  } else {
352
352
  attrs = { ...span.attributes };
353
353
  }
354
+ delete attrs["gen_ai.system"];
354
355
 
355
356
  // Read baggage from active context
356
357
  const baggage = propagation.getBaggage(otelContext.active());
@@ -97,7 +97,7 @@ export interface IntrospectionTracingProcessorOptions {
97
97
  *
98
98
  * Extracts OTel Gen AI semantic convention attributes from span data:
99
99
  * - Agent spans: `gen_ai.agent.name`, `gen_ai.tool.definitions`, `gen_ai.agent.handoffs`
100
- * - Function spans: `gen_ai.tool.name`, `gen_ai.tool.input`, `gen_ai.tool.output`
100
+ * - Function spans: `gen_ai.tool.name`, `gen_ai.tool.call.arguments`, `gen_ai.tool.call.result`
101
101
  * - Response spans: `gen_ai.input/output.messages`, `gen_ai.usage.*`, `gen_ai.request.model`
102
102
  * - Generation spans: `gen_ai.request.model`, `gen_ai.usage.*`
103
103
  * - Handoff spans: `gen_ai.handoff.from_agent`, `gen_ai.handoff.to_agent`
@@ -336,12 +336,15 @@ export class IntrospectionTracingProcessor implements TracingProcessor {
336
336
  */
337
337
  private _processAgentSpan(otelSpan: OtelSpan, spanData: AgentSpanData): void {
338
338
  otelSpan.setAttribute("gen_ai.agent.name", spanData.name);
339
- otelSpan.setAttribute("gen_ai.system", "openai");
339
+ otelSpan.setAttribute("gen_ai.provider.name", "openai");
340
340
  otelSpan.setAttribute("openinference.span.kind", "AGENT");
341
341
 
342
342
  if (spanData.tools) {
343
343
  // Wrap tool names as objects for ClickHouse Array(JSON) compatibility
344
- const toolDefs = spanData.tools.map((t: string) => ({ name: t }));
344
+ const toolDefs = spanData.tools.map((t: string) => ({
345
+ type: "function",
346
+ name: t,
347
+ }));
345
348
  otelSpan.setAttribute(
346
349
  "gen_ai.tool.definitions",
347
350
  JSON.stringify(toolDefs),
@@ -367,15 +370,18 @@ export class IntrospectionTracingProcessor implements TracingProcessor {
367
370
  otelSpan: OtelSpan,
368
371
  spanData: FunctionSpanData,
369
372
  ): void {
373
+ otelSpan.updateName(`execute_tool ${spanData.name}`);
374
+ otelSpan.setAttribute("gen_ai.operation.name", "execute_tool");
370
375
  otelSpan.setAttribute("gen_ai.tool.name", spanData.name);
376
+ otelSpan.setAttribute("gen_ai.tool.type", "function");
371
377
  otelSpan.setAttribute("openinference.span.kind", "TOOL");
372
378
 
373
379
  if (spanData.input) {
374
- otelSpan.setAttribute("gen_ai.tool.input", spanData.input);
380
+ otelSpan.setAttribute("gen_ai.tool.call.arguments", spanData.input);
375
381
  }
376
382
 
377
383
  if (spanData.output) {
378
- otelSpan.setAttribute("gen_ai.tool.output", String(spanData.output));
384
+ otelSpan.setAttribute("gen_ai.tool.call.result", String(spanData.output));
379
385
  }
380
386
  }
381
387
 
@@ -388,7 +394,7 @@ export class IntrospectionTracingProcessor implements TracingProcessor {
388
394
  spanData: ResponseSpanData,
389
395
  ): void {
390
396
  otelSpan.setAttribute("gen_ai.operation.name", "chat");
391
- otelSpan.setAttribute("gen_ai.system", "openai");
397
+ otelSpan.setAttribute("gen_ai.provider.name", "openai");
392
398
  otelSpan.setAttribute("openinference.span.kind", "LLM");
393
399
 
394
400
  // Response ID (from span_data directly)
@@ -494,7 +500,7 @@ export class IntrospectionTracingProcessor implements TracingProcessor {
494
500
  spanData: GenerationSpanData,
495
501
  ): void {
496
502
  otelSpan.setAttribute("gen_ai.operation.name", "chat");
497
- otelSpan.setAttribute("gen_ai.system", "openai");
503
+ otelSpan.setAttribute("gen_ai.provider.name", "openai");
498
504
  otelSpan.setAttribute("openinference.span.kind", "LLM");
499
505
 
500
506
  if (spanData.model) {