@introspection-sdk/introspection-pi 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.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Subscribe to a pi-agent {@link Agent} to emit `execute_tool` spans for every
3
- * tool call the loop runs.
3
+ * tool call the loop runs, and (optionally) one `invoke_agent` span per run.
4
4
  */
5
- import { type Attributes, type Context as OtelContext, type Tracer } from "@opentelemetry/api";
5
+ import { type Attributes, type Context as OtelContext, type Meter, type Tracer } from "@opentelemetry/api";
6
6
  import type { Agent, AgentEvent } from "@earendil-works/pi-agent-core";
7
7
  import { type AgentMeta } from "./attributes.js";
8
8
  export interface InstrumentAgentOptions {
@@ -10,9 +10,31 @@ export interface InstrumentAgentOptions {
10
10
  tracer: Tracer;
11
11
  /** Required metadata stamped onto every execute_tool span. */
12
12
  meta: AgentMeta;
13
+ /**
14
+ * Optional meter. When set, the wrapper also records
15
+ * `gen_ai.execute_tool.duration` per tool call and (when `runSpans` is
16
+ * enabled) `gen_ai.invoke_agent.duration` per run.
17
+ */
18
+ meter?: Meter;
19
+ /**
20
+ * Emit one `invoke_agent {agent.name}` span per agent run
21
+ * (`agent_start` → `agent_end`), carrying conversation/agent identity
22
+ * and the final finish reason. Usage attributes stay on the chat spans
23
+ * only, so aggregations that sum a whole trace don't double-count.
24
+ *
25
+ * Off by default: hosts that already create their own turn/run spans
26
+ * (and parent chat spans onto them via `getParentContext`) would get
27
+ * duplicate run spans otherwise. When enabled, wire the chat spans under
28
+ * the run span with
29
+ * `instrumentStream(fn, { getParentContext: () => handle.getRunContext() })`;
30
+ * tool spans parent onto the run span automatically unless
31
+ * `getParentContext` is provided.
32
+ */
33
+ runSpans?: boolean;
13
34
  /**
14
35
  * Returns the parent OTel context for the next tool span. If undefined or
15
- * returns null, `context.active()` is used at the time the start event fires.
36
+ * returns null, the active run span (when `runSpans` is enabled) or
37
+ * `context.active()` is used at the time the start event fires.
16
38
  */
17
39
  getParentContext?: () => OtelContext | null | undefined;
18
40
  /**
@@ -26,13 +48,20 @@ export interface InstrumentAgentOptions {
26
48
  spanName?: (toolName: string) => string;
27
49
  }
28
50
  export interface AgentInstrumentation {
29
- /** Stop subscribing and finalize any tool spans still open. */
51
+ /** Stop subscribing and finalize any spans still open. */
30
52
  stop: () => void;
53
+ /**
54
+ * OTel context of the active `invoke_agent` span, when `runSpans` is
55
+ * enabled and a run is in flight. Pass to `instrumentStream`'s
56
+ * `getParentContext` so chat spans nest under the run span.
57
+ */
58
+ getRunContext: () => OtelContext | undefined;
31
59
  }
32
60
  /**
33
61
  * Subscribe to {@link Agent.subscribe} and emit one `execute_tool ${name}`
34
- * span per tool call. Returns a handle whose `stop()` unsubscribes and
35
- * finalizes any still-open spans.
62
+ * span per tool call (plus one `invoke_agent` span per run when enabled).
63
+ * Returns a handle whose `stop()` unsubscribes and finalizes any still-open
64
+ * spans.
36
65
  *
37
66
  * Tool call IDs are assumed unique per agent run; concurrent tool execution
38
67
  * (`ToolExecutionMode = "parallel"`) is supported because each id keys its
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-agent.d.ts","sourceRoot":"","sources":["../src/instrument-agent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,OAAO,IAAI,WAAW,EAE3B,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,sBAAsB,CAAA;KAAE,CAAC,KACzD,UAAU,CAAC;IAChB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,sBAAsB,GAC3B,oBAAoB,CAkBtB"}
1
+ {"version":3,"file":"instrument-agent.d.ts","sourceRoot":"","sources":["../src/instrument-agent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,OAAO,IAAI,WAAW,EAC3B,KAAK,KAAK,EAEV,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAMvE,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAKzB,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,sBAAsB,CAAA;KAAE,CAAC,KACzD,UAAU,CAAC;IAChB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB;;;;OAIG;IACH,aAAa,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;CAC9C;AAeD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,sBAAsB,GAC3B,oBAAoB,CAiCtB"}
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Subscribe to a pi-agent {@link Agent} to emit `execute_tool` spans for every
3
- * tool call the loop runs.
3
+ * tool call the loop runs, and (optionally) one `invoke_agent` span per run.
4
4
  */
5
- import { context as otelContext, SpanKind, SpanStatusCode, } from "@opentelemetry/api";
6
- import { GenAiSpanName, IntrospectionAttr } from "@introspection-sdk/types";
7
- import { executeToolAttributes, executeToolResultAttribute, } from "./attributes.js";
5
+ import { context as otelContext, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
6
+ import { GenAi, GenAiSpanName, IntrospectionAttr, } from "@introspection-sdk/types";
7
+ import { executeToolAttributes, executeToolResultAttribute, invokeAgentAttributes, } from "./attributes.js";
8
+ import { semconvFinishReason } from "./convert.js";
9
+ import { classifyErrorType } from "./error-type.js";
10
+ import { genAiMetrics } from "./metrics.js";
8
11
  /**
9
12
  * Subscribe to {@link Agent.subscribe} and emit one `execute_tool ${name}`
10
- * span per tool call. Returns a handle whose `stop()` unsubscribes and
11
- * finalizes any still-open spans.
13
+ * span per tool call (plus one `invoke_agent` span per run when enabled).
14
+ * Returns a handle whose `stop()` unsubscribes and finalizes any still-open
15
+ * spans.
12
16
  *
13
17
  * Tool call IDs are assumed unique per agent run; concurrent tool execution
14
18
  * (`ToolExecutionMode = "parallel"`) is supported because each id keys its
@@ -17,39 +21,85 @@ import { executeToolAttributes, executeToolResultAttribute, } from "./attributes
17
21
  export function instrumentAgent(agent, opts) {
18
22
  const buildSpanName = opts.spanName ?? GenAiSpanName.executeTool;
19
23
  const activeSpans = new Map();
24
+ const metrics = opts.meter ? genAiMetrics(opts.meter) : undefined;
25
+ const state = { run: undefined };
20
26
  const unsubscribe = agent.subscribe((event, signal) => {
21
- handleEvent(event, opts, activeSpans, buildSpanName, signal);
27
+ handleEvent(event, agent, opts, activeSpans, state, metrics, buildSpanName, signal);
22
28
  });
23
29
  return {
24
30
  stop: () => {
25
31
  unsubscribe();
26
- for (const span of activeSpans.values()) {
27
- span.setStatus({ code: SpanStatusCode.OK });
32
+ for (const { span } of activeSpans.values()) {
28
33
  span.end();
29
34
  }
30
35
  activeSpans.clear();
36
+ if (state.run) {
37
+ state.run.span.end();
38
+ state.run = undefined;
39
+ }
31
40
  },
41
+ getRunContext: () => state.run?.context,
32
42
  };
33
43
  }
34
- function handleEvent(event, opts, activeSpans, buildSpanName, signal) {
44
+ function handleEvent(event, agent, opts, activeSpans, state, metrics, buildSpanName, signal) {
35
45
  switch (event.type) {
36
- case "tool_execution_start": {
46
+ case "agent_start": {
47
+ if (!opts.runSpans)
48
+ return;
37
49
  const parentContext = opts.getParentContext?.() ?? otelContext.active();
38
- const span = opts.tracer.startSpan(buildSpanName(event.toolName), { kind: SpanKind.INTERNAL }, parentContext ?? undefined);
39
- span.setAttributes(executeToolAttributes(event.toolName, event.toolCallId, event.args, opts.meta));
40
- const extra = opts.extraAttributes?.(event);
41
- if (extra) {
42
- span.setAttributes(extra);
50
+ const span = opts.tracer.startSpan(GenAiSpanName.invokeAgent(opts.meta.agentName), {
51
+ kind: SpanKind.INTERNAL,
52
+ attributes: invokeAgentAttributes(opts.meta),
53
+ }, parentContext ?? undefined);
54
+ state.run = {
55
+ span,
56
+ context: trace.setSpan(parentContext ?? otelContext.active(), span),
57
+ startedAt: Date.now(),
58
+ };
59
+ return;
60
+ }
61
+ case "message_end": {
62
+ const run = state.run;
63
+ const message = event.message;
64
+ if (!run || message.role !== "assistant" || !("usage" in message)) {
65
+ return;
43
66
  }
44
- activeSpans.set(event.toolCallId, span);
67
+ // Track outcome only. The run span deliberately carries NO
68
+ // gen_ai.usage.* attributes: platform aggregations sum usage across
69
+ // every span in a conversation, so aggregated usage on the run span
70
+ // would double-count the chat spans beneath it.
71
+ run.lastStopReason = message.stopReason;
72
+ run.errorMessage = message.errorMessage;
73
+ return;
74
+ }
75
+ case "agent_end": {
76
+ const run = state.run;
77
+ if (!run)
78
+ return;
79
+ state.run = undefined;
80
+ finishRunSpan(run, opts.meta, metrics, signal);
81
+ return;
82
+ }
83
+ case "tool_execution_start": {
84
+ const parentContext = opts.getParentContext?.() ?? state.run?.context ?? otelContext.active();
85
+ const description = lookupToolDescription(agent, event.toolName);
86
+ // Full attribute set at span creation time so samplers see
87
+ // gen_ai.operation.name / tool.name / tool.type.
88
+ const attributes = {
89
+ ...executeToolAttributes(event.toolName, event.toolCallId, event.args, opts.meta, description),
90
+ ...opts.extraAttributes?.(event),
91
+ };
92
+ const span = opts.tracer.startSpan(buildSpanName(event.toolName), { kind: SpanKind.INTERNAL, attributes }, parentContext ?? undefined);
93
+ activeSpans.set(event.toolCallId, { span, startedAt: Date.now() });
45
94
  return;
46
95
  }
47
96
  case "tool_execution_end": {
48
- const span = activeSpans.get(event.toolCallId);
49
- if (!span)
97
+ const active = activeSpans.get(event.toolCallId);
98
+ if (!active)
50
99
  return;
51
100
  activeSpans.delete(event.toolCallId);
52
- span.setAttributes(executeToolResultAttribute(event.result));
101
+ const { span, startedAt } = active;
102
+ let errorType;
53
103
  if (event.isError && signal?.aborted) {
54
104
  // The run's AbortSignal fired: pi synthesizes "Operation aborted"
55
105
  // error results for tool calls cut short by a requested abort. A
@@ -58,6 +108,8 @@ function handleEvent(event, opts, activeSpans, buildSpanName, signal) {
58
108
  span.setAttribute(IntrospectionAttr.TERMINATION_REASON, "cancelled");
59
109
  }
60
110
  else if (event.isError) {
111
+ errorType = "tool_error";
112
+ span.setAttribute("error.type", errorType);
61
113
  span.setStatus({
62
114
  code: SpanStatusCode.ERROR,
63
115
  message: typeof event.result === "string"
@@ -68,15 +120,70 @@ function handleEvent(event, opts, activeSpans, buildSpanName, signal) {
68
120
  });
69
121
  }
70
122
  else {
71
- span.setStatus({ code: SpanStatusCode.OK });
123
+ // Semconv scopes gen_ai.tool.call.result to successful executions;
124
+ // error text is carried on the span status instead.
125
+ span.setAttributes(executeToolResultAttribute(event.result));
72
126
  }
73
127
  span.end();
128
+ if (metrics) {
129
+ const attributes = {
130
+ [GenAi.TOOL_NAME]: event.toolName,
131
+ [GenAi.TOOL_TYPE]: "function",
132
+ [GenAi.AGENT_NAME]: opts.meta.agentName,
133
+ };
134
+ if (errorType)
135
+ attributes["error.type"] = errorType;
136
+ metrics.executeToolDuration.record((Date.now() - startedAt) / 1000, attributes);
137
+ }
74
138
  return;
75
139
  }
76
140
  default:
77
141
  return;
78
142
  }
79
143
  }
144
+ function finishRunSpan(run, meta, metrics, signal) {
145
+ const { span } = run;
146
+ if (run.lastStopReason) {
147
+ span.setAttribute(GenAi.RESPONSE_FINISH_REASONS, [
148
+ semconvFinishReason(run.lastStopReason),
149
+ ]);
150
+ }
151
+ let errorType;
152
+ const aborted = run.lastStopReason === "aborted" || signal?.aborted === true;
153
+ if (aborted) {
154
+ // Mirrors the chat-span contract: a requested cancellation is an
155
+ // outcome, not a failure.
156
+ span.setAttribute(IntrospectionAttr.TERMINATION_REASON, "cancelled");
157
+ }
158
+ else if (run.lastStopReason === "error") {
159
+ const errorMessage = run.errorMessage ?? "Unknown error";
160
+ errorType = classifyErrorType(errorMessage, "model_error");
161
+ span.setAttribute("error.type", errorType);
162
+ span.recordException(new Error(errorMessage));
163
+ span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });
164
+ }
165
+ span.end();
166
+ if (metrics) {
167
+ const attributes = { [GenAi.AGENT_NAME]: meta.agentName };
168
+ if (errorType)
169
+ attributes["error.type"] = errorType;
170
+ metrics.invokeAgentDuration.record((Date.now() - run.startedAt) / 1000, attributes);
171
+ }
172
+ }
173
+ /**
174
+ * Best-effort `gen_ai.tool.description` lookup from the agent's tool
175
+ * registry. Defensive against partial Agent implementations (tests, mocks)
176
+ * that don't expose `state.tools`.
177
+ */
178
+ function lookupToolDescription(agent, toolName) {
179
+ try {
180
+ return agent.state?.tools?.find((tool) => tool.name === toolName)
181
+ ?.description;
182
+ }
183
+ catch {
184
+ return undefined;
185
+ }
186
+ }
80
187
  function safeStringify(value) {
81
188
  try {
82
189
  return JSON.stringify(value);
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-agent.js","sourceRoot":"","sources":["../src/instrument-agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrument-agent.js","sourceRoot":"","sources":["../src/instrument-agent.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Wrap a pi-agent `StreamFn` so each invocation emits a `chat` span that
3
3
  * conforms to the OTEL GenAI semantic convention.
4
4
  */
5
- import { type Attributes, type Context as OtelContext, type Tracer } from "@opentelemetry/api";
5
+ import { type Attributes, type Context as OtelContext, type Meter, type Tracer } from "@opentelemetry/api";
6
6
  import { type Api, type Context, type Model } from "@earendil-works/pi-ai";
7
7
  import type { StreamFn } from "@earendil-works/pi-agent-core";
8
8
  import { type AbortTerminationReason } from "@introspection-sdk/types";
@@ -12,6 +12,13 @@ export interface InstrumentStreamOptions {
12
12
  tracer: Tracer;
13
13
  /** Required metadata stamped onto every chat span. */
14
14
  meta: AgentMeta;
15
+ /**
16
+ * Optional meter. When set, the wrapper also records the GenAI client
17
+ * metrics (`gen_ai.client.token.usage`, `gen_ai.client.operation.duration`,
18
+ * `gen_ai.client.operation.time_to_first_chunk`,
19
+ * `gen_ai.client.operation.time_per_output_chunk`).
20
+ */
21
+ meter?: Meter;
15
22
  /**
16
23
  * Returns the parent OTel context for the next chat span. If undefined or
17
24
  * returns null, `context.active()` is used at call time.
@@ -22,7 +29,7 @@ export interface InstrumentStreamOptions {
22
29
  * on top of the GenAI semconv attributes for each chat span.
23
30
  */
24
31
  extraAttributes?: (model: Model<Api>, context: Context) => Attributes;
25
- /** Override the default span name builder. */
32
+ /** Override the default span name builder (default: `chat {model.id}`). */
26
33
  spanName?: (model: Model<Api>, context: Context) => string;
27
34
  /**
28
35
  * Returns the compaction summaries known for the session, read at span
@@ -47,7 +54,8 @@ export interface InstrumentStreamOptions {
47
54
  abortTerminationReason?: () => AbortTerminationReason | null;
48
55
  }
49
56
  /**
50
- * Wrap a {@link StreamFn} to emit `chat ${provider}` spans.
57
+ * Wrap a {@link StreamFn} to emit `chat {model}` spans (CLIENT kind — the
58
+ * model runs in a remote process).
51
59
  *
52
60
  * Reassign `agent.streamFn` with the result:
53
61
  *
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-stream.d.ts","sourceRoot":"","sources":["../src/instrument-stream.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,OAAO,IAAI,WAAW,EAE3B,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAKL,KAAK,GAAG,EACR,KAAK,OAAO,EACZ,KAAK,KAAK,EACX,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,uBAAuB;IACtC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,UAAU,CAAC;IACtE,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC3D;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;;;;;;OAUG;IACH,sBAAsB,CAAC,EAAE,MAAM,sBAAsB,GAAG,IAAI,CAAC;CAC9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,uBAAuB,GAC5B,QAAQ,CA0CV"}
1
+ {"version":3,"file":"instrument-stream.d.ts","sourceRoot":"","sources":["../src/instrument-stream.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,OAAO,IAAI,WAAW,EAC3B,KAAK,KAAK,EAEV,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAKL,KAAK,GAAG,EACR,KAAK,OAAO,EACZ,KAAK,KAAK,EACX,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAIzB,MAAM,WAAW,uBAAuB;IACtC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,UAAU,CAAC;IACtE,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC3D;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;;;;;;OAUG;IACH,sBAAsB,CAAC,EAAE,MAAM,sBAAsB,GAAG,IAAI,CAAC;CAC9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,uBAAuB,GAC5B,QAAQ,CA6CV"}
@@ -4,10 +4,13 @@
4
4
  */
5
5
  import { context as otelContext, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
6
6
  import { createAssistantMessageEventStream, } from "@earendil-works/pi-ai";
7
- import { GenAiSpanName, IntrospectionAttr, } from "@introspection-sdk/types";
8
- import { chatRequestAttributes, chatResponseAttributes, } from "./attributes.js";
7
+ import { GenAi, GenAiSpanName, IntrospectionAttr, } from "@introspection-sdk/types";
8
+ import { chatRequestAttributes, chatResponseAttributes, serverAttributes, } from "./attributes.js";
9
+ import { classifyErrorType, classifyThrownErrorType } from "./error-type.js";
10
+ import { genAiMetrics } from "./metrics.js";
9
11
  /**
10
- * Wrap a {@link StreamFn} to emit `chat ${provider}` spans.
12
+ * Wrap a {@link StreamFn} to emit `chat {model}` spans (CLIENT kind — the
13
+ * model runs in a remote process).
11
14
  *
12
15
  * Reassign `agent.streamFn` with the result:
13
16
  *
@@ -20,17 +23,21 @@ import { chatRequestAttributes, chatResponseAttributes, } from "./attributes.js"
20
23
  * `error` events, never thrown synchronously.
21
24
  */
22
25
  export function instrumentStream(streamFn, opts) {
23
- const buildSpanName = opts.spanName ?? ((model) => GenAiSpanName.chat(model.provider));
26
+ const buildSpanName = opts.spanName ?? ((model) => GenAiSpanName.chat(model.id));
27
+ const metrics = opts.meter ? genAiMetrics(opts.meter) : undefined;
24
28
  return ((model, context, options) => {
25
29
  const parentContext = opts.getParentContext?.() ?? otelContext.active();
26
- const span = opts.tracer.startSpan(buildSpanName(model, context), { kind: SpanKind.INTERNAL }, parentContext ?? undefined);
27
- span.setAttributes(chatRequestAttributes(model, context, opts.meta, {
28
- compactionSummaries: opts.getCompactionSummaries?.(),
29
- }));
30
- const extra = opts.extraAttributes?.(model, context);
31
- if (extra) {
32
- span.setAttributes(extra);
33
- }
30
+ // Compute the full attribute set up front and pass it at span creation
31
+ // time so samplers see gen_ai.operation.name / provider / model /
32
+ // server.*.
33
+ const attributes = {
34
+ ...chatRequestAttributes(model, context, opts.meta, {
35
+ compactionSummaries: opts.getCompactionSummaries?.(),
36
+ streamOptions: options,
37
+ }),
38
+ ...opts.extraAttributes?.(model, context),
39
+ };
40
+ const span = opts.tracer.startSpan(buildSpanName(model, context), { kind: SpanKind.CLIENT, attributes }, parentContext ?? undefined);
34
41
  const proxy = createAssistantMessageEventStream();
35
42
  const spanContext = trace.setSpan(parentContext ?? otelContext.active(), span);
36
43
  void runUpstream({
@@ -41,20 +48,43 @@ export function instrumentStream(streamFn, opts) {
41
48
  spanContext,
42
49
  span,
43
50
  proxy,
51
+ metrics,
44
52
  abortTerminationReason: opts.abortTerminationReason,
45
53
  });
46
54
  return proxy;
47
55
  });
48
56
  }
49
- async function runUpstream({ streamFn, model, context, options, spanContext, span, proxy, abortTerminationReason, }) {
57
+ async function runUpstream({ streamFn, model, context, options, spanContext, span, proxy, metrics, abortTerminationReason, }) {
50
58
  let finished = false;
59
+ const startedAt = Date.now();
60
+ const startedAtMonotonic = performance.now();
61
+ let lastChunkAt;
51
62
  try {
52
63
  const upstream = await otelContext.with(spanContext, () => streamFn(model, context, options));
53
64
  for await (const event of upstream) {
65
+ if (isOutputChunk(event)) {
66
+ const now = performance.now();
67
+ if (lastChunkAt === undefined) {
68
+ const timeToFirstChunk = (now - startedAtMonotonic) / 1000;
69
+ span.setAttribute("gen_ai.response.time_to_first_chunk", timeToFirstChunk);
70
+ metrics?.timeToFirstChunk.record(timeToFirstChunk, chatMetricAttributes(model));
71
+ }
72
+ else {
73
+ metrics?.timePerOutputChunk.record((now - lastChunkAt) / 1000, chatMetricAttributes(model));
74
+ }
75
+ lastChunkAt = now;
76
+ }
54
77
  proxy.push(event);
55
78
  if (!finished && (event.type === "done" || event.type === "error")) {
56
79
  finished = true;
57
- finishSpan(span, event, abortTerminationReason);
80
+ finishSpan({
81
+ span,
82
+ event,
83
+ model,
84
+ startedAt,
85
+ metrics,
86
+ abortTerminationReason,
87
+ });
58
88
  }
59
89
  }
60
90
  }
@@ -62,26 +92,40 @@ async function runUpstream({ streamFn, model, context, options, spanContext, spa
62
92
  const errorMessage = err instanceof Error ? err.message : String(err);
63
93
  const errorEvent = {
64
94
  type: "error",
65
- reason: "error",
66
- error: assistantErrorMessage(model, errorMessage),
95
+ reason: options?.signal?.aborted === true ? "aborted" : "error",
96
+ error: assistantErrorMessage(model, errorMessage, options?.signal?.aborted === true),
67
97
  };
68
- span.recordException(err instanceof Error ? err : new Error(errorMessage));
98
+ if (options?.signal?.aborted !== true) {
99
+ recordGenAiException(span, err, classifyThrownErrorType(err));
100
+ }
69
101
  proxy.push(errorEvent);
70
102
  if (!finished) {
71
103
  finished = true;
72
- finishSpan(span, errorEvent, abortTerminationReason);
104
+ finishSpan({
105
+ span,
106
+ event: errorEvent,
107
+ model,
108
+ startedAt,
109
+ metrics,
110
+ abortTerminationReason,
111
+ });
73
112
  }
74
113
  }
75
114
  finally {
76
115
  if (!finished) {
77
116
  // Upstream ended without a terminal event — close the span anyway so
78
117
  // we don't leak.
79
- span.setStatus({ code: SpanStatusCode.OK });
80
118
  span.end();
81
119
  }
82
120
  }
83
121
  }
84
- function finishSpan(span, event, abortTerminationReason) {
122
+ function isOutputChunk(event) {
123
+ return ((event.type === "text_delta" ||
124
+ event.type === "thinking_delta" ||
125
+ event.type === "toolcall_delta") &&
126
+ event.delta.length > 0);
127
+ }
128
+ function finishSpan({ span, event, model, startedAt, metrics, abortTerminationReason, }) {
85
129
  const message = event.type === "done" ? event.message : event.error;
86
130
  span.setAttributes(chatResponseAttributes(message));
87
131
  const aborted = (event.type === "error" && event.reason === "aborted") ||
@@ -91,6 +135,7 @@ function finishSpan(span, event, abortTerminationReason) {
91
135
  : abortTerminationReason
92
136
  ? abortTerminationReason()
93
137
  : "cancelled";
138
+ let errorType;
94
139
  if (terminationReason !== null) {
95
140
  // A requested cancellation is an outcome, not a failure: status stays
96
141
  // Unset (no success assertion over a truncated generation, no error),
@@ -105,15 +150,57 @@ function finishSpan(span, event, abortTerminationReason) {
105
150
  // abort (callback returned null) — fail toward a false error, never a
106
151
  // hidden one.
107
152
  const errorMessage = message.errorMessage ?? "Unknown error";
108
- span.recordException(new Error(errorMessage));
153
+ errorType = classifyErrorType(errorMessage, "model_error");
154
+ recordGenAiException(span, new Error(errorMessage), errorType);
109
155
  span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });
110
156
  }
111
- else {
112
- span.setStatus({ code: SpanStatusCode.OK });
157
+ if (metrics) {
158
+ recordCompletionMetrics(metrics, model, message, startedAt, errorType);
113
159
  }
114
160
  span.end();
115
161
  }
116
- function assistantErrorMessage(model, errorMessage) {
162
+ function chatMetricAttributes(model) {
163
+ return {
164
+ [GenAi.OPERATION_NAME]: "chat",
165
+ [GenAi.PROVIDER_NAME]: model.provider,
166
+ [GenAi.REQUEST_MODEL]: model.id,
167
+ ...serverAttributes(model.baseUrl),
168
+ };
169
+ }
170
+ function recordCompletionMetrics(metrics, model, message, startedAt, errorType) {
171
+ const attributes = chatMetricAttributes(model);
172
+ if (message.model) {
173
+ attributes[GenAi.RESPONSE_MODEL] = message.model;
174
+ }
175
+ const durationAttributes = errorType
176
+ ? { ...attributes, "error.type": errorType }
177
+ : attributes;
178
+ metrics.operationDuration.record((Date.now() - startedAt) / 1000, durationAttributes);
179
+ const inputTokens = message.usage.input + message.usage.cacheRead + message.usage.cacheWrite;
180
+ if (inputTokens > 0) {
181
+ metrics.tokenUsage.record(inputTokens, {
182
+ ...attributes,
183
+ "gen_ai.token.type": "input",
184
+ });
185
+ }
186
+ if (message.usage.output > 0) {
187
+ metrics.tokenUsage.record(message.usage.output, {
188
+ ...attributes,
189
+ "gen_ai.token.type": "output",
190
+ });
191
+ }
192
+ }
193
+ function recordGenAiException(span, error, errorType) {
194
+ const exception = error instanceof Error ? error : new Error(String(error));
195
+ span.setAttribute("error.type", errorType);
196
+ // recordException writes the standard `exception` span event
197
+ // (exception.type / exception.message / exception.stacktrace). The
198
+ // semconv `gen_ai.client.operation.exception` signal is a *log* event —
199
+ // duplicating it as a second span event is not part of the convention,
200
+ // so only the standard exception event is recorded here.
201
+ span.recordException(exception);
202
+ }
203
+ function assistantErrorMessage(model, errorMessage, aborted = false) {
117
204
  return {
118
205
  role: "assistant",
119
206
  content: [],
@@ -128,7 +215,7 @@ function assistantErrorMessage(model, errorMessage) {
128
215
  totalTokens: 0,
129
216
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
130
217
  },
131
- stopReason: "error",
218
+ stopReason: aborted ? "aborted" : "error",
132
219
  errorMessage,
133
220
  timestamp: Date.now(),
134
221
  };
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-stream.js","sourceRoot":"","sources":["../src/instrument-stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrument-stream.js","sourceRoot":"","sources":["../src/instrument-stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * GenAI client metric instruments per the OTel GenAI semconv metrics doc.
3
+ *
4
+ * Instruments are created lazily per `Meter` and cached, so repeated
5
+ * `instrumentStream` / `instrumentAgent` calls sharing a meter reuse the
6
+ * same histograms. Bucket boundaries follow the spec's
7
+ * ExplicitBucketBoundaries advice where one is defined.
8
+ */
9
+ import type { Histogram, Meter } from "@opentelemetry/api";
10
+ export interface GenAiMetrics {
11
+ /** `gen_ai.client.token.usage` — record once per token type per call. */
12
+ tokenUsage: Histogram;
13
+ /** `gen_ai.client.operation.duration` — seconds per chat call. */
14
+ operationDuration: Histogram;
15
+ /** `gen_ai.client.operation.time_to_first_chunk` — streaming only. */
16
+ timeToFirstChunk: Histogram;
17
+ /** `gen_ai.client.operation.time_per_output_chunk` — per chunk after the first. */
18
+ timePerOutputChunk: Histogram;
19
+ /** `gen_ai.execute_tool.duration` — seconds per tool execution. */
20
+ executeToolDuration: Histogram;
21
+ /** `gen_ai.invoke_agent.duration` — seconds per in-process agent run. */
22
+ invokeAgentDuration: Histogram;
23
+ }
24
+ /** Get (or create) the GenAI instruments for a meter. */
25
+ export declare function genAiMetrics(meter: Meter): GenAiMetrics;
26
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAmB3D,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,UAAU,EAAE,SAAS,CAAC;IACtB,kEAAkE;IAClE,iBAAiB,EAAE,SAAS,CAAC;IAC7B,sEAAsE;IACtE,gBAAgB,EAAE,SAAS,CAAC;IAC5B,mFAAmF;IACnF,kBAAkB,EAAE,SAAS,CAAC;IAC9B,mEAAmE;IACnE,mBAAmB,EAAE,SAAS,CAAC;IAC/B,yEAAyE;IACzE,mBAAmB,EAAE,SAAS,CAAC;CAChC;AAID,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,YAAY,CAmDvD"}