@ai-sdk/otel 1.0.0-beta.57 → 1.0.0-beta.59
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/CHANGELOG.md +48 -0
- package/dist/index.d.ts +29 -32
- package/dist/index.js +121 -73
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
- package/src/index.ts +1 -1
- package/src/{gen-ai-open-telemetry.ts → legacy-open-telemetry.ts} +462 -282
- package/src/open-telemetry.ts +379 -485
package/src/open-telemetry.ts
CHANGED
|
@@ -1,43 +1,50 @@
|
|
|
1
|
-
import { LanguageModelV4Prompt } from '@ai-sdk/provider';
|
|
2
|
-
import type { Context as AISDKContext } from '@ai-sdk/provider-utils';
|
|
3
1
|
import {
|
|
4
2
|
Attributes,
|
|
5
3
|
AttributeValue,
|
|
6
4
|
context,
|
|
7
5
|
Context as OpenTelemetryContext,
|
|
8
6
|
Span,
|
|
7
|
+
SpanKind,
|
|
9
8
|
SpanStatusCode,
|
|
10
9
|
trace,
|
|
11
10
|
Tracer,
|
|
12
11
|
} from '@opentelemetry/api';
|
|
13
12
|
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
EmbedOnStartEvent,
|
|
13
|
+
EmbeddingModelCallEndEvent,
|
|
14
|
+
EmbedEndEvent,
|
|
17
15
|
EmbedStartEvent,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
LanguageModelCallEndEvent,
|
|
17
|
+
LanguageModelCallStartEvent,
|
|
18
|
+
EmbeddingModelCallStartEvent,
|
|
19
|
+
GenerateObjectEndEvent,
|
|
20
|
+
GenerateObjectStartEvent,
|
|
21
|
+
GenerateObjectStepEndEvent,
|
|
22
|
+
GenerateObjectStepStartEvent,
|
|
23
|
+
StreamTextChunkEvent,
|
|
24
|
+
GenerateTextEndEvent,
|
|
25
|
+
GenerateTextStartEvent,
|
|
26
|
+
GenerateTextStepEndEvent,
|
|
27
|
+
GenerateTextStepStartEvent,
|
|
27
28
|
ToolExecutionEndEvent,
|
|
28
29
|
ToolExecutionStartEvent,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
RerankOnFinishEvent,
|
|
32
|
-
RerankOnStartEvent,
|
|
30
|
+
RerankingModelCallEndEvent,
|
|
31
|
+
RerankEndEvent,
|
|
33
32
|
RerankStartEvent,
|
|
33
|
+
RerankingModelCallStartEvent,
|
|
34
|
+
InferTelemetryEvent,
|
|
34
35
|
Telemetry,
|
|
35
36
|
TelemetryOptions,
|
|
36
37
|
ToolSet,
|
|
37
38
|
} from 'ai';
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
import {
|
|
40
|
+
formatInputMessages,
|
|
41
|
+
formatModelMessages,
|
|
42
|
+
formatObjectOutputMessages,
|
|
43
|
+
formatOutputMessages,
|
|
44
|
+
formatSystemInstructions,
|
|
45
|
+
mapOperationName,
|
|
46
|
+
mapProviderName,
|
|
47
|
+
} from './gen-ai-format-messages';
|
|
41
48
|
|
|
42
49
|
function recordSpanError(span: Span, error: unknown): void {
|
|
43
50
|
if (error instanceof Error) {
|
|
@@ -108,16 +115,6 @@ function selectAttributes(
|
|
|
108
115
|
return result;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
interface OtelStepStartEvent<
|
|
112
|
-
TOOLS extends ToolSet = ToolSet,
|
|
113
|
-
RUNTIME_CONTEXT extends AISDKContext = AISDKContext,
|
|
114
|
-
OUTPUT extends Output = Output,
|
|
115
|
-
> extends OnStepStartEvent<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
|
|
116
|
-
readonly promptMessages?: LanguageModelV4Prompt;
|
|
117
|
-
readonly stepTools?: ReadonlyArray<Record<string, unknown>>;
|
|
118
|
-
readonly stepToolChoice?: unknown;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
118
|
interface CallState {
|
|
122
119
|
operationId: string;
|
|
123
120
|
telemetry: TelemetryOptions | undefined;
|
|
@@ -125,19 +122,19 @@ interface CallState {
|
|
|
125
122
|
rootContext: OpenTelemetryContext | undefined;
|
|
126
123
|
stepSpan: Span | undefined;
|
|
127
124
|
stepContext: OpenTelemetryContext | undefined;
|
|
125
|
+
inferenceSpan: Span | undefined;
|
|
126
|
+
inferenceContext: OpenTelemetryContext | undefined;
|
|
128
127
|
embedSpans: Map<string, { span: Span; context: OpenTelemetryContext }>;
|
|
129
128
|
rerankSpan: { span: Span; context: OpenTelemetryContext } | undefined;
|
|
130
129
|
toolSpans: Map<string, { span: Span; context: OpenTelemetryContext }>;
|
|
131
|
-
baseTelemetryAttributes: Attributes;
|
|
132
130
|
settings: Record<string, unknown>;
|
|
131
|
+
provider: string;
|
|
132
|
+
modelId: string;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
export class OpenTelemetry implements Telemetry {
|
|
136
136
|
private readonly callStates = new Map<string, CallState>();
|
|
137
137
|
|
|
138
|
-
/**
|
|
139
|
-
* The tracer to use for the telemetry data.
|
|
140
|
-
*/
|
|
141
138
|
private readonly tracer: Tracer;
|
|
142
139
|
|
|
143
140
|
constructor(
|
|
@@ -145,7 +142,7 @@ export class OpenTelemetry implements Telemetry {
|
|
|
145
142
|
tracer?: Tracer;
|
|
146
143
|
} = {},
|
|
147
144
|
) {
|
|
148
|
-
this.tracer = options.tracer ?? trace.getTracer('
|
|
145
|
+
this.tracer = options.tracer ?? trace.getTracer('gen_ai');
|
|
149
146
|
}
|
|
150
147
|
|
|
151
148
|
private getCallState(callId: string): CallState | undefined {
|
|
@@ -176,23 +173,23 @@ export class OpenTelemetry implements Telemetry {
|
|
|
176
173
|
|
|
177
174
|
onStart(
|
|
178
175
|
event:
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
176
|
+
| InferTelemetryEvent<GenerateTextStartEvent>
|
|
177
|
+
| InferTelemetryEvent<GenerateObjectStartEvent>
|
|
178
|
+
| InferTelemetryEvent<EmbedStartEvent>
|
|
179
|
+
| InferTelemetryEvent<RerankStartEvent>,
|
|
183
180
|
): void {
|
|
184
|
-
if (event.isEnabled === false) return;
|
|
185
|
-
|
|
186
181
|
if (
|
|
187
182
|
event.operationId === 'ai.embed' ||
|
|
188
183
|
event.operationId === 'ai.embedMany'
|
|
189
184
|
) {
|
|
190
|
-
this.onEmbedOperationStart(event as
|
|
185
|
+
this.onEmbedOperationStart(event as InferTelemetryEvent<EmbedStartEvent>);
|
|
191
186
|
return;
|
|
192
187
|
}
|
|
193
188
|
|
|
194
189
|
if (event.operationId === 'ai.rerank') {
|
|
195
|
-
this.onRerankOperationStart(
|
|
190
|
+
this.onRerankOperationStart(
|
|
191
|
+
event as InferTelemetryEvent<RerankStartEvent>,
|
|
192
|
+
);
|
|
196
193
|
return;
|
|
197
194
|
}
|
|
198
195
|
|
|
@@ -200,16 +197,19 @@ export class OpenTelemetry implements Telemetry {
|
|
|
200
197
|
event.operationId === 'ai.generateObject' ||
|
|
201
198
|
event.operationId === 'ai.streamObject'
|
|
202
199
|
) {
|
|
203
|
-
this.onObjectOperationStart(
|
|
200
|
+
this.onObjectOperationStart(
|
|
201
|
+
event as InferTelemetryEvent<GenerateObjectStartEvent>,
|
|
202
|
+
);
|
|
204
203
|
return;
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
this.onGenerateStart(event as
|
|
206
|
+
this.onGenerateStart(event as InferTelemetryEvent<GenerateTextStartEvent>);
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
private onGenerateStart(
|
|
209
|
+
private onGenerateStart(
|
|
210
|
+
event: InferTelemetryEvent<GenerateTextStartEvent>,
|
|
211
|
+
): void {
|
|
211
212
|
const telemetry: TelemetryOptions = {
|
|
212
|
-
isEnabled: event.isEnabled,
|
|
213
213
|
recordInputs: event.recordInputs,
|
|
214
214
|
recordOutputs: event.recordOutputs,
|
|
215
215
|
functionId: event.functionId,
|
|
@@ -227,32 +227,46 @@ export class OpenTelemetry implements Telemetry {
|
|
|
227
227
|
maxRetries: event.maxRetries,
|
|
228
228
|
};
|
|
229
229
|
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
headers: event.headers,
|
|
233
|
-
settings,
|
|
234
|
-
context: event.runtimeContext as Record<string, unknown> | undefined,
|
|
235
|
-
});
|
|
230
|
+
const providerName = mapProviderName(event.provider);
|
|
231
|
+
const operationName = mapOperationName(event.operationId);
|
|
236
232
|
|
|
237
233
|
const attributes = selectAttributes(telemetry, {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
'
|
|
244
|
-
'
|
|
245
|
-
'
|
|
234
|
+
'gen_ai.operation.name': operationName,
|
|
235
|
+
'gen_ai.provider.name': providerName,
|
|
236
|
+
'gen_ai.request.model': event.modelId,
|
|
237
|
+
'gen_ai.agent.name': telemetry.functionId,
|
|
238
|
+
'gen_ai.request.frequency_penalty': event.frequencyPenalty,
|
|
239
|
+
'gen_ai.request.max_tokens': event.maxOutputTokens,
|
|
240
|
+
'gen_ai.request.presence_penalty': event.presencePenalty,
|
|
241
|
+
'gen_ai.request.temperature': (event.temperature ?? undefined) as
|
|
242
|
+
| number
|
|
243
|
+
| undefined,
|
|
244
|
+
'gen_ai.request.top_k': event.topK,
|
|
245
|
+
'gen_ai.request.top_p': event.topP,
|
|
246
|
+
'gen_ai.request.stop_sequences': event.stopSequences,
|
|
247
|
+
'gen_ai.request.seed': event.seed,
|
|
248
|
+
'gen_ai.system_instructions': event.system
|
|
249
|
+
? {
|
|
250
|
+
input: () =>
|
|
251
|
+
JSON.stringify(formatSystemInstructions(event.system!)),
|
|
252
|
+
}
|
|
253
|
+
: undefined,
|
|
254
|
+
'gen_ai.input.messages': {
|
|
246
255
|
input: () =>
|
|
247
|
-
JSON.stringify(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
JSON.stringify(
|
|
257
|
+
formatModelMessages({
|
|
258
|
+
prompt: undefined,
|
|
259
|
+
messages: event.messages,
|
|
260
|
+
}),
|
|
261
|
+
),
|
|
252
262
|
},
|
|
253
263
|
});
|
|
254
264
|
|
|
255
|
-
const
|
|
265
|
+
const spanName = `${operationName} ${event.modelId}`;
|
|
266
|
+
const rootSpan = this.tracer.startSpan(spanName, {
|
|
267
|
+
attributes,
|
|
268
|
+
kind: SpanKind.INTERNAL,
|
|
269
|
+
});
|
|
256
270
|
const rootContext = trace.setSpan(context.active(), rootSpan);
|
|
257
271
|
|
|
258
272
|
this.callStates.set(event.callId, {
|
|
@@ -262,17 +276,21 @@ export class OpenTelemetry implements Telemetry {
|
|
|
262
276
|
rootContext,
|
|
263
277
|
stepSpan: undefined,
|
|
264
278
|
stepContext: undefined,
|
|
279
|
+
inferenceSpan: undefined,
|
|
280
|
+
inferenceContext: undefined,
|
|
265
281
|
embedSpans: new Map(),
|
|
266
282
|
rerankSpan: undefined,
|
|
267
283
|
toolSpans: new Map(),
|
|
268
|
-
baseTelemetryAttributes,
|
|
269
284
|
settings,
|
|
285
|
+
provider: event.provider,
|
|
286
|
+
modelId: event.modelId,
|
|
270
287
|
});
|
|
271
288
|
}
|
|
272
289
|
|
|
273
|
-
private onObjectOperationStart(
|
|
290
|
+
private onObjectOperationStart(
|
|
291
|
+
event: InferTelemetryEvent<GenerateObjectStartEvent>,
|
|
292
|
+
): void {
|
|
274
293
|
const telemetry: TelemetryOptions = {
|
|
275
|
-
isEnabled: event.isEnabled,
|
|
276
294
|
recordInputs: event.recordInputs,
|
|
277
295
|
recordOutputs: event.recordOutputs,
|
|
278
296
|
functionId: event.functionId,
|
|
@@ -289,36 +307,46 @@ export class OpenTelemetry implements Telemetry {
|
|
|
289
307
|
maxRetries: event.maxRetries,
|
|
290
308
|
};
|
|
291
309
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
headers: event.headers,
|
|
295
|
-
settings,
|
|
296
|
-
context: undefined,
|
|
297
|
-
});
|
|
310
|
+
const providerName = mapProviderName(event.provider);
|
|
311
|
+
const operationName = mapOperationName(event.operationId);
|
|
298
312
|
|
|
299
313
|
const attributes = selectAttributes(telemetry, {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
'
|
|
314
|
+
'gen_ai.operation.name': operationName,
|
|
315
|
+
'gen_ai.provider.name': providerName,
|
|
316
|
+
'gen_ai.request.model': event.modelId,
|
|
317
|
+
'gen_ai.agent.name': telemetry.functionId,
|
|
318
|
+
'gen_ai.output.type': 'json',
|
|
319
|
+
'gen_ai.request.frequency_penalty': event.frequencyPenalty,
|
|
320
|
+
'gen_ai.request.max_tokens': event.maxOutputTokens,
|
|
321
|
+
'gen_ai.request.presence_penalty': event.presencePenalty,
|
|
322
|
+
'gen_ai.request.temperature': (event.temperature ?? undefined) as
|
|
323
|
+
| number
|
|
324
|
+
| undefined,
|
|
325
|
+
'gen_ai.request.top_k': event.topK,
|
|
326
|
+
'gen_ai.request.top_p': event.topP,
|
|
327
|
+
'gen_ai.request.seed': event.seed,
|
|
328
|
+
'gen_ai.system_instructions': event.system
|
|
329
|
+
? {
|
|
330
|
+
input: () =>
|
|
331
|
+
JSON.stringify(formatSystemInstructions(event.system!)),
|
|
332
|
+
}
|
|
333
|
+
: undefined,
|
|
334
|
+
'gen_ai.input.messages': {
|
|
306
335
|
input: () =>
|
|
307
|
-
JSON.stringify(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
336
|
+
JSON.stringify(
|
|
337
|
+
formatModelMessages({
|
|
338
|
+
prompt: event.prompt,
|
|
339
|
+
messages: event.messages,
|
|
340
|
+
}),
|
|
341
|
+
),
|
|
312
342
|
},
|
|
313
|
-
'ai.schema': event.schema
|
|
314
|
-
? { input: () => JSON.stringify(event.schema) }
|
|
315
|
-
: undefined,
|
|
316
|
-
'ai.schema.name': event.schemaName,
|
|
317
|
-
'ai.schema.description': event.schemaDescription,
|
|
318
|
-
'ai.settings.output': event.output,
|
|
319
343
|
});
|
|
320
344
|
|
|
321
|
-
const
|
|
345
|
+
const spanName = `${operationName} ${event.modelId}`;
|
|
346
|
+
const rootSpan = this.tracer.startSpan(spanName, {
|
|
347
|
+
attributes,
|
|
348
|
+
kind: SpanKind.INTERNAL,
|
|
349
|
+
});
|
|
322
350
|
const rootContext = trace.setSpan(context.active(), rootSpan);
|
|
323
351
|
|
|
324
352
|
this.callStates.set(event.callId, {
|
|
@@ -328,41 +356,30 @@ export class OpenTelemetry implements Telemetry {
|
|
|
328
356
|
rootContext,
|
|
329
357
|
stepSpan: undefined,
|
|
330
358
|
stepContext: undefined,
|
|
359
|
+
inferenceSpan: undefined,
|
|
360
|
+
inferenceContext: undefined,
|
|
331
361
|
embedSpans: new Map(),
|
|
332
362
|
rerankSpan: undefined,
|
|
333
363
|
toolSpans: new Map(),
|
|
334
|
-
baseTelemetryAttributes,
|
|
335
364
|
settings,
|
|
365
|
+
provider: event.provider,
|
|
366
|
+
modelId: event.modelId,
|
|
336
367
|
});
|
|
337
368
|
}
|
|
338
369
|
|
|
339
370
|
/** @deprecated */
|
|
340
|
-
onObjectStepStart(event:
|
|
371
|
+
onObjectStepStart(event: GenerateObjectStepStartEvent): void {
|
|
341
372
|
const state = this.getCallState(event.callId);
|
|
342
373
|
if (!state?.rootSpan || !state.rootContext) return;
|
|
343
374
|
|
|
344
375
|
const { telemetry } = state;
|
|
345
|
-
|
|
346
|
-
const stepOperationId =
|
|
347
|
-
state.operationId === 'ai.streamObject'
|
|
348
|
-
? 'ai.streamObject.doStream'
|
|
349
|
-
: 'ai.generateObject.doGenerate';
|
|
376
|
+
const providerName = mapProviderName(event.provider);
|
|
350
377
|
|
|
351
378
|
const attributes = selectAttributes(telemetry, {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
telemetry,
|
|
355
|
-
}),
|
|
356
|
-
...state.baseTelemetryAttributes,
|
|
357
|
-
'ai.prompt.messages': {
|
|
358
|
-
input: () =>
|
|
359
|
-
event.promptMessages
|
|
360
|
-
? stringifyForTelemetry(event.promptMessages)
|
|
361
|
-
: undefined,
|
|
362
|
-
},
|
|
363
|
-
|
|
364
|
-
'gen_ai.system': event.provider,
|
|
379
|
+
'gen_ai.operation.name': 'chat',
|
|
380
|
+
'gen_ai.provider.name': providerName,
|
|
365
381
|
'gen_ai.request.model': event.modelId,
|
|
382
|
+
'gen_ai.output.type': 'json',
|
|
366
383
|
'gen_ai.request.frequency_penalty': state.settings.frequencyPenalty as
|
|
367
384
|
| number
|
|
368
385
|
| undefined,
|
|
@@ -377,73 +394,67 @@ export class OpenTelemetry implements Telemetry {
|
|
|
377
394
|
| undefined,
|
|
378
395
|
'gen_ai.request.top_k': state.settings.topK as number | undefined,
|
|
379
396
|
'gen_ai.request.top_p': state.settings.topP as number | undefined,
|
|
397
|
+
'gen_ai.input.messages': {
|
|
398
|
+
input: () =>
|
|
399
|
+
event.promptMessages
|
|
400
|
+
? JSON.stringify(formatInputMessages(event.promptMessages))
|
|
401
|
+
: undefined,
|
|
402
|
+
},
|
|
380
403
|
});
|
|
381
404
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
405
|
+
const spanName = `chat ${event.modelId}`;
|
|
406
|
+
state.inferenceSpan = this.tracer.startSpan(
|
|
407
|
+
spanName,
|
|
408
|
+
{ attributes, kind: SpanKind.CLIENT },
|
|
385
409
|
state.rootContext,
|
|
386
410
|
);
|
|
387
|
-
state.
|
|
411
|
+
state.inferenceContext = trace.setSpan(
|
|
412
|
+
state.rootContext,
|
|
413
|
+
state.inferenceSpan,
|
|
414
|
+
);
|
|
388
415
|
}
|
|
389
416
|
|
|
390
417
|
/** @deprecated */
|
|
391
|
-
onObjectStepFinish(event:
|
|
418
|
+
onObjectStepFinish(event: GenerateObjectStepEndEvent): void {
|
|
392
419
|
const state = this.getCallState(event.callId);
|
|
393
|
-
if (!state?.
|
|
420
|
+
if (!state?.inferenceSpan) return;
|
|
394
421
|
|
|
395
422
|
const { telemetry } = state;
|
|
396
423
|
|
|
397
|
-
state.
|
|
424
|
+
state.inferenceSpan.setAttributes(
|
|
398
425
|
selectAttributes(telemetry, {
|
|
399
|
-
'
|
|
400
|
-
'
|
|
426
|
+
'gen_ai.response.finish_reasons': [event.finishReason],
|
|
427
|
+
'gen_ai.response.id': event.response.id,
|
|
428
|
+
'gen_ai.response.model': event.response.modelId,
|
|
429
|
+
'gen_ai.usage.input_tokens': event.usage.inputTokens,
|
|
430
|
+
'gen_ai.usage.output_tokens': event.usage.outputTokens,
|
|
431
|
+
'gen_ai.usage.cache_read.input_tokens': event.usage.cachedInputTokens,
|
|
432
|
+
'gen_ai.output.messages': {
|
|
401
433
|
output: () => {
|
|
402
434
|
try {
|
|
403
|
-
return JSON.stringify(
|
|
435
|
+
return JSON.stringify(
|
|
436
|
+
formatObjectOutputMessages({
|
|
437
|
+
objectText: event.objectText,
|
|
438
|
+
finishReason: event.finishReason,
|
|
439
|
+
}),
|
|
440
|
+
);
|
|
404
441
|
} catch {
|
|
405
442
|
return event.objectText;
|
|
406
443
|
}
|
|
407
444
|
},
|
|
408
445
|
},
|
|
409
|
-
'ai.response.id': event.response.id,
|
|
410
|
-
'ai.response.model': event.response.modelId,
|
|
411
|
-
'ai.response.timestamp': event.response.timestamp.toISOString(),
|
|
412
|
-
'ai.response.providerMetadata': event.providerMetadata
|
|
413
|
-
? JSON.stringify(event.providerMetadata)
|
|
414
|
-
: undefined,
|
|
415
|
-
|
|
416
|
-
'ai.usage.inputTokens': event.usage.inputTokens,
|
|
417
|
-
'ai.usage.outputTokens': event.usage.outputTokens,
|
|
418
|
-
'ai.usage.totalTokens': event.usage.totalTokens,
|
|
419
|
-
'ai.usage.reasoningTokens': event.usage.reasoningTokens,
|
|
420
|
-
'ai.usage.cachedInputTokens': event.usage.cachedInputTokens,
|
|
421
|
-
|
|
422
|
-
'gen_ai.response.finish_reasons': [event.finishReason],
|
|
423
|
-
'gen_ai.response.id': event.response.id,
|
|
424
|
-
'gen_ai.response.model': event.response.modelId,
|
|
425
|
-
'gen_ai.usage.input_tokens': event.usage.inputTokens,
|
|
426
|
-
'gen_ai.usage.output_tokens': event.usage.outputTokens,
|
|
427
446
|
}),
|
|
428
447
|
);
|
|
429
448
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
});
|
|
434
|
-
state.stepSpan.setAttributes({
|
|
435
|
-
'ai.stream.msToFirstChunk': event.msToFirstChunk,
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
state.stepSpan.end();
|
|
440
|
-
state.stepSpan = undefined;
|
|
441
|
-
state.stepContext = undefined;
|
|
449
|
+
state.inferenceSpan.end();
|
|
450
|
+
state.inferenceSpan = undefined;
|
|
451
|
+
state.inferenceContext = undefined;
|
|
442
452
|
}
|
|
443
453
|
|
|
444
|
-
private onEmbedOperationStart(
|
|
454
|
+
private onEmbedOperationStart(
|
|
455
|
+
event: InferTelemetryEvent<EmbedStartEvent>,
|
|
456
|
+
): void {
|
|
445
457
|
const telemetry: TelemetryOptions = {
|
|
446
|
-
isEnabled: event.isEnabled,
|
|
447
458
|
recordInputs: event.recordInputs,
|
|
448
459
|
recordOutputs: event.recordOutputs,
|
|
449
460
|
functionId: event.functionId,
|
|
@@ -453,36 +464,19 @@ export class OpenTelemetry implements Telemetry {
|
|
|
453
464
|
maxRetries: event.maxRetries,
|
|
454
465
|
};
|
|
455
466
|
|
|
456
|
-
const
|
|
457
|
-
model: { provider: event.provider, modelId: event.modelId },
|
|
458
|
-
headers: event.headers,
|
|
459
|
-
settings,
|
|
460
|
-
context: undefined,
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
const value = event.value;
|
|
464
|
-
const isMany = event.operationId === 'ai.embedMany';
|
|
467
|
+
const providerName = mapProviderName(event.provider);
|
|
465
468
|
|
|
466
469
|
const attributes = selectAttributes(telemetry, {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}),
|
|
471
|
-
...baseTelemetryAttributes,
|
|
472
|
-
...(isMany
|
|
473
|
-
? {
|
|
474
|
-
'ai.values': {
|
|
475
|
-
input: () => (value as string[]).map(v => JSON.stringify(v)),
|
|
476
|
-
},
|
|
477
|
-
}
|
|
478
|
-
: {
|
|
479
|
-
'ai.value': {
|
|
480
|
-
input: () => JSON.stringify(value),
|
|
481
|
-
},
|
|
482
|
-
}),
|
|
470
|
+
'gen_ai.operation.name': 'embeddings',
|
|
471
|
+
'gen_ai.provider.name': providerName,
|
|
472
|
+
'gen_ai.request.model': event.modelId,
|
|
483
473
|
});
|
|
484
474
|
|
|
485
|
-
const
|
|
475
|
+
const spanName = `embeddings ${event.modelId}`;
|
|
476
|
+
const rootSpan = this.tracer.startSpan(spanName, {
|
|
477
|
+
attributes,
|
|
478
|
+
kind: SpanKind.CLIENT,
|
|
479
|
+
});
|
|
486
480
|
const rootContext = trace.setSpan(context.active(), rootSpan);
|
|
487
481
|
|
|
488
482
|
this.callStates.set(event.callId, {
|
|
@@ -492,51 +486,44 @@ export class OpenTelemetry implements Telemetry {
|
|
|
492
486
|
rootContext,
|
|
493
487
|
stepSpan: undefined,
|
|
494
488
|
stepContext: undefined,
|
|
489
|
+
inferenceSpan: undefined,
|
|
490
|
+
inferenceContext: undefined,
|
|
495
491
|
embedSpans: new Map(),
|
|
496
492
|
rerankSpan: undefined,
|
|
497
493
|
toolSpans: new Map(),
|
|
498
|
-
baseTelemetryAttributes,
|
|
499
494
|
settings,
|
|
495
|
+
provider: event.provider,
|
|
496
|
+
modelId: event.modelId,
|
|
500
497
|
});
|
|
501
498
|
}
|
|
502
499
|
|
|
503
|
-
onStepStart(event:
|
|
500
|
+
onStepStart(event: GenerateTextStepStartEvent<ToolSet>): void {
|
|
504
501
|
const state = this.getCallState(event.callId);
|
|
505
502
|
if (!state?.rootSpan || !state.rootContext) return;
|
|
506
503
|
|
|
507
504
|
const { telemetry } = state;
|
|
505
|
+
const stepAttributes = selectAttributes(telemetry, {
|
|
506
|
+
'gen_ai.operation.name': 'agent_step',
|
|
507
|
+
});
|
|
508
508
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
509
|
+
state.stepSpan = this.tracer.startSpan(
|
|
510
|
+
`step ${event.steps.length + 1}`,
|
|
511
|
+
{ attributes: stepAttributes, kind: SpanKind.INTERNAL },
|
|
512
|
+
state.rootContext,
|
|
513
|
+
);
|
|
514
|
+
state.stepContext = trace.setSpan(state.rootContext, state.stepSpan);
|
|
515
|
+
}
|
|
513
516
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
telemetry,
|
|
518
|
-
}),
|
|
519
|
-
...state.baseTelemetryAttributes,
|
|
520
|
-
'ai.model.provider': event.provider,
|
|
521
|
-
'ai.model.id': event.modelId,
|
|
517
|
+
onLanguageModelCallStart(event: LanguageModelCallStartEvent): void {
|
|
518
|
+
const state = this.getCallState(event.callId);
|
|
519
|
+
if (!state?.stepContext) return;
|
|
522
520
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
event.promptMessages
|
|
526
|
-
? stringifyForTelemetry(event.promptMessages)
|
|
527
|
-
: undefined,
|
|
528
|
-
},
|
|
529
|
-
'ai.prompt.tools': {
|
|
530
|
-
input: () => event.stepTools?.map(tool => JSON.stringify(tool)),
|
|
531
|
-
},
|
|
532
|
-
'ai.prompt.toolChoice': {
|
|
533
|
-
input: () =>
|
|
534
|
-
event.stepToolChoice != null
|
|
535
|
-
? JSON.stringify(event.stepToolChoice)
|
|
536
|
-
: undefined,
|
|
537
|
-
},
|
|
521
|
+
const { telemetry } = state;
|
|
522
|
+
const providerName = mapProviderName(event.provider);
|
|
538
523
|
|
|
539
|
-
|
|
524
|
+
const inferenceAttributes = selectAttributes(telemetry, {
|
|
525
|
+
'gen_ai.operation.name': 'chat',
|
|
526
|
+
'gen_ai.provider.name': providerName,
|
|
540
527
|
'gen_ai.request.model': event.modelId,
|
|
541
528
|
'gen_ai.request.frequency_penalty': state.settings.frequencyPenalty as
|
|
542
529
|
| number
|
|
@@ -555,14 +542,75 @@ export class OpenTelemetry implements Telemetry {
|
|
|
555
542
|
| undefined,
|
|
556
543
|
'gen_ai.request.top_k': state.settings.topK as number | undefined,
|
|
557
544
|
'gen_ai.request.top_p': state.settings.topP as number | undefined,
|
|
545
|
+
'gen_ai.input.messages': {
|
|
546
|
+
input: () => {
|
|
547
|
+
const formattedMessages = formatModelMessages({
|
|
548
|
+
prompt: undefined,
|
|
549
|
+
messages: event.messages,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
return formattedMessages.length > 0
|
|
553
|
+
? JSON.stringify(formattedMessages)
|
|
554
|
+
: undefined;
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
'gen_ai.tool.definitions': {
|
|
558
|
+
input: () => (event.tools ? JSON.stringify(event.tools) : undefined),
|
|
559
|
+
},
|
|
558
560
|
});
|
|
559
561
|
|
|
560
|
-
state.
|
|
561
|
-
|
|
562
|
-
{ attributes },
|
|
563
|
-
state.
|
|
562
|
+
state.inferenceSpan = this.tracer.startSpan(
|
|
563
|
+
`chat ${event.modelId}`,
|
|
564
|
+
{ attributes: inferenceAttributes, kind: SpanKind.CLIENT },
|
|
565
|
+
state.stepContext,
|
|
566
|
+
);
|
|
567
|
+
state.inferenceContext = trace.setSpan(
|
|
568
|
+
state.stepContext,
|
|
569
|
+
state.inferenceSpan,
|
|
564
570
|
);
|
|
565
|
-
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
onLanguageModelCallEnd(event: LanguageModelCallEndEvent<ToolSet>): void {
|
|
574
|
+
const state = this.getCallState(event.callId);
|
|
575
|
+
if (!state?.inferenceSpan) return;
|
|
576
|
+
|
|
577
|
+
const { telemetry } = state;
|
|
578
|
+
|
|
579
|
+
state.inferenceSpan.setAttributes(
|
|
580
|
+
selectAttributes(telemetry, {
|
|
581
|
+
'gen_ai.response.finish_reasons': [event.finishReason],
|
|
582
|
+
'gen_ai.response.id': event.responseId,
|
|
583
|
+
'gen_ai.usage.input_tokens': event.usage.inputTokens,
|
|
584
|
+
'gen_ai.usage.output_tokens': event.usage.outputTokens,
|
|
585
|
+
'gen_ai.usage.cache_read.input_tokens':
|
|
586
|
+
event.usage.inputTokenDetails?.cacheReadTokens ??
|
|
587
|
+
event.usage.cachedInputTokens,
|
|
588
|
+
'gen_ai.usage.cache_creation.input_tokens':
|
|
589
|
+
event.usage.inputTokenDetails?.cacheWriteTokens,
|
|
590
|
+
'gen_ai.output.messages': {
|
|
591
|
+
output: () =>
|
|
592
|
+
JSON.stringify(
|
|
593
|
+
formatOutputMessages({
|
|
594
|
+
text:
|
|
595
|
+
event.content
|
|
596
|
+
.filter(p => p.type === 'text')
|
|
597
|
+
.map(p => p.text)
|
|
598
|
+
.join('') || undefined,
|
|
599
|
+
reasoning: event.content.filter(p => p.type === 'reasoning'),
|
|
600
|
+
toolCalls: event.content.filter(p => p.type === 'tool-call'),
|
|
601
|
+
files: event.content
|
|
602
|
+
.filter(p => p.type === 'file')
|
|
603
|
+
.map(p => p.file),
|
|
604
|
+
finishReason: event.finishReason,
|
|
605
|
+
}),
|
|
606
|
+
),
|
|
607
|
+
},
|
|
608
|
+
}),
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
state.inferenceSpan.end();
|
|
612
|
+
state.inferenceSpan = undefined;
|
|
613
|
+
state.inferenceContext = undefined;
|
|
566
614
|
}
|
|
567
615
|
|
|
568
616
|
onToolExecutionStart(event: ToolExecutionStartEvent<ToolSet>): void {
|
|
@@ -573,20 +621,19 @@ export class OpenTelemetry implements Telemetry {
|
|
|
573
621
|
const { toolCall } = event;
|
|
574
622
|
|
|
575
623
|
const attributes = selectAttributes(telemetry, {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
'
|
|
581
|
-
|
|
582
|
-
'ai.toolCall.args': {
|
|
583
|
-
output: () => JSON.stringify(toolCall.input),
|
|
624
|
+
'gen_ai.operation.name': 'execute_tool',
|
|
625
|
+
'gen_ai.tool.name': toolCall.toolName,
|
|
626
|
+
'gen_ai.tool.call.id': toolCall.toolCallId,
|
|
627
|
+
'gen_ai.tool.type': 'function',
|
|
628
|
+
'gen_ai.tool.call.arguments': {
|
|
629
|
+
input: () => JSON.stringify(toolCall.input),
|
|
584
630
|
},
|
|
585
631
|
});
|
|
586
632
|
|
|
633
|
+
const spanName = `execute_tool ${toolCall.toolName}`;
|
|
587
634
|
const toolSpan = this.tracer.startSpan(
|
|
588
|
-
|
|
589
|
-
{ attributes },
|
|
635
|
+
spanName,
|
|
636
|
+
{ attributes, kind: SpanKind.INTERNAL },
|
|
590
637
|
state.stepContext,
|
|
591
638
|
);
|
|
592
639
|
const toolContext = trace.setSpan(state.stepContext, toolSpan);
|
|
@@ -607,12 +654,13 @@ export class OpenTelemetry implements Telemetry {
|
|
|
607
654
|
const { span } = toolSpanEntry;
|
|
608
655
|
const { telemetry } = state;
|
|
609
656
|
|
|
610
|
-
|
|
657
|
+
const { toolOutput } = event;
|
|
658
|
+
if (toolOutput.type === 'tool-result') {
|
|
611
659
|
try {
|
|
612
660
|
span.setAttributes(
|
|
613
661
|
selectAttributes(telemetry, {
|
|
614
|
-
'
|
|
615
|
-
output: () => JSON.stringify(
|
|
662
|
+
'gen_ai.tool.call.result': {
|
|
663
|
+
output: () => JSON.stringify(toolOutput.output),
|
|
616
664
|
},
|
|
617
665
|
}),
|
|
618
666
|
);
|
|
@@ -620,89 +668,17 @@ export class OpenTelemetry implements Telemetry {
|
|
|
620
668
|
// JSON.stringify might fail for non-serializable results
|
|
621
669
|
}
|
|
622
670
|
} else {
|
|
623
|
-
recordSpanError(span,
|
|
671
|
+
recordSpanError(span, toolOutput.error);
|
|
624
672
|
}
|
|
625
673
|
|
|
626
674
|
span.end();
|
|
627
675
|
state.toolSpans.delete(event.toolCall.toolCallId);
|
|
628
676
|
}
|
|
629
677
|
|
|
630
|
-
onStepFinish(event:
|
|
678
|
+
onStepFinish(event: GenerateTextStepEndEvent<ToolSet>): void {
|
|
631
679
|
const state = this.getCallState(event.callId);
|
|
632
680
|
if (!state?.stepSpan) return;
|
|
633
681
|
|
|
634
|
-
const { telemetry } = state;
|
|
635
|
-
|
|
636
|
-
state.stepSpan.setAttributes(
|
|
637
|
-
selectAttributes(telemetry, {
|
|
638
|
-
'ai.response.finishReason': event.finishReason,
|
|
639
|
-
'ai.response.text': {
|
|
640
|
-
output: () => event.text ?? undefined,
|
|
641
|
-
},
|
|
642
|
-
'ai.response.reasoning': {
|
|
643
|
-
output: () =>
|
|
644
|
-
event.reasoning.length > 0
|
|
645
|
-
? event.reasoning
|
|
646
|
-
.filter(part => 'text' in part)
|
|
647
|
-
.map(part => part.text)
|
|
648
|
-
.join('\n')
|
|
649
|
-
: undefined,
|
|
650
|
-
},
|
|
651
|
-
'ai.response.toolCalls': {
|
|
652
|
-
output: () =>
|
|
653
|
-
event.toolCalls.length > 0
|
|
654
|
-
? JSON.stringify(
|
|
655
|
-
event.toolCalls.map(toolCall => ({
|
|
656
|
-
toolCallId: toolCall.toolCallId,
|
|
657
|
-
toolName: toolCall.toolName,
|
|
658
|
-
input: toolCall.input,
|
|
659
|
-
})),
|
|
660
|
-
)
|
|
661
|
-
: undefined,
|
|
662
|
-
},
|
|
663
|
-
'ai.response.files': {
|
|
664
|
-
output: () =>
|
|
665
|
-
event.files.length > 0
|
|
666
|
-
? JSON.stringify(
|
|
667
|
-
event.files.map(file => ({
|
|
668
|
-
type: 'file',
|
|
669
|
-
mediaType: file.mediaType,
|
|
670
|
-
data: file.base64,
|
|
671
|
-
})),
|
|
672
|
-
)
|
|
673
|
-
: undefined,
|
|
674
|
-
},
|
|
675
|
-
'ai.response.id': event.response.id,
|
|
676
|
-
'ai.response.model': event.response.modelId,
|
|
677
|
-
'ai.response.timestamp': event.response.timestamp.toISOString(),
|
|
678
|
-
'ai.response.providerMetadata': event.providerMetadata
|
|
679
|
-
? JSON.stringify(event.providerMetadata)
|
|
680
|
-
: undefined,
|
|
681
|
-
|
|
682
|
-
'ai.usage.inputTokens': event.usage.inputTokens,
|
|
683
|
-
'ai.usage.outputTokens': event.usage.outputTokens,
|
|
684
|
-
'ai.usage.totalTokens': event.usage.totalTokens,
|
|
685
|
-
'ai.usage.reasoningTokens': event.usage.reasoningTokens,
|
|
686
|
-
'ai.usage.cachedInputTokens': event.usage.cachedInputTokens,
|
|
687
|
-
'ai.usage.inputTokenDetails.noCacheTokens':
|
|
688
|
-
event.usage.inputTokenDetails?.noCacheTokens,
|
|
689
|
-
'ai.usage.inputTokenDetails.cacheReadTokens':
|
|
690
|
-
event.usage.inputTokenDetails?.cacheReadTokens,
|
|
691
|
-
'ai.usage.inputTokenDetails.cacheWriteTokens':
|
|
692
|
-
event.usage.inputTokenDetails?.cacheWriteTokens,
|
|
693
|
-
'ai.usage.outputTokenDetails.textTokens':
|
|
694
|
-
event.usage.outputTokenDetails?.textTokens,
|
|
695
|
-
'ai.usage.outputTokenDetails.reasoningTokens':
|
|
696
|
-
event.usage.outputTokenDetails?.reasoningTokens,
|
|
697
|
-
|
|
698
|
-
'gen_ai.response.finish_reasons': [event.finishReason],
|
|
699
|
-
'gen_ai.response.id': event.response.id,
|
|
700
|
-
'gen_ai.response.model': event.response.modelId,
|
|
701
|
-
'gen_ai.usage.input_tokens': event.usage.inputTokens,
|
|
702
|
-
'gen_ai.usage.output_tokens': event.usage.outputTokens,
|
|
703
|
-
}),
|
|
704
|
-
);
|
|
705
|
-
|
|
706
682
|
state.stepSpan.end();
|
|
707
683
|
state.stepSpan = undefined;
|
|
708
684
|
state.stepContext = undefined;
|
|
@@ -710,10 +686,10 @@ export class OpenTelemetry implements Telemetry {
|
|
|
710
686
|
|
|
711
687
|
onFinish(
|
|
712
688
|
event:
|
|
713
|
-
|
|
|
714
|
-
|
|
|
715
|
-
|
|
|
716
|
-
|
|
|
689
|
+
| GenerateTextEndEvent<ToolSet>
|
|
690
|
+
| GenerateObjectEndEvent<unknown>
|
|
691
|
+
| EmbedEndEvent
|
|
692
|
+
| RerankEndEvent,
|
|
717
693
|
): void {
|
|
718
694
|
const state = this.getCallState(event.callId);
|
|
719
695
|
if (!state?.rootSpan) return;
|
|
@@ -722,12 +698,12 @@ export class OpenTelemetry implements Telemetry {
|
|
|
722
698
|
state.operationId === 'ai.embed' ||
|
|
723
699
|
state.operationId === 'ai.embedMany'
|
|
724
700
|
) {
|
|
725
|
-
this.onEmbedOperationFinish(event as
|
|
701
|
+
this.onEmbedOperationFinish(event as EmbedEndEvent);
|
|
726
702
|
return;
|
|
727
703
|
}
|
|
728
704
|
|
|
729
705
|
if (state.operationId === 'ai.rerank') {
|
|
730
|
-
this.onRerankOperationFinish(event as
|
|
706
|
+
this.onRerankOperationFinish(event as RerankEndEvent);
|
|
731
707
|
return;
|
|
732
708
|
}
|
|
733
709
|
|
|
@@ -735,14 +711,14 @@ export class OpenTelemetry implements Telemetry {
|
|
|
735
711
|
state.operationId === 'ai.generateObject' ||
|
|
736
712
|
state.operationId === 'ai.streamObject'
|
|
737
713
|
) {
|
|
738
|
-
this.onObjectOperationFinish(event as
|
|
714
|
+
this.onObjectOperationFinish(event as GenerateObjectEndEvent<unknown>);
|
|
739
715
|
return;
|
|
740
716
|
}
|
|
741
717
|
|
|
742
|
-
this.onGenerateFinish(event as
|
|
718
|
+
this.onGenerateFinish(event as GenerateTextEndEvent<ToolSet>);
|
|
743
719
|
}
|
|
744
720
|
|
|
745
|
-
private onGenerateFinish(event:
|
|
721
|
+
private onGenerateFinish(event: GenerateTextEndEvent<ToolSet>): void {
|
|
746
722
|
const state = this.getCallState(event.callId);
|
|
747
723
|
if (!state?.rootSpan) return;
|
|
748
724
|
|
|
@@ -750,62 +726,26 @@ export class OpenTelemetry implements Telemetry {
|
|
|
750
726
|
|
|
751
727
|
state.rootSpan.setAttributes(
|
|
752
728
|
selectAttributes(telemetry, {
|
|
753
|
-
'
|
|
754
|
-
'
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
.map(part => part.text)
|
|
763
|
-
.join('\n')
|
|
764
|
-
: undefined,
|
|
765
|
-
},
|
|
766
|
-
'ai.response.toolCalls': {
|
|
767
|
-
output: () =>
|
|
768
|
-
event.toolCalls.length > 0
|
|
769
|
-
? JSON.stringify(
|
|
770
|
-
event.toolCalls.map(toolCall => ({
|
|
771
|
-
toolCallId: toolCall.toolCallId,
|
|
772
|
-
toolName: toolCall.toolName,
|
|
773
|
-
input: toolCall.input,
|
|
774
|
-
})),
|
|
775
|
-
)
|
|
776
|
-
: undefined,
|
|
777
|
-
},
|
|
778
|
-
'ai.response.files': {
|
|
729
|
+
'gen_ai.response.finish_reasons': [event.finishReason],
|
|
730
|
+
'gen_ai.usage.input_tokens': event.totalUsage.inputTokens,
|
|
731
|
+
'gen_ai.usage.output_tokens': event.totalUsage.outputTokens,
|
|
732
|
+
'gen_ai.usage.cache_read.input_tokens':
|
|
733
|
+
event.totalUsage.inputTokenDetails?.cacheReadTokens ??
|
|
734
|
+
event.totalUsage.cachedInputTokens,
|
|
735
|
+
'gen_ai.usage.cache_creation.input_tokens':
|
|
736
|
+
event.totalUsage.inputTokenDetails?.cacheWriteTokens,
|
|
737
|
+
'gen_ai.output.messages': {
|
|
779
738
|
output: () =>
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
739
|
+
JSON.stringify(
|
|
740
|
+
formatOutputMessages({
|
|
741
|
+
text: event.text ?? undefined,
|
|
742
|
+
reasoning: event.reasoning as ReadonlyArray<{ text?: string }>,
|
|
743
|
+
toolCalls: event.toolCalls,
|
|
744
|
+
files: event.files,
|
|
745
|
+
finishReason: event.finishReason,
|
|
746
|
+
}),
|
|
747
|
+
),
|
|
789
748
|
},
|
|
790
|
-
'ai.response.providerMetadata': event.providerMetadata
|
|
791
|
-
? JSON.stringify(event.providerMetadata)
|
|
792
|
-
: undefined,
|
|
793
|
-
|
|
794
|
-
'ai.usage.inputTokens': event.totalUsage.inputTokens,
|
|
795
|
-
'ai.usage.outputTokens': event.totalUsage.outputTokens,
|
|
796
|
-
'ai.usage.totalTokens': event.totalUsage.totalTokens,
|
|
797
|
-
'ai.usage.reasoningTokens': event.totalUsage.reasoningTokens,
|
|
798
|
-
'ai.usage.cachedInputTokens': event.totalUsage.cachedInputTokens,
|
|
799
|
-
'ai.usage.inputTokenDetails.noCacheTokens':
|
|
800
|
-
event.totalUsage.inputTokenDetails?.noCacheTokens,
|
|
801
|
-
'ai.usage.inputTokenDetails.cacheReadTokens':
|
|
802
|
-
event.totalUsage.inputTokenDetails?.cacheReadTokens,
|
|
803
|
-
'ai.usage.inputTokenDetails.cacheWriteTokens':
|
|
804
|
-
event.totalUsage.inputTokenDetails?.cacheWriteTokens,
|
|
805
|
-
'ai.usage.outputTokenDetails.textTokens':
|
|
806
|
-
event.totalUsage.outputTokenDetails?.textTokens,
|
|
807
|
-
'ai.usage.outputTokenDetails.reasoningTokens':
|
|
808
|
-
event.totalUsage.outputTokenDetails?.reasoningTokens,
|
|
809
749
|
}),
|
|
810
750
|
);
|
|
811
751
|
|
|
@@ -813,7 +753,9 @@ export class OpenTelemetry implements Telemetry {
|
|
|
813
753
|
this.cleanupCallState(event.callId);
|
|
814
754
|
}
|
|
815
755
|
|
|
816
|
-
private onObjectOperationFinish(
|
|
756
|
+
private onObjectOperationFinish(
|
|
757
|
+
event: GenerateObjectEndEvent<unknown>,
|
|
758
|
+
): void {
|
|
817
759
|
const state = this.getCallState(event.callId);
|
|
818
760
|
if (!state?.rootSpan) return;
|
|
819
761
|
|
|
@@ -821,20 +763,21 @@ export class OpenTelemetry implements Telemetry {
|
|
|
821
763
|
|
|
822
764
|
state.rootSpan.setAttributes(
|
|
823
765
|
selectAttributes(telemetry, {
|
|
824
|
-
'
|
|
825
|
-
'
|
|
766
|
+
'gen_ai.response.finish_reasons': [event.finishReason],
|
|
767
|
+
'gen_ai.usage.input_tokens': event.usage.inputTokens,
|
|
768
|
+
'gen_ai.usage.output_tokens': event.usage.outputTokens,
|
|
769
|
+
'gen_ai.usage.cache_read.input_tokens': event.usage.cachedInputTokens,
|
|
770
|
+
'gen_ai.output.messages': {
|
|
826
771
|
output: () =>
|
|
827
|
-
event.object != null
|
|
772
|
+
event.object != null
|
|
773
|
+
? JSON.stringify(
|
|
774
|
+
formatObjectOutputMessages({
|
|
775
|
+
objectText: JSON.stringify(event.object),
|
|
776
|
+
finishReason: event.finishReason,
|
|
777
|
+
}),
|
|
778
|
+
)
|
|
779
|
+
: undefined,
|
|
828
780
|
},
|
|
829
|
-
'ai.response.providerMetadata': event.providerMetadata
|
|
830
|
-
? JSON.stringify(event.providerMetadata)
|
|
831
|
-
: undefined,
|
|
832
|
-
|
|
833
|
-
'ai.usage.inputTokens': event.usage.inputTokens,
|
|
834
|
-
'ai.usage.outputTokens': event.usage.outputTokens,
|
|
835
|
-
'ai.usage.totalTokens': event.usage.totalTokens,
|
|
836
|
-
'ai.usage.reasoningTokens': event.usage.reasoningTokens,
|
|
837
|
-
'ai.usage.cachedInputTokens': event.usage.cachedInputTokens,
|
|
838
781
|
}),
|
|
839
782
|
);
|
|
840
783
|
|
|
@@ -842,28 +785,15 @@ export class OpenTelemetry implements Telemetry {
|
|
|
842
785
|
this.cleanupCallState(event.callId);
|
|
843
786
|
}
|
|
844
787
|
|
|
845
|
-
private onEmbedOperationFinish(event:
|
|
788
|
+
private onEmbedOperationFinish(event: EmbedEndEvent): void {
|
|
846
789
|
const state = this.getCallState(event.callId);
|
|
847
790
|
if (!state?.rootSpan) return;
|
|
848
791
|
|
|
849
792
|
const { telemetry } = state;
|
|
850
|
-
const isMany = state.operationId === 'ai.embedMany';
|
|
851
793
|
|
|
852
794
|
state.rootSpan.setAttributes(
|
|
853
795
|
selectAttributes(telemetry, {
|
|
854
|
-
|
|
855
|
-
? {
|
|
856
|
-
'ai.embeddings': {
|
|
857
|
-
output: () =>
|
|
858
|
-
(event.embedding as number[][]).map(e => JSON.stringify(e)),
|
|
859
|
-
},
|
|
860
|
-
}
|
|
861
|
-
: {
|
|
862
|
-
'ai.embedding': {
|
|
863
|
-
output: () => JSON.stringify(event.embedding),
|
|
864
|
-
},
|
|
865
|
-
}),
|
|
866
|
-
'ai.usage.tokens': event.usage.tokens,
|
|
796
|
+
'gen_ai.usage.input_tokens': event.usage.tokens,
|
|
867
797
|
}),
|
|
868
798
|
);
|
|
869
799
|
|
|
@@ -871,26 +801,23 @@ export class OpenTelemetry implements Telemetry {
|
|
|
871
801
|
this.cleanupCallState(event.callId);
|
|
872
802
|
}
|
|
873
803
|
|
|
874
|
-
onEmbedStart(event:
|
|
804
|
+
onEmbedStart(event: EmbeddingModelCallStartEvent): void {
|
|
875
805
|
const state = this.getCallState(event.callId);
|
|
876
806
|
if (!state?.rootSpan || !state.rootContext) return;
|
|
877
807
|
|
|
878
808
|
const { telemetry } = state;
|
|
809
|
+
const providerName = mapProviderName(state.provider);
|
|
879
810
|
|
|
880
811
|
const attributes = selectAttributes(telemetry, {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}),
|
|
885
|
-
...state.baseTelemetryAttributes,
|
|
886
|
-
'ai.values': {
|
|
887
|
-
input: () => event.values.map(v => JSON.stringify(v)),
|
|
888
|
-
},
|
|
812
|
+
'gen_ai.operation.name': 'embeddings',
|
|
813
|
+
'gen_ai.provider.name': providerName,
|
|
814
|
+
'gen_ai.request.model': state.modelId,
|
|
889
815
|
});
|
|
890
816
|
|
|
817
|
+
const spanName = `embeddings ${state.modelId}`;
|
|
891
818
|
const embedSpan = this.tracer.startSpan(
|
|
892
|
-
|
|
893
|
-
{ attributes },
|
|
819
|
+
spanName,
|
|
820
|
+
{ attributes, kind: SpanKind.CLIENT },
|
|
894
821
|
state.rootContext,
|
|
895
822
|
);
|
|
896
823
|
const embedContext = trace.setSpan(state.rootContext, embedSpan);
|
|
@@ -901,7 +828,7 @@ export class OpenTelemetry implements Telemetry {
|
|
|
901
828
|
});
|
|
902
829
|
}
|
|
903
830
|
|
|
904
|
-
onEmbedFinish(event:
|
|
831
|
+
onEmbedFinish(event: EmbeddingModelCallEndEvent): void {
|
|
905
832
|
const state = this.getCallState(event.callId);
|
|
906
833
|
if (!state) return;
|
|
907
834
|
|
|
@@ -913,11 +840,7 @@ export class OpenTelemetry implements Telemetry {
|
|
|
913
840
|
|
|
914
841
|
span.setAttributes(
|
|
915
842
|
selectAttributes(telemetry, {
|
|
916
|
-
'
|
|
917
|
-
output: () =>
|
|
918
|
-
event.embeddings.map(embedding => JSON.stringify(embedding)),
|
|
919
|
-
},
|
|
920
|
-
'ai.usage.tokens': event.usage.tokens,
|
|
843
|
+
'gen_ai.usage.input_tokens': event.usage.tokens,
|
|
921
844
|
}),
|
|
922
845
|
);
|
|
923
846
|
|
|
@@ -925,9 +848,10 @@ export class OpenTelemetry implements Telemetry {
|
|
|
925
848
|
state.embedSpans.delete(event.embedCallId);
|
|
926
849
|
}
|
|
927
850
|
|
|
928
|
-
private onRerankOperationStart(
|
|
851
|
+
private onRerankOperationStart(
|
|
852
|
+
event: InferTelemetryEvent<RerankStartEvent>,
|
|
853
|
+
): void {
|
|
929
854
|
const telemetry: TelemetryOptions = {
|
|
930
|
-
isEnabled: event.isEnabled,
|
|
931
855
|
recordInputs: event.recordInputs,
|
|
932
856
|
recordOutputs: event.recordOutputs,
|
|
933
857
|
functionId: event.functionId,
|
|
@@ -937,25 +861,19 @@ export class OpenTelemetry implements Telemetry {
|
|
|
937
861
|
maxRetries: event.maxRetries,
|
|
938
862
|
};
|
|
939
863
|
|
|
940
|
-
const
|
|
941
|
-
model: { provider: event.provider, modelId: event.modelId },
|
|
942
|
-
headers: event.headers,
|
|
943
|
-
settings,
|
|
944
|
-
context: undefined,
|
|
945
|
-
});
|
|
864
|
+
const providerName = mapProviderName(event.provider);
|
|
946
865
|
|
|
947
866
|
const attributes = selectAttributes(telemetry, {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}),
|
|
952
|
-
...baseTelemetryAttributes,
|
|
953
|
-
'ai.documents': {
|
|
954
|
-
input: () => event.documents.map(d => JSON.stringify(d)),
|
|
955
|
-
},
|
|
867
|
+
'gen_ai.operation.name': 'rerank',
|
|
868
|
+
'gen_ai.provider.name': providerName,
|
|
869
|
+
'gen_ai.request.model': event.modelId,
|
|
956
870
|
});
|
|
957
871
|
|
|
958
|
-
const
|
|
872
|
+
const spanName = `rerank ${event.modelId}`;
|
|
873
|
+
const rootSpan = this.tracer.startSpan(spanName, {
|
|
874
|
+
attributes,
|
|
875
|
+
kind: SpanKind.CLIENT,
|
|
876
|
+
});
|
|
959
877
|
const rootContext = trace.setSpan(context.active(), rootSpan);
|
|
960
878
|
|
|
961
879
|
this.callStates.set(event.callId, {
|
|
@@ -965,15 +883,18 @@ export class OpenTelemetry implements Telemetry {
|
|
|
965
883
|
rootContext,
|
|
966
884
|
stepSpan: undefined,
|
|
967
885
|
stepContext: undefined,
|
|
886
|
+
inferenceSpan: undefined,
|
|
887
|
+
inferenceContext: undefined,
|
|
968
888
|
embedSpans: new Map(),
|
|
969
889
|
rerankSpan: undefined,
|
|
970
890
|
toolSpans: new Map(),
|
|
971
|
-
baseTelemetryAttributes,
|
|
972
891
|
settings,
|
|
892
|
+
provider: event.provider,
|
|
893
|
+
modelId: event.modelId,
|
|
973
894
|
});
|
|
974
895
|
}
|
|
975
896
|
|
|
976
|
-
private onRerankOperationFinish(event:
|
|
897
|
+
private onRerankOperationFinish(event: RerankEndEvent): void {
|
|
977
898
|
const state = this.getCallState(event.callId);
|
|
978
899
|
if (!state?.rootSpan) return;
|
|
979
900
|
|
|
@@ -981,26 +902,23 @@ export class OpenTelemetry implements Telemetry {
|
|
|
981
902
|
this.cleanupCallState(event.callId);
|
|
982
903
|
}
|
|
983
904
|
|
|
984
|
-
onRerankStart(event:
|
|
905
|
+
onRerankStart(event: RerankingModelCallStartEvent): void {
|
|
985
906
|
const state = this.getCallState(event.callId);
|
|
986
907
|
if (!state?.rootSpan || !state.rootContext) return;
|
|
987
908
|
|
|
988
909
|
const { telemetry } = state;
|
|
910
|
+
const providerName = mapProviderName(state.provider);
|
|
989
911
|
|
|
990
912
|
const attributes = selectAttributes(telemetry, {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}),
|
|
995
|
-
...state.baseTelemetryAttributes,
|
|
996
|
-
'ai.documents': {
|
|
997
|
-
input: () => event.documents.map(d => JSON.stringify(d)),
|
|
998
|
-
},
|
|
913
|
+
'gen_ai.operation.name': 'rerank',
|
|
914
|
+
'gen_ai.provider.name': providerName,
|
|
915
|
+
'gen_ai.request.model': state.modelId,
|
|
999
916
|
});
|
|
1000
917
|
|
|
918
|
+
const spanName = `rerank ${state.modelId}`;
|
|
1001
919
|
const rerankSpan = this.tracer.startSpan(
|
|
1002
|
-
|
|
1003
|
-
{ attributes },
|
|
920
|
+
spanName,
|
|
921
|
+
{ attributes, kind: SpanKind.CLIENT },
|
|
1004
922
|
state.rootContext,
|
|
1005
923
|
);
|
|
1006
924
|
const rerankContext = trace.setSpan(state.rootContext, rerankSpan);
|
|
@@ -1008,57 +926,18 @@ export class OpenTelemetry implements Telemetry {
|
|
|
1008
926
|
state.rerankSpan = { span: rerankSpan, context: rerankContext };
|
|
1009
927
|
}
|
|
1010
928
|
|
|
1011
|
-
onRerankFinish(event:
|
|
929
|
+
onRerankFinish(event: RerankingModelCallEndEvent): void {
|
|
1012
930
|
const state = this.getCallState(event.callId);
|
|
1013
931
|
if (!state?.rerankSpan) return;
|
|
1014
932
|
|
|
1015
933
|
const { span } = state.rerankSpan;
|
|
1016
|
-
const { telemetry } = state;
|
|
1017
|
-
|
|
1018
|
-
span.setAttributes(
|
|
1019
|
-
selectAttributes(telemetry, {
|
|
1020
|
-
'ai.ranking.type': event.documentsType,
|
|
1021
|
-
'ai.ranking': {
|
|
1022
|
-
output: () => event.ranking.map(r => JSON.stringify(r)),
|
|
1023
|
-
},
|
|
1024
|
-
}),
|
|
1025
|
-
);
|
|
1026
934
|
|
|
1027
935
|
span.end();
|
|
1028
936
|
state.rerankSpan = undefined;
|
|
1029
937
|
}
|
|
1030
938
|
|
|
1031
|
-
onChunk(
|
|
1032
|
-
|
|
1033
|
-
type: string;
|
|
1034
|
-
callId?: unknown;
|
|
1035
|
-
attributes?: unknown;
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
if (typeof chunk.callId !== 'string') {
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
if (
|
|
1043
|
-
chunk.type !== 'ai.stream.firstChunk' &&
|
|
1044
|
-
chunk.type !== 'ai.stream.finish'
|
|
1045
|
-
) {
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
const state = this.getCallState(chunk.callId);
|
|
1050
|
-
if (!state?.stepSpan) return;
|
|
1051
|
-
|
|
1052
|
-
const attributes = Object.fromEntries(
|
|
1053
|
-
Object.entries(
|
|
1054
|
-
(chunk.attributes as Record<string, unknown>) ?? {},
|
|
1055
|
-
).filter(([, value]) => value != null),
|
|
1056
|
-
) as Attributes;
|
|
1057
|
-
|
|
1058
|
-
state.stepSpan.addEvent(chunk.type, attributes);
|
|
1059
|
-
if (Object.keys(attributes).length > 0) {
|
|
1060
|
-
state.stepSpan.setAttributes(attributes);
|
|
1061
|
-
}
|
|
939
|
+
onChunk(_event: StreamTextChunkEvent<ToolSet>): void {
|
|
940
|
+
// No-op: streaming chunk events are not part of the GenAI SemConv.
|
|
1062
941
|
}
|
|
1063
942
|
|
|
1064
943
|
onError(error: unknown): void {
|
|
@@ -1070,9 +949,24 @@ export class OpenTelemetry implements Telemetry {
|
|
|
1070
949
|
|
|
1071
950
|
const actualError = event.error ?? error;
|
|
1072
951
|
|
|
952
|
+
for (const { span: toolSpan } of state.toolSpans.values()) {
|
|
953
|
+
recordSpanError(toolSpan, actualError);
|
|
954
|
+
toolSpan.end();
|
|
955
|
+
}
|
|
956
|
+
state.toolSpans.clear();
|
|
957
|
+
|
|
958
|
+
if (state.inferenceSpan) {
|
|
959
|
+
recordSpanError(state.inferenceSpan, actualError);
|
|
960
|
+
state.inferenceSpan.end();
|
|
961
|
+
state.inferenceSpan = undefined;
|
|
962
|
+
state.inferenceContext = undefined;
|
|
963
|
+
}
|
|
964
|
+
|
|
1073
965
|
if (state.stepSpan) {
|
|
1074
966
|
recordSpanError(state.stepSpan, actualError);
|
|
1075
967
|
state.stepSpan.end();
|
|
968
|
+
state.stepSpan = undefined;
|
|
969
|
+
state.stepContext = undefined;
|
|
1076
970
|
}
|
|
1077
971
|
|
|
1078
972
|
for (const { span: embedSpan } of state.embedSpans.values()) {
|