ai 7.0.126 → 7.0.128
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 +29 -0
- package/dist/index.d.ts +234 -154
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +307 -199
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +278 -68
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +371 -72
- package/dist/internal/index.js.map +1 -1
- package/dist/test/index.d.ts +25 -17
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +27 -21
- package/dist/test/index.js.map +1 -1
- package/docs/02-getting-started/03-nextjs-pages-router.mdx +1 -1
- package/docs/02-getting-started/07-expo.mdx +1 -1
- package/docs/03-agents/07-workflow-agent.mdx +80 -12
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +44 -0
- package/docs/03-ai-sdk-core/19-tool-search.mdx +40 -4
- package/docs/03-ai-sdk-core/{32-evaluation.mdx → 32-decisions.mdx} +49 -49
- package/docs/03-ai-sdk-core/35-image-generation.mdx +6 -1
- package/docs/03-ai-sdk-core/36-transcription.mdx +42 -39
- package/docs/03-ai-sdk-core/37-speech.mdx +2 -0
- package/docs/03-ai-sdk-core/45-provider-management.mdx +8 -8
- package/docs/03-ai-sdk-core/60-telemetry.mdx +40 -31
- package/docs/03-ai-sdk-core/65-lifecycle-callbacks.mdx +21 -22
- package/docs/03-ai-sdk-harnesses/08-terminal-ui.mdx +2 -2
- package/docs/04-ai-sdk-ui/02-chatbot.mdx +2 -1
- package/docs/06-advanced/09-multistep-interfaces.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/14-decide.mdx +74 -0
- package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +58 -4
- package/docs/07-reference/01-ai-sdk-core/23-tool-search.mdx +62 -6
- package/docs/07-reference/01-ai-sdk-core/40-provider-registry.mdx +11 -11
- package/docs/07-reference/01-ai-sdk-core/42-custom-provider.mdx +9 -9
- package/docs/07-reference/01-ai-sdk-core/66-extract-reasoning-middleware.mdx +24 -8
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +13 -0
- package/docs/07-reference/02-ai-sdk-ui/32-prune-messages.mdx +1 -1
- package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +1 -0
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +39 -2
- package/docs/07-reference/05-ai-sdk-errors/ai-decision-unsupported-question-type-error.mdx +31 -0
- package/docs/07-reference/05-ai-sdk-errors/ai-no-such-model-error.mdx +3 -3
- package/docs/07-reference/05-ai-sdk-errors/ai-no-such-provider-error.mdx +3 -3
- package/docs/08-migration-guides/23-migration-guide-7-0.mdx +1 -1
- package/docs/08-migration-guides/29-migration-guide-4-0.mdx +1 -1
- package/docs/09-troubleshooting/05-tool-invocation-missing-result.mdx +2 -2
- package/package.json +12 -12
- package/src/decide/decide-events.ts +129 -0
- package/src/{evaluate/evaluate.ts → decide/decide.ts} +36 -38
- package/src/decide/decision-provider.ts +11 -0
- package/src/decide/decision-result.ts +39 -0
- package/src/decide/index.ts +64 -0
- package/src/{evaluate → decide}/restricted-telemetry-dispatcher.ts +7 -7
- package/src/{evaluate/validate-evaluation.ts → decide/validate-decision.ts} +9 -9
- package/src/error/index.ts +10 -1
- package/src/generate-speech/generated-audio-file.ts +5 -4
- package/src/generate-text/default-generate-text-result.ts +127 -0
- package/src/generate-text/generate-text.ts +2 -122
- package/src/generate-text/validate-tool-approvals.ts +5 -1
- package/src/global.ts +1 -1
- package/src/index.ts +1 -1
- package/src/middleware/extract-reasoning-middleware.ts +25 -6
- package/src/model/resolve-model.ts +45 -14
- package/src/registry/custom-provider.ts +38 -26
- package/src/registry/index.ts +13 -1
- package/src/registry/no-such-provider-error.ts +2 -0
- package/src/registry/provider-registry.ts +56 -29
- package/src/telemetry/create-telemetry-dispatcher.ts +17 -5
- package/src/telemetry/telemetry.ts +35 -19
- package/src/telemetry/tracing-channel.ts +1 -1
- package/src/test/decision-mock-model-v4.ts +35 -0
- package/src/tool-search/prepare-tool-search.ts +49 -29
- package/src/tool-search/tool-search.ts +58 -4
- package/src/transcribe/transcribe.ts +1 -1
- package/src/ui/chat.ts +9 -1
- package/src/ui/process-ui-message-stream.ts +21 -0
- package/src/ui-message-stream/create-ui-message-stream.ts +29 -4
- package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +0 -74
- package/docs/07-reference/05-ai-sdk-errors/ai-evaluation-unsupported-question-type-error.mdx +0 -31
- package/src/evaluate/evaluate-events.ts +0 -131
- package/src/evaluate/evaluation-provider.ts +0 -6
- package/src/evaluate/evaluation-result.ts +0 -39
- package/src/evaluate/index.ts +0 -13
- package/src/test/evaluation-mock-model-v4.ts +0 -27
package/src/registry/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ProviderV3, ProviderV4 } from '@ai-sdk/provider';
|
|
2
|
+
import type { DecisionProviderRegistry } from './provider-registry';
|
|
3
|
+
|
|
1
4
|
export { customProvider } from './custom-provider';
|
|
2
5
|
export { NoSuchProviderError } from './no-such-provider-error';
|
|
3
6
|
export {
|
|
@@ -6,4 +9,13 @@ export {
|
|
|
6
9
|
} from './provider-registry';
|
|
7
10
|
export type { ProviderRegistryProvider } from './provider-registry';
|
|
8
11
|
|
|
9
|
-
export type {
|
|
12
|
+
export type { DecisionProviderRegistry as Experimental_DecisionProviderRegistry } from './provider-registry';
|
|
13
|
+
|
|
14
|
+
/** @deprecated Use `Experimental_DecisionProviderRegistry` instead. */
|
|
15
|
+
export type Experimental_EvaluationProviderRegistry<
|
|
16
|
+
PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
|
|
17
|
+
string,
|
|
18
|
+
ProviderV4 | ProviderV3
|
|
19
|
+
>,
|
|
20
|
+
SEPARATOR extends string = ':',
|
|
21
|
+
> = DecisionProviderRegistry<PROVIDERS, SEPARATOR>;
|
|
@@ -26,6 +26,8 @@ export class NoSuchProviderError extends NoSuchModelError {
|
|
|
26
26
|
| 'speechModel'
|
|
27
27
|
| 'rerankingModel'
|
|
28
28
|
| 'videoModel'
|
|
29
|
+
| 'decisionModel'
|
|
30
|
+
/** @deprecated Use `decisionModel` instead. */
|
|
29
31
|
| 'evaluationModel';
|
|
30
32
|
providerId: string;
|
|
31
33
|
availableProviders: string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type
|
|
2
|
+
type Experimental_DecisionModelV4 as DecisionModelV4,
|
|
3
3
|
type EmbeddingModelV4,
|
|
4
4
|
type Experimental_VideoModelV3,
|
|
5
5
|
type Experimental_VideoModelV4,
|
|
@@ -14,8 +14,12 @@ import {
|
|
|
14
14
|
type SpeechModelV4,
|
|
15
15
|
type TranscriptionModelV4,
|
|
16
16
|
} from '@ai-sdk/provider';
|
|
17
|
-
import type {
|
|
18
|
-
import {
|
|
17
|
+
import type { DecisionModel } from '../decide/decision-result';
|
|
18
|
+
import type { DecisionProvider } from '../decide/decision-provider';
|
|
19
|
+
import {
|
|
20
|
+
resolveDecisionModel,
|
|
21
|
+
asEvaluationModel,
|
|
22
|
+
} from '../model/resolve-model';
|
|
19
23
|
import { wrapImageModel } from '../middleware/wrap-image-model';
|
|
20
24
|
import { wrapLanguageModel } from '../middleware/wrap-language-model';
|
|
21
25
|
import { asProviderV4 } from '../model/as-provider-v4';
|
|
@@ -38,7 +42,7 @@ type RegistryModelType =
|
|
|
38
42
|
| 'speechModel'
|
|
39
43
|
| 'rerankingModel'
|
|
40
44
|
| 'videoModel'
|
|
41
|
-
| '
|
|
45
|
+
| 'decisionModel';
|
|
42
46
|
|
|
43
47
|
type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
|
|
44
48
|
videoModel: (...args: infer ARGS) => unknown;
|
|
@@ -46,28 +50,40 @@ type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
|
|
|
46
50
|
? ExtractLiteralUnion<ARGS[0]>
|
|
47
51
|
: never;
|
|
48
52
|
|
|
49
|
-
type
|
|
50
|
-
|
|
53
|
+
type ProviderDecisionModelIdentifier<PROVIDER> = PROVIDER extends {
|
|
54
|
+
decisionModel: (...args: infer ARGS) => unknown;
|
|
51
55
|
}
|
|
52
56
|
? ExtractLiteralUnion<ARGS[0]>
|
|
53
|
-
:
|
|
57
|
+
: PROVIDER extends { evaluationModel: (...args: infer ARGS) => unknown }
|
|
58
|
+
? ExtractLiteralUnion<ARGS[0]>
|
|
59
|
+
: never;
|
|
54
60
|
|
|
55
|
-
/** Registry with experimental
|
|
56
|
-
export type
|
|
61
|
+
/** Registry with experimental decision access, separate from the stable interface. */
|
|
62
|
+
export type DecisionProviderRegistry<
|
|
57
63
|
PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
|
|
58
64
|
string,
|
|
59
65
|
ProviderV4 | ProviderV3
|
|
60
66
|
>,
|
|
61
67
|
SEPARATOR extends string = ':',
|
|
62
68
|
> = ProviderRegistryProvider<PROVIDERS, SEPARATOR> & {
|
|
69
|
+
decisionModel<KEY extends keyof PROVIDERS>(
|
|
70
|
+
id: KEY extends string
|
|
71
|
+
? `${KEY & string}${SEPARATOR}${ProviderDecisionModelIdentifier<PROVIDERS[KEY]>}`
|
|
72
|
+
: never,
|
|
73
|
+
): DecisionModelV4;
|
|
74
|
+
decisionModel<KEY extends keyof PROVIDERS>(
|
|
75
|
+
id: KEY extends string ? `${KEY & string}${SEPARATOR}${string}` : never,
|
|
76
|
+
): DecisionModelV4;
|
|
77
|
+
/** @deprecated Use `decisionModel` instead. */
|
|
63
78
|
evaluationModel<KEY extends keyof PROVIDERS>(
|
|
64
79
|
id: KEY extends string
|
|
65
|
-
? `${KEY & string}${SEPARATOR}${
|
|
80
|
+
? `${KEY & string}${SEPARATOR}${ProviderDecisionModelIdentifier<PROVIDERS[KEY]>}`
|
|
66
81
|
: never,
|
|
67
|
-
):
|
|
82
|
+
): DecisionModelV4 & { doEvaluate: DecisionModelV4['doDecide'] };
|
|
83
|
+
/** @deprecated Use `decisionModel` instead. */
|
|
68
84
|
evaluationModel<KEY extends keyof PROVIDERS>(
|
|
69
85
|
id: KEY extends string ? `${KEY & string}${SEPARATOR}${string}` : never,
|
|
70
|
-
):
|
|
86
|
+
): DecisionModelV4 & { doEvaluate: DecisionModelV4['doDecide'] };
|
|
71
87
|
};
|
|
72
88
|
|
|
73
89
|
export interface ProviderRegistryProvider<
|
|
@@ -178,7 +194,7 @@ export function createProviderRegistry<
|
|
|
178
194
|
| LanguageModelMiddleware[];
|
|
179
195
|
imageModelMiddleware?: ImageModelMiddleware | ImageModelMiddleware[];
|
|
180
196
|
} = {},
|
|
181
|
-
):
|
|
197
|
+
): DecisionProviderRegistry<PROVIDERS, SEPARATOR> {
|
|
182
198
|
const registry = new DefaultProviderRegistry<PROVIDERS, SEPARATOR>({
|
|
183
199
|
separator,
|
|
184
200
|
languageModelMiddleware,
|
|
@@ -206,12 +222,12 @@ class DefaultProviderRegistry<
|
|
|
206
222
|
>
|
|
207
223
|
implements
|
|
208
224
|
ProviderRegistryProvider<PROVIDERS, SEPARATOR>,
|
|
209
|
-
|
|
225
|
+
DecisionProviderRegistry<PROVIDERS, SEPARATOR>
|
|
210
226
|
{
|
|
211
227
|
private providers: Partial<
|
|
212
228
|
Record<
|
|
213
229
|
keyof PROVIDERS,
|
|
214
|
-
ProviderV4 & ProviderWithOptionalVideoModel &
|
|
230
|
+
ProviderV4 & ProviderWithOptionalVideoModel & DecisionProvider
|
|
215
231
|
>
|
|
216
232
|
> = Object.create(null);
|
|
217
233
|
private separator: SEPARATOR;
|
|
@@ -248,9 +264,10 @@ class DefaultProviderRegistry<
|
|
|
248
264
|
provider as ProviderWithOptionalVideoModel
|
|
249
265
|
).videoModel?.bind(provider);
|
|
250
266
|
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
267
|
+
const decisionProvider = provider as DecisionProvider;
|
|
268
|
+
const decisionModel = (
|
|
269
|
+
decisionProvider.decisionModel ?? decisionProvider.evaluationModel
|
|
270
|
+
)?.bind(provider);
|
|
254
271
|
|
|
255
272
|
const registeredProvider =
|
|
256
273
|
videoModel == null
|
|
@@ -261,9 +278,9 @@ class DefaultProviderRegistry<
|
|
|
261
278
|
asVideoModelV4(videoModel(modelId)),
|
|
262
279
|
});
|
|
263
280
|
|
|
264
|
-
// Keep v4 instances intact. Adapted providers need the original
|
|
265
|
-
if (registeredProvider !== provider &&
|
|
266
|
-
Object.assign(registeredProvider, {
|
|
281
|
+
// Keep v4 instances intact. Adapted providers need the original decision receiver.
|
|
282
|
+
if (registeredProvider !== provider && decisionModel != null) {
|
|
283
|
+
Object.assign(registeredProvider, { decisionModel });
|
|
267
284
|
}
|
|
268
285
|
this.providers[id] = registeredProvider;
|
|
269
286
|
}
|
|
@@ -271,7 +288,7 @@ class DefaultProviderRegistry<
|
|
|
271
288
|
private getProvider(
|
|
272
289
|
id: string,
|
|
273
290
|
modelType: RegistryModelType,
|
|
274
|
-
): ProviderV4 & ProviderWithOptionalVideoModel &
|
|
291
|
+
): ProviderV4 & ProviderWithOptionalVideoModel & DecisionProvider {
|
|
275
292
|
const provider = this.providers[id as keyof PROVIDERS];
|
|
276
293
|
|
|
277
294
|
if (provider == null) {
|
|
@@ -427,18 +444,28 @@ class DefaultProviderRegistry<
|
|
|
427
444
|
return asVideoModelV4(model);
|
|
428
445
|
}
|
|
429
446
|
|
|
430
|
-
|
|
447
|
+
decisionModel<KEY extends keyof PROVIDERS>(
|
|
431
448
|
id: `${KEY & string}${SEPARATOR}${string}`,
|
|
432
|
-
):
|
|
433
|
-
const [providerId, modelId] = this.splitId(id, '
|
|
434
|
-
const provider = this.getProvider(providerId, '
|
|
435
|
-
const
|
|
449
|
+
): DecisionModelV4 {
|
|
450
|
+
const [providerId, modelId] = this.splitId(id, 'decisionModel');
|
|
451
|
+
const provider = this.getProvider(providerId, 'decisionModel');
|
|
452
|
+
const factory:
|
|
453
|
+
| ((modelId: string) => Exclude<DecisionModel, string>)
|
|
454
|
+
| undefined = provider.decisionModel ?? provider.evaluationModel;
|
|
455
|
+
const model = factory?.call(provider, modelId);
|
|
436
456
|
|
|
437
457
|
if (model == null) {
|
|
438
|
-
throw new NoSuchModelError({ modelId: id, modelType: '
|
|
458
|
+
throw new NoSuchModelError({ modelId: id, modelType: 'decisionModel' });
|
|
439
459
|
}
|
|
440
460
|
|
|
441
|
-
return
|
|
461
|
+
return resolveDecisionModel(model);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** @deprecated Use `decisionModel` instead. */
|
|
465
|
+
evaluationModel<KEY extends keyof PROVIDERS>(
|
|
466
|
+
id: `${KEY & string}${SEPARATOR}${string}`,
|
|
467
|
+
) {
|
|
468
|
+
return asEvaluationModel(this.decisionModel(id));
|
|
442
469
|
}
|
|
443
470
|
|
|
444
471
|
files<KEY extends keyof PROVIDERS>(id: KEY & string): FilesV4 {
|
|
@@ -142,10 +142,18 @@ export function createTelemetryDispatcher({
|
|
|
142
142
|
|
|
143
143
|
const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>(
|
|
144
144
|
key: KEY,
|
|
145
|
+
deprecatedKey?: keyof Telemetry,
|
|
145
146
|
): Callback<TelemetryEvent<KEY>> | undefined => {
|
|
146
147
|
const integrationCallbacks = (
|
|
147
148
|
integrations
|
|
148
|
-
.map(integration =>
|
|
149
|
+
.map(integration => {
|
|
150
|
+
const callback =
|
|
151
|
+
integration[key] ??
|
|
152
|
+
(deprecatedKey == null ? undefined : integration[deprecatedKey]);
|
|
153
|
+
return typeof callback === 'function'
|
|
154
|
+
? callback.bind(integration)
|
|
155
|
+
: undefined;
|
|
156
|
+
})
|
|
149
157
|
.filter(Boolean) as Array<
|
|
150
158
|
Callback<InferTelemetryEvent<TelemetryEvent<KEY>>>
|
|
151
159
|
>
|
|
@@ -222,16 +230,20 @@ export function createTelemetryDispatcher({
|
|
|
222
230
|
onEmbedEnd: mergeTelemetryCallback('onEmbedEnd'),
|
|
223
231
|
onRerankStart: mergeTelemetryCallback('onRerankStart'),
|
|
224
232
|
onRerankEnd: mergeTelemetryCallback('onRerankEnd'),
|
|
225
|
-
|
|
233
|
+
experimental_onDecideStart: mergeTelemetryCallback(
|
|
234
|
+
'experimental_onDecideStart',
|
|
226
235
|
'experimental_onEvaluateStart',
|
|
227
236
|
),
|
|
228
|
-
|
|
237
|
+
experimental_onDecisionModelCallStart: mergeTelemetryCallback(
|
|
238
|
+
'experimental_onDecisionModelCallStart',
|
|
229
239
|
'experimental_onEvaluationModelCallStart',
|
|
230
240
|
),
|
|
231
|
-
|
|
241
|
+
experimental_onDecisionModelCallEnd: mergeTelemetryCallback(
|
|
242
|
+
'experimental_onDecisionModelCallEnd',
|
|
232
243
|
'experimental_onEvaluationModelCallEnd',
|
|
233
244
|
),
|
|
234
|
-
|
|
245
|
+
experimental_onDecideEnd: mergeTelemetryCallback(
|
|
246
|
+
'experimental_onDecideEnd',
|
|
235
247
|
'experimental_onEvaluateEnd',
|
|
236
248
|
),
|
|
237
249
|
experimental_onStreamTranscriptionStart: mergeTelemetryCallback(
|
|
@@ -6,11 +6,11 @@ import type {
|
|
|
6
6
|
EmbeddingModelCallStartEvent,
|
|
7
7
|
} from '../embed/embed-events';
|
|
8
8
|
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from '../
|
|
9
|
+
DecideEndEvent,
|
|
10
|
+
DecideStartEvent,
|
|
11
|
+
DecisionModelCallEndEvent,
|
|
12
|
+
DecisionModelCallStartEvent,
|
|
13
|
+
} from '../decide/decide-events';
|
|
14
14
|
import type {
|
|
15
15
|
GenerateObjectEndEvent,
|
|
16
16
|
GenerateObjectStartEvent,
|
|
@@ -116,10 +116,10 @@ export interface TelemetryDispatcher {
|
|
|
116
116
|
onEmbedEnd?: Callback<EmbeddingModelCallEndEvent>;
|
|
117
117
|
onRerankStart?: Callback<RerankingModelCallStartEvent>;
|
|
118
118
|
onRerankEnd?: Callback<RerankingModelCallEndEvent>;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
experimental_onDecideStart?: Callback<DecideStartEvent>;
|
|
120
|
+
experimental_onDecisionModelCallStart?: Callback<DecisionModelCallStartEvent>;
|
|
121
|
+
experimental_onDecisionModelCallEnd?: Callback<DecisionModelCallEndEvent>;
|
|
122
|
+
experimental_onDecideEnd?: Callback<DecideEndEvent>;
|
|
123
123
|
experimental_onStreamTranscriptionStart?: Callback<StreamTranscriptionStartEvent>;
|
|
124
124
|
experimental_onStreamTranscriptionEnd?: Callback<StreamTranscriptionEndEvent>;
|
|
125
125
|
onEnd?: Callback<OperationEndEvent>;
|
|
@@ -245,29 +245,45 @@ export interface Telemetry {
|
|
|
245
245
|
*/
|
|
246
246
|
onRerankEnd?: Callback<InferTelemetryEvent<RerankingModelCallEndEvent>>;
|
|
247
247
|
|
|
248
|
-
/**
|
|
248
|
+
/** @deprecated Use `experimental_onDecideStart` instead. */
|
|
249
249
|
experimental_onEvaluateStart?: Callback<
|
|
250
|
-
InferTelemetryEvent<
|
|
250
|
+
InferTelemetryEvent<DecideStartEvent>
|
|
251
251
|
>;
|
|
252
252
|
|
|
253
|
+
/** @deprecated Use `experimental_onDecisionModelCallStart` instead. */
|
|
254
|
+
experimental_onEvaluationModelCallStart?: Callback<
|
|
255
|
+
InferTelemetryEvent<DecisionModelCallStartEvent>
|
|
256
|
+
>;
|
|
257
|
+
|
|
258
|
+
/** @deprecated Use `experimental_onDecisionModelCallEnd` instead. */
|
|
259
|
+
experimental_onEvaluationModelCallEnd?: Callback<
|
|
260
|
+
InferTelemetryEvent<DecisionModelCallEndEvent>
|
|
261
|
+
>;
|
|
262
|
+
|
|
263
|
+
/** @deprecated Use `experimental_onDecideEnd` instead. */
|
|
264
|
+
experimental_onEvaluateEnd?: Callback<InferTelemetryEvent<DecideEndEvent>>;
|
|
265
|
+
|
|
266
|
+
/** Called when an experimental decision operation begins. */
|
|
267
|
+
experimental_onDecideStart?: Callback<InferTelemetryEvent<DecideStartEvent>>;
|
|
268
|
+
|
|
253
269
|
/**
|
|
254
|
-
* Called immediately before an experimental
|
|
270
|
+
* Called immediately before an experimental decision model call begins.
|
|
255
271
|
* The logical model call includes any provider retries.
|
|
256
272
|
*/
|
|
257
|
-
|
|
258
|
-
InferTelemetryEvent<
|
|
273
|
+
experimental_onDecisionModelCallStart?: Callback<
|
|
274
|
+
InferTelemetryEvent<DecisionModelCallStartEvent>
|
|
259
275
|
>;
|
|
260
276
|
|
|
261
277
|
/**
|
|
262
|
-
* Called after an experimental
|
|
278
|
+
* Called after an experimental decision model response has been validated.
|
|
263
279
|
* The logical model call includes any provider retries.
|
|
264
280
|
*/
|
|
265
|
-
|
|
266
|
-
InferTelemetryEvent<
|
|
281
|
+
experimental_onDecisionModelCallEnd?: Callback<
|
|
282
|
+
InferTelemetryEvent<DecisionModelCallEndEvent>
|
|
267
283
|
>;
|
|
268
284
|
|
|
269
|
-
/** Called when an experimental
|
|
270
|
-
|
|
285
|
+
/** Called when an experimental decision operation completes. */
|
|
286
|
+
experimental_onDecideEnd?: Callback<InferTelemetryEvent<DecideEndEvent>>;
|
|
271
287
|
|
|
272
288
|
/** Called when an experimental streaming transcription operation begins. */
|
|
273
289
|
experimental_onStreamTranscriptionStart?: Callback<
|
|
@@ -12,7 +12,7 @@ export type TelemetryTracingEventType =
|
|
|
12
12
|
| 'generateSpeech'
|
|
13
13
|
| 'transcribe'
|
|
14
14
|
| 'streamTranscribe'
|
|
15
|
-
| '
|
|
15
|
+
| 'experimental_decide';
|
|
16
16
|
|
|
17
17
|
export type TelemetryTracingChannelMessage<EVENT = unknown> = {
|
|
18
18
|
readonly type: TelemetryTracingEventType;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Experimental_DecisionModelV4 as DecisionModelV4 } from '@ai-sdk/provider';
|
|
2
|
+
import { notImplemented } from './not-implemented';
|
|
3
|
+
|
|
4
|
+
export class DecisionMockModelV4 implements DecisionModelV4 {
|
|
5
|
+
readonly specificationVersion = 'v4';
|
|
6
|
+
readonly provider: string;
|
|
7
|
+
readonly modelId: string;
|
|
8
|
+
readonly supportedQuestionTypes: DecisionModelV4['supportedQuestionTypes'];
|
|
9
|
+
doDecide: DecisionModelV4['doDecide'];
|
|
10
|
+
|
|
11
|
+
/** @deprecated Use `doDecide` instead. */
|
|
12
|
+
doEvaluate(options: Parameters<DecisionModelV4['doDecide']>[0]) {
|
|
13
|
+
return this.doDecide(options);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor({
|
|
17
|
+
provider = 'mock-provider',
|
|
18
|
+
modelId = 'mock-model-id',
|
|
19
|
+
supportedQuestionTypes = ['choice', 'score', 'boolean'],
|
|
20
|
+
doDecide,
|
|
21
|
+
doEvaluate,
|
|
22
|
+
}: {
|
|
23
|
+
provider?: string;
|
|
24
|
+
modelId?: string;
|
|
25
|
+
supportedQuestionTypes?: DecisionModelV4['supportedQuestionTypes'];
|
|
26
|
+
doDecide?: DecisionModelV4['doDecide'];
|
|
27
|
+
/** @deprecated Use `doDecide` instead. */
|
|
28
|
+
doEvaluate?: DecisionModelV4['doDecide'];
|
|
29
|
+
} = {}) {
|
|
30
|
+
this.provider = provider;
|
|
31
|
+
this.modelId = modelId;
|
|
32
|
+
this.supportedQuestionTypes = supportedQuestionTypes;
|
|
33
|
+
this.doDecide = doDecide ?? doEvaluate ?? notImplemented;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
} from '../generate-text/tool-caller-configuration';
|
|
12
12
|
import { resolveToolDescription } from '../prompt/prepare-tools';
|
|
13
13
|
import { getOwn } from '../util/get-own';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getToolSearchFunction,
|
|
16
|
+
getToolSearchMaxResults,
|
|
17
|
+
isToolSearch,
|
|
18
|
+
} from './tool-search';
|
|
15
19
|
|
|
16
20
|
/** Create discovery state for one generation, never for a shared tool instance. */
|
|
17
21
|
export function createToolSearchState({
|
|
@@ -90,41 +94,57 @@ export function createToolSearchState({
|
|
|
90
94
|
searchName,
|
|
91
95
|
{
|
|
92
96
|
...tool,
|
|
93
|
-
execute: ({ query }: { query: string }) => {
|
|
97
|
+
execute: async ({ query }: { query: string }) => {
|
|
98
|
+
const availableTools = candidates.map(([name, candidate]) => {
|
|
99
|
+
const description = resolveToolDescription({
|
|
100
|
+
tool: candidate,
|
|
101
|
+
toolName: name,
|
|
102
|
+
toolsContext,
|
|
103
|
+
experimental_sandbox,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
name,
|
|
107
|
+
...(description == null ? {} : { description }),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const toolsByName = new Map(
|
|
111
|
+
availableTools.map(tool => [tool.name, tool]),
|
|
112
|
+
);
|
|
113
|
+
const customSearch = getToolSearchFunction(tool);
|
|
94
114
|
const terms = [...new Set(tokenize(query))];
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
const rankedNames = customSearch
|
|
116
|
+
? await customSearch({
|
|
117
|
+
query,
|
|
118
|
+
tools: availableTools.map(tool => ({ ...tool })),
|
|
119
|
+
})
|
|
120
|
+
: availableTools
|
|
121
|
+
.map(({ name, description }) => {
|
|
122
|
+
const nameTerms = tokenize(name);
|
|
123
|
+
const descriptionTerms = tokenize(description ?? '');
|
|
124
|
+
const score = terms.reduce(
|
|
125
|
+
(score, term) =>
|
|
126
|
+
score +
|
|
127
|
+
(nameTerms.includes(term) ? 2 : 0) +
|
|
128
|
+
(descriptionTerms.includes(term) ? 1 : 0),
|
|
129
|
+
0,
|
|
130
|
+
);
|
|
131
|
+
return { name, score };
|
|
132
|
+
})
|
|
133
|
+
.filter(match => match.score > 0)
|
|
134
|
+
.sort((a, b) => b.score - a.score)
|
|
135
|
+
.map(match => match.name);
|
|
136
|
+
const matches = [...new Set(rankedNames)]
|
|
137
|
+
.flatMap(name => {
|
|
138
|
+
const match = toolsByName.get(name);
|
|
139
|
+
return match == null ? [] : [match];
|
|
113
140
|
})
|
|
114
|
-
.
|
|
115
|
-
.sort((a, b) => b.score - a.score)
|
|
116
|
-
.slice(0, 5);
|
|
141
|
+
.slice(0, getToolSearchMaxResults(tool));
|
|
117
142
|
|
|
118
143
|
for (const { name } of matches) {
|
|
119
144
|
discovered.add(name);
|
|
120
145
|
}
|
|
121
146
|
|
|
122
|
-
return {
|
|
123
|
-
tools: matches.map(({ name, description }) => ({
|
|
124
|
-
name,
|
|
125
|
-
...(description == null ? {} : { description }),
|
|
126
|
-
})),
|
|
127
|
-
};
|
|
147
|
+
return { tools: matches };
|
|
128
148
|
},
|
|
129
149
|
},
|
|
130
150
|
];
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { jsonSchema, tool, type Tool } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
2
3
|
|
|
3
4
|
const toolSearchSymbol = Symbol.for('vercel.ai.toolSearch');
|
|
5
|
+
const toolSearchFunctionSymbol = Symbol.for('vercel.ai.toolSearch.search');
|
|
6
|
+
const toolSearchMaxResultsSymbol = Symbol.for(
|
|
7
|
+
'vercel.ai.toolSearch.maxResults',
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
type ToolSearchFunction = (options: {
|
|
11
|
+
query: string;
|
|
12
|
+
tools: Array<{ name: string; description?: string }>;
|
|
13
|
+
}) => string[] | PromiseLike<string[]>;
|
|
4
14
|
|
|
5
15
|
type ToolSearchInput = { query: string };
|
|
6
16
|
type ToolSearchOutput = {
|
|
@@ -12,13 +22,36 @@ type ToolSearchOutput = {
|
|
|
12
22
|
* binds the search registry and makes matches available on the next step.
|
|
13
23
|
* Use directly or through code mode with `toolDiscovery: 'conversation'`.
|
|
14
24
|
*/
|
|
15
|
-
export function toolSearch(
|
|
25
|
+
export function toolSearch({
|
|
26
|
+
search,
|
|
27
|
+
maxResults = 5,
|
|
28
|
+
}: {
|
|
29
|
+
/**
|
|
30
|
+
* Search eligible deferred tools and return their names in ranked order.
|
|
31
|
+
* Supports synchronous and asynchronous callbacks. Unknown names and
|
|
32
|
+
* duplicates are ignored before applying the result limit.
|
|
33
|
+
*/
|
|
34
|
+
search?: ToolSearchFunction;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of matching tools returned per search.
|
|
37
|
+
*
|
|
38
|
+
* @default 5
|
|
39
|
+
*/
|
|
40
|
+
maxResults?: number;
|
|
41
|
+
} = {}): Tool<ToolSearchInput, ToolSearchOutput> & {
|
|
16
42
|
type: 'function';
|
|
17
43
|
} {
|
|
44
|
+
if (!Number.isSafeInteger(maxResults) || maxResults < 1) {
|
|
45
|
+
throw new InvalidArgumentError({
|
|
46
|
+
parameter: 'maxResults',
|
|
47
|
+
value: maxResults,
|
|
48
|
+
message: 'maxResults must be a positive safe integer.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
18
52
|
return Object.assign(
|
|
19
53
|
tool({
|
|
20
|
-
description:
|
|
21
|
-
'Search for tools by keywords in their names and descriptions. Returns up to five matching tools. Matches become available on the next model step, after this execution finishes. Wait for their tool definitions before calling the discovered tools. If no tools match, try different keywords.',
|
|
54
|
+
description: `${search == null ? 'Search for tools by keywords in their names and descriptions.' : 'Search for tools matching a query.'} Returns up to ${maxResults === 5 ? 'five' : maxResults} matching tools. Matches become available on the next model step, after this execution finishes. Wait for their tool definitions before calling the discovered tools. If no tools match, try different keywords.`,
|
|
22
55
|
inputSchema: jsonSchema<ToolSearchInput>({
|
|
23
56
|
type: 'object',
|
|
24
57
|
properties: { query: { type: 'string', minLength: 1 } },
|
|
@@ -48,10 +81,31 @@ export function toolSearch(): Tool<ToolSearchInput, ToolSearchOutput> & {
|
|
|
48
81
|
throw new Error('toolSearch must be bound by an AI SDK generation.');
|
|
49
82
|
},
|
|
50
83
|
}),
|
|
51
|
-
{
|
|
84
|
+
{
|
|
85
|
+
type: 'function' as const,
|
|
86
|
+
[toolSearchSymbol]: true,
|
|
87
|
+
[toolSearchFunctionSymbol]: search,
|
|
88
|
+
[toolSearchMaxResultsSymbol]: maxResults,
|
|
89
|
+
},
|
|
52
90
|
);
|
|
53
91
|
}
|
|
54
92
|
|
|
93
|
+
export function getToolSearchMaxResults(tool: Tool): number {
|
|
94
|
+
return (
|
|
95
|
+
(tool as Tool & { [toolSearchMaxResultsSymbol]?: number })[
|
|
96
|
+
toolSearchMaxResultsSymbol
|
|
97
|
+
] ?? 5
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getToolSearchFunction(
|
|
102
|
+
tool: Tool,
|
|
103
|
+
): ToolSearchFunction | undefined {
|
|
104
|
+
return (tool as Tool & { [toolSearchFunctionSymbol]?: ToolSearchFunction })[
|
|
105
|
+
toolSearchFunctionSymbol
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
|
|
55
109
|
export function isToolSearch(tool: Tool): boolean {
|
|
56
110
|
return (
|
|
57
111
|
(tool as Tool & { [toolSearchSymbol]?: boolean })[toolSearchSymbol] === true
|
package/src/ui/chat.ts
CHANGED
|
@@ -849,11 +849,18 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
849
849
|
responseMessageIndex === -1
|
|
850
850
|
? lastMessage
|
|
851
851
|
: this.state.messages[responseMessageIndex];
|
|
852
|
+
// the continued stream can start with either
|
|
853
|
+
// 1) input deltas
|
|
854
|
+
// 2) or the result of an answered tool approval request
|
|
855
|
+
// Keep the tool part so in case 2, those chunks can find their tool call
|
|
852
856
|
const resumableResponseMessage =
|
|
853
857
|
trigger === 'resume-stream' &&
|
|
854
858
|
responseMessage?.role === 'assistant' &&
|
|
855
859
|
responseMessage.parts.some(
|
|
856
|
-
part =>
|
|
860
|
+
part =>
|
|
861
|
+
isToolUIPart(part) &&
|
|
862
|
+
(part.state === 'input-streaming' ||
|
|
863
|
+
part.state === 'approval-responded'),
|
|
857
864
|
)
|
|
858
865
|
? this.state.snapshot(responseMessage)
|
|
859
866
|
: undefined;
|
|
@@ -952,6 +959,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
952
959
|
await consumeStream({
|
|
953
960
|
stream: processUIMessageStream({
|
|
954
961
|
stream,
|
|
962
|
+
resetStateOnMessageIdChange: trigger === 'resume-stream',
|
|
955
963
|
onToolCall: this.onToolCall,
|
|
956
964
|
onData: this.onData,
|
|
957
965
|
messageMetadataSchema: this.messageMetadataSchema,
|
|
@@ -120,6 +120,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
120
120
|
onError,
|
|
121
121
|
onToolCall,
|
|
122
122
|
onData,
|
|
123
|
+
resetStateOnMessageIdChange = false,
|
|
123
124
|
}: {
|
|
124
125
|
// input stream is not fully typed yet:
|
|
125
126
|
stream: ReadableStream<UIMessageChunk>;
|
|
@@ -136,6 +137,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
136
137
|
}) => Promise<void>,
|
|
137
138
|
) => Promise<void>;
|
|
138
139
|
onError: ErrorHandler;
|
|
140
|
+
// During resume, a different message ID identifies a separate response.
|
|
141
|
+
resetStateOnMessageIdChange?: boolean;
|
|
139
142
|
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
140
143
|
return stream.pipeThrough(
|
|
141
144
|
new TransformStream<UIMessageChunk, InferUIMessageChunk<UI_MESSAGE>>({
|
|
@@ -953,6 +956,24 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
953
956
|
}
|
|
954
957
|
|
|
955
958
|
case 'start': {
|
|
959
|
+
if (
|
|
960
|
+
resetStateOnMessageIdChange &&
|
|
961
|
+
chunk.messageId != null &&
|
|
962
|
+
chunk.messageId !== state.message.id
|
|
963
|
+
) {
|
|
964
|
+
// Start the separate response with empty parts and metadata.
|
|
965
|
+
// Reset the active-part maps too, so earlier chunks stay with
|
|
966
|
+
// the previous response. Keep the state object for its callers.
|
|
967
|
+
Object.assign(
|
|
968
|
+
state,
|
|
969
|
+
createStreamingUIMessageState({
|
|
970
|
+
lastMessage: undefined,
|
|
971
|
+
messageId: chunk.messageId,
|
|
972
|
+
}),
|
|
973
|
+
{ finishReason: undefined },
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
|
|
956
977
|
if (chunk.messageId != null) {
|
|
957
978
|
state.message.id = chunk.messageId;
|
|
958
979
|
}
|