@ai-sdk/otel 1.0.159 → 1.0.160
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 +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/open-telemetry.ts +82 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/otel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.160",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@ai-sdk/provider": "4.0.35",
|
|
30
30
|
"@opentelemetry/api": "1.9.1",
|
|
31
|
-
"ai": "7.0.
|
|
31
|
+
"ai": "7.0.160"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@ai-sdk/provider-utils": "5.0.72",
|
|
35
|
+
"@opentelemetry/sdk-metrics": "2.8.0",
|
|
35
36
|
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
36
37
|
"@types/node": "22.19.19",
|
|
37
38
|
"@vercel/ai-tsconfig": "0.0.0",
|
package/src/open-telemetry.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
context,
|
|
3
|
+
metrics,
|
|
3
4
|
SpanKind,
|
|
4
5
|
SpanStatusCode,
|
|
5
6
|
trace,
|
|
@@ -91,6 +92,11 @@ interface CallState {
|
|
|
91
92
|
stepContext: OpenTelemetryContext | undefined;
|
|
92
93
|
inferenceSpan: Span | undefined;
|
|
93
94
|
inferenceContext: OpenTelemetryContext | undefined;
|
|
95
|
+
inferenceMetrics?: {
|
|
96
|
+
startTime: number;
|
|
97
|
+
provider: string;
|
|
98
|
+
modelId: string;
|
|
99
|
+
};
|
|
94
100
|
inferenceToolDefinitions?: ReadonlyArray<Record<string, unknown>>;
|
|
95
101
|
embedSpans: Map<string, { span: Span; context: OpenTelemetryContext }>;
|
|
96
102
|
rerankSpan: { span: Span; context: OpenTelemetryContext } | undefined;
|
|
@@ -145,6 +151,52 @@ export class OpenTelemetry implements Telemetry {
|
|
|
145
151
|
this.callStates.delete(callId);
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
private recordInferenceDuration(
|
|
155
|
+
state: CallState,
|
|
156
|
+
{
|
|
157
|
+
responseTimeMs,
|
|
158
|
+
provider,
|
|
159
|
+
responseModelId,
|
|
160
|
+
errorType,
|
|
161
|
+
}: {
|
|
162
|
+
responseTimeMs?: number;
|
|
163
|
+
provider?: string;
|
|
164
|
+
responseModelId?: string;
|
|
165
|
+
errorType?: string;
|
|
166
|
+
} = {},
|
|
167
|
+
): void {
|
|
168
|
+
const inference = state.inferenceMetrics;
|
|
169
|
+
if (inference == null) return;
|
|
170
|
+
state.inferenceMetrics = undefined;
|
|
171
|
+
|
|
172
|
+
// Resolve the meter when recording so a provider registered after this
|
|
173
|
+
// integration is constructed can still receive metrics.
|
|
174
|
+
const histogram = metrics
|
|
175
|
+
.getMeter('gen_ai')
|
|
176
|
+
.createHistogram('gen_ai.client.inference.duration', {
|
|
177
|
+
description: 'GenAI client inference operation duration.',
|
|
178
|
+
unit: 's',
|
|
179
|
+
advice: {
|
|
180
|
+
explicitBucketBoundaries: [
|
|
181
|
+
0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24,
|
|
182
|
+
20.48, 40.96, 81.92,
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
histogram.record(
|
|
188
|
+
(responseTimeMs ?? performance.now() - inference.startTime) / 1000,
|
|
189
|
+
selectAttributes(state.telemetry, {
|
|
190
|
+
'gen_ai.operation.name': 'chat',
|
|
191
|
+
'gen_ai.provider.name': mapProviderName(provider ?? inference.provider),
|
|
192
|
+
'gen_ai.request.model': inference.modelId,
|
|
193
|
+
'gen_ai.response.model': responseModelId,
|
|
194
|
+
'error.type': errorType,
|
|
195
|
+
}),
|
|
196
|
+
state.inferenceContext,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
148
200
|
private getAgentSpanName({
|
|
149
201
|
operationName,
|
|
150
202
|
modelId,
|
|
@@ -859,9 +911,17 @@ export class OpenTelemetry implements Telemetry {
|
|
|
859
911
|
const state = this.getCallState(event.callId);
|
|
860
912
|
if (!state?.stepContext) return;
|
|
861
913
|
|
|
914
|
+
// A retried stream may start a new attempt without ending the failed one.
|
|
915
|
+
this.recordInferenceDuration(state, { errorType: '_OTHER' });
|
|
916
|
+
|
|
862
917
|
const { telemetry } = state;
|
|
863
918
|
const providerName = mapProviderName(event.provider);
|
|
864
919
|
state.inferenceToolDefinitions = event.tools;
|
|
920
|
+
state.inferenceMetrics = {
|
|
921
|
+
startTime: performance.now(),
|
|
922
|
+
provider: event.provider,
|
|
923
|
+
modelId: event.modelId,
|
|
924
|
+
};
|
|
865
925
|
|
|
866
926
|
const inferenceAttributes = selectAttributes(telemetry, {
|
|
867
927
|
'gen_ai.operation.name': 'chat',
|
|
@@ -932,6 +992,13 @@ export class OpenTelemetry implements Telemetry {
|
|
|
932
992
|
const state = this.getCallState(event.callId);
|
|
933
993
|
if (!state?.inferenceSpan) return;
|
|
934
994
|
|
|
995
|
+
this.recordInferenceDuration(state, {
|
|
996
|
+
responseTimeMs: event.performance.responseTimeMs,
|
|
997
|
+
provider: event.provider,
|
|
998
|
+
responseModelId: event.modelId,
|
|
999
|
+
errorType: event.finishReason === 'error' ? '_OTHER' : undefined,
|
|
1000
|
+
});
|
|
1001
|
+
|
|
935
1002
|
if (event.finishReason === 'error') {
|
|
936
1003
|
state.inferenceSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
937
1004
|
}
|
|
@@ -1175,6 +1242,11 @@ export class OpenTelemetry implements Telemetry {
|
|
|
1175
1242
|
const state = this.getCallState(event.callId);
|
|
1176
1243
|
if (!state?.stepSpan) return;
|
|
1177
1244
|
|
|
1245
|
+
// Streams can close after an error chunk without a model-call end event.
|
|
1246
|
+
this.recordInferenceDuration(state, {
|
|
1247
|
+
errorType: event.finishReason === 'error' ? '_OTHER' : undefined,
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1178
1250
|
if (event.finishReason === 'error') {
|
|
1179
1251
|
state.stepSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
1180
1252
|
}
|
|
@@ -1206,6 +1278,10 @@ export class OpenTelemetry implements Telemetry {
|
|
|
1206
1278
|
const state = this.getCallState(event.callId);
|
|
1207
1279
|
if (!state?.stepSpan) return;
|
|
1208
1280
|
|
|
1281
|
+
this.recordInferenceDuration(state, {
|
|
1282
|
+
errorType: event.error instanceof Error ? event.error.name : '_OTHER',
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1209
1285
|
if (state.inferenceSpan) {
|
|
1210
1286
|
recordErrorOnSpan(state.inferenceSpan, event.error);
|
|
1211
1287
|
state.inferenceSpan.end();
|
|
@@ -1907,6 +1983,8 @@ export class OpenTelemetry implements Telemetry {
|
|
|
1907
1983
|
const state = this.getCallState(event.callId);
|
|
1908
1984
|
if (!state?.rootSpan) return;
|
|
1909
1985
|
|
|
1986
|
+
this.recordInferenceDuration(state, { errorType: 'AbortError' });
|
|
1987
|
+
|
|
1910
1988
|
for (const { span: toolSpan } of state.toolSpans.values()) {
|
|
1911
1989
|
toolSpan.end();
|
|
1912
1990
|
}
|
|
@@ -1952,6 +2030,10 @@ export class OpenTelemetry implements Telemetry {
|
|
|
1952
2030
|
|
|
1953
2031
|
const actualError = event.error ?? error;
|
|
1954
2032
|
|
|
2033
|
+
this.recordInferenceDuration(state, {
|
|
2034
|
+
errorType: actualError instanceof Error ? actualError.name : '_OTHER',
|
|
2035
|
+
});
|
|
2036
|
+
|
|
1955
2037
|
for (const { span: toolSpan } of state.toolSpans.values()) {
|
|
1956
2038
|
recordErrorOnSpan(toolSpan, actualError);
|
|
1957
2039
|
toolSpan.end();
|