ai 7.0.110 → 7.0.111

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.
@@ -3,26 +3,40 @@ import {
3
3
  type Experimental_EvaluationModelV4CallOptions as EvaluationModelV4CallOptions,
4
4
  } from '@ai-sdk/provider';
5
5
  import {
6
+ createIdGenerator,
6
7
  withUserAgentSuffix,
8
+ type Context,
7
9
  type ProviderOptions,
8
10
  } from '@ai-sdk/provider-utils';
9
11
  import { resolveEvaluationModel } from '../model/resolve-model';
10
12
  import { logWarnings } from '../logger/log-warnings';
13
+ import { filterIncludedContext } from '../telemetry/filter-included-context';
14
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
15
+ import type { Callback } from '../util/callback';
16
+ import { notify } from '../util/notify';
11
17
  import { prepareRetries } from '../util/prepare-retries';
12
18
  import { VERSION } from '../version';
19
+ import type { EvaluateEndEvent, EvaluateStartEvent } from './evaluate-events';
13
20
  import type {
14
21
  EvaluationModel,
15
22
  EvaluationQuestion,
16
23
  EvaluationResult,
17
24
  } from './evaluation-result';
25
+ import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher';
18
26
  import {
19
27
  validateEvaluationInput,
20
28
  validateEvaluationAnswers,
21
29
  } from './validate-evaluation';
22
30
 
31
+ const originalGenerateCallId = createIdGenerator({
32
+ prefix: 'call',
33
+ size: 24,
34
+ });
35
+
23
36
  /** Evaluate typed questions against one shared state. Experimental. */
24
37
  export async function evaluate<
25
38
  const QUESTIONS extends Record<string, EvaluationQuestion>,
39
+ RUNTIME_CONTEXT extends Context = Context,
26
40
  >({
27
41
  model: modelArg,
28
42
  state,
@@ -31,6 +45,11 @@ export async function evaluate<
31
45
  abortSignal,
32
46
  headers,
33
47
  providerOptions = {},
48
+ telemetry,
49
+ runtimeContext = {} as RUNTIME_CONTEXT,
50
+ onStart,
51
+ onEnd,
52
+ _internal: { generateCallId = originalGenerateCallId } = {},
34
53
  }: {
35
54
  /** An evaluation model instance or an ID resolved by the configured default provider. */
36
55
  model: EvaluationModel;
@@ -41,6 +60,18 @@ export async function evaluate<
41
60
  abortSignal?: AbortSignal;
42
61
  headers?: Record<string, string>;
43
62
  providerOptions?: ProviderOptions;
63
+ /** Optional telemetry configuration. */
64
+ telemetry?: TelemetryOptions<RUNTIME_CONTEXT>;
65
+ /** User-defined runtime context. Treat runtime context as immutable. */
66
+ runtimeContext?: RUNTIME_CONTEXT;
67
+ /** Called when the evaluate operation begins. */
68
+ onStart?: Callback<EvaluateStartEvent<RUNTIME_CONTEXT>>;
69
+ /** Called when the evaluate operation completes. */
70
+ onEnd?: Callback<EvaluateEndEvent<RUNTIME_CONTEXT>>;
71
+ /** Internal. For test use only. May change without notice. */
72
+ _internal?: {
73
+ generateCallId?: () => string;
74
+ };
44
75
  }): Promise<EvaluationResult<QUESTIONS>> {
45
76
  const model = resolveEvaluationModel(modelArg);
46
77
 
@@ -57,50 +88,124 @@ export async function evaluate<
57
88
  }
58
89
  }
59
90
 
60
- const { retry } = prepareRetries({ maxRetries, abortSignal });
61
- const result = await retry(() => {
62
- abortSignal?.throwIfAborted();
63
- return model.doEvaluate({
64
- state,
65
- questions,
66
- abortSignal,
67
- headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
68
- providerOptions,
69
- });
91
+ const callId = generateCallId();
92
+ const { maxRetries: resolvedMaxRetries, retry } = prepareRetries({
93
+ maxRetries,
94
+ abortSignal,
70
95
  });
71
-
72
- abortSignal?.throwIfAborted();
73
- validateEvaluationAnswers({
74
- questions,
75
- answers: result.answers,
76
- rounding: result.rounding,
96
+ const telemetryDispatcher = createRestrictedTelemetryDispatcher({
97
+ telemetry,
77
98
  });
78
- logWarnings({
79
- warnings: result.warnings,
99
+ const runInTracingChannelSpan =
100
+ telemetryDispatcher.runInTracingChannelSpan ??
101
+ (async <T>({ execute }: { execute: () => PromiseLike<T> }) =>
102
+ await execute());
103
+ const startEvent = {
104
+ callId,
105
+ operationId: 'ai.evaluate' as const,
106
+ runtimeContext,
80
107
  provider: model.provider,
81
- model: model.modelId,
82
- });
108
+ modelId: model.modelId,
109
+ state,
110
+ questions,
111
+ maxRetries: resolvedMaxRetries,
112
+ headers,
113
+ providerOptions,
114
+ };
115
+ const tracingStartEvent = {
116
+ ...startEvent,
117
+ runtimeContext: filterIncludedContext({
118
+ context: runtimeContext,
119
+ includeContext: telemetry?.includeRuntimeContext,
120
+ }),
121
+ };
83
122
 
84
- const inputTokens = result.usage?.inputTokens;
85
- const outputTokens = result.usage?.outputTokens;
123
+ return await runInTracingChannelSpan({
124
+ type: 'experimental_evaluate',
125
+ event: tracingStartEvent,
126
+ execute: async () => {
127
+ await notify({
128
+ event: startEvent,
129
+ callbacks: [onStart, telemetryDispatcher.onStart],
130
+ });
86
131
 
87
- return {
88
- answers: result.answers as EvaluationResult<QUESTIONS>['answers'],
89
- usage: {
90
- inputTokens,
91
- outputTokens,
92
- totalTokens:
93
- inputTokens != null && outputTokens != null
94
- ? inputTokens + outputTokens
95
- : undefined,
96
- },
97
- warnings: result.warnings,
98
- rounding: result.rounding,
99
- providerMetadata: result.providerMetadata,
100
- response: {
101
- ...result.response,
102
- timestamp: result.response?.timestamp ?? new Date(),
103
- modelId: result.response?.modelId ?? model.modelId,
132
+ try {
133
+ const modelCallEvent = {
134
+ callId,
135
+ operationId: 'ai.evaluate.doEvaluate' as const,
136
+ provider: model.provider,
137
+ modelId: model.modelId,
138
+ state,
139
+ questions,
140
+ };
141
+ await notify({
142
+ event: modelCallEvent,
143
+ callbacks: [
144
+ telemetryDispatcher.experimental_onEvaluationModelCallStart,
145
+ ],
146
+ });
147
+ const result = await retry(async () => {
148
+ abortSignal?.throwIfAborted();
149
+ return await model.doEvaluate({
150
+ state,
151
+ questions,
152
+ abortSignal,
153
+ headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
154
+ providerOptions,
155
+ });
156
+ });
157
+
158
+ abortSignal?.throwIfAborted();
159
+ validateEvaluationAnswers({
160
+ questions,
161
+ answers: result.answers,
162
+ rounding: result.rounding,
163
+ });
164
+ await notify({
165
+ event: { ...modelCallEvent, ...result },
166
+ callbacks: [
167
+ telemetryDispatcher.experimental_onEvaluationModelCallEnd,
168
+ ],
169
+ });
170
+
171
+ logWarnings({
172
+ warnings: result.warnings,
173
+ provider: model.provider,
174
+ model: model.modelId,
175
+ });
176
+
177
+ const inputTokens = result.usage?.inputTokens;
178
+ const outputTokens = result.usage?.outputTokens;
179
+ const evaluationResult: EvaluationResult<QUESTIONS> = {
180
+ answers: result.answers as EvaluationResult<QUESTIONS>['answers'],
181
+ usage: {
182
+ inputTokens,
183
+ outputTokens,
184
+ totalTokens:
185
+ inputTokens != null && outputTokens != null
186
+ ? inputTokens + outputTokens
187
+ : undefined,
188
+ },
189
+ warnings: result.warnings,
190
+ rounding: result.rounding,
191
+ providerMetadata: result.providerMetadata,
192
+ response: {
193
+ ...result.response,
194
+ timestamp: result.response?.timestamp ?? new Date(),
195
+ modelId: result.response?.modelId ?? model.modelId,
196
+ },
197
+ };
198
+
199
+ await notify({
200
+ event: { ...startEvent, ...evaluationResult },
201
+ callbacks: [onEnd, telemetryDispatcher.onEnd],
202
+ });
203
+
204
+ return evaluationResult;
205
+ } catch (error) {
206
+ await telemetryDispatcher.onError?.({ callId, error });
207
+ throw error;
208
+ }
104
209
  },
105
- };
210
+ });
106
211
  }
@@ -1,4 +1,10 @@
1
1
  export { evaluate as experimental_evaluate } from './evaluate';
2
+ export type {
3
+ EvaluateStartEvent as Experimental_EvaluateStartEvent,
4
+ EvaluateEndEvent as Experimental_EvaluateEndEvent,
5
+ EvaluationModelCallStartEvent as Experimental_EvaluationModelCallStartEvent,
6
+ EvaluationModelCallEndEvent as Experimental_EvaluationModelCallEndEvent,
7
+ } from './evaluate-events';
2
8
  export type {
3
9
  EvaluationModel as Experimental_EvaluationModel,
4
10
  EvaluationQuestion as Experimental_EvaluationQuestion,
@@ -0,0 +1,46 @@
1
+ import type { Context } from '@ai-sdk/provider-utils';
2
+ import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
3
+ import { filterIncludedContext } from '../telemetry/filter-included-context';
4
+ import type { TelemetryDispatcher } from '../telemetry/telemetry';
5
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
6
+ import type { Callback } from '../util/callback';
7
+ import type { EvaluateEndEvent, EvaluateStartEvent } from './evaluate-events';
8
+
9
+ export function createRestrictedTelemetryDispatcher<
10
+ RUNTIME_CONTEXT extends Context,
11
+ >({
12
+ telemetry,
13
+ }: {
14
+ telemetry?: TelemetryOptions<RUNTIME_CONTEXT>;
15
+ }): Omit<
16
+ TelemetryDispatcher,
17
+ | 'onStart'
18
+ | 'onEnd'
19
+ | 'experimental_onEvaluateStart'
20
+ | 'experimental_onEvaluateEnd'
21
+ > & {
22
+ onStart: Callback<EvaluateStartEvent<RUNTIME_CONTEXT>>;
23
+ onEnd: Callback<EvaluateEndEvent<RUNTIME_CONTEXT>>;
24
+ } {
25
+ const dispatcher = createTelemetryDispatcher({ telemetry });
26
+
27
+ return {
28
+ ...dispatcher,
29
+ onStart: event =>
30
+ dispatcher.experimental_onEvaluateStart?.({
31
+ ...event,
32
+ runtimeContext: filterIncludedContext({
33
+ context: event.runtimeContext,
34
+ includeContext: telemetry?.includeRuntimeContext,
35
+ }),
36
+ }),
37
+ onEnd: event =>
38
+ dispatcher.experimental_onEvaluateEnd?.({
39
+ ...event,
40
+ runtimeContext: filterIncludedContext({
41
+ context: event.runtimeContext,
42
+ includeContext: telemetry?.includeRuntimeContext,
43
+ }),
44
+ }),
45
+ };
46
+ }
@@ -4,6 +4,10 @@ import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-f
4
4
  import { getOwn } from '../util/get-own';
5
5
  import type { ContentPart } from './content-part';
6
6
  import { DefaultGeneratedFile } from './generated-file';
7
+ import {
8
+ resolveGeneratedFileData,
9
+ type GeneratedFileDataCache,
10
+ } from './resolve-generated-file-data';
7
11
  import type { ToolApprovalRequestOutput } from './tool-approval-request-output';
8
12
  import type { ToolApprovalResponseOutput } from './tool-approval-response-output';
9
13
  import type { TypedToolCall } from './tool-call';
@@ -11,13 +15,15 @@ import type { TypedToolError } from './tool-error';
11
15
  import type { ToolOutput } from './tool-output';
12
16
  import type { TypedToolResult } from './tool-result';
13
17
 
14
- export function convertLanguageModelContent<TOOLS extends ToolSet>({
18
+ export async function convertLanguageModelContent<TOOLS extends ToolSet>({
15
19
  content,
16
20
  toolCalls,
17
21
  toolOutputs,
18
22
  toolApprovalRequests,
19
23
  toolApprovalResponses,
20
24
  tools,
25
+ abortSignal,
26
+ generatedFileDataCache,
21
27
  }: {
22
28
  content: Array<LanguageModelV4Content>;
23
29
  toolCalls: Array<TypedToolCall<TOOLS>>;
@@ -25,7 +31,9 @@ export function convertLanguageModelContent<TOOLS extends ToolSet>({
25
31
  toolApprovalRequests: Array<ToolApprovalRequestOutput<TOOLS>>;
26
32
  toolApprovalResponses: Array<ToolApprovalResponseOutput<TOOLS>>;
27
33
  tools: TOOLS | undefined;
28
- }): Array<ContentPart<TOOLS>> {
34
+ abortSignal?: AbortSignal;
35
+ generatedFileDataCache?: GeneratedFileDataCache;
36
+ }): Promise<Array<ContentPart<TOOLS>>> {
29
37
  const contentParts: Array<ContentPart<TOOLS>> = [];
30
38
  const toolOutputsWithApprovalResponses: Array<ToolOutput<TOOLS>> = [];
31
39
  const toolOutputsWithoutApprovalResponses: Array<ToolOutput<TOOLS>> = [];
@@ -49,10 +57,11 @@ export function convertLanguageModelContent<TOOLS extends ToolSet>({
49
57
  contentParts.push({
50
58
  type: part.type as 'file' | 'reasoning-file',
51
59
  file: new DefaultGeneratedFile({
52
- data:
53
- part.data.type === 'data'
54
- ? part.data.data
55
- : part.data.url.toString(),
60
+ data: await resolveGeneratedFileData({
61
+ data: part.data,
62
+ abortSignal,
63
+ cache: generatedFileDataCache,
64
+ }),
56
65
  mediaType: part.mediaType,
57
66
  }),
58
67
  ...(part.providerMetadata != null
@@ -1099,13 +1099,16 @@ export async function generateText<
1099
1099
  > = {};
1100
1100
  const blockedToolCallIds = new Set<string>();
1101
1101
 
1102
- const modelCallContent = convertLanguageModelContent({
1102
+ const generatedFileDataCache = new WeakMap();
1103
+ const modelCallContent = await convertLanguageModelContent({
1103
1104
  content: currentModelResponse.content,
1104
1105
  toolCalls: stepToolCalls,
1105
1106
  toolOutputs: [],
1106
1107
  toolApprovalRequests: [],
1107
1108
  toolApprovalResponses: [],
1108
1109
  tools,
1110
+ abortSignal: mergedAbortSignal,
1111
+ generatedFileDataCache,
1109
1112
  });
1110
1113
 
1111
1114
  await notify({
@@ -1426,13 +1429,15 @@ export async function generateText<
1426
1429
  }
1427
1430
 
1428
1431
  // content:
1429
- const stepContent = convertLanguageModelContent({
1432
+ const stepContent = await convertLanguageModelContent({
1430
1433
  content: currentModelResponse.content,
1431
1434
  toolCalls: stepToolCalls,
1432
1435
  toolOutputs: clientToolOutputs,
1433
1436
  toolApprovalRequests: Object.values(toolApprovalRequests),
1434
1437
  toolApprovalResponses,
1435
1438
  tools,
1439
+ abortSignal: mergedAbortSignal,
1440
+ generatedFileDataCache,
1436
1441
  });
1437
1442
 
1438
1443
  const stepResponseMessages = await toResponseMessages({
@@ -0,0 +1,41 @@
1
+ import type { LanguageModelV4Content } from '@ai-sdk/provider';
2
+ import { download } from '../util/download/download';
3
+
4
+ type GeneratedFileData = Extract<
5
+ LanguageModelV4Content,
6
+ { type: 'file' | 'reasoning-file' }
7
+ >['data'];
8
+
9
+ export type GeneratedFileDataCache = WeakMap<
10
+ GeneratedFileData,
11
+ string | Uint8Array
12
+ >;
13
+
14
+ export async function resolveGeneratedFileData({
15
+ data,
16
+ abortSignal,
17
+ cache,
18
+ }: {
19
+ data: GeneratedFileData;
20
+ abortSignal?: AbortSignal;
21
+ cache?: GeneratedFileDataCache;
22
+ }): Promise<string | Uint8Array> {
23
+ if (data.type === 'data') {
24
+ return data.data;
25
+ }
26
+
27
+ const cachedData = cache?.get(data);
28
+ if (cachedData != null) {
29
+ return cachedData;
30
+ }
31
+
32
+ const downloadedData = (
33
+ await download({
34
+ url: data.url,
35
+ abortSignal,
36
+ })
37
+ ).data;
38
+
39
+ cache?.set(data, downloadedData);
40
+ return downloadedData;
41
+ }
@@ -53,6 +53,7 @@ import type {
53
53
  } from './language-model-events';
54
54
  import type { Output } from './output';
55
55
  import { parseToolCall } from './parse-tool-call';
56
+ import { resolveGeneratedFileData } from './resolve-generated-file-data';
56
57
  import type {
57
58
  TextStreamFilePart,
58
59
  TextStreamPart,
@@ -565,10 +566,10 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
565
566
  case 'file':
566
567
  case 'reasoning-file': {
567
568
  const file = new DefaultGeneratedFileWithType({
568
- data:
569
- chunk.data.type === 'data'
570
- ? chunk.data.data
571
- : chunk.data.url.toString(),
569
+ data: await resolveGeneratedFileData({
570
+ data: chunk.data,
571
+ abortSignal,
572
+ }),
572
573
  mediaType: chunk.mediaType,
573
574
  });
574
575
 
@@ -163,6 +163,18 @@ export function createTelemetryDispatcher({
163
163
  onEmbedEnd: mergeTelemetryCallback('onEmbedEnd'),
164
164
  onRerankStart: mergeTelemetryCallback('onRerankStart'),
165
165
  onRerankEnd: mergeTelemetryCallback('onRerankEnd'),
166
+ experimental_onEvaluateStart: mergeTelemetryCallback(
167
+ 'experimental_onEvaluateStart',
168
+ ),
169
+ experimental_onEvaluationModelCallStart: mergeTelemetryCallback(
170
+ 'experimental_onEvaluationModelCallStart',
171
+ ),
172
+ experimental_onEvaluationModelCallEnd: mergeTelemetryCallback(
173
+ 'experimental_onEvaluationModelCallEnd',
174
+ ),
175
+ experimental_onEvaluateEnd: mergeTelemetryCallback(
176
+ 'experimental_onEvaluateEnd',
177
+ ),
166
178
  onEnd: mergeTelemetryCallback('onEnd'),
167
179
  onAbort: mergeTelemetryCallback('onAbort'),
168
180
  onError: mergeTelemetryCallback('onError'),
@@ -5,6 +5,12 @@ import type {
5
5
  EmbeddingModelCallEndEvent,
6
6
  EmbeddingModelCallStartEvent,
7
7
  } from '../embed/embed-events';
8
+ import type {
9
+ EvaluateEndEvent,
10
+ EvaluateStartEvent,
11
+ EvaluationModelCallEndEvent,
12
+ EvaluationModelCallStartEvent,
13
+ } from '../evaluate/evaluate-events';
8
14
  import type {
9
15
  GenerateObjectEndEvent,
10
16
  GenerateObjectStartEvent,
@@ -92,6 +98,10 @@ export interface TelemetryDispatcher {
92
98
  onEmbedEnd?: Callback<EmbeddingModelCallEndEvent>;
93
99
  onRerankStart?: Callback<RerankingModelCallStartEvent>;
94
100
  onRerankEnd?: Callback<RerankingModelCallEndEvent>;
101
+ experimental_onEvaluateStart?: Callback<EvaluateStartEvent>;
102
+ experimental_onEvaluationModelCallStart?: Callback<EvaluationModelCallStartEvent>;
103
+ experimental_onEvaluationModelCallEnd?: Callback<EvaluationModelCallEndEvent>;
104
+ experimental_onEvaluateEnd?: Callback<EvaluateEndEvent>;
95
105
  onEnd?: Callback<OperationEndEvent>;
96
106
  onAbort?: Callback<GenerateTextAbortEvent<ToolSet>>;
97
107
  onError?: Callback<unknown>;
@@ -214,6 +224,30 @@ export interface Telemetry {
214
224
  */
215
225
  onRerankEnd?: Callback<InferTelemetryEvent<RerankingModelCallEndEvent>>;
216
226
 
227
+ /** Called when an experimental evaluation operation begins. */
228
+ experimental_onEvaluateStart?: Callback<
229
+ InferTelemetryEvent<EvaluateStartEvent>
230
+ >;
231
+
232
+ /**
233
+ * Called immediately before an experimental evaluation model call begins.
234
+ * The logical model call includes any provider retries.
235
+ */
236
+ experimental_onEvaluationModelCallStart?: Callback<
237
+ InferTelemetryEvent<EvaluationModelCallStartEvent>
238
+ >;
239
+
240
+ /**
241
+ * Called after an experimental evaluation model response has been validated.
242
+ * The logical model call includes any provider retries.
243
+ */
244
+ experimental_onEvaluationModelCallEnd?: Callback<
245
+ InferTelemetryEvent<EvaluationModelCallEndEvent>
246
+ >;
247
+
248
+ /** Called when an experimental evaluation operation completes. */
249
+ experimental_onEvaluateEnd?: Callback<InferTelemetryEvent<EvaluateEndEvent>>;
250
+
217
251
  /**
218
252
  * Called when an operation completes. Fired for text generation
219
253
  * (generateText/streamText), object generation (generateObject/streamObject),
@@ -8,7 +8,8 @@ export type TelemetryTracingEventType =
8
8
  | 'executeTool'
9
9
  | 'embed'
10
10
  | 'embedMany'
11
- | 'rerank';
11
+ | 'rerank'
12
+ | 'experimental_evaluate';
12
13
 
13
14
  export type TelemetryTracingChannelMessage<EVENT = unknown> = {
14
15
  readonly type: TelemetryTracingEventType;