toad-eye 2.3.0 → 2.4.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/dist/agent.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentStepInput, AgentQueryOptions } from "./types/index.js";
1
+ import type { AgentStepInput, AgentQueryOptions, AgentQueryInput } from "./types/index.js";
2
2
  /**
3
3
  * Record a single agent step as a child span (ReAct pattern).
4
4
  * Supports: think, act, observe, answer, handoff.
@@ -7,6 +7,9 @@ declare function traceAgentStep(input: AgentStepInput): void;
7
7
  /**
8
8
  * Wrap an entire agent query in a parent span with ReAct tracing.
9
9
  *
10
+ * Accepts either a string query (backward-compatible) or an object with
11
+ * agent metadata per OTel GenAI spec (agentName, agentId).
12
+ *
10
13
  * Supports multi-agent via nesting — child traceAgentQuery calls
11
14
  * automatically become child spans in Jaeger.
12
15
  *
@@ -15,18 +18,27 @@ declare function traceAgentStep(input: AgentStepInput): void;
15
18
  *
16
19
  * @example
17
20
  * ```ts
21
+ * // Simple form (backward-compatible)
22
+ * await traceAgentQuery('Is anything dangerous?', async (step) => { ... });
23
+ *
24
+ * // Object form with agent metadata (OTel GenAI spec)
25
+ * await traceAgentQuery(
26
+ * { query: 'Is anything dangerous?', agentName: 'space-monitor', agentId: 'agent-001' },
27
+ * async (step) => { ... }
28
+ * );
29
+ *
18
30
  * // Multi-agent: orchestrator delegates to specialist
19
- * await traceAgentQuery('orchestrator', async (step) => {
31
+ * await traceAgentQuery({ query: 'Analyze data', agentName: 'orchestrator' }, async (step) => {
20
32
  * step({ type: 'think', stepNumber: 1, content: 'Need domain expert' });
21
33
  * step({ type: 'handoff', stepNumber: 2, toAgent: 'specialist', handoffReason: 'domain expertise' });
22
34
  *
23
- * const result = await traceAgentQuery('specialist', async (step) => {
24
- * step({ type: 'act', stepNumber: 1, toolName: 'analyze' });
35
+ * const result = await traceAgentQuery({ query: 'Analyze', agentName: 'specialist' }, async (step) => {
36
+ * step({ type: 'act', stepNumber: 1, toolName: 'analyze', toolType: 'function' });
25
37
  * step({ type: 'answer', stepNumber: 2, content: 'analysis complete' });
26
38
  * return { answer: 'done' };
27
39
  * });
28
40
  * });
29
41
  * ```
30
42
  */
31
- export declare function traceAgentQuery<T>(query: string, fn: (step: (input: AgentStepInput) => void) => Promise<T>, options?: AgentQueryOptions): Promise<T>;
43
+ export declare function traceAgentQuery<T>(queryOrInput: string | AgentQueryInput, fn: (step: (input: AgentStepInput) => void) => Promise<T>, options?: AgentQueryOptions): Promise<T>;
32
44
  export { traceAgentStep };
package/dist/agent.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { trace, SpanStatusCode } from "@opentelemetry/api";
2
2
  import { GEN_AI_ATTRS, INSTRUMENTATION_NAME } from "./types/index.js";
3
3
  import { recordAgentSteps, recordAgentToolUsage } from "./core/metrics.js";
4
- import { getConfig } from "./core/tracer.js";
4
+ import { getConfig, shouldEmitDeprecatedAttrs } from "./core/tracer.js";
5
5
  const tracer = trace.getTracer(INSTRUMENTATION_NAME);
6
6
  const DEFAULT_MAX_STEPS = 25;
7
7
  /**
@@ -9,25 +9,52 @@ const DEFAULT_MAX_STEPS = 25;
9
9
  * Supports: think, act, observe, answer, handoff.
10
10
  */
11
11
  function traceAgentStep(input) {
12
- const span = tracer.startSpan(`agent.step.${input.type}`);
12
+ const spanName = input.type === "act" && input.toolName !== undefined
13
+ ? `execute_tool ${input.toolName}`
14
+ : `gen_ai.agent.step.${input.type}`;
15
+ const span = tracer.startSpan(spanName);
13
16
  const config = getConfig();
14
17
  const recordContent = config?.recordContent !== false;
18
+ const emitDeprecated = shouldEmitDeprecatedAttrs();
15
19
  span.setAttributes({
16
- [GEN_AI_ATTRS.AGENT_STEP_TYPE]: input.type,
17
- [GEN_AI_ATTRS.AGENT_STEP_NUMBER]: input.stepNumber,
20
+ // Canonical toad_eye namespace
21
+ [GEN_AI_ATTRS.TOAD_AGENT_STEP_TYPE]: input.type,
22
+ [GEN_AI_ATTRS.TOAD_AGENT_STEP_NUMBER]: input.stepNumber,
23
+ // Deprecated aliases (skipped when OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental)
24
+ ...(emitDeprecated && {
25
+ [GEN_AI_ATTRS.AGENT_STEP_TYPE]: input.type,
26
+ [GEN_AI_ATTRS.AGENT_STEP_NUMBER]: input.stepNumber,
27
+ }),
18
28
  ...(input.toolName !== undefined && {
19
- [GEN_AI_ATTRS.AGENT_TOOL_NAME]: input.toolName,
29
+ // OTel spec attribute
30
+ [GEN_AI_ATTRS.TOOL_NAME]: input.toolName,
31
+ // Deprecated alias
32
+ ...(emitDeprecated && {
33
+ [GEN_AI_ATTRS.AGENT_TOOL_NAME]: input.toolName,
34
+ }),
35
+ }),
36
+ ...(input.toolType !== undefined && {
37
+ [GEN_AI_ATTRS.TOOL_TYPE]: input.toolType,
20
38
  }),
21
39
  ...(recordContent &&
22
40
  input.content !== undefined && {
23
- [GEN_AI_ATTRS.AGENT_STEP_CONTENT]: input.content,
41
+ [GEN_AI_ATTRS.TOAD_AGENT_STEP_CONTENT]: input.content,
42
+ ...(emitDeprecated && {
43
+ [GEN_AI_ATTRS.AGENT_STEP_CONTENT]: input.content,
44
+ }),
24
45
  }),
25
46
  // Handoff attributes
26
47
  ...(input.toAgent !== undefined && {
27
- [GEN_AI_ATTRS.AGENT_HANDOFF_TO]: input.toAgent,
48
+ [GEN_AI_ATTRS.TOAD_AGENT_HANDOFF_TO]: input.toAgent,
49
+ ...(emitDeprecated && {
50
+ [GEN_AI_ATTRS.AGENT_HANDOFF_TO]: input.toAgent,
51
+ }),
28
52
  }),
29
53
  ...(input.handoffReason !== undefined && {
30
- [GEN_AI_ATTRS.AGENT_HANDOFF_REASON]: input.handoffReason,
54
+ [GEN_AI_ATTRS.TOAD_AGENT_HANDOFF_REASON]: input.handoffReason,
55
+ ...(emitDeprecated && {
56
+ [GEN_AI_ATTRS.AGENT_HANDOFF_REASON]: input.handoffReason,
57
+ }),
31
58
  }),
32
59
  });
33
60
  if (input.type === "act" && input.toolName !== undefined) {
@@ -39,6 +66,9 @@ function traceAgentStep(input) {
39
66
  /**
40
67
  * Wrap an entire agent query in a parent span with ReAct tracing.
41
68
  *
69
+ * Accepts either a string query (backward-compatible) or an object with
70
+ * agent metadata per OTel GenAI spec (agentName, agentId).
71
+ *
42
72
  * Supports multi-agent via nesting — child traceAgentQuery calls
43
73
  * automatically become child spans in Jaeger.
44
74
  *
@@ -47,26 +77,52 @@ function traceAgentStep(input) {
47
77
  *
48
78
  * @example
49
79
  * ```ts
80
+ * // Simple form (backward-compatible)
81
+ * await traceAgentQuery('Is anything dangerous?', async (step) => { ... });
82
+ *
83
+ * // Object form with agent metadata (OTel GenAI spec)
84
+ * await traceAgentQuery(
85
+ * { query: 'Is anything dangerous?', agentName: 'space-monitor', agentId: 'agent-001' },
86
+ * async (step) => { ... }
87
+ * );
88
+ *
50
89
  * // Multi-agent: orchestrator delegates to specialist
51
- * await traceAgentQuery('orchestrator', async (step) => {
90
+ * await traceAgentQuery({ query: 'Analyze data', agentName: 'orchestrator' }, async (step) => {
52
91
  * step({ type: 'think', stepNumber: 1, content: 'Need domain expert' });
53
92
  * step({ type: 'handoff', stepNumber: 2, toAgent: 'specialist', handoffReason: 'domain expertise' });
54
93
  *
55
- * const result = await traceAgentQuery('specialist', async (step) => {
56
- * step({ type: 'act', stepNumber: 1, toolName: 'analyze' });
94
+ * const result = await traceAgentQuery({ query: 'Analyze', agentName: 'specialist' }, async (step) => {
95
+ * step({ type: 'act', stepNumber: 1, toolName: 'analyze', toolType: 'function' });
57
96
  * step({ type: 'answer', stepNumber: 2, content: 'analysis complete' });
58
97
  * return { answer: 'done' };
59
98
  * });
60
99
  * });
61
100
  * ```
62
101
  */
63
- export async function traceAgentQuery(query, fn, options) {
64
- return tracer.startActiveSpan(`agent.query`, async (span) => {
102
+ export async function traceAgentQuery(queryOrInput, fn, options) {
103
+ const resolved = typeof queryOrInput === "string"
104
+ ? { query: queryOrInput, agentName: undefined, agentId: undefined }
105
+ : queryOrInput;
106
+ const spanName = resolved.agentName
107
+ ? `invoke_agent ${resolved.agentName}`
108
+ : `invoke_agent`;
109
+ return tracer.startActiveSpan(spanName, async (span) => {
65
110
  const config = getConfig();
66
111
  const recordContent = config?.recordContent !== false;
112
+ const emitDeprecated = shouldEmitDeprecatedAttrs();
67
113
  const maxSteps = options?.maxSteps ?? DEFAULT_MAX_STEPS;
114
+ // OTel GenAI agent attributes
115
+ if (resolved.agentName !== undefined) {
116
+ span.setAttribute(GEN_AI_ATTRS.AGENT_NAME, resolved.agentName);
117
+ }
118
+ if (resolved.agentId !== undefined) {
119
+ span.setAttribute(GEN_AI_ATTRS.AGENT_ID, resolved.agentId);
120
+ }
68
121
  if (recordContent) {
69
- span.setAttribute(GEN_AI_ATTRS.AGENT_STEP_CONTENT, query);
122
+ span.setAttribute(GEN_AI_ATTRS.TOAD_AGENT_STEP_CONTENT, resolved.query);
123
+ if (emitDeprecated) {
124
+ span.setAttribute(GEN_AI_ATTRS.AGENT_STEP_CONTENT, resolved.query);
125
+ }
70
126
  }
71
127
  let stepCount = 0;
72
128
  let loopCount = 0;
@@ -91,13 +147,19 @@ export async function traceAgentQuery(query, fn, options) {
91
147
  }
92
148
  traceAgentStep(input);
93
149
  });
94
- span.setAttribute(GEN_AI_ATTRS.AGENT_LOOP_COUNT, loopCount);
150
+ span.setAttribute(GEN_AI_ATTRS.TOAD_AGENT_LOOP_COUNT, loopCount);
151
+ if (emitDeprecated) {
152
+ span.setAttribute(GEN_AI_ATTRS.AGENT_LOOP_COUNT, loopCount);
153
+ }
95
154
  span.setStatus({ code: SpanStatusCode.OK });
96
155
  return result;
97
156
  }
98
157
  catch (error) {
99
158
  const message = error instanceof Error ? error.message : String(error);
100
- span.setAttribute(GEN_AI_ATTRS.AGENT_LOOP_COUNT, loopCount);
159
+ span.setAttribute(GEN_AI_ATTRS.TOAD_AGENT_LOOP_COUNT, loopCount);
160
+ if (emitDeprecated) {
161
+ span.setAttribute(GEN_AI_ATTRS.AGENT_LOOP_COUNT, loopCount);
162
+ }
101
163
  span.setStatus({ code: SpanStatusCode.ERROR, message });
102
164
  throw error;
103
165
  }
@@ -107,7 +107,7 @@ export function recordAgentSteps(stepCount) {
107
107
  }
108
108
  export function recordAgentToolUsage(toolName) {
109
109
  agentToolUsage.add(1, {
110
- [GEN_AI_ATTRS.AGENT_TOOL_NAME]: toolName,
110
+ [GEN_AI_ATTRS.TOOL_NAME]: toolName,
111
111
  });
112
112
  }
113
113
  export function recordGuardEvaluation(ruleName) {
@@ -153,7 +153,7 @@ export async function traceLLMCall(input, fn) {
153
153
  recordBudgetDowngraded(override.budget);
154
154
  }
155
155
  }
156
- return tracer.startActiveSpan(`gen_ai.${effectiveInput.provider}.${effectiveInput.model}`, async (span) => {
156
+ return tracer.startActiveSpan(`chat ${effectiveInput.model}`, async (span) => {
157
157
  const start = performance.now();
158
158
  setBaseAttributes(span, effectiveInput);
159
159
  try {
@@ -1,6 +1,12 @@
1
1
  import type { ToadEyeConfig } from "../types/index.js";
2
2
  import { BudgetTracker } from "../budget/index.js";
3
3
  export declare function getConfig(): ToadEyeConfig | null;
4
+ /**
5
+ * Check if deprecated semconv aliases should be emitted.
6
+ * When OTEL_SEMCONV_STABILITY_OPT_IN includes "gen_ai_latest_experimental",
7
+ * only new (canonical) attributes are emitted — deprecated aliases are skipped.
8
+ */
9
+ export declare function shouldEmitDeprecatedAttrs(): boolean;
4
10
  export declare function getBudgetTracker(): BudgetTracker | null;
5
11
  export declare function initObservability(config: ToadEyeConfig): void;
6
12
  export declare function shutdown(): Promise<void>;
@@ -18,6 +18,15 @@ let budgetTracker = null;
18
18
  export function getConfig() {
19
19
  return currentConfig;
20
20
  }
21
+ /**
22
+ * Check if deprecated semconv aliases should be emitted.
23
+ * When OTEL_SEMCONV_STABILITY_OPT_IN includes "gen_ai_latest_experimental",
24
+ * only new (canonical) attributes are emitted — deprecated aliases are skipped.
25
+ */
26
+ export function shouldEmitDeprecatedAttrs() {
27
+ const optIn = process.env["OTEL_SEMCONV_STABILITY_OPT_IN"] ?? "";
28
+ return !optIn.split(",").includes("gen_ai_latest_experimental");
29
+ }
21
30
  export function getBudgetTracker() {
22
31
  return budgetTracker;
23
32
  }
@@ -84,7 +84,7 @@ original, patch, providerName) {
84
84
  recordBudgetDowngraded(override.budget);
85
85
  }
86
86
  }
87
- const span = tracer.startSpan(`gen_ai.${effectiveProvider}.${effectiveModel}`);
87
+ const span = tracer.startSpan(`chat ${effectiveModel}`);
88
88
  const ctx = trace.setSpan(context.active(), span);
89
89
  const sessionId = config?.sessionExtractor?.() ?? config?.sessionId;
90
90
  span.setAttributes({
@@ -15,12 +15,29 @@ export declare const GEN_AI_ATTRS: {
15
15
  readonly OPERATION: "gen_ai.operation.name";
16
16
  readonly FINISH_REASONS: "gen_ai.response.finish_reasons";
17
17
  readonly ERROR: "error.type";
18
+ readonly AGENT_NAME: "gen_ai.agent.name";
19
+ readonly AGENT_ID: "gen_ai.agent.id";
20
+ readonly TOOL_NAME: "gen_ai.tool.name";
21
+ readonly TOOL_TYPE: "gen_ai.tool.type";
22
+ readonly TOAD_AGENT_STEP_TYPE: "gen_ai.toad_eye.agent.step.type";
23
+ readonly TOAD_AGENT_STEP_NUMBER: "gen_ai.toad_eye.agent.step.number";
24
+ readonly TOAD_AGENT_STEP_CONTENT: "gen_ai.toad_eye.agent.step.content";
25
+ readonly TOAD_AGENT_HANDOFF_TO: "gen_ai.toad_eye.agent.handoff.to";
26
+ readonly TOAD_AGENT_HANDOFF_REASON: "gen_ai.toad_eye.agent.handoff.reason";
27
+ readonly TOAD_AGENT_LOOP_COUNT: "gen_ai.toad_eye.agent.loop_count";
28
+ /** @deprecated Use TOAD_AGENT_STEP_TYPE instead. Will be removed in v3.0. */
18
29
  readonly AGENT_STEP_TYPE: "gen_ai.agent.step.type";
30
+ /** @deprecated Use TOAD_AGENT_STEP_NUMBER instead. Will be removed in v3.0. */
19
31
  readonly AGENT_STEP_NUMBER: "gen_ai.agent.step.number";
32
+ /** @deprecated Use TOOL_NAME instead. Will be removed in v3.0. */
20
33
  readonly AGENT_TOOL_NAME: "gen_ai.agent.tool.name";
34
+ /** @deprecated Use TOAD_AGENT_STEP_CONTENT instead. Will be removed in v3.0. */
21
35
  readonly AGENT_STEP_CONTENT: "gen_ai.agent.step.content";
36
+ /** @deprecated Use TOAD_AGENT_HANDOFF_TO instead. Will be removed in v3.0. */
22
37
  readonly AGENT_HANDOFF_TO: "gen_ai.agent.handoff.to";
38
+ /** @deprecated Use TOAD_AGENT_HANDOFF_REASON instead. Will be removed in v3.0. */
23
39
  readonly AGENT_HANDOFF_REASON: "gen_ai.agent.handoff.reason";
40
+ /** @deprecated Use TOAD_AGENT_LOOP_COUNT instead. Will be removed in v3.0. */
24
41
  readonly AGENT_LOOP_COUNT: "gen_ai.agent.loop_count";
25
42
  readonly GUARD_MODE: "gen_ai.toad_eye.guard.mode";
26
43
  readonly GUARD_PASSED: "gen_ai.toad_eye.guard.passed";
@@ -16,13 +16,32 @@ export const GEN_AI_ATTRS = {
16
16
  OPERATION: "gen_ai.operation.name",
17
17
  FINISH_REASONS: "gen_ai.response.finish_reasons",
18
18
  ERROR: "error.type",
19
- // Agent step attributes
19
+ // OTel GenAI agent attributes
20
+ AGENT_NAME: "gen_ai.agent.name",
21
+ AGENT_ID: "gen_ai.agent.id",
22
+ TOOL_NAME: "gen_ai.tool.name",
23
+ TOOL_TYPE: "gen_ai.tool.type",
24
+ // toad-eye ReAct extensions (gen_ai.toad_eye.agent.* — correct namespace)
25
+ TOAD_AGENT_STEP_TYPE: "gen_ai.toad_eye.agent.step.type",
26
+ TOAD_AGENT_STEP_NUMBER: "gen_ai.toad_eye.agent.step.number",
27
+ TOAD_AGENT_STEP_CONTENT: "gen_ai.toad_eye.agent.step.content",
28
+ TOAD_AGENT_HANDOFF_TO: "gen_ai.toad_eye.agent.handoff.to",
29
+ TOAD_AGENT_HANDOFF_REASON: "gen_ai.toad_eye.agent.handoff.reason",
30
+ TOAD_AGENT_LOOP_COUNT: "gen_ai.toad_eye.agent.loop_count",
31
+ // Agent step attributes — @deprecated aliases (will be removed in v3.0)
32
+ /** @deprecated Use TOAD_AGENT_STEP_TYPE instead. Will be removed in v3.0. */
20
33
  AGENT_STEP_TYPE: "gen_ai.agent.step.type",
34
+ /** @deprecated Use TOAD_AGENT_STEP_NUMBER instead. Will be removed in v3.0. */
21
35
  AGENT_STEP_NUMBER: "gen_ai.agent.step.number",
36
+ /** @deprecated Use TOOL_NAME instead. Will be removed in v3.0. */
22
37
  AGENT_TOOL_NAME: "gen_ai.agent.tool.name",
38
+ /** @deprecated Use TOAD_AGENT_STEP_CONTENT instead. Will be removed in v3.0. */
23
39
  AGENT_STEP_CONTENT: "gen_ai.agent.step.content",
40
+ /** @deprecated Use TOAD_AGENT_HANDOFF_TO instead. Will be removed in v3.0. */
24
41
  AGENT_HANDOFF_TO: "gen_ai.agent.handoff.to",
42
+ /** @deprecated Use TOAD_AGENT_HANDOFF_REASON instead. Will be removed in v3.0. */
25
43
  AGENT_HANDOFF_REASON: "gen_ai.agent.handoff.reason",
44
+ /** @deprecated Use TOAD_AGENT_LOOP_COUNT instead. Will be removed in v3.0. */
26
45
  AGENT_LOOP_COUNT: "gen_ai.agent.loop_count",
27
46
  // Guard (shadow mode) attributes
28
47
  GUARD_MODE: "gen_ai.toad_eye.guard.mode",
@@ -14,4 +14,4 @@ export type { ToadEyeConfig, SamplingConfig } from "./config.js";
14
14
  export { GEN_AI_ATTRS, LLM_ATTRS } from "./attributes.js";
15
15
  export { GEN_AI_METRICS, LLM_METRICS } from "./metrics.js";
16
16
  export type { MetricName } from "./metrics.js";
17
- export type { SpanStatus, LLMSpanAttributes, AgentStepType, AgentStepInput, AgentQueryOptions, GuardMode, GuardResult, } from "./spans.js";
17
+ export type { SpanStatus, LLMSpanAttributes, AgentStepType, AgentStepInput, AgentQueryOptions, AgentQueryInput, GuardMode, GuardResult, } from "./spans.js";
@@ -9,6 +9,8 @@ export interface AgentStepInput {
9
9
  readonly stepNumber: number;
10
10
  readonly content?: string | undefined;
11
11
  readonly toolName?: string | undefined;
12
+ /** Tool type per OTel GenAI spec: function, extension, retrieval, builtin */
13
+ readonly toolType?: "function" | "extension" | "retrieval" | "builtin" | undefined;
12
14
  /** Target agent name for handoff steps */
13
15
  readonly toAgent?: string | undefined;
14
16
  /** Reason for handoff */
@@ -19,6 +21,14 @@ export interface AgentQueryOptions {
19
21
  /** Max steps before recording a warning. Default: 25 */
20
22
  readonly maxSteps?: number | undefined;
21
23
  }
24
+ /** Input for traceAgentQuery — object form with agent metadata */
25
+ export interface AgentQueryInput {
26
+ readonly query: string;
27
+ /** Agent name — maps to gen_ai.agent.name and span name invoke_agent {agentName} */
28
+ readonly agentName?: string | undefined;
29
+ /** Agent identifier — maps to gen_ai.agent.id */
30
+ readonly agentId?: string | undefined;
31
+ }
22
32
  /** Guard execution mode */
23
33
  export type GuardMode = "shadow" | "enforce";
24
34
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toad-eye",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Auto-instrumentation, cost tracking, budget guards, and observability for LLM services",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",