-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Scenario
In my case, I simply want to execute a multi-node loop that exits when certain conditions are met.
However, I found that the edges seem to be ignored inside a loop.
Then I tried to workaround by using cyclic edges + conditional branching (which is discouraged by the documentation). But the workflow terminates before the loop.
Then I tried using a subflow so that I only need one node in the parent flow. When I did so, I got both type errors and runtime errors.
I followed everything relevant in the documentation about subflows and loops. If I truly dismissed something in the documentation, please point out.
How to Reproduce
Loop Error
import { createFlow, FlowRuntime, createDefaultContainer, UnsafeEvaluator } from 'flowcraft';
import { A, D, E, F } from './nodes.ts';
const flow = createFlow('myFlow')
.node('A', A)
.node('D', D)
.node('E', E)
.node('F', F)
.edge('A', 'D')
.edge('D', 'E')
.edge('E', 'F')
.loop('DEF', {
startNodeId: 'D',
endNodeId: 'F',
condition: 'map1 !== 0 && map2 < 3',
});
const container = createDefaultContainer({
eventBus: {
emit: (e) => {
if (e.type === 'node:start')
console.log(`run: ${e.payload.nodeId}`);
},
},
evaluator: new UnsafeEvaluator(),
});
const runtime = new FlowRuntime(container);
export default async function run() {
const blueprint = flow.toBlueprint();
const functionRegistry = flow.getFunctionRegistry();
await runtime.run(
blueprint,
{ input: 'input' },
{ functionRegistry },
);
}Output: (the condition is mocked to be true for 3 times so that the loop should continue)
run: A
run: D
run: E
run: F
run: DEF
run: DThe execution always stops at the first node of the second loop cycle. (in this case, D)
Conditional Branching Error
import { createFlow, FlowRuntime, createDefaultContainer, UnsafeEvaluator } from 'flowcraft';
import { A, D, E, F, G } from './nodes.ts';
const flow = createFlow('myFlow')
.node('A', A)
.node('D', D)
.node('E', E)
.node('F', F)
.node('G', G)
.edge('A', 'D')
.edge('D', 'E')
.edge('E', 'F')
.edge('F', 'D', { action: 'failure' })
.edge('F', 'G', { action: 'success' });
const container = createDefaultContainer({
eventBus: {
emit: (e) => {
if (e.type === 'node:start')
console.log(`run: ${e.payload.nodeId}`);
},
},
evaluator: new UnsafeEvaluator(),
});
const runtime = new FlowRuntime(container);
export default async function run() {
const blueprint = flow.toBlueprint();
const functionRegistry = flow.getFunctionRegistry();
await runtime.run(
blueprint,
{ input: 'input' },
{ functionRegistry },
);
}Output: (in my test, I mocked 3 'failure' and a final 'success', so it should loop for 3 times)
run: AOnly the nodes before the cycle are executed. (in this case, A only)
Subflow Error
Then I tried to use a subflow so that I could avoid multi-node loops. However, the situation is worse:
import { createFlow, FlowRuntime, createDefaultContainer, UnsafeEvaluator } from 'flowcraft';
import { A, D, E, F } from './nodes.ts';
const flow = createFlow('myFlow')
.node('A', A)
.node('sub', {
uses: 'subflow',
params: {
blueprintId: 'mySubflow',
// Map parent context keys to subflow context keys
inputs: {
map1: 'map1',
map2: 'map2',
},
},
})
.edge('A', 'sub')
.loop('loop', {
startNodeId: 'sub',
endNodeId: 'sub',
condition: 'map1 !== 0 && map2 < 3',
});
const subflow = createFlow('mySubflow')
.node('D', D)
.node('E', E)
.node('F', F)
.edge('D', 'E')
.edge('E', 'F');
const container = createDefaultContainer({
eventBus: {
emit: (e) => {
if (e.type === 'node:start')
console.log(`run: ${e.payload.nodeId}`);
},
},
evaluator: new UnsafeEvaluator(),
blueprints: { 'mySubflow': subflow.toBlueprint() },
});
const runtime = new FlowRuntime(container);
export default async function run() {
const blueprint = flow.toBlueprint();
const functionRegistry = flow.getFunctionRegistry();
await runtime.run(
blueprint,
{ input: 'input' },
{ functionRegistry },
);
}Then I got both type error and the subsequent runtime error, the type error is:
Object literal may only specify known properties, and 'uses' does not exist in type 'NodeFunction<CTX, Record<string, any>, any, any, string> | NodeClass<CTX, Record<string, any>, any, any, string>'.