ai 7.0.103 → 7.0.104
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 +17 -0
- package/dist/index.d.ts +37 -7
- package/dist/index.js +522 -298
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.js +4 -1
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/18-code-mode.mdx +11 -0
- package/docs/03-ai-sdk-core/19-tool-search.mdx +80 -0
- package/docs/03-ai-sdk-core/32-evaluation.mdx +152 -7
- package/docs/03-ai-sdk-core/45-provider-management.mdx +10 -0
- package/docs/03-ai-sdk-core/index.mdx +6 -0
- package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +22 -9
- package/docs/07-reference/01-ai-sdk-core/20-tool.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/23-tool-search.mdx +75 -0
- package/docs/07-reference/01-ai-sdk-core/40-provider-registry.mdx +17 -0
- package/docs/07-reference/01-ai-sdk-core/42-custom-provider.mdx +16 -0
- package/docs/07-reference/01-ai-sdk-core/index.mdx +6 -0
- package/docs/07-reference/05-ai-sdk-errors/ai-no-such-model-error.mdx +4 -0
- package/docs/07-reference/05-ai-sdk-errors/ai-no-such-provider-error.mdx +4 -0
- package/package.json +12 -12
- package/src/evaluate/evaluate.ts +4 -10
- package/src/evaluate/evaluation-provider.ts +6 -0
- package/src/evaluate/evaluation-result.ts +1 -1
- package/src/generate-text/generate-text.ts +9 -1
- package/src/generate-text/stream-text.ts +9 -1
- package/src/generate-text/tool-caller-configuration.ts +1 -1
- package/src/global.ts +1 -0
- package/src/index.ts +1 -0
- package/src/model/resolve-model.ts +55 -10
- package/src/prompt/prepare-tools.ts +1 -1
- package/src/realtime/browser-realtime-transport.ts +12 -1
- package/src/realtime/realtime-event-channel.ts +4 -0
- package/src/realtime/realtime-session.ts +9 -4
- package/src/registry/custom-provider.ts +37 -0
- package/src/registry/index.ts +2 -0
- package/src/registry/no-such-provider-error.ts +2 -1
- package/src/registry/provider-registry.ts +65 -6
- package/src/tool-search/prepare-tool-search.ts +143 -0
- package/src/tool-search/tool-search.ts +59 -0
|
@@ -63,6 +63,7 @@ import type { ActiveTools } from './active-tools';
|
|
|
63
63
|
import { calculateTokensPerSecond } from './calculate-tokens-per-second';
|
|
64
64
|
import { collectToolApprovals } from './collect-tool-approvals';
|
|
65
65
|
import { convertLanguageModelContent } from './convert-language-model-content';
|
|
66
|
+
import { createToolSearchState } from '../tool-search/prepare-tool-search';
|
|
66
67
|
import { executeToolCall } from './execute-tool-call';
|
|
67
68
|
import {
|
|
68
69
|
filterActiveTools,
|
|
@@ -575,6 +576,10 @@ export async function generateText<
|
|
|
575
576
|
tools,
|
|
576
577
|
toolCallers: experimental_toolCallers,
|
|
577
578
|
});
|
|
579
|
+
const prepareToolSearch = createToolSearchState({
|
|
580
|
+
tools,
|
|
581
|
+
toolCallers: resolvedToolCallers,
|
|
582
|
+
});
|
|
578
583
|
const stopConditions = asArray(stopWhen);
|
|
579
584
|
const resolvedOnStart = onStart ?? experimental_onStart;
|
|
580
585
|
const resolvedOnStepStart = onStepStart ?? experimental_onStepStart;
|
|
@@ -920,7 +925,10 @@ export async function generateText<
|
|
|
920
925
|
modelTools: stepModelTools,
|
|
921
926
|
toolCallerMessages,
|
|
922
927
|
} = prepareToolsForToolCallers({
|
|
923
|
-
tools: stepActiveTools,
|
|
928
|
+
tools: prepareToolSearch(stepActiveTools, {
|
|
929
|
+
toolsContext,
|
|
930
|
+
experimental_sandbox: stepSandbox,
|
|
931
|
+
}),
|
|
924
932
|
toolCallers: resolvedToolCallers,
|
|
925
933
|
});
|
|
926
934
|
const stepToolOrder = prepareStepResult?.toolOrder ?? toolOrder;
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
executeToolsFromStream,
|
|
90
90
|
type ExecuteToolsStreamPart,
|
|
91
91
|
} from './execute-tools-from-stream';
|
|
92
|
+
import { createToolSearchState } from '../tool-search/prepare-tool-search';
|
|
92
93
|
import { executeToolCall } from './execute-tool-call';
|
|
93
94
|
import {
|
|
94
95
|
filterActiveTools,
|
|
@@ -1382,6 +1383,10 @@ class DefaultStreamTextResult<
|
|
|
1382
1383
|
tools,
|
|
1383
1384
|
toolCallers: experimental_toolCallers,
|
|
1384
1385
|
});
|
|
1386
|
+
const prepareToolSearch = createToolSearchState({
|
|
1387
|
+
tools,
|
|
1388
|
+
toolCallers: resolvedToolCallers,
|
|
1389
|
+
});
|
|
1385
1390
|
|
|
1386
1391
|
const telemetryDispatcher = createRestrictedTelemetryDispatcher<
|
|
1387
1392
|
TOOLS,
|
|
@@ -2333,7 +2338,10 @@ class DefaultStreamTextResult<
|
|
|
2333
2338
|
modelTools: stepModelTools,
|
|
2334
2339
|
toolCallerMessages,
|
|
2335
2340
|
} = prepareToolsForToolCallers({
|
|
2336
|
-
tools: stepActiveTools,
|
|
2341
|
+
tools: prepareToolSearch(stepActiveTools, {
|
|
2342
|
+
toolsContext,
|
|
2343
|
+
experimental_sandbox: stepSandbox,
|
|
2344
|
+
}),
|
|
2337
2345
|
toolCallers: resolvedToolCallers,
|
|
2338
2346
|
});
|
|
2339
2347
|
const stepToolOrder = prepareStepResult?.toolOrder ?? toolOrder;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@ai-sdk/provider-utils';
|
|
9
9
|
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
10
10
|
|
|
11
|
-
const DIRECT_TOOL_CALL = 'AI_SDK_DIRECT_TOOL_CALL';
|
|
11
|
+
export const DIRECT_TOOL_CALL = 'AI_SDK_DIRECT_TOOL_CALL';
|
|
12
12
|
|
|
13
13
|
type ToolCallerName<TOOLS extends ToolSet> = {
|
|
14
14
|
[NAME in keyof TOOLS]: TOOLS[NAME] extends Experimental_ToolCallerTool
|
package/src/global.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare global {
|
|
|
9
9
|
* String model ids are resolved to the default provider and model id.
|
|
10
10
|
*
|
|
11
11
|
* If not set, the default provider is the Vercel AI gateway provider.
|
|
12
|
+
* Experimental evaluation requires an explicitly configured evaluation-capable provider.
|
|
12
13
|
*
|
|
13
14
|
* @see https://ai-sdk.dev/docs/ai-sdk-core/provider-management#global-provider-configuration
|
|
14
15
|
*/
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export * from './registry';
|
|
|
55
55
|
export * from './rerank';
|
|
56
56
|
export * from './telemetry';
|
|
57
57
|
export * from './text-stream';
|
|
58
|
+
export { toolSearch } from './tool-search/tool-search';
|
|
58
59
|
export * from './transcribe';
|
|
59
60
|
export * from './translate';
|
|
60
61
|
export * from './types';
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { gateway } from '@ai-sdk/gateway';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import {
|
|
3
|
+
NoSuchModelError,
|
|
4
|
+
type Experimental_EvaluationModelV4 as EvaluationModelV4,
|
|
5
|
+
type EmbeddingModelV4,
|
|
6
|
+
type Experimental_SpeechTranslationModelV4,
|
|
7
|
+
type Experimental_VideoModelV4,
|
|
8
|
+
type ImageModelV4,
|
|
9
|
+
type LanguageModelV4,
|
|
10
|
+
type ProviderV4,
|
|
11
|
+
type RerankingModelV4,
|
|
12
|
+
type SpeechModelV4,
|
|
13
|
+
type TranscriptionModelV4,
|
|
12
14
|
} from '@ai-sdk/provider';
|
|
15
|
+
import type { EvaluationModel } from '../evaluate/evaluation-result';
|
|
16
|
+
import type { EvaluationProvider } from '../evaluate/evaluation-provider';
|
|
13
17
|
import { UnsupportedModelVersionError } from '../error';
|
|
14
18
|
import type { EmbeddingModel } from '../types/embedding-model';
|
|
15
19
|
import type { LanguageModel } from '../types/language-model';
|
|
@@ -213,6 +217,47 @@ export function resolveRerankingModel(model: RerankingModel): RerankingModelV4 {
|
|
|
213
217
|
return asRerankingModelV4(model);
|
|
214
218
|
}
|
|
215
219
|
|
|
220
|
+
export function resolveEvaluationModel(
|
|
221
|
+
model: EvaluationModel,
|
|
222
|
+
): EvaluationModelV4 {
|
|
223
|
+
if (typeof model === 'string') {
|
|
224
|
+
// Use the original provider so experimental methods and their receiver survive.
|
|
225
|
+
// Evaluation does not default to Gateway.
|
|
226
|
+
const provider = globalThis.AI_SDK_DEFAULT_PROVIDER as
|
|
227
|
+
| EvaluationProvider
|
|
228
|
+
| undefined;
|
|
229
|
+
|
|
230
|
+
if (typeof provider?.evaluationModel !== 'function') {
|
|
231
|
+
throw new NoSuchModelError({
|
|
232
|
+
modelId: model,
|
|
233
|
+
modelType: 'evaluationModel',
|
|
234
|
+
message:
|
|
235
|
+
'The default provider does not support evaluation models. ' +
|
|
236
|
+
'Pass an evaluation model instance or configure AI_SDK_DEFAULT_PROVIDER with an evaluationModel method.',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const resolvedModel = provider.evaluationModel(model);
|
|
241
|
+
if (resolvedModel == null) {
|
|
242
|
+
throw new NoSuchModelError({
|
|
243
|
+
modelId: model,
|
|
244
|
+
modelType: 'evaluationModel',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
model = resolvedModel;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (model.specificationVersion !== 'v4') {
|
|
251
|
+
throw new UnsupportedModelVersionError({
|
|
252
|
+
version: model.specificationVersion,
|
|
253
|
+
provider: model.provider,
|
|
254
|
+
modelId: model.modelId,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return model;
|
|
259
|
+
}
|
|
260
|
+
|
|
216
261
|
function getGlobalProvider(): ProviderV4 {
|
|
217
262
|
const provider = globalThis.AI_SDK_DEFAULT_PROVIDER ?? gateway;
|
|
218
263
|
return asProviderV4(provider);
|
|
@@ -105,7 +105,7 @@ function orderToolEntries<TOOLS extends ToolSet>({
|
|
|
105
105
|
return [...orderedTools, ...unorderedTools];
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function resolveToolDescription<TOOLS extends ToolSet>({
|
|
108
|
+
export function resolveToolDescription<TOOLS extends ToolSet>({
|
|
109
109
|
tool,
|
|
110
110
|
toolName,
|
|
111
111
|
toolsContext,
|
|
@@ -169,9 +169,20 @@ export class BrowserRealtimeTransport {
|
|
|
169
169
|
if (this.ws === ws) {
|
|
170
170
|
this.ws = null;
|
|
171
171
|
const closeError = getCloseError(event) ?? connectionError;
|
|
172
|
+
const reportCloseErrorImmediately =
|
|
173
|
+
closeError != null && !codec.hasPendingIncoming;
|
|
172
174
|
codec.stopWriting();
|
|
173
175
|
this.notifyClosing();
|
|
174
176
|
if (this.epoch !== epoch) return;
|
|
177
|
+
const drain = codec.finish();
|
|
178
|
+
if (reportCloseErrorImmediately) {
|
|
179
|
+
try {
|
|
180
|
+
this.onError(closeError);
|
|
181
|
+
} catch {
|
|
182
|
+
/* Application callbacks cannot interrupt teardown. */
|
|
183
|
+
}
|
|
184
|
+
if (this.epoch !== epoch) return;
|
|
185
|
+
}
|
|
175
186
|
const complete = () => {
|
|
176
187
|
if (this.epoch !== epoch) return;
|
|
177
188
|
clearTimeout(this.drainTimer);
|
|
@@ -184,7 +195,7 @@ export class BrowserRealtimeTransport {
|
|
|
184
195
|
}
|
|
185
196
|
};
|
|
186
197
|
this.drainTimer = setTimeout(complete, 1_000);
|
|
187
|
-
void this.awaitDrain(
|
|
198
|
+
void this.awaitDrain(drain, complete);
|
|
188
199
|
}
|
|
189
200
|
};
|
|
190
201
|
}
|
|
@@ -206,6 +206,7 @@ export abstract class AbstractRealtimeSession {
|
|
|
206
206
|
if (current())
|
|
207
207
|
this.fail(new Error('Realtime session startup timed out'));
|
|
208
208
|
});
|
|
209
|
+
const reportedTransportErrors = new WeakSet<Error>();
|
|
209
210
|
const callbacks = {
|
|
210
211
|
model,
|
|
211
212
|
onEvent: async (event: RealtimeServerEvent) => {
|
|
@@ -221,7 +222,10 @@ export abstract class AbstractRealtimeSession {
|
|
|
221
222
|
}
|
|
222
223
|
},
|
|
223
224
|
onError: (error: Error) => {
|
|
224
|
-
if (current())
|
|
225
|
+
if (current()) {
|
|
226
|
+
reportedTransportErrors.add(error);
|
|
227
|
+
void this.reportError(error, attempt);
|
|
228
|
+
}
|
|
225
229
|
},
|
|
226
230
|
onFatalError: (error: Error, drain?: Promise<void>) => {
|
|
227
231
|
if (current()) this.fail(error, drain);
|
|
@@ -244,7 +248,8 @@ export abstract class AbstractRealtimeSession {
|
|
|
244
248
|
const finalizationConfirmed =
|
|
245
249
|
model.capabilities?.finalization === 'session-close' &&
|
|
246
250
|
this.state.session?.finalization === 'confirmed';
|
|
247
|
-
if (error != null && !finalizationConfirmed)
|
|
251
|
+
if (error != null && !finalizationConfirmed)
|
|
252
|
+
this.fail(error, undefined, !reportedTransportErrors.has(error));
|
|
248
253
|
else if (!attempt.ready && !finalizationConfirmed)
|
|
249
254
|
this.fail(
|
|
250
255
|
new Error('Realtime connection closed before becoming ready'),
|
|
@@ -348,7 +353,7 @@ export abstract class AbstractRealtimeSession {
|
|
|
348
353
|
}
|
|
349
354
|
}
|
|
350
355
|
|
|
351
|
-
private fail(error: unknown, drain?: Promise<void
|
|
356
|
+
private fail(error: unknown, drain?: Promise<void>, report = true): void {
|
|
352
357
|
const attempt = this.attempt;
|
|
353
358
|
if (attempt == null || !attempt.active || attempt.cause != null) return;
|
|
354
359
|
attempt.cause = error instanceof Error ? error : new Error(String(error));
|
|
@@ -359,7 +364,7 @@ export abstract class AbstractRealtimeSession {
|
|
|
359
364
|
if (this.attempt !== attempt || !attempt.active) return;
|
|
360
365
|
if (drain == null) this.disconnect();
|
|
361
366
|
else void this.drainAttempt(attempt, drain);
|
|
362
|
-
void this.reportError(attempt.cause, attempt);
|
|
367
|
+
if (report) void this.reportError(attempt.cause, attempt);
|
|
363
368
|
}
|
|
364
369
|
|
|
365
370
|
private async drainAttempt(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type Experimental_EvaluationModelV4 as EvaluationModelV4,
|
|
2
3
|
type EmbeddingModelV4,
|
|
3
4
|
type Experimental_VideoModelV4,
|
|
4
5
|
type FilesV4,
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
type SpeechModelV4,
|
|
14
15
|
type TranscriptionModelV4,
|
|
15
16
|
} from '@ai-sdk/provider';
|
|
17
|
+
import type { EvaluationModel } from '../evaluate/evaluation-result';
|
|
18
|
+
import type { EvaluationProvider } from '../evaluate/evaluation-provider';
|
|
16
19
|
import { asProviderV4 } from '../model/as-provider-v4';
|
|
17
20
|
import {
|
|
18
21
|
resolveEmbeddingModel,
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
resolveSpeechModel,
|
|
23
26
|
resolveTranscriptionModel,
|
|
24
27
|
resolveVideoModel,
|
|
28
|
+
resolveEvaluationModel,
|
|
25
29
|
} from '../model/resolve-model';
|
|
26
30
|
import type { EmbeddingModel } from '../types/embedding-model';
|
|
27
31
|
import type { ImageModel } from '../types/image-model';
|
|
@@ -42,6 +46,7 @@ import type { VideoModel } from '../types/video-model';
|
|
|
42
46
|
* @param {Record<string, SpeechModel>} [options.speechModels] - A record of speech models, where keys are model IDs and values are speech model instances.
|
|
43
47
|
* @param {Record<string, RerankingModel>} [options.rerankingModels] - A record of reranking models, where keys are model IDs and values are reranking model instances.
|
|
44
48
|
* @param {Record<string, VideoModel>} [options.videoModels] - A record of video models, where keys are model IDs and values are video model instances.
|
|
49
|
+
* @param {Record<string, EvaluationModel>} [options.evaluationModels] - Experimental evaluation models or default-provider model IDs, keyed by alias.
|
|
45
50
|
* @param {FilesV4} [options.files] - A files interface for uploading files.
|
|
46
51
|
* @param {SkillsV4} [options.skills] - A skills interface for uploading skills.
|
|
47
52
|
* @param {ProviderV2 | ProviderV3 | ProviderV4} [options.fallbackProvider] - An optional fallback provider to use when a requested model is not found in the custom provider.
|
|
@@ -60,6 +65,10 @@ export function customProvider<
|
|
|
60
65
|
FILES extends FilesV4 | undefined = undefined,
|
|
61
66
|
SKILLS extends SkillsV4 | undefined = undefined,
|
|
62
67
|
FALLBACK extends ProviderV2 | ProviderV3 | ProviderV4 | undefined = undefined,
|
|
68
|
+
EVALUATION_MODELS extends Record<string, EvaluationModel> = Record<
|
|
69
|
+
string,
|
|
70
|
+
EvaluationModel
|
|
71
|
+
>,
|
|
63
72
|
>({
|
|
64
73
|
languageModels,
|
|
65
74
|
embeddingModels,
|
|
@@ -68,6 +77,7 @@ export function customProvider<
|
|
|
68
77
|
speechModels,
|
|
69
78
|
rerankingModels,
|
|
70
79
|
videoModels,
|
|
80
|
+
evaluationModels,
|
|
71
81
|
files,
|
|
72
82
|
skills,
|
|
73
83
|
fallbackProvider: fallbackProviderArg,
|
|
@@ -79,6 +89,7 @@ export function customProvider<
|
|
|
79
89
|
speechModels?: SPEECH_MODELS;
|
|
80
90
|
rerankingModels?: RERANKING_MODELS;
|
|
81
91
|
videoModels?: VIDEO_MODELS;
|
|
92
|
+
evaluationModels?: EVALUATION_MODELS;
|
|
82
93
|
files?: FILES;
|
|
83
94
|
skills?: SKILLS;
|
|
84
95
|
fallbackProvider?: FALLBACK;
|
|
@@ -92,6 +103,9 @@ export function customProvider<
|
|
|
92
103
|
rerankingModel(modelId: ExtractModelId<RERANKING_MODELS>): RerankingModelV4;
|
|
93
104
|
speechModel(modelId: ExtractModelId<SPEECH_MODELS>): SpeechModelV4;
|
|
94
105
|
videoModel(modelId: ExtractModelId<VIDEO_MODELS>): Experimental_VideoModelV4;
|
|
106
|
+
evaluationModel(
|
|
107
|
+
modelId: ExtractModelId<EVALUATION_MODELS>,
|
|
108
|
+
): EvaluationModelV4;
|
|
95
109
|
} & (FILES extends FilesV4
|
|
96
110
|
? { files(): FilesV4 }
|
|
97
111
|
: [FALLBACK] extends [{ files: () => FilesV4 }]
|
|
@@ -117,6 +131,9 @@ export function customProvider<
|
|
|
117
131
|
videoModel(
|
|
118
132
|
modelId: ExtractModelId<VIDEO_MODELS>,
|
|
119
133
|
): Experimental_VideoModelV4;
|
|
134
|
+
evaluationModel(
|
|
135
|
+
modelId: ExtractModelId<EVALUATION_MODELS>,
|
|
136
|
+
): EvaluationModelV4;
|
|
120
137
|
} = {
|
|
121
138
|
specificationVersion: 'v4',
|
|
122
139
|
languageModel(modelId: ExtractModelId<LANGUAGE_MODELS>): LanguageModelV4 {
|
|
@@ -203,6 +220,26 @@ export function customProvider<
|
|
|
203
220
|
|
|
204
221
|
throw new NoSuchModelError({ modelId, modelType: 'rerankingModel' });
|
|
205
222
|
},
|
|
223
|
+
evaluationModel(
|
|
224
|
+
modelId: ExtractModelId<EVALUATION_MODELS>,
|
|
225
|
+
): EvaluationModelV4 {
|
|
226
|
+
if (
|
|
227
|
+
evaluationModels != null &&
|
|
228
|
+
Object.hasOwn(evaluationModels, modelId)
|
|
229
|
+
) {
|
|
230
|
+
return resolveEvaluationModel(evaluationModels[modelId]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const provider = fallbackProviderArg as EvaluationProvider | undefined;
|
|
234
|
+
if (typeof provider?.evaluationModel === 'function') {
|
|
235
|
+
const model = provider.evaluationModel(modelId);
|
|
236
|
+
if (model != null) {
|
|
237
|
+
return resolveEvaluationModel(model);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
throw new NoSuchModelError({ modelId, modelType: 'evaluationModel' });
|
|
242
|
+
},
|
|
206
243
|
videoModel(
|
|
207
244
|
modelId: ExtractModelId<VIDEO_MODELS>,
|
|
208
245
|
): Experimental_VideoModelV4 {
|
package/src/registry/index.ts
CHANGED
|
@@ -25,7 +25,8 @@ export class NoSuchProviderError extends NoSuchModelError {
|
|
|
25
25
|
| 'transcriptionModel'
|
|
26
26
|
| 'speechModel'
|
|
27
27
|
| 'rerankingModel'
|
|
28
|
-
| 'videoModel'
|
|
28
|
+
| 'videoModel'
|
|
29
|
+
| 'evaluationModel';
|
|
29
30
|
providerId: string;
|
|
30
31
|
availableProviders: string[];
|
|
31
32
|
message?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type Experimental_EvaluationModelV4 as EvaluationModelV4,
|
|
2
3
|
type EmbeddingModelV4,
|
|
3
4
|
type Experimental_VideoModelV3,
|
|
4
5
|
type Experimental_VideoModelV4,
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
type SpeechModelV4,
|
|
14
15
|
type TranscriptionModelV4,
|
|
15
16
|
} from '@ai-sdk/provider';
|
|
17
|
+
import type { EvaluationProvider } from '../evaluate/evaluation-provider';
|
|
18
|
+
import { resolveEvaluationModel } from '../model/resolve-model';
|
|
16
19
|
import { wrapImageModel } from '../middleware/wrap-image-model';
|
|
17
20
|
import { wrapLanguageModel } from '../middleware/wrap-language-model';
|
|
18
21
|
import { asProviderV4 } from '../model/as-provider-v4';
|
|
@@ -34,7 +37,8 @@ type RegistryModelType =
|
|
|
34
37
|
| 'transcriptionModel'
|
|
35
38
|
| 'speechModel'
|
|
36
39
|
| 'rerankingModel'
|
|
37
|
-
| 'videoModel'
|
|
40
|
+
| 'videoModel'
|
|
41
|
+
| 'evaluationModel';
|
|
38
42
|
|
|
39
43
|
type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
|
|
40
44
|
videoModel: (...args: infer ARGS) => unknown;
|
|
@@ -42,6 +46,30 @@ type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
|
|
|
42
46
|
? ExtractLiteralUnion<ARGS[0]>
|
|
43
47
|
: never;
|
|
44
48
|
|
|
49
|
+
type ProviderEvaluationModelIdentifier<PROVIDER> = PROVIDER extends {
|
|
50
|
+
evaluationModel: (...args: infer ARGS) => unknown;
|
|
51
|
+
}
|
|
52
|
+
? ExtractLiteralUnion<ARGS[0]>
|
|
53
|
+
: never;
|
|
54
|
+
|
|
55
|
+
/** Registry with experimental evaluation access, separate from the stable interface. */
|
|
56
|
+
export type EvaluationProviderRegistry<
|
|
57
|
+
PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
|
|
58
|
+
string,
|
|
59
|
+
ProviderV4 | ProviderV3
|
|
60
|
+
>,
|
|
61
|
+
SEPARATOR extends string = ':',
|
|
62
|
+
> = ProviderRegistryProvider<PROVIDERS, SEPARATOR> & {
|
|
63
|
+
evaluationModel<KEY extends keyof PROVIDERS>(
|
|
64
|
+
id: KEY extends string
|
|
65
|
+
? `${KEY & string}${SEPARATOR}${ProviderEvaluationModelIdentifier<PROVIDERS[KEY]>}`
|
|
66
|
+
: never,
|
|
67
|
+
): EvaluationModelV4;
|
|
68
|
+
evaluationModel<KEY extends keyof PROVIDERS>(
|
|
69
|
+
id: KEY extends string ? `${KEY & string}${SEPARATOR}${string}` : never,
|
|
70
|
+
): EvaluationModelV4;
|
|
71
|
+
};
|
|
72
|
+
|
|
45
73
|
export interface ProviderRegistryProvider<
|
|
46
74
|
PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
|
|
47
75
|
string,
|
|
@@ -150,7 +178,7 @@ export function createProviderRegistry<
|
|
|
150
178
|
| LanguageModelMiddleware[];
|
|
151
179
|
imageModelMiddleware?: ImageModelMiddleware | ImageModelMiddleware[];
|
|
152
180
|
} = {},
|
|
153
|
-
):
|
|
181
|
+
): EvaluationProviderRegistry<PROVIDERS, SEPARATOR> {
|
|
154
182
|
const registry = new DefaultProviderRegistry<PROVIDERS, SEPARATOR>({
|
|
155
183
|
separator,
|
|
156
184
|
languageModelMiddleware,
|
|
@@ -175,9 +203,16 @@ export const experimental_createProviderRegistry = createProviderRegistry;
|
|
|
175
203
|
class DefaultProviderRegistry<
|
|
176
204
|
PROVIDERS extends Record<string, ProviderV4 | ProviderV3>,
|
|
177
205
|
SEPARATOR extends string,
|
|
178
|
-
>
|
|
206
|
+
>
|
|
207
|
+
implements
|
|
208
|
+
ProviderRegistryProvider<PROVIDERS, SEPARATOR>,
|
|
209
|
+
EvaluationProviderRegistry<PROVIDERS, SEPARATOR>
|
|
210
|
+
{
|
|
179
211
|
private providers: Partial<
|
|
180
|
-
Record<
|
|
212
|
+
Record<
|
|
213
|
+
keyof PROVIDERS,
|
|
214
|
+
ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider
|
|
215
|
+
>
|
|
181
216
|
> = {};
|
|
182
217
|
private separator: SEPARATOR;
|
|
183
218
|
private languageModelMiddleware?:
|
|
@@ -213,7 +248,11 @@ class DefaultProviderRegistry<
|
|
|
213
248
|
provider as ProviderWithOptionalVideoModel
|
|
214
249
|
).videoModel?.bind(provider);
|
|
215
250
|
|
|
216
|
-
|
|
251
|
+
const evaluationModel = (
|
|
252
|
+
provider as EvaluationProvider
|
|
253
|
+
).evaluationModel?.bind(provider);
|
|
254
|
+
|
|
255
|
+
const registeredProvider =
|
|
217
256
|
videoModel == null
|
|
218
257
|
? providerV4
|
|
219
258
|
: Object.assign(Object.create(Object.getPrototypeOf(providerV4)), {
|
|
@@ -221,12 +260,18 @@ class DefaultProviderRegistry<
|
|
|
221
260
|
videoModel: (modelId: string) =>
|
|
222
261
|
asVideoModelV4(videoModel(modelId)),
|
|
223
262
|
});
|
|
263
|
+
|
|
264
|
+
// Keep v4 instances intact. Adapted providers need the original evaluation receiver.
|
|
265
|
+
if (registeredProvider !== provider && evaluationModel != null) {
|
|
266
|
+
Object.assign(registeredProvider, { evaluationModel });
|
|
267
|
+
}
|
|
268
|
+
this.providers[id] = registeredProvider;
|
|
224
269
|
}
|
|
225
270
|
|
|
226
271
|
private getProvider(
|
|
227
272
|
id: string,
|
|
228
273
|
modelType: RegistryModelType,
|
|
229
|
-
): ProviderV4 & ProviderWithOptionalVideoModel {
|
|
274
|
+
): ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider {
|
|
230
275
|
const provider = this.providers[id as keyof PROVIDERS];
|
|
231
276
|
|
|
232
277
|
if (provider == null) {
|
|
@@ -382,6 +427,20 @@ class DefaultProviderRegistry<
|
|
|
382
427
|
return asVideoModelV4(model);
|
|
383
428
|
}
|
|
384
429
|
|
|
430
|
+
evaluationModel<KEY extends keyof PROVIDERS>(
|
|
431
|
+
id: `${KEY & string}${SEPARATOR}${string}`,
|
|
432
|
+
): EvaluationModelV4 {
|
|
433
|
+
const [providerId, modelId] = this.splitId(id, 'evaluationModel');
|
|
434
|
+
const provider = this.getProvider(providerId, 'evaluationModel');
|
|
435
|
+
const model = provider.evaluationModel?.(modelId);
|
|
436
|
+
|
|
437
|
+
if (model == null) {
|
|
438
|
+
throw new NoSuchModelError({ modelId: id, modelType: 'evaluationModel' });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return resolveEvaluationModel(model);
|
|
442
|
+
}
|
|
443
|
+
|
|
385
444
|
files<KEY extends keyof PROVIDERS>(id: KEY & string): FilesV4 {
|
|
386
445
|
const provider = this.getProvider(id, 'languageModel');
|
|
387
446
|
const files = provider.files?.();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
experimental_getToolCaller,
|
|
3
|
+
type ToolSet,
|
|
4
|
+
type InferToolSetContext,
|
|
5
|
+
type Experimental_SandboxSession as SandboxSession,
|
|
6
|
+
} from '@ai-sdk/provider-utils';
|
|
7
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
8
|
+
import {
|
|
9
|
+
DIRECT_TOOL_CALL,
|
|
10
|
+
type ResolvedToolCallers,
|
|
11
|
+
} from '../generate-text/tool-caller-configuration';
|
|
12
|
+
import { resolveToolDescription } from '../prompt/prepare-tools';
|
|
13
|
+
import { getOwn } from '../util/get-own';
|
|
14
|
+
import { isToolSearch } from './tool-search';
|
|
15
|
+
|
|
16
|
+
/** Create discovery state for one generation, never for a shared tool instance. */
|
|
17
|
+
export function createToolSearchState({
|
|
18
|
+
tools,
|
|
19
|
+
toolCallers,
|
|
20
|
+
}: {
|
|
21
|
+
tools: ToolSet | undefined;
|
|
22
|
+
toolCallers: ResolvedToolCallers | undefined;
|
|
23
|
+
}): (
|
|
24
|
+
activeTools: ToolSet | undefined,
|
|
25
|
+
options?: {
|
|
26
|
+
toolsContext?: InferToolSetContext<ToolSet>;
|
|
27
|
+
experimental_sandbox?: SandboxSession;
|
|
28
|
+
},
|
|
29
|
+
) => ToolSet | undefined {
|
|
30
|
+
const searchTools = Object.entries(tools ?? {}).filter(
|
|
31
|
+
([, tool]) => tool.deferLoading || isToolSearch(tool),
|
|
32
|
+
);
|
|
33
|
+
if (searchTools.length === 0) {
|
|
34
|
+
return activeTools => activeTools;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const discovered = new Set<string>();
|
|
38
|
+
// An omitted entry permits direct calls; an explicit empty list permits none.
|
|
39
|
+
const getCallers = (name: string) =>
|
|
40
|
+
getOwn(toolCallers, name) ?? [DIRECT_TOOL_CALL];
|
|
41
|
+
|
|
42
|
+
for (const [name, tool] of searchTools) {
|
|
43
|
+
const callers = getCallers(name);
|
|
44
|
+
if (
|
|
45
|
+
callers.some(name => {
|
|
46
|
+
if (name === DIRECT_TOOL_CALL) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const caller = experimental_getToolCaller(tools?.[name]);
|
|
50
|
+
return caller?.type !== 'local' || caller.prepareModelMessage == null;
|
|
51
|
+
}) ||
|
|
52
|
+
(isToolSearch(tool) && tool.deferLoading)
|
|
53
|
+
) {
|
|
54
|
+
throw new InvalidArgumentError({
|
|
55
|
+
parameter: 'tools',
|
|
56
|
+
value: name,
|
|
57
|
+
message: `tool "${name}" must be callable directly or through code mode with toolDiscovery: 'conversation'. The search tool itself must not defer loading.`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (activeTools, { toolsContext = {}, experimental_sandbox } = {}) => {
|
|
63
|
+
if (activeTools == null) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Snapshot eligibility before binding callers. Newly discovered tools are
|
|
68
|
+
// intentionally absent from this step's execution bindings and catalog.
|
|
69
|
+
const entries = Object.entries(activeTools);
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
entries
|
|
72
|
+
.filter(([name, tool]) => !tool.deferLoading || discovered.has(name))
|
|
73
|
+
.map(([searchName, tool]) => {
|
|
74
|
+
if (!isToolSearch(tool)) {
|
|
75
|
+
return [searchName, tool];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const callers = getCallers(searchName).filter(
|
|
79
|
+
name =>
|
|
80
|
+
name === DIRECT_TOOL_CALL || Object.hasOwn(activeTools, name),
|
|
81
|
+
);
|
|
82
|
+
const candidates = entries.filter(
|
|
83
|
+
([name, candidate]) =>
|
|
84
|
+
candidate.deferLoading &&
|
|
85
|
+
!isToolSearch(candidate) &&
|
|
86
|
+
callers.some(caller => getCallers(name).includes(caller)),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return [
|
|
90
|
+
searchName,
|
|
91
|
+
{
|
|
92
|
+
...tool,
|
|
93
|
+
execute: ({ query }: { query: string }) => {
|
|
94
|
+
const terms = [...new Set(tokenize(query))];
|
|
95
|
+
const matches = candidates
|
|
96
|
+
.map(([name, candidate]) => {
|
|
97
|
+
const description = resolveToolDescription({
|
|
98
|
+
tool: candidate,
|
|
99
|
+
toolName: name,
|
|
100
|
+
toolsContext,
|
|
101
|
+
experimental_sandbox,
|
|
102
|
+
});
|
|
103
|
+
const nameTerms = tokenize(name);
|
|
104
|
+
const descriptionTerms = tokenize(description ?? '');
|
|
105
|
+
const score = terms.reduce(
|
|
106
|
+
(score, term) =>
|
|
107
|
+
score +
|
|
108
|
+
(nameTerms.includes(term) ? 2 : 0) +
|
|
109
|
+
(descriptionTerms.includes(term) ? 1 : 0),
|
|
110
|
+
0,
|
|
111
|
+
);
|
|
112
|
+
return { name, description, score };
|
|
113
|
+
})
|
|
114
|
+
.filter(match => match.score > 0)
|
|
115
|
+
.sort((a, b) => b.score - a.score)
|
|
116
|
+
.slice(0, 5);
|
|
117
|
+
|
|
118
|
+
for (const { name } of matches) {
|
|
119
|
+
discovered.add(name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
tools: matches.map(({ name, description }) => ({
|
|
124
|
+
name,
|
|
125
|
+
...(description == null ? {} : { description }),
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function tokenize(text: string): string[] {
|
|
137
|
+
return (
|
|
138
|
+
text
|
|
139
|
+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
140
|
+
.toLowerCase()
|
|
141
|
+
.match(/[\p{L}\p{N}]+/gu) ?? []
|
|
142
|
+
);
|
|
143
|
+
}
|