@introspection-sdk/introspection-node 0.4.2 → 0.4.3
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check-types.log +4 -0
- package/.turbo/turbo-lint.log +5 -0
- package/dist/client.d.ts +42 -132
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +67 -366
- package/dist/client.js.map +1 -0
- package/dist/converters/claude.d.ts +4 -0
- package/dist/converters/claude.d.ts.map +1 -1
- package/dist/converters/claude.js +7 -0
- package/dist/converters/claude.js.map +1 -0
- package/dist/converters/gemini.d.ts +90 -0
- package/dist/converters/gemini.d.ts.map +1 -0
- package/dist/converters/gemini.js +171 -0
- package/dist/converters/gemini.js.map +1 -0
- package/dist/converters/index.d.ts +1 -1
- package/dist/converters/index.d.ts.map +1 -1
- package/dist/converters/index.js +2 -1
- package/dist/converters/index.js.map +1 -0
- package/dist/converters/openai.js +1 -0
- package/dist/converters/openai.js.map +1 -0
- package/dist/converters/openclaw.d.ts +16 -0
- package/dist/converters/openclaw.d.ts.map +1 -0
- package/dist/converters/openclaw.js +49 -0
- package/dist/converters/openinference.js +1 -0
- package/dist/converters/openinference.js.map +1 -0
- package/dist/converters/vercel.d.ts.map +1 -1
- package/dist/converters/vercel.js +31 -3
- package/dist/converters/vercel.js.map +1 -0
- package/dist/http.d.ts +36 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +145 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +24 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -32
- package/dist/index.js.map +1 -0
- package/dist/otel/anthropic.d.ts +62 -0
- package/dist/otel/anthropic.d.ts.map +1 -0
- package/dist/otel/anthropic.js +425 -0
- package/dist/otel/anthropic.js.map +1 -0
- package/dist/otel/claude-hooks.d.ts +346 -0
- package/dist/otel/claude-hooks.d.ts.map +1 -0
- package/dist/otel/claude-hooks.js +645 -0
- package/dist/otel/claude-hooks.js.map +1 -0
- package/dist/otel/claude-wrapper.d.ts +93 -0
- package/dist/otel/claude-wrapper.d.ts.map +1 -0
- package/dist/otel/claude-wrapper.js +155 -0
- package/dist/otel/claude-wrapper.js.map +1 -0
- package/dist/otel/gemini.d.ts +48 -0
- package/dist/otel/gemini.d.ts.map +1 -0
- package/dist/otel/gemini.js +297 -0
- package/dist/otel/gemini.js.map +1 -0
- package/dist/otel/index.d.ts +27 -0
- package/dist/otel/index.d.ts.map +1 -0
- package/dist/otel/index.js +33 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/otel/langchain-handler.d.ts +112 -0
- package/dist/otel/langchain-handler.d.ts.map +1 -0
- package/dist/otel/langchain-handler.js +555 -0
- package/dist/otel/langchain-handler.js.map +1 -0
- package/dist/otel/logs.d.ts +82 -0
- package/dist/otel/logs.d.ts.map +1 -0
- package/dist/otel/logs.js +271 -0
- package/dist/otel/logs.js.map +1 -0
- package/dist/otel/mastra-exporter.d.ts +87 -0
- package/dist/otel/mastra-exporter.d.ts.map +1 -0
- package/dist/otel/mastra-exporter.js +525 -0
- package/dist/otel/mastra-exporter.js.map +1 -0
- package/dist/otel/pi.d.ts +59 -0
- package/dist/otel/pi.d.ts.map +1 -0
- package/dist/otel/pi.js +67 -0
- package/dist/otel/pi.js.map +1 -0
- package/dist/otel/setup.d.ts +23 -0
- package/dist/otel/setup.d.ts.map +1 -0
- package/dist/otel/setup.js +127 -0
- package/dist/otel/setup.js.map +1 -0
- package/dist/otel/span-processor.d.ts +62 -0
- package/dist/otel/span-processor.d.ts.map +1 -0
- package/dist/otel/span-processor.js +319 -0
- package/dist/otel/span-processor.js.map +1 -0
- package/dist/otel/tracing-processor.d.ts +139 -0
- package/dist/otel/tracing-processor.d.ts.map +1 -0
- package/dist/otel/tracing-processor.js +418 -0
- package/dist/otel/tracing-processor.js.map +1 -0
- package/dist/resources/experiments.d.ts +31 -0
- package/dist/resources/experiments.d.ts.map +1 -0
- package/dist/resources/experiments.js +133 -0
- package/dist/resources/experiments.js.map +1 -0
- package/dist/resources/recipes.d.ts +22 -0
- package/dist/resources/recipes.d.ts.map +1 -0
- package/dist/resources/recipes.js +61 -0
- package/dist/resources/recipes.js.map +1 -0
- package/dist/resources/runtimes.d.ts +76 -0
- package/dist/resources/runtimes.d.ts.map +1 -0
- package/dist/resources/runtimes.js +179 -0
- package/dist/resources/runtimes.js.map +1 -0
- package/dist/runner-resources/files.d.ts +35 -0
- package/dist/runner-resources/files.d.ts.map +1 -0
- package/dist/runner-resources/files.js +131 -0
- package/dist/runner-resources/files.js.map +1 -0
- package/dist/runner-resources/tasks.d.ts +39 -0
- package/dist/runner-resources/tasks.d.ts.map +1 -0
- package/dist/runner-resources/tasks.js +129 -0
- package/dist/runner-resources/tasks.js.map +1 -0
- package/dist/runner.d.ts +80 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +159 -0
- package/dist/runner.js.map +1 -0
- package/dist/streaming.d.ts +8 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +67 -0
- package/dist/streaming.js.map +1 -0
- package/dist/types/genai.d.ts +165 -0
- package/dist/types/genai.d.ts.map +1 -0
- package/dist/types/genai.js +82 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -0
- package/dist/version.js +1 -0
- package/dist/version.js.map +1 -0
- package/package.json +81 -24
- package/src/client.ts +76 -473
- package/src/converters/claude.ts +10 -0
- package/src/converters/gemini.ts +243 -0
- package/src/converters/index.ts +13 -7
- package/src/converters/vercel.ts +41 -3
- package/src/http.ts +163 -0
- package/src/index.ts +110 -75
- package/src/{anthropic.ts → otel/anthropic.ts} +95 -8
- package/src/{claude-hooks.ts → otel/claude-hooks.ts} +119 -20
- package/src/{claude-wrapper.ts → otel/claude-wrapper.ts} +1 -1
- package/src/otel/gemini.ts +397 -0
- package/src/otel/index.ts +80 -0
- package/src/{langchain-handler.ts → otel/langchain-handler.ts} +53 -6
- package/src/otel/logs.ts +394 -0
- package/src/{mastra-exporter.ts → otel/mastra-exporter.ts} +83 -22
- package/src/otel/pi.ts +88 -0
- package/src/otel/setup.ts +174 -0
- package/src/{span-processor.ts → otel/span-processor.ts} +11 -6
- package/src/{tracing-processor.ts → otel/tracing-processor.ts} +94 -19
- package/src/resources/experiments.ts +170 -0
- package/src/resources/recipes.ts +73 -0
- package/src/resources/runtimes.ts +231 -0
- package/src/runner-resources/files.ts +163 -0
- package/src/runner-resources/tasks.ts +156 -0
- package/src/runner.ts +194 -0
- package/src/streaming.ts +57 -0
- package/src/ai-sdk-integration.ts +0 -691
- package/src/converters/ai-sdk.ts +0 -323
|
@@ -1,691 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI SDK TelemetryIntegration for Introspection.
|
|
3
|
-
*
|
|
4
|
-
* Implements the Vercel AI SDK's TelemetryIntegration interface to capture
|
|
5
|
-
* LLM interactions and export them as OpenTelemetry spans with gen_ai.*
|
|
6
|
-
* semantic convention attributes to the Introspection backend.
|
|
7
|
-
*
|
|
8
|
-
* Similar to IntrospectionTracingProcessor for OpenAI Agents SDK and
|
|
9
|
-
* IntrospectionClaudeHooks for Claude Agent SDK.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* import { IntrospectionAISDKIntegration } from "@introspection-sdk/introspection-node";
|
|
14
|
-
* import { generateText } from "ai";
|
|
15
|
-
* import { openai } from "@ai-sdk/openai";
|
|
16
|
-
*
|
|
17
|
-
* const introspection = new IntrospectionAISDKIntegration({
|
|
18
|
-
* token: "sk-intro-...",
|
|
19
|
-
* serviceName: "my-app",
|
|
20
|
-
* });
|
|
21
|
-
*
|
|
22
|
-
* const { text } = await generateText({
|
|
23
|
-
* model: openai("gpt-4o"),
|
|
24
|
-
* prompt: "Hello!",
|
|
25
|
-
* experimental_telemetry: {
|
|
26
|
-
* isEnabled: true,
|
|
27
|
-
* integrations: [introspection],
|
|
28
|
-
* },
|
|
29
|
-
* });
|
|
30
|
-
*
|
|
31
|
-
* await introspection.shutdown();
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import {
|
|
36
|
-
context as otelContext,
|
|
37
|
-
trace as otelTrace,
|
|
38
|
-
type Span as OtelSpan,
|
|
39
|
-
} from "@opentelemetry/api";
|
|
40
|
-
import {
|
|
41
|
-
BasicTracerProvider,
|
|
42
|
-
BatchSpanProcessor,
|
|
43
|
-
SimpleSpanProcessor,
|
|
44
|
-
type SpanExporter,
|
|
45
|
-
type IdGenerator,
|
|
46
|
-
} from "@opentelemetry/sdk-trace-base";
|
|
47
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
48
|
-
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
49
|
-
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
50
|
-
import { randomUUID } from "crypto";
|
|
51
|
-
|
|
52
|
-
import {
|
|
53
|
-
convertMessagesToInputMessages,
|
|
54
|
-
extractSystemInstructions,
|
|
55
|
-
buildOutputMessages,
|
|
56
|
-
convertToolsToToolDefinitions,
|
|
57
|
-
} from "./converters/ai-sdk.js";
|
|
58
|
-
import type { InputMessage } from "@introspection-sdk/types";
|
|
59
|
-
import { logger, withOtlpHttpsProxy } from "./utils.js";
|
|
60
|
-
import { VERSION } from "./version.js";
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Configuration types
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
/** Advanced options for testing and customization. */
|
|
67
|
-
export interface AISDKIntegrationAdvancedOptions {
|
|
68
|
-
/** Custom span exporter (for testing — use InMemorySpanExporter). */
|
|
69
|
-
spanExporter?: SpanExporter;
|
|
70
|
-
/** Custom ID generator (for testing). */
|
|
71
|
-
idGenerator?: IdGenerator;
|
|
72
|
-
/** Use SimpleSpanProcessor instead of BatchSpanProcessor (for testing). */
|
|
73
|
-
useSimpleSpanProcessor?: boolean;
|
|
74
|
-
/** Maximum batch size for span export. */
|
|
75
|
-
maxExportBatchSize?: number;
|
|
76
|
-
/** Delay between batch exports in milliseconds. */
|
|
77
|
-
scheduledDelayMillis?: number;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Configuration for {@link IntrospectionAISDKIntegration}. */
|
|
81
|
-
export interface IntrospectionAISDKIntegrationOptions {
|
|
82
|
-
/** Authentication token (env: INTROSPECTION_TOKEN). */
|
|
83
|
-
token?: string;
|
|
84
|
-
/** Base URL for the API (env: INTROSPECTION_BASE_URL, default: "https://otel.introspection.dev"). */
|
|
85
|
-
baseUrl?: string;
|
|
86
|
-
/** Service name for telemetry (env: INTROSPECTION_SERVICE_NAME). */
|
|
87
|
-
serviceName?: string;
|
|
88
|
-
/** Additional headers to include in requests. */
|
|
89
|
-
additionalHeaders?: Record<string, string>;
|
|
90
|
-
/** Advanced options for testing and customization. */
|
|
91
|
-
advanced?: AISDKIntegrationAdvancedOptions;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
// Internal state
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
interface GenerationState {
|
|
99
|
-
rootSpan: OtelSpan;
|
|
100
|
-
conversationId: string;
|
|
101
|
-
system: unknown;
|
|
102
|
-
metadata: Record<string, unknown> | undefined;
|
|
103
|
-
stepSpans: Map<number, OtelSpan>;
|
|
104
|
-
stepMessages: Map<number, InputMessage[]>;
|
|
105
|
-
toolCallSpans: Map<string, OtelSpan>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// Integration class
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* AI SDK {@link TelemetryIntegration} that captures LLM interactions and
|
|
114
|
-
* exports them as OpenTelemetry spans with `gen_ai.*` semantic convention
|
|
115
|
-
* attributes to the Introspection backend.
|
|
116
|
-
*
|
|
117
|
-
* Creates its own {@link BasicTracerProvider} — no external OTel setup required.
|
|
118
|
-
* Each generation (generateText/streamText call) produces:
|
|
119
|
-
* - A root span grouping the entire operation
|
|
120
|
-
* - Step spans (one per LLM call) with full gen_ai attributes
|
|
121
|
-
* - Tool call spans nested under their parent step
|
|
122
|
-
*/
|
|
123
|
-
export class IntrospectionAISDKIntegration {
|
|
124
|
-
private _tracerProvider: BasicTracerProvider;
|
|
125
|
-
private _tracer: ReturnType<BasicTracerProvider["getTracer"]>;
|
|
126
|
-
private _generation: GenerationState | null = null;
|
|
127
|
-
|
|
128
|
-
constructor(options: IntrospectionAISDKIntegrationOptions = {}) {
|
|
129
|
-
const advanced = options.advanced;
|
|
130
|
-
const serviceName =
|
|
131
|
-
options.serviceName || process.env.INTROSPECTION_SERVICE_NAME;
|
|
132
|
-
const resource = serviceName
|
|
133
|
-
? resourceFromAttributes({ [ATTR_SERVICE_NAME]: serviceName })
|
|
134
|
-
: undefined;
|
|
135
|
-
|
|
136
|
-
// Production mode — requires a token
|
|
137
|
-
if (!advanced?.spanExporter) {
|
|
138
|
-
const token = options.token || process.env.INTROSPECTION_TOKEN;
|
|
139
|
-
if (!token) {
|
|
140
|
-
throw new Error("INTROSPECTION_TOKEN is required");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const baseUrl =
|
|
144
|
-
options.baseUrl ||
|
|
145
|
-
process.env.INTROSPECTION_BASE_URL ||
|
|
146
|
-
"https://otel.introspection.dev";
|
|
147
|
-
|
|
148
|
-
const headers: Record<string, string> = {
|
|
149
|
-
"User-Agent": `introspection-sdk/${VERSION}`,
|
|
150
|
-
Authorization: `Bearer ${token}`,
|
|
151
|
-
...options.additionalHeaders,
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const endpoint = baseUrl.endsWith("/v1/traces")
|
|
155
|
-
? baseUrl
|
|
156
|
-
: `${baseUrl.replace(/\/$/, "")}/v1/traces`;
|
|
157
|
-
|
|
158
|
-
logger.info(
|
|
159
|
-
`IntrospectionAISDKIntegration initialized: endpoint=${endpoint}`,
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
const spanExporter = new OTLPTraceExporter(
|
|
163
|
-
withOtlpHttpsProxy({ url: endpoint, headers }),
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// Use SimpleSpanProcessor for sequential export (dev/staging tokens)
|
|
167
|
-
const effectiveBatchSize =
|
|
168
|
-
advanced?.maxExportBatchSize ??
|
|
169
|
-
(token.startsWith("intro_dev") || token.startsWith("intro_staging")
|
|
170
|
-
? 1
|
|
171
|
-
: undefined);
|
|
172
|
-
const useSimple =
|
|
173
|
-
advanced?.useSimpleSpanProcessor || effectiveBatchSize === 1;
|
|
174
|
-
const scheduledDelayMillis = advanced?.scheduledDelayMillis ?? 1000;
|
|
175
|
-
|
|
176
|
-
const spanProcessor = useSimple
|
|
177
|
-
? new SimpleSpanProcessor(spanExporter)
|
|
178
|
-
: new BatchSpanProcessor(spanExporter, {
|
|
179
|
-
scheduledDelayMillis,
|
|
180
|
-
...(effectiveBatchSize
|
|
181
|
-
? { maxExportBatchSize: effectiveBatchSize }
|
|
182
|
-
: {}),
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
this._tracerProvider = new BasicTracerProvider({
|
|
186
|
-
resource,
|
|
187
|
-
idGenerator: advanced?.idGenerator,
|
|
188
|
-
spanProcessors: [spanProcessor],
|
|
189
|
-
});
|
|
190
|
-
} else {
|
|
191
|
-
// Testing mode — use provided exporter
|
|
192
|
-
logger.info(
|
|
193
|
-
`IntrospectionAISDKIntegration initialized in test mode with custom exporter`,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const spanProcessor = advanced.useSimpleSpanProcessor
|
|
197
|
-
? new SimpleSpanProcessor(advanced.spanExporter)
|
|
198
|
-
: new BatchSpanProcessor(advanced.spanExporter, {
|
|
199
|
-
scheduledDelayMillis: 100,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
this._tracerProvider = new BasicTracerProvider({
|
|
203
|
-
resource,
|
|
204
|
-
idGenerator: advanced.idGenerator,
|
|
205
|
-
spanProcessors: [spanProcessor],
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
this._tracer = this._tracerProvider.getTracer("ai-sdk", VERSION);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// -------------------------------------------------------------------------
|
|
213
|
-
// TelemetryIntegration lifecycle methods
|
|
214
|
-
//
|
|
215
|
-
// Defined as arrow functions to preserve `this` binding when the AI SDK
|
|
216
|
-
// calls them as plain function references.
|
|
217
|
-
//
|
|
218
|
-
// Event parameters use `unknown` for TypeScript compatibility with the
|
|
219
|
-
// AI SDK's TelemetryIntegration interface (avoids index signature mismatch).
|
|
220
|
-
// -------------------------------------------------------------------------
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Called when a generation operation begins (before any LLM calls).
|
|
224
|
-
* Creates a root span to group all steps in this generation.
|
|
225
|
-
*/
|
|
226
|
-
onStart = (event: unknown): void => {
|
|
227
|
-
try {
|
|
228
|
-
const e = event as Record<string, unknown>;
|
|
229
|
-
// End any lingering previous generation (safety net)
|
|
230
|
-
if (this._generation) {
|
|
231
|
-
this._endGeneration();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const model = e.model as
|
|
235
|
-
| { provider?: string; modelId?: string }
|
|
236
|
-
| undefined;
|
|
237
|
-
const functionId = e.functionId as string | undefined;
|
|
238
|
-
const metadata = e.metadata as Record<string, unknown> | undefined;
|
|
239
|
-
const name = functionId || "ai-sdk-generation";
|
|
240
|
-
|
|
241
|
-
// Conversation ID: metadata > auto-generate
|
|
242
|
-
const metadataConvId = metadata?.["gen_ai.conversation.id"] as
|
|
243
|
-
| string
|
|
244
|
-
| undefined;
|
|
245
|
-
const conversationId =
|
|
246
|
-
metadataConvId || `intro_conv_${randomUUID().replace(/-/g, "")}`;
|
|
247
|
-
|
|
248
|
-
const rootSpan = this._tracer.startSpan(name);
|
|
249
|
-
rootSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
250
|
-
if (functionId) {
|
|
251
|
-
rootSpan.setAttribute("gen_ai.agent.name", functionId);
|
|
252
|
-
}
|
|
253
|
-
if (model?.provider) {
|
|
254
|
-
rootSpan.setAttribute("gen_ai.system", model.provider);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Pass through all telemetry metadata as span attributes
|
|
258
|
-
if (metadata) {
|
|
259
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
260
|
-
if (value !== undefined && value !== null) {
|
|
261
|
-
rootSpan.setAttribute(
|
|
262
|
-
`ai.telemetry.metadata.${key}`,
|
|
263
|
-
typeof value === "string" ? value : JSON.stringify(value),
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
this._generation = {
|
|
270
|
-
rootSpan,
|
|
271
|
-
conversationId,
|
|
272
|
-
system: e.system,
|
|
273
|
-
metadata,
|
|
274
|
-
stepSpans: new Map(),
|
|
275
|
-
stepMessages: new Map(),
|
|
276
|
-
toolCallSpans: new Map(),
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
logger.debug(`Generation started: ${name} (${conversationId})`);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
logger.error(
|
|
282
|
-
`onStart error: ${error instanceof Error ? error.message : String(error)}`,
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Called when a step (individual LLM call) begins.
|
|
289
|
-
* Creates a child span under the root span and captures input messages.
|
|
290
|
-
*/
|
|
291
|
-
onStepStart = (event: unknown): void => {
|
|
292
|
-
try {
|
|
293
|
-
const e = event as Record<string, unknown>;
|
|
294
|
-
const gen = this._generation;
|
|
295
|
-
if (!gen) return;
|
|
296
|
-
|
|
297
|
-
const stepNumber = (e.stepNumber as number) ?? 0;
|
|
298
|
-
const model = e.model as
|
|
299
|
-
| { provider?: string; modelId?: string }
|
|
300
|
-
| undefined;
|
|
301
|
-
const messages = (e.messages as readonly unknown[]) || [];
|
|
302
|
-
const system = e.system ?? gen.system;
|
|
303
|
-
const tools = e.tools;
|
|
304
|
-
const functionId = e.functionId as string | undefined;
|
|
305
|
-
|
|
306
|
-
// Create step span as child of root
|
|
307
|
-
const parentCtx = otelTrace.setSpan(otelContext.active(), gen.rootSpan);
|
|
308
|
-
const spanName = model?.modelId
|
|
309
|
-
? `chat ${model.modelId}`
|
|
310
|
-
: `step-${stepNumber}`;
|
|
311
|
-
const stepSpan = this._tracer.startSpan(spanName, {}, parentCtx);
|
|
312
|
-
|
|
313
|
-
gen.stepSpans.set(stepNumber, stepSpan);
|
|
314
|
-
|
|
315
|
-
// Capture input messages for use in onStepFinish
|
|
316
|
-
const inputMessages = convertMessagesToInputMessages(messages);
|
|
317
|
-
gen.stepMessages.set(stepNumber, inputMessages);
|
|
318
|
-
|
|
319
|
-
// Set input-side attributes immediately
|
|
320
|
-
stepSpan.setAttribute("gen_ai.operation.name", "chat");
|
|
321
|
-
stepSpan.setAttribute("gen_ai.conversation.id", gen.conversationId);
|
|
322
|
-
|
|
323
|
-
if (model?.provider) {
|
|
324
|
-
stepSpan.setAttribute("gen_ai.system", model.provider);
|
|
325
|
-
}
|
|
326
|
-
if (model?.modelId) {
|
|
327
|
-
stepSpan.setAttribute("gen_ai.request.model", model.modelId);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// System instructions
|
|
331
|
-
const sysInstructions = extractSystemInstructions(system, messages);
|
|
332
|
-
if (sysInstructions) {
|
|
333
|
-
stepSpan.setAttribute(
|
|
334
|
-
"gen_ai.system_instructions",
|
|
335
|
-
JSON.stringify(sysInstructions),
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Tool definitions
|
|
340
|
-
const toolDefs = convertToolsToToolDefinitions(tools);
|
|
341
|
-
if (toolDefs) {
|
|
342
|
-
stepSpan.setAttribute(
|
|
343
|
-
"gen_ai.tool.definitions",
|
|
344
|
-
JSON.stringify(toolDefs),
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Agent name from functionId
|
|
349
|
-
if (functionId) {
|
|
350
|
-
stepSpan.setAttribute("gen_ai.agent.name", functionId);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Pass through telemetry metadata to step spans
|
|
354
|
-
if (gen.metadata) {
|
|
355
|
-
for (const [key, value] of Object.entries(gen.metadata)) {
|
|
356
|
-
if (value !== undefined && value !== null) {
|
|
357
|
-
stepSpan.setAttribute(
|
|
358
|
-
`ai.telemetry.metadata.${key}`,
|
|
359
|
-
typeof value === "string" ? value : JSON.stringify(value),
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
logger.debug(`Step ${stepNumber} started: ${spanName}`);
|
|
366
|
-
} catch (error) {
|
|
367
|
-
logger.error(
|
|
368
|
-
`onStepStart error: ${error instanceof Error ? error.message : String(error)}`,
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Called when a tool execution begins.
|
|
375
|
-
* Creates a child span under the current step span.
|
|
376
|
-
*/
|
|
377
|
-
onToolCallStart = (event: unknown): void => {
|
|
378
|
-
try {
|
|
379
|
-
const e = event as Record<string, unknown>;
|
|
380
|
-
const gen = this._generation;
|
|
381
|
-
if (!gen) return;
|
|
382
|
-
|
|
383
|
-
const toolCall = e.toolCall as
|
|
384
|
-
| { toolCallId?: string; toolName?: string; input?: unknown }
|
|
385
|
-
| undefined;
|
|
386
|
-
if (!toolCall) return;
|
|
387
|
-
|
|
388
|
-
const stepNumber = (e.stepNumber as number) ?? 0;
|
|
389
|
-
const parentSpan = gen.stepSpans.get(stepNumber) || gen.rootSpan;
|
|
390
|
-
const parentCtx = otelTrace.setSpan(otelContext.active(), parentSpan);
|
|
391
|
-
|
|
392
|
-
const toolName = toolCall.toolName || "unknown-tool";
|
|
393
|
-
const toolSpan = this._tracer.startSpan(toolName, {}, parentCtx);
|
|
394
|
-
toolSpan.setAttribute("gen_ai.tool.name", toolName);
|
|
395
|
-
toolSpan.setAttribute("openinference.span.kind", "TOOL");
|
|
396
|
-
|
|
397
|
-
if (toolCall.input !== undefined) {
|
|
398
|
-
toolSpan.setAttribute(
|
|
399
|
-
"gen_ai.tool.input",
|
|
400
|
-
typeof toolCall.input === "string"
|
|
401
|
-
? toolCall.input
|
|
402
|
-
: JSON.stringify(toolCall.input),
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Propagate conversation ID
|
|
407
|
-
toolSpan.setAttribute("gen_ai.conversation.id", gen.conversationId);
|
|
408
|
-
|
|
409
|
-
const toolCallId =
|
|
410
|
-
toolCall.toolCallId || `tool-${stepNumber}-${toolName}`;
|
|
411
|
-
gen.toolCallSpans.set(toolCallId, toolSpan);
|
|
412
|
-
|
|
413
|
-
logger.debug(`Tool call started: ${toolName} (${toolCallId})`);
|
|
414
|
-
} catch (error) {
|
|
415
|
-
logger.error(
|
|
416
|
-
`onToolCallStart error: ${error instanceof Error ? error.message : String(error)}`,
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Called when a tool execution completes.
|
|
423
|
-
* Sets output/error attributes and ends the tool span.
|
|
424
|
-
*/
|
|
425
|
-
onToolCallFinish = (event: unknown): void => {
|
|
426
|
-
try {
|
|
427
|
-
const e = event as Record<string, unknown>;
|
|
428
|
-
const gen = this._generation;
|
|
429
|
-
if (!gen) return;
|
|
430
|
-
|
|
431
|
-
const toolCall = e.toolCall as
|
|
432
|
-
| { toolCallId?: string; toolName?: string }
|
|
433
|
-
| undefined;
|
|
434
|
-
if (!toolCall) return;
|
|
435
|
-
|
|
436
|
-
const stepNumber = (e.stepNumber as number) ?? 0;
|
|
437
|
-
const toolCallId =
|
|
438
|
-
toolCall.toolCallId ||
|
|
439
|
-
`tool-${stepNumber}-${toolCall.toolName || "unknown"}`;
|
|
440
|
-
const toolSpan = gen.toolCallSpans.get(toolCallId);
|
|
441
|
-
if (!toolSpan) return;
|
|
442
|
-
|
|
443
|
-
gen.toolCallSpans.delete(toolCallId);
|
|
444
|
-
|
|
445
|
-
const success = e.success as boolean;
|
|
446
|
-
if (success && e.output !== undefined) {
|
|
447
|
-
toolSpan.setAttribute(
|
|
448
|
-
"gen_ai.tool.output",
|
|
449
|
-
typeof e.output === "string" ? e.output : JSON.stringify(e.output),
|
|
450
|
-
);
|
|
451
|
-
} else if (!success && e.error !== undefined) {
|
|
452
|
-
toolSpan.setAttribute(
|
|
453
|
-
"gen_ai.tool.error",
|
|
454
|
-
typeof e.error === "string"
|
|
455
|
-
? e.error
|
|
456
|
-
: e.error instanceof Error
|
|
457
|
-
? e.error.message
|
|
458
|
-
: JSON.stringify(e.error),
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (typeof e.durationMs === "number") {
|
|
463
|
-
toolSpan.setAttribute("gen_ai.tool.duration_ms", e.durationMs);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
toolSpan.end();
|
|
467
|
-
logger.debug(`Tool call finished: ${toolCall.toolName} (${toolCallId})`);
|
|
468
|
-
} catch (error) {
|
|
469
|
-
logger.error(
|
|
470
|
-
`onToolCallFinish error: ${error instanceof Error ? error.message : String(error)}`,
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Called when a step (LLM call) completes.
|
|
477
|
-
* Sets gen_ai attributes (input/output messages, usage, model) and ends
|
|
478
|
-
* the step span.
|
|
479
|
-
*/
|
|
480
|
-
onStepFinish = (event: unknown): void => {
|
|
481
|
-
try {
|
|
482
|
-
const e = event as Record<string, unknown>;
|
|
483
|
-
const gen = this._generation;
|
|
484
|
-
if (!gen) return;
|
|
485
|
-
|
|
486
|
-
const stepNumber = (e.stepNumber as number) ?? 0;
|
|
487
|
-
const stepSpan = gen.stepSpans.get(stepNumber);
|
|
488
|
-
if (!stepSpan) {
|
|
489
|
-
logger.debug(`No step span found for step ${stepNumber}`);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
gen.stepSpans.delete(stepNumber);
|
|
494
|
-
|
|
495
|
-
// Input messages (captured in onStepStart)
|
|
496
|
-
const inputMessages = gen.stepMessages.get(stepNumber);
|
|
497
|
-
gen.stepMessages.delete(stepNumber);
|
|
498
|
-
|
|
499
|
-
if (inputMessages && inputMessages.length > 0) {
|
|
500
|
-
stepSpan.setAttribute(
|
|
501
|
-
"gen_ai.input.messages",
|
|
502
|
-
JSON.stringify(inputMessages),
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Output messages
|
|
507
|
-
const outputMessages = buildOutputMessages({
|
|
508
|
-
text: e.text as string | undefined,
|
|
509
|
-
reasoningText: e.reasoningText as string | undefined,
|
|
510
|
-
reasoning: e.reasoning as readonly { text?: string }[] | undefined,
|
|
511
|
-
toolCalls: e.toolCalls as
|
|
512
|
-
| readonly {
|
|
513
|
-
toolCallId: string;
|
|
514
|
-
toolName: string;
|
|
515
|
-
input: unknown;
|
|
516
|
-
}[]
|
|
517
|
-
| undefined,
|
|
518
|
-
finishReason: e.finishReason as string | undefined,
|
|
519
|
-
});
|
|
520
|
-
if (outputMessages.length > 0) {
|
|
521
|
-
stepSpan.setAttribute(
|
|
522
|
-
"gen_ai.output.messages",
|
|
523
|
-
JSON.stringify(outputMessages),
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// Token usage
|
|
528
|
-
const usage = e.usage as
|
|
529
|
-
| {
|
|
530
|
-
inputTokens?: number;
|
|
531
|
-
outputTokens?: number;
|
|
532
|
-
inputTokenDetails?: {
|
|
533
|
-
cacheReadTokens?: number;
|
|
534
|
-
cacheWriteTokens?: number;
|
|
535
|
-
};
|
|
536
|
-
outputTokenDetails?: { reasoningTokens?: number };
|
|
537
|
-
}
|
|
538
|
-
| undefined;
|
|
539
|
-
if (usage) {
|
|
540
|
-
if (usage.inputTokens !== undefined) {
|
|
541
|
-
stepSpan.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens);
|
|
542
|
-
}
|
|
543
|
-
if (usage.outputTokens !== undefined) {
|
|
544
|
-
stepSpan.setAttribute(
|
|
545
|
-
"gen_ai.usage.output_tokens",
|
|
546
|
-
usage.outputTokens,
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
if (usage.inputTokenDetails?.cacheReadTokens !== undefined) {
|
|
550
|
-
stepSpan.setAttribute(
|
|
551
|
-
"gen_ai.usage.cache_read.input_tokens",
|
|
552
|
-
usage.inputTokenDetails.cacheReadTokens,
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
if (usage.inputTokenDetails?.cacheWriteTokens !== undefined) {
|
|
556
|
-
stepSpan.setAttribute(
|
|
557
|
-
"gen_ai.usage.cache_creation.input_tokens",
|
|
558
|
-
usage.inputTokenDetails.cacheWriteTokens,
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Response metadata
|
|
564
|
-
const response = e.response as
|
|
565
|
-
| { id?: string; modelId?: string }
|
|
566
|
-
| undefined;
|
|
567
|
-
if (response?.id) {
|
|
568
|
-
stepSpan.setAttribute("gen_ai.response.id", response.id);
|
|
569
|
-
}
|
|
570
|
-
if (response?.modelId) {
|
|
571
|
-
stepSpan.setAttribute("gen_ai.response.model", response.modelId);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Finish reason
|
|
575
|
-
const finishReason = e.finishReason as string | undefined;
|
|
576
|
-
if (finishReason) {
|
|
577
|
-
stepSpan.setAttribute("gen_ai.response.finish_reasons", [finishReason]);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Model info (reinforces what was set in onStepStart)
|
|
581
|
-
const model = e.model as
|
|
582
|
-
| { provider?: string; modelId?: string }
|
|
583
|
-
| undefined;
|
|
584
|
-
if (model?.provider) {
|
|
585
|
-
stepSpan.setAttribute("gen_ai.system", model.provider);
|
|
586
|
-
}
|
|
587
|
-
if (model?.modelId) {
|
|
588
|
-
stepSpan.setAttribute("gen_ai.request.model", model.modelId);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
stepSpan.setAttribute("openinference.span.kind", "LLM");
|
|
592
|
-
stepSpan.end();
|
|
593
|
-
logger.debug(`Step ${stepNumber} finished`);
|
|
594
|
-
} catch (error) {
|
|
595
|
-
logger.error(
|
|
596
|
-
`onStepFinish error: ${error instanceof Error ? error.message : String(error)}`,
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* Called when the entire generation completes (all steps finished).
|
|
603
|
-
* Sets total usage on the root span and ends it.
|
|
604
|
-
*/
|
|
605
|
-
onFinish = (event: unknown): void => {
|
|
606
|
-
try {
|
|
607
|
-
const e = event as Record<string, unknown>;
|
|
608
|
-
const gen = this._generation;
|
|
609
|
-
if (!gen) return;
|
|
610
|
-
|
|
611
|
-
// End any orphaned tool call spans
|
|
612
|
-
for (const [id, span] of gen.toolCallSpans) {
|
|
613
|
-
span.end();
|
|
614
|
-
logger.debug(`Ending orphaned tool call span: ${id}`);
|
|
615
|
-
}
|
|
616
|
-
gen.toolCallSpans.clear();
|
|
617
|
-
|
|
618
|
-
// End any orphaned step spans
|
|
619
|
-
for (const [stepNumber, span] of gen.stepSpans) {
|
|
620
|
-
span.end();
|
|
621
|
-
logger.debug(`Ending orphaned step span: ${stepNumber}`);
|
|
622
|
-
}
|
|
623
|
-
gen.stepSpans.clear();
|
|
624
|
-
|
|
625
|
-
// Total usage on root span
|
|
626
|
-
const totalUsage = e.totalUsage as
|
|
627
|
-
| { inputTokens?: number; outputTokens?: number }
|
|
628
|
-
| undefined;
|
|
629
|
-
if (totalUsage) {
|
|
630
|
-
if (totalUsage.inputTokens !== undefined) {
|
|
631
|
-
gen.rootSpan.setAttribute(
|
|
632
|
-
"gen_ai.usage.input_tokens",
|
|
633
|
-
totalUsage.inputTokens,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
if (totalUsage.outputTokens !== undefined) {
|
|
637
|
-
gen.rootSpan.setAttribute(
|
|
638
|
-
"gen_ai.usage.output_tokens",
|
|
639
|
-
totalUsage.outputTokens,
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
gen.rootSpan.end();
|
|
645
|
-
this._generation = null;
|
|
646
|
-
logger.debug("Generation finished");
|
|
647
|
-
} catch (error) {
|
|
648
|
-
logger.error(
|
|
649
|
-
`onFinish error: ${error instanceof Error ? error.message : String(error)}`,
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
// -------------------------------------------------------------------------
|
|
655
|
-
// Lifecycle methods
|
|
656
|
-
// -------------------------------------------------------------------------
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Shut down the tracer provider, flushing all pending spans.
|
|
660
|
-
*
|
|
661
|
-
* @returns A promise that resolves once shutdown is complete.
|
|
662
|
-
*/
|
|
663
|
-
async shutdown(): Promise<void> {
|
|
664
|
-
this._endGeneration();
|
|
665
|
-
await this._tracerProvider.shutdown();
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Force-flush any buffered spans to the Introspection backend.
|
|
670
|
-
*
|
|
671
|
-
* @returns A promise that resolves once the flush completes.
|
|
672
|
-
*/
|
|
673
|
-
async forceFlush(): Promise<void> {
|
|
674
|
-
await this._tracerProvider.forceFlush();
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// -------------------------------------------------------------------------
|
|
678
|
-
// Internal helpers
|
|
679
|
-
// -------------------------------------------------------------------------
|
|
680
|
-
|
|
681
|
-
/** Cleanly end any active generation, closing all open spans. */
|
|
682
|
-
private _endGeneration(): void {
|
|
683
|
-
const gen = this._generation;
|
|
684
|
-
if (!gen) return;
|
|
685
|
-
|
|
686
|
-
for (const span of gen.toolCallSpans.values()) span.end();
|
|
687
|
-
for (const span of gen.stepSpans.values()) span.end();
|
|
688
|
-
gen.rootSpan.end();
|
|
689
|
-
this._generation = null;
|
|
690
|
-
}
|
|
691
|
-
}
|