@ai-sdk/workflow 1.0.66 → 1.0.68
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 +14 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +134 -47
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/do-stream-step.ts +111 -33
- package/src/providers/mock-function-wrapper.ts +101 -4
- package/src/providers/mock.ts +4 -93
- package/src/stream-text-iterator.ts +58 -15
- package/src/test/agent-e2e-workflows.ts +41 -0
- package/src/test/retrying-model.ts +79 -0
- package/src/workflow-agent.ts +48 -13
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { APICallError } from '@ai-sdk/provider';
|
|
2
|
+
import {
|
|
3
|
+
WORKFLOW_DESERIALIZE,
|
|
4
|
+
WORKFLOW_SERIALIZE,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
6
|
+
import { convertArrayToReadableStream, MockLanguageModelV4 } from 'ai/test';
|
|
7
|
+
import { getStepMetadata } from 'workflow';
|
|
8
|
+
|
|
9
|
+
type MockStreamResult = Awaited<ReturnType<MockLanguageModelV4['doStream']>>;
|
|
10
|
+
type MockStreamPart = MockStreamResult extends {
|
|
11
|
+
stream: ReadableStream<infer PART>;
|
|
12
|
+
}
|
|
13
|
+
? PART
|
|
14
|
+
: never;
|
|
15
|
+
|
|
16
|
+
class SerializableRetryingModel extends MockLanguageModelV4 {
|
|
17
|
+
static [WORKFLOW_SERIALIZE](model: SerializableRetryingModel) {
|
|
18
|
+
return { failuresBeforeSuccess: model.failuresBeforeSuccess };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static [WORKFLOW_DESERIALIZE](options: { failuresBeforeSuccess: number }) {
|
|
22
|
+
return new SerializableRetryingModel(options.failuresBeforeSuccess);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor(readonly failuresBeforeSuccess: number) {
|
|
26
|
+
let modelAttempts = 0;
|
|
27
|
+
|
|
28
|
+
super({
|
|
29
|
+
provider: 'workflow-retry-test',
|
|
30
|
+
modelId: 'workflow-retry-test-model',
|
|
31
|
+
doStream: async () => {
|
|
32
|
+
modelAttempts++;
|
|
33
|
+
|
|
34
|
+
if (modelAttempts <= failuresBeforeSuccess) {
|
|
35
|
+
throw new APICallError({
|
|
36
|
+
message: `model call failed on attempt ${modelAttempts}`,
|
|
37
|
+
url: 'https://example.com/model',
|
|
38
|
+
requestBodyValues: {},
|
|
39
|
+
statusCode: 500,
|
|
40
|
+
responseHeaders: { 'retry-after-ms': '0' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const text = `model-attempts=${modelAttempts};step-attempt=${getStepMetadata().attempt}`;
|
|
45
|
+
const streamParts: MockStreamPart[] = [
|
|
46
|
+
{ type: 'stream-start', warnings: [] },
|
|
47
|
+
{ type: 'text-start', id: '1' },
|
|
48
|
+
{ type: 'text-delta', id: '1', delta: text },
|
|
49
|
+
{ type: 'text-end', id: '1' },
|
|
50
|
+
{
|
|
51
|
+
type: 'finish',
|
|
52
|
+
finishReason: { unified: 'stop', raw: 'stop' },
|
|
53
|
+
usage: {
|
|
54
|
+
inputTokens: {
|
|
55
|
+
total: 1,
|
|
56
|
+
noCache: 1,
|
|
57
|
+
cacheRead: undefined,
|
|
58
|
+
cacheWrite: undefined,
|
|
59
|
+
},
|
|
60
|
+
outputTokens: {
|
|
61
|
+
total: 1,
|
|
62
|
+
text: 1,
|
|
63
|
+
reasoning: undefined,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return { stream: convertArrayToReadableStream(streamParts) };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Keep construction outside the workflow module to avoid the SWC closure
|
|
76
|
+
// transformation issue tracked in https://github.com/vercel/workflow/issues/1365.
|
|
77
|
+
export function retryingModel(): MockLanguageModelV4 {
|
|
78
|
+
return new SerializableRetryingModel(2);
|
|
79
|
+
}
|
package/src/workflow-agent.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from '@ai-sdk/provider';
|
|
8
8
|
import {
|
|
9
9
|
getErrorMessage,
|
|
10
|
+
isAbortError,
|
|
10
11
|
validateTypes,
|
|
11
12
|
withUserAgentSuffix,
|
|
12
13
|
type Context,
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
createRestrictedTelemetryDispatcher,
|
|
40
41
|
collectToolApprovals,
|
|
41
42
|
convertToLanguageModelPrompt,
|
|
42
|
-
mergeAbortSignals,
|
|
43
43
|
mergeCallbacks,
|
|
44
44
|
standardizePrompt,
|
|
45
45
|
validateApprovedToolApprovals,
|
|
@@ -212,8 +212,7 @@ export interface GenerationSettings {
|
|
|
212
212
|
seed?: number;
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
|
-
* Maximum number of retries. Set to 0 to disable retries.
|
|
216
|
-
* Note: In workflow context, retries are typically handled by the workflow step mechanism.
|
|
215
|
+
* Maximum number of retries for retryable model call failures. Set to 0 to disable retries.
|
|
217
216
|
* @default 2
|
|
218
217
|
*/
|
|
219
218
|
maxRetries?: number;
|
|
@@ -1191,6 +1190,15 @@ export interface WorkflowAgentStreamResult<
|
|
|
1191
1190
|
*/
|
|
1192
1191
|
finishReason: FinishReason;
|
|
1193
1192
|
|
|
1193
|
+
/**
|
|
1194
|
+
* The original value from a model stream error part.
|
|
1195
|
+
*
|
|
1196
|
+
* This property is present when the model emitted an error part, including
|
|
1197
|
+
* when the supplied value is `undefined`. Check with `'error' in result` to
|
|
1198
|
+
* distinguish that case from a result without a model stream error.
|
|
1199
|
+
*/
|
|
1200
|
+
error?: unknown;
|
|
1201
|
+
|
|
1194
1202
|
/**
|
|
1195
1203
|
* The total token usage across all steps.
|
|
1196
1204
|
*/
|
|
@@ -1788,10 +1796,10 @@ export class WorkflowAgent<
|
|
|
1788
1796
|
download,
|
|
1789
1797
|
});
|
|
1790
1798
|
|
|
1791
|
-
const effectiveAbortSignal =
|
|
1792
|
-
options.abortSignal ?? effectiveGenerationSettings.abortSignal
|
|
1793
|
-
|
|
1794
|
-
|
|
1799
|
+
const effectiveAbortSignal =
|
|
1800
|
+
options.abortSignal ?? effectiveGenerationSettings.abortSignal;
|
|
1801
|
+
const timeoutAt =
|
|
1802
|
+
options.timeout == null ? undefined : Date.now() + options.timeout;
|
|
1795
1803
|
|
|
1796
1804
|
// Merge generation settings: constructor defaults < prepareCall < stream options
|
|
1797
1805
|
const mergedGenerationSettings: GenerationSettings = {
|
|
@@ -2146,7 +2154,6 @@ export class WorkflowAgent<
|
|
|
2146
2154
|
stopConditions: effectiveStopWhenFromPrepare,
|
|
2147
2155
|
onStepEnd: mergedOnStepEnd as any,
|
|
2148
2156
|
onStepStart: mergedOnStepStart as any,
|
|
2149
|
-
onError: options.onError,
|
|
2150
2157
|
prepareStep: (options.prepareStep ??
|
|
2151
2158
|
(this.prepareStep as
|
|
2152
2159
|
| PrepareStepCallback<ToolSet, TRuntimeContext>
|
|
@@ -2157,6 +2164,7 @@ export class WorkflowAgent<
|
|
|
2157
2164
|
toolsContext,
|
|
2158
2165
|
telemetry: effectiveTelemetry,
|
|
2159
2166
|
includeRawChunks: options.includeRawChunks ?? false,
|
|
2167
|
+
timeoutAt,
|
|
2160
2168
|
repairToolCall: (options.repairToolCall ??
|
|
2161
2169
|
options.experimental_repairToolCall ??
|
|
2162
2170
|
this.repairToolCall) as ToolCallRepairFunction<ToolSet> | undefined,
|
|
@@ -2167,7 +2175,10 @@ export class WorkflowAgent<
|
|
|
2167
2175
|
// Track the final conversation messages from the iterator
|
|
2168
2176
|
let finalMessages: LanguageModelV4Prompt | undefined;
|
|
2169
2177
|
let encounteredError: unknown;
|
|
2178
|
+
let hasEncounteredError = false;
|
|
2170
2179
|
let wasAborted = false;
|
|
2180
|
+
let terminalError: unknown;
|
|
2181
|
+
let hasTerminalError = false;
|
|
2171
2182
|
|
|
2172
2183
|
try {
|
|
2173
2184
|
let result = await iterator.next();
|
|
@@ -2519,14 +2530,29 @@ export class WorkflowAgent<
|
|
|
2519
2530
|
}
|
|
2520
2531
|
}
|
|
2521
2532
|
|
|
2522
|
-
// When the iterator completes normally, result.value contains the final
|
|
2533
|
+
// When the iterator completes normally, result.value contains the final
|
|
2534
|
+
// conversation prompt. Aborts inside the retryable model step are
|
|
2535
|
+
// returned as data so the workflow runtime does not retry them.
|
|
2523
2536
|
if (result.done) {
|
|
2524
|
-
|
|
2537
|
+
if (Array.isArray(result.value)) {
|
|
2538
|
+
finalMessages = result.value;
|
|
2539
|
+
} else if ('error' in result.value) {
|
|
2540
|
+
finalMessages = result.value.messages;
|
|
2541
|
+
terminalError = result.value.error;
|
|
2542
|
+
hasTerminalError = true;
|
|
2543
|
+
} else {
|
|
2544
|
+
finalMessages = result.value.messages;
|
|
2545
|
+
wasAborted = true;
|
|
2546
|
+
if (options.onAbort) {
|
|
2547
|
+
await options.onAbort({ steps });
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2525
2550
|
}
|
|
2526
2551
|
} catch (error) {
|
|
2527
2552
|
encounteredError = error;
|
|
2553
|
+
hasEncounteredError = true;
|
|
2528
2554
|
// Check if this is an abort error
|
|
2529
|
-
if (error
|
|
2555
|
+
if (isAbortError(error)) {
|
|
2530
2556
|
wasAborted = true;
|
|
2531
2557
|
if (options.onAbort) {
|
|
2532
2558
|
await options.onAbort({ steps });
|
|
@@ -2539,6 +2565,13 @@ export class WorkflowAgent<
|
|
|
2539
2565
|
// Don't throw yet - we want to call onEnd first
|
|
2540
2566
|
}
|
|
2541
2567
|
|
|
2568
|
+
if (hasTerminalError) {
|
|
2569
|
+
if (options.onError) {
|
|
2570
|
+
await options.onError({ error: terminalError });
|
|
2571
|
+
}
|
|
2572
|
+
await telemetryDispatcher.onError?.(terminalError);
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2542
2575
|
// Use the final messages from the iterator, or fall back to standardized messages
|
|
2543
2576
|
const messages = (finalMessages ??
|
|
2544
2577
|
prompt.messages) as unknown as ModelMessage[];
|
|
@@ -2562,8 +2595,9 @@ export class WorkflowAgent<
|
|
|
2562
2595
|
} catch (parseError) {
|
|
2563
2596
|
// If there's already an error, don't override it
|
|
2564
2597
|
// If not, set this as the error
|
|
2565
|
-
if (!
|
|
2598
|
+
if (!hasEncounteredError) {
|
|
2566
2599
|
encounteredError = parseError;
|
|
2600
|
+
hasEncounteredError = true;
|
|
2567
2601
|
}
|
|
2568
2602
|
}
|
|
2569
2603
|
}
|
|
@@ -2599,7 +2633,7 @@ export class WorkflowAgent<
|
|
|
2599
2633
|
}
|
|
2600
2634
|
|
|
2601
2635
|
// Re-throw any error that occurred
|
|
2602
|
-
if (
|
|
2636
|
+
if (hasEncounteredError) {
|
|
2603
2637
|
// Close the stream before throwing
|
|
2604
2638
|
if (options.writable) {
|
|
2605
2639
|
const sendFinish = options.sendFinish ?? true;
|
|
@@ -2628,6 +2662,7 @@ export class WorkflowAgent<
|
|
|
2628
2662
|
finishReason,
|
|
2629
2663
|
totalUsage,
|
|
2630
2664
|
output: experimentalOutput,
|
|
2665
|
+
...(hasTerminalError ? { error: terminalError } : {}),
|
|
2631
2666
|
};
|
|
2632
2667
|
}
|
|
2633
2668
|
}
|