graphai 0.5.15 → 0.5.17
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/lib/bundle.cjs.js +2 -0
- package/lib/bundle.cjs.js.map +1 -0
- package/lib/bundle.esm.js +1310 -0
- package/lib/bundle.esm.js.map +1 -0
- package/lib/bundle.umd.js +2 -0
- package/lib/bundle.umd.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/package.json +5 -4
|
@@ -0,0 +1,1310 @@
|
|
|
1
|
+
const sleep = async (milliseconds) => {
|
|
2
|
+
return await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
3
|
+
};
|
|
4
|
+
const parseNodeName = (inputNodeId) => {
|
|
5
|
+
if (typeof inputNodeId === "string") {
|
|
6
|
+
const regex = /^:(.*)$/;
|
|
7
|
+
const match = inputNodeId.match(regex);
|
|
8
|
+
if (!match) {
|
|
9
|
+
return { value: inputNodeId }; // string literal
|
|
10
|
+
}
|
|
11
|
+
const parts = match[1].split(".");
|
|
12
|
+
if (parts.length == 1) {
|
|
13
|
+
return { nodeId: parts[0] };
|
|
14
|
+
}
|
|
15
|
+
return { nodeId: parts[0], propIds: parts.slice(1) };
|
|
16
|
+
}
|
|
17
|
+
return { value: inputNodeId }; // non-string literal
|
|
18
|
+
};
|
|
19
|
+
function assert(condition, message, isWarn = false) {
|
|
20
|
+
if (!condition) {
|
|
21
|
+
if (!isWarn) {
|
|
22
|
+
throw new Error(message);
|
|
23
|
+
}
|
|
24
|
+
console.warn("warn: " + message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const isObject = (x) => {
|
|
28
|
+
return x !== null && typeof x === "object";
|
|
29
|
+
};
|
|
30
|
+
const isNull = (data) => {
|
|
31
|
+
return data === null || data === undefined;
|
|
32
|
+
};
|
|
33
|
+
const strIntentionalError = "Intentional Error for Debugging";
|
|
34
|
+
const defaultAgentInfo = {
|
|
35
|
+
name: "defaultAgentInfo",
|
|
36
|
+
samples: [
|
|
37
|
+
{
|
|
38
|
+
inputs: [],
|
|
39
|
+
params: {},
|
|
40
|
+
result: {},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
description: "",
|
|
44
|
+
category: [],
|
|
45
|
+
author: "",
|
|
46
|
+
repository: "",
|
|
47
|
+
license: "",
|
|
48
|
+
};
|
|
49
|
+
const agentInfoWrapper = (agent) => {
|
|
50
|
+
return {
|
|
51
|
+
agent,
|
|
52
|
+
mock: agent,
|
|
53
|
+
...defaultAgentInfo,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const objectToKeyArray = (innerData) => {
|
|
57
|
+
const ret = [];
|
|
58
|
+
Object.keys(innerData).forEach((key) => {
|
|
59
|
+
ret.push([key]);
|
|
60
|
+
if (Object.keys(innerData[key]).length > 0) {
|
|
61
|
+
objectToKeyArray(innerData[key]).forEach((tmp) => {
|
|
62
|
+
ret.push([key, ...tmp]);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return ret;
|
|
67
|
+
};
|
|
68
|
+
const debugResultKey = (agentId, result) => {
|
|
69
|
+
return objectToKeyArray({ [agentId]: debugResultKeyInner(result) }).map((objectKeys) => {
|
|
70
|
+
return ":" + objectKeys.join(".");
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
const debugResultKeyInner = (result) => {
|
|
74
|
+
if (result === null || result === undefined) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
if (typeof result === "string") {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(result)) {
|
|
81
|
+
return Array.from(result.keys()).reduce((tmp, index) => {
|
|
82
|
+
tmp["$" + String(index)] = debugResultKeyInner(result[index]);
|
|
83
|
+
return tmp;
|
|
84
|
+
}, {});
|
|
85
|
+
}
|
|
86
|
+
return Object.keys(result).reduce((tmp, key) => {
|
|
87
|
+
tmp[key] = debugResultKeyInner(result[key]);
|
|
88
|
+
return tmp;
|
|
89
|
+
}, {});
|
|
90
|
+
};
|
|
91
|
+
const isLogicallyTrue = (value) => {
|
|
92
|
+
// Notice that empty aray is not true under GraphAI
|
|
93
|
+
if (Array.isArray(value) ? value.length === 0 : !value) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
98
|
+
const defaultTestContext = {
|
|
99
|
+
debugInfo: {
|
|
100
|
+
nodeId: "test",
|
|
101
|
+
retry: 0,
|
|
102
|
+
verbose: true,
|
|
103
|
+
},
|
|
104
|
+
params: {},
|
|
105
|
+
filterParams: {},
|
|
106
|
+
agents: {},
|
|
107
|
+
log: [],
|
|
108
|
+
};
|
|
109
|
+
const isNamedInputs = (namedInputs) => {
|
|
110
|
+
return isObject(namedInputs) && !Array.isArray(namedInputs) && Object.keys(namedInputs || {}).length > 0;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// for dataSource
|
|
114
|
+
const inputs2dataSources = (inputs) => {
|
|
115
|
+
if (Array.isArray(inputs)) {
|
|
116
|
+
return inputs.map((inp) => inputs2dataSources(inp)).flat();
|
|
117
|
+
}
|
|
118
|
+
if (isObject(inputs)) {
|
|
119
|
+
return Object.values(inputs)
|
|
120
|
+
.map((input) => inputs2dataSources(input))
|
|
121
|
+
.flat();
|
|
122
|
+
}
|
|
123
|
+
if (typeof inputs === "string") {
|
|
124
|
+
const templateMatch = [...inputs.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
|
|
125
|
+
if (templateMatch.length > 0) {
|
|
126
|
+
return inputs2dataSources(templateMatch);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return parseNodeName(inputs);
|
|
130
|
+
};
|
|
131
|
+
const dataSourceNodeIds = (sources) => {
|
|
132
|
+
return sources.filter((source) => source.nodeId).map((source) => source.nodeId);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
var NodeState;
|
|
136
|
+
(function (NodeState) {
|
|
137
|
+
NodeState["Waiting"] = "waiting";
|
|
138
|
+
NodeState["Queued"] = "queued";
|
|
139
|
+
NodeState["Executing"] = "executing";
|
|
140
|
+
NodeState["ExecutingServer"] = "executing-server";
|
|
141
|
+
NodeState["Failed"] = "failed";
|
|
142
|
+
NodeState["TimedOut"] = "timed-out";
|
|
143
|
+
NodeState["Completed"] = "completed";
|
|
144
|
+
NodeState["Injected"] = "injected";
|
|
145
|
+
NodeState["Skipped"] = "skipped";
|
|
146
|
+
})(NodeState || (NodeState = {}));
|
|
147
|
+
|
|
148
|
+
class TransactionLog {
|
|
149
|
+
constructor(nodeId) {
|
|
150
|
+
this.nodeId = nodeId;
|
|
151
|
+
this.state = NodeState.Waiting;
|
|
152
|
+
}
|
|
153
|
+
initForComputedNode(node, graph) {
|
|
154
|
+
this.agentId = node.getAgentId();
|
|
155
|
+
this.params = node.params;
|
|
156
|
+
graph.appendLog(this);
|
|
157
|
+
}
|
|
158
|
+
onInjected(node, graph, injectFrom) {
|
|
159
|
+
const isUpdating = "endTime" in this;
|
|
160
|
+
this.result = node.result;
|
|
161
|
+
this.state = node.state;
|
|
162
|
+
this.endTime = Date.now();
|
|
163
|
+
this.injectFrom = injectFrom;
|
|
164
|
+
graph.setLoopLog(this);
|
|
165
|
+
// console.log(this)
|
|
166
|
+
if (isUpdating) {
|
|
167
|
+
graph.updateLog(this);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
graph.appendLog(this);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
onComplete(node, graph, localLog) {
|
|
174
|
+
this.result = node.result;
|
|
175
|
+
this.resultKeys = debugResultKey(this.agentId || "", node.result);
|
|
176
|
+
this.state = node.state;
|
|
177
|
+
this.endTime = Date.now();
|
|
178
|
+
graph.setLoopLog(this);
|
|
179
|
+
if (localLog.length > 0) {
|
|
180
|
+
this.log = localLog;
|
|
181
|
+
}
|
|
182
|
+
graph.updateLog(this);
|
|
183
|
+
}
|
|
184
|
+
beforeExecute(node, graph, transactionId, inputs) {
|
|
185
|
+
this.state = node.state;
|
|
186
|
+
this.retryCount = node.retryCount > 0 ? node.retryCount : undefined;
|
|
187
|
+
this.startTime = transactionId;
|
|
188
|
+
this.inputs = dataSourceNodeIds(node.dataSources);
|
|
189
|
+
this.inputsData = inputs.length > 0 ? inputs : undefined;
|
|
190
|
+
graph.setLoopLog(this);
|
|
191
|
+
graph.appendLog(this);
|
|
192
|
+
}
|
|
193
|
+
beforeAddTask(node, graph) {
|
|
194
|
+
this.state = node.state;
|
|
195
|
+
graph.setLoopLog(this);
|
|
196
|
+
graph.appendLog(this);
|
|
197
|
+
}
|
|
198
|
+
onError(node, graph, errorMessage) {
|
|
199
|
+
this.state = node.state;
|
|
200
|
+
this.errorMessage = errorMessage;
|
|
201
|
+
this.endTime = Date.now();
|
|
202
|
+
graph.setLoopLog(this);
|
|
203
|
+
graph.updateLog(this);
|
|
204
|
+
}
|
|
205
|
+
onSkipped(node, graph) {
|
|
206
|
+
this.state = node.state;
|
|
207
|
+
graph.setLoopLog(this);
|
|
208
|
+
graph.updateLog(this);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class Node {
|
|
213
|
+
constructor(nodeId, graph) {
|
|
214
|
+
this.waitlist = new Set(); // List of nodes which need data from this node.
|
|
215
|
+
this.state = NodeState.Waiting;
|
|
216
|
+
this.result = undefined;
|
|
217
|
+
this.nodeId = nodeId;
|
|
218
|
+
this.graph = graph;
|
|
219
|
+
this.log = new TransactionLog(nodeId);
|
|
220
|
+
this.console = {};
|
|
221
|
+
}
|
|
222
|
+
asString() {
|
|
223
|
+
return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
|
|
224
|
+
}
|
|
225
|
+
// This method is called either as the result of computation (computed node) or
|
|
226
|
+
// injection (static node).
|
|
227
|
+
onSetResult() {
|
|
228
|
+
this.waitlist.forEach((waitingNodeId) => {
|
|
229
|
+
const waitingNode = this.graph.nodes[waitingNodeId];
|
|
230
|
+
if (waitingNode.isComputedNode) {
|
|
231
|
+
waitingNode.removePending(this.nodeId);
|
|
232
|
+
this.graph.pushQueueIfReadyAndRunning(waitingNode);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
afterConsoleLog(result) {
|
|
237
|
+
if (this.console.after === true) {
|
|
238
|
+
console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
239
|
+
}
|
|
240
|
+
else if (this.console.after) {
|
|
241
|
+
console.log(this.console.after);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
class ComputedNode extends Node {
|
|
246
|
+
constructor(graphId, nodeId, data, graph) {
|
|
247
|
+
super(nodeId, graph);
|
|
248
|
+
this.retryCount = 0;
|
|
249
|
+
this.dataSources = []; // no longer needed. This is for transaction log.
|
|
250
|
+
this.isNamedInputs = false;
|
|
251
|
+
this.isStaticNode = false;
|
|
252
|
+
this.isComputedNode = true;
|
|
253
|
+
this.graphId = graphId;
|
|
254
|
+
this.params = data.params ?? {};
|
|
255
|
+
this.console = data.console ?? {};
|
|
256
|
+
this.filterParams = data.filterParams ?? {};
|
|
257
|
+
this.passThrough = data.passThrough;
|
|
258
|
+
this.retryLimit = data.retry ?? graph.retryLimit ?? 0;
|
|
259
|
+
this.timeout = data.timeout;
|
|
260
|
+
this.isResult = data.isResult ?? false;
|
|
261
|
+
this.priority = data.priority ?? 0;
|
|
262
|
+
this.anyInput = data.anyInput ?? false;
|
|
263
|
+
this.inputs = data.inputs;
|
|
264
|
+
this.isNamedInputs = isObject(data.inputs) && !Array.isArray(data.inputs);
|
|
265
|
+
this.dataSources = data.inputs ? inputs2dataSources(data.inputs).flat(10) : [];
|
|
266
|
+
if (data.inputs && !this.isNamedInputs) {
|
|
267
|
+
console.warn(`array inputs have been deprecated. nodeId: ${nodeId}: see https://github.com/receptron/graphai/blob/main/docs/NamedInputs.md`);
|
|
268
|
+
}
|
|
269
|
+
this.pendings = new Set(dataSourceNodeIds(this.dataSources));
|
|
270
|
+
assert(["function", "string"].includes(typeof data.agent), "agent must be either string or function");
|
|
271
|
+
if (typeof data.agent === "string") {
|
|
272
|
+
this.agentId = data.agent;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const agent = data.agent;
|
|
276
|
+
this.agentFunction = this.isNamedInputs ? async ({ namedInputs }) => agent(namedInputs) : async ({ inputs }) => agent(...inputs);
|
|
277
|
+
}
|
|
278
|
+
if (data.graph) {
|
|
279
|
+
this.nestedGraph = typeof data.graph === "string" ? this.addPendingNode(data.graph) : data.graph;
|
|
280
|
+
}
|
|
281
|
+
if (data.graphLoader && graph.graphLoader) {
|
|
282
|
+
this.nestedGraph = graph.graphLoader(data.graphLoader);
|
|
283
|
+
}
|
|
284
|
+
if (data.if) {
|
|
285
|
+
this.ifSource = this.addPendingNode(data.if);
|
|
286
|
+
}
|
|
287
|
+
if (data.unless) {
|
|
288
|
+
this.unlessSource = this.addPendingNode(data.unless);
|
|
289
|
+
}
|
|
290
|
+
this.dynamicParams = Object.keys(this.params).reduce((tmp, key) => {
|
|
291
|
+
const dataSource = parseNodeName(this.params[key]);
|
|
292
|
+
if (dataSource.nodeId) {
|
|
293
|
+
assert(!this.anyInput, "Dynamic params are not supported with anyInput");
|
|
294
|
+
tmp[key] = dataSource;
|
|
295
|
+
this.pendings.add(dataSource.nodeId);
|
|
296
|
+
}
|
|
297
|
+
return tmp;
|
|
298
|
+
}, {});
|
|
299
|
+
this.log.initForComputedNode(this, graph);
|
|
300
|
+
}
|
|
301
|
+
getAgentId() {
|
|
302
|
+
return this.agentId ?? "__custom__function"; // only for display purpose in the log.
|
|
303
|
+
}
|
|
304
|
+
addPendingNode(nodeId) {
|
|
305
|
+
const source = parseNodeName(nodeId);
|
|
306
|
+
assert(!!source.nodeId, `Invalid data source ${nodeId}`);
|
|
307
|
+
this.pendings.add(source.nodeId);
|
|
308
|
+
return source;
|
|
309
|
+
}
|
|
310
|
+
isReadyNode() {
|
|
311
|
+
if (this.state !== NodeState.Waiting || this.pendings.size !== 0) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
if ((this.ifSource && !isLogicallyTrue(this.graph.resultOf(this.ifSource))) ||
|
|
315
|
+
(this.unlessSource && isLogicallyTrue(this.graph.resultOf(this.unlessSource)))) {
|
|
316
|
+
this.state = NodeState.Skipped;
|
|
317
|
+
this.log.onSkipped(this, this.graph);
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
// This private method (only called while executing execute()) performs
|
|
323
|
+
// the "retry" if specified. The transaction log must be updated before
|
|
324
|
+
// callling this method.
|
|
325
|
+
retry(state, error) {
|
|
326
|
+
this.state = state; // this.execute() will update to NodeState.Executing
|
|
327
|
+
this.log.onError(this, this.graph, error.message);
|
|
328
|
+
if (this.retryCount < this.retryLimit) {
|
|
329
|
+
this.retryCount++;
|
|
330
|
+
this.execute();
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
this.result = undefined;
|
|
334
|
+
this.error = error;
|
|
335
|
+
this.transactionId = undefined; // This is necessary for timeout case
|
|
336
|
+
this.graph.onExecutionComplete(this);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
checkDataAvailability() {
|
|
340
|
+
return Object.values(this.graph.resultsOf(this.inputs))
|
|
341
|
+
.flat()
|
|
342
|
+
.some((result) => result !== undefined);
|
|
343
|
+
}
|
|
344
|
+
// This method is called right before the Graph add this node to the task manager.
|
|
345
|
+
beforeAddTask() {
|
|
346
|
+
this.state = NodeState.Queued;
|
|
347
|
+
this.log.beforeAddTask(this, this.graph);
|
|
348
|
+
}
|
|
349
|
+
// This method is called when the data became available on one of nodes,
|
|
350
|
+
// which this node needs data from.
|
|
351
|
+
removePending(nodeId) {
|
|
352
|
+
if (this.anyInput) {
|
|
353
|
+
if (this.checkDataAvailability()) {
|
|
354
|
+
this.pendings.clear();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
this.pendings.delete(nodeId);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
isCurrentTransaction(transactionId) {
|
|
362
|
+
return this.transactionId === transactionId;
|
|
363
|
+
}
|
|
364
|
+
// This private method (called only fro execute) checks if the callback from
|
|
365
|
+
// the timer came before the completion of agent function call, record it
|
|
366
|
+
// and attempt to retry (if specified).
|
|
367
|
+
executeTimeout(transactionId) {
|
|
368
|
+
if (this.state === NodeState.Executing && this.isCurrentTransaction(transactionId)) {
|
|
369
|
+
console.warn(`-- timeout ${this.timeout} with ${this.nodeId}`);
|
|
370
|
+
this.retry(NodeState.TimedOut, Error("Timeout"));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Check if we need to apply this filter to this node or not.
|
|
374
|
+
shouldApplyAgentFilter(agentFilter) {
|
|
375
|
+
if (agentFilter.agentIds && Array.isArray(agentFilter.agentIds) && agentFilter.agentIds.length > 0) {
|
|
376
|
+
if (this.agentId && agentFilter.agentIds.includes(this.agentId)) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (agentFilter.nodeIds && Array.isArray(agentFilter.nodeIds) && agentFilter.nodeIds.length > 0) {
|
|
381
|
+
if (agentFilter.nodeIds.includes(this.nodeId)) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return !agentFilter.agentIds && !agentFilter.nodeIds;
|
|
386
|
+
}
|
|
387
|
+
agentFilterHandler(context, agentFunction) {
|
|
388
|
+
let index = 0;
|
|
389
|
+
const next = (innerContext) => {
|
|
390
|
+
const agentFilter = this.graph.agentFilters[index++];
|
|
391
|
+
if (agentFilter) {
|
|
392
|
+
if (this.shouldApplyAgentFilter(agentFilter)) {
|
|
393
|
+
if (agentFilter.filterParams) {
|
|
394
|
+
innerContext.filterParams = { ...agentFilter.filterParams, ...innerContext.filterParams };
|
|
395
|
+
}
|
|
396
|
+
return agentFilter.agent(innerContext, next);
|
|
397
|
+
}
|
|
398
|
+
return next(innerContext);
|
|
399
|
+
}
|
|
400
|
+
return agentFunction(innerContext);
|
|
401
|
+
};
|
|
402
|
+
return next(context);
|
|
403
|
+
}
|
|
404
|
+
// This method is called when this computed node became ready to run.
|
|
405
|
+
// It asynchronously calls the associated with agent function and set the result,
|
|
406
|
+
// then it removes itself from the "running node" list of the graph.
|
|
407
|
+
// Notice that setting the result of this node may make other nodes ready to run.
|
|
408
|
+
async execute() {
|
|
409
|
+
const previousResults = this.graph.resultsOf(this.inputs, this.anyInput);
|
|
410
|
+
const transactionId = Date.now();
|
|
411
|
+
this.prepareExecute(transactionId, Object.values(previousResults));
|
|
412
|
+
if (this.timeout && this.timeout > 0) {
|
|
413
|
+
setTimeout(() => {
|
|
414
|
+
this.executeTimeout(transactionId);
|
|
415
|
+
}, this.timeout);
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const agentFunction = this.agentFunction ?? this.graph.getAgentFunctionInfo(this.agentId).agent;
|
|
419
|
+
const localLog = [];
|
|
420
|
+
const context = this.getContext(previousResults, localLog);
|
|
421
|
+
// NOTE: We use the existence of graph object in the agent-specific params to determine
|
|
422
|
+
// if this is a nested agent or not.
|
|
423
|
+
if (this.nestedGraph) {
|
|
424
|
+
this.graph.taskManager.prepareForNesting();
|
|
425
|
+
context.taskManager = this.graph.taskManager;
|
|
426
|
+
context.onLogCallback = this.graph.onLogCallback;
|
|
427
|
+
if ("nodes" in this.nestedGraph) {
|
|
428
|
+
context.graphData = this.nestedGraph;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
context.graphData = this.graph.resultOf(this.nestedGraph); // HACK: compiler work-around
|
|
432
|
+
}
|
|
433
|
+
context.agents = this.graph.agentFunctionInfoDictionary;
|
|
434
|
+
}
|
|
435
|
+
this.beforeConsoleLog(context);
|
|
436
|
+
const result = await this.agentFilterHandler(context, agentFunction);
|
|
437
|
+
this.afterConsoleLog(result);
|
|
438
|
+
if (this.nestedGraph) {
|
|
439
|
+
this.graph.taskManager.restoreAfterNesting();
|
|
440
|
+
}
|
|
441
|
+
if (!this.isCurrentTransaction(transactionId)) {
|
|
442
|
+
// This condition happens when the agent function returns
|
|
443
|
+
// after the timeout (either retried or not).
|
|
444
|
+
console.log(`-- transactionId mismatch with ${this.nodeId} (probably timeout)`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
this.state = NodeState.Completed;
|
|
448
|
+
this.result = this.getResult(result);
|
|
449
|
+
this.log.onComplete(this, this.graph, localLog);
|
|
450
|
+
this.onSetResult();
|
|
451
|
+
this.graph.onExecutionComplete(this);
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
this.errorProcess(error, transactionId, previousResults);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// This private method (called only by execute()) prepares the ComputedNode object
|
|
458
|
+
// for execution, and create a new transaction to record it.
|
|
459
|
+
prepareExecute(transactionId, inputs) {
|
|
460
|
+
this.state = NodeState.Executing;
|
|
461
|
+
this.log.beforeExecute(this, this.graph, transactionId, inputs);
|
|
462
|
+
this.transactionId = transactionId;
|
|
463
|
+
}
|
|
464
|
+
// This private method (called only by execute) processes an error received from
|
|
465
|
+
// the agent function. It records the error in the transaction log and handles
|
|
466
|
+
// the retry if specified.
|
|
467
|
+
errorProcess(error, transactionId, namedInputs) {
|
|
468
|
+
if (error instanceof Error && error.message !== strIntentionalError) {
|
|
469
|
+
console.error(`<-- NodeId: ${this.nodeId}, Agent: ${this.agentId}`);
|
|
470
|
+
console.error({ namedInputs });
|
|
471
|
+
console.error(error);
|
|
472
|
+
console.error("-->");
|
|
473
|
+
}
|
|
474
|
+
if (!this.isCurrentTransaction(transactionId)) {
|
|
475
|
+
console.warn(`-- transactionId mismatch with ${this.nodeId} (not timeout)`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (error instanceof Error) {
|
|
479
|
+
this.retry(NodeState.Failed, error);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
console.error(`-- NodeId: ${this.nodeId}: Unknown error was caught`);
|
|
483
|
+
this.retry(NodeState.Failed, Error("Unknown"));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
getParams() {
|
|
487
|
+
return Object.keys(this.dynamicParams).reduce((tmp, key) => {
|
|
488
|
+
const result = this.graph.resultOf(this.dynamicParams[key]);
|
|
489
|
+
tmp[key] = result;
|
|
490
|
+
return tmp;
|
|
491
|
+
}, { ...this.params });
|
|
492
|
+
}
|
|
493
|
+
getInputs(previousResults) {
|
|
494
|
+
if (Array.isArray(this.inputs)) {
|
|
495
|
+
return (this.inputs ?? []).map((key) => previousResults[String(key)]).filter((a) => !this.anyInput || a);
|
|
496
|
+
}
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
getContext(previousResults, localLog) {
|
|
500
|
+
const context = {
|
|
501
|
+
params: this.getParams(),
|
|
502
|
+
inputs: this.getInputs(previousResults),
|
|
503
|
+
namedInputs: this.isNamedInputs ? previousResults : {},
|
|
504
|
+
inputSchema: this.agentFunction ? undefined : this.graph.getAgentFunctionInfo(this.agentId)?.inputs,
|
|
505
|
+
debugInfo: this.getDebugInfo(),
|
|
506
|
+
filterParams: this.filterParams,
|
|
507
|
+
agentFilters: this.graph.agentFilters,
|
|
508
|
+
config: this.graph.config,
|
|
509
|
+
log: localLog,
|
|
510
|
+
};
|
|
511
|
+
return context;
|
|
512
|
+
}
|
|
513
|
+
getResult(result) {
|
|
514
|
+
if (result && this.passThrough) {
|
|
515
|
+
if (isObject(result) && !Array.isArray(result)) {
|
|
516
|
+
return { ...result, ...this.passThrough };
|
|
517
|
+
}
|
|
518
|
+
else if (Array.isArray(result)) {
|
|
519
|
+
return result.map((r) => (isObject(r) && !Array.isArray(r) ? { ...r, ...this.passThrough } : r));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
getDebugInfo() {
|
|
525
|
+
return {
|
|
526
|
+
nodeId: this.nodeId,
|
|
527
|
+
agentId: this.agentId,
|
|
528
|
+
retry: this.retryCount,
|
|
529
|
+
verbose: this.graph.verbose,
|
|
530
|
+
version: this.graph.version,
|
|
531
|
+
isResult: this.isResult,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
beforeConsoleLog(context) {
|
|
535
|
+
if (this.console.before === true) {
|
|
536
|
+
console.log(JSON.stringify(this.isNamedInputs ? context.namedInputs : context.inputs, null, 2));
|
|
537
|
+
}
|
|
538
|
+
else if (this.console.before) {
|
|
539
|
+
console.log(this.console.before);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
class StaticNode extends Node {
|
|
544
|
+
constructor(nodeId, data, graph) {
|
|
545
|
+
super(nodeId, graph);
|
|
546
|
+
this.isStaticNode = true;
|
|
547
|
+
this.isComputedNode = false;
|
|
548
|
+
this.value = data.value;
|
|
549
|
+
this.update = data.update ? parseNodeName(data.update) : undefined;
|
|
550
|
+
this.isResult = data.isResult ?? false;
|
|
551
|
+
this.console = data.console ?? {};
|
|
552
|
+
}
|
|
553
|
+
injectValue(value, injectFrom) {
|
|
554
|
+
this.state = NodeState.Injected;
|
|
555
|
+
this.result = value;
|
|
556
|
+
this.log.onInjected(this, this.graph, injectFrom);
|
|
557
|
+
this.onSetResult();
|
|
558
|
+
}
|
|
559
|
+
consoleLog() {
|
|
560
|
+
this.afterConsoleLog(this.result);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const propFunctionRegex = /^[a-zA-Z]+\([^)]*\)$/;
|
|
565
|
+
const propArrayFunction = (result, propId) => {
|
|
566
|
+
if (Array.isArray(result)) {
|
|
567
|
+
if (propId === "length()") {
|
|
568
|
+
return result.length;
|
|
569
|
+
}
|
|
570
|
+
if (propId === "flat()") {
|
|
571
|
+
return result.flat();
|
|
572
|
+
}
|
|
573
|
+
if (propId === "toJSON()") {
|
|
574
|
+
return JSON.stringify(result);
|
|
575
|
+
}
|
|
576
|
+
if (propId === "isEmpty()") {
|
|
577
|
+
return result.length === 0;
|
|
578
|
+
}
|
|
579
|
+
// array join
|
|
580
|
+
const matchJoin = propId.match(/^join\(([,-]?)\)$/);
|
|
581
|
+
if (matchJoin && Array.isArray(matchJoin)) {
|
|
582
|
+
return result.join(matchJoin[1] ?? "");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return undefined;
|
|
586
|
+
};
|
|
587
|
+
const propObjectFunction = (result, propId) => {
|
|
588
|
+
if (isObject(result)) {
|
|
589
|
+
if (propId === "keys()") {
|
|
590
|
+
return Object.keys(result);
|
|
591
|
+
}
|
|
592
|
+
if (propId === "values()") {
|
|
593
|
+
return Object.values(result);
|
|
594
|
+
}
|
|
595
|
+
if (propId === "toJSON()") {
|
|
596
|
+
return JSON.stringify(result);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return undefined;
|
|
600
|
+
};
|
|
601
|
+
const propStringFunction = (result, propId) => {
|
|
602
|
+
if (typeof result === "string") {
|
|
603
|
+
if (propId === "codeBlock()") {
|
|
604
|
+
const match = ("\n" + result).match(/\n```[a-zA-z]*([\s\S]*?)\n```/);
|
|
605
|
+
if (match) {
|
|
606
|
+
return match[1];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (propId === "jsonParse()") {
|
|
610
|
+
return JSON.parse(result);
|
|
611
|
+
}
|
|
612
|
+
if (propId === "toNumber()") {
|
|
613
|
+
const ret = Number(result);
|
|
614
|
+
if (!isNaN(ret)) {
|
|
615
|
+
return ret;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return undefined;
|
|
620
|
+
};
|
|
621
|
+
const propNumberFunction = (result, propId) => {
|
|
622
|
+
if (result !== undefined && Number.isFinite(result)) {
|
|
623
|
+
if (propId === "toString()") {
|
|
624
|
+
return String(result);
|
|
625
|
+
}
|
|
626
|
+
const regex = /^add\((-?\d+)\)$/;
|
|
627
|
+
const match = propId.match(regex);
|
|
628
|
+
if (match) {
|
|
629
|
+
return Number(result) + Number(match[1]);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return undefined;
|
|
633
|
+
};
|
|
634
|
+
const propBooleanFunction = (result, propId) => {
|
|
635
|
+
if (typeof result === "boolean") {
|
|
636
|
+
if (propId === "not()") {
|
|
637
|
+
return !result;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return undefined;
|
|
641
|
+
};
|
|
642
|
+
const propFunctions = [propArrayFunction, propObjectFunction, propStringFunction, propNumberFunction, propBooleanFunction];
|
|
643
|
+
|
|
644
|
+
const getNestedData = (result, propId, propFunctions) => {
|
|
645
|
+
const match = propId.match(propFunctionRegex);
|
|
646
|
+
if (match) {
|
|
647
|
+
for (const propFunction of propFunctions) {
|
|
648
|
+
const ret = propFunction(result, propId);
|
|
649
|
+
if (!isNull(ret)) {
|
|
650
|
+
return ret;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// for array.
|
|
655
|
+
if (Array.isArray(result)) {
|
|
656
|
+
// $0, $1. array value.
|
|
657
|
+
const regex = /^\$(\d+)$/;
|
|
658
|
+
const match = propId.match(regex);
|
|
659
|
+
if (match) {
|
|
660
|
+
const index = parseInt(match[1], 10);
|
|
661
|
+
return result[index];
|
|
662
|
+
}
|
|
663
|
+
if (propId === "$last") {
|
|
664
|
+
return result[result.length - 1];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
else if (isObject(result)) {
|
|
668
|
+
if (propId in result) {
|
|
669
|
+
return result[propId];
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return undefined;
|
|
673
|
+
};
|
|
674
|
+
const innerGetDataFromSource = (result, propIds, propFunctions) => {
|
|
675
|
+
if (!isNull(result) && propIds && propIds.length > 0) {
|
|
676
|
+
const propId = propIds[0];
|
|
677
|
+
const ret = getNestedData(result, propId, propFunctions);
|
|
678
|
+
if (ret === undefined) {
|
|
679
|
+
console.error(`prop: ${propIds.join(".")} is not hit`);
|
|
680
|
+
}
|
|
681
|
+
if (propIds.length > 1) {
|
|
682
|
+
return innerGetDataFromSource(ret, propIds.slice(1), propFunctions);
|
|
683
|
+
}
|
|
684
|
+
return ret;
|
|
685
|
+
}
|
|
686
|
+
return result;
|
|
687
|
+
};
|
|
688
|
+
const getDataFromSource = (result, source, propFunctions = []) => {
|
|
689
|
+
if (!source.nodeId) {
|
|
690
|
+
return source.value;
|
|
691
|
+
}
|
|
692
|
+
return innerGetDataFromSource(result, source.propIds, propFunctions);
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const resultsOfInner = (input, nodes, propFunctions) => {
|
|
696
|
+
if (Array.isArray(input)) {
|
|
697
|
+
return input.map((inp) => resultsOfInner(inp, nodes, propFunctions));
|
|
698
|
+
}
|
|
699
|
+
if (isNamedInputs(input)) {
|
|
700
|
+
return resultsOf(input, nodes, propFunctions);
|
|
701
|
+
}
|
|
702
|
+
if (typeof input === "string") {
|
|
703
|
+
const templateMatch = [...input.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
|
|
704
|
+
if (templateMatch.length > 0) {
|
|
705
|
+
const results = resultsOfInner(templateMatch, nodes, propFunctions);
|
|
706
|
+
return Array.from(templateMatch.keys()).reduce((tmp, key) => {
|
|
707
|
+
return tmp.replaceAll("${" + templateMatch[key] + "}", results[key]);
|
|
708
|
+
}, input);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return resultOf(parseNodeName(input), nodes, propFunctions);
|
|
712
|
+
};
|
|
713
|
+
const resultsOf = (inputs, nodes, propFunctions) => {
|
|
714
|
+
// for inputs. TODO remove if array input is not supported
|
|
715
|
+
if (Array.isArray(inputs)) {
|
|
716
|
+
return inputs.reduce((tmp, key) => {
|
|
717
|
+
tmp[key] = resultsOfInner(key, nodes, propFunctions);
|
|
718
|
+
return tmp;
|
|
719
|
+
}, {});
|
|
720
|
+
}
|
|
721
|
+
return Object.keys(inputs).reduce((tmp, key) => {
|
|
722
|
+
const input = inputs[key];
|
|
723
|
+
tmp[key] = isNamedInputs(input) ? resultsOf(input, nodes, propFunctions) : resultsOfInner(input, nodes, propFunctions);
|
|
724
|
+
return tmp;
|
|
725
|
+
}, {});
|
|
726
|
+
};
|
|
727
|
+
const resultOf = (source, nodes, propFunctions) => {
|
|
728
|
+
const { result } = source.nodeId ? nodes[source.nodeId] : { result: undefined };
|
|
729
|
+
return getDataFromSource(result, source, propFunctions);
|
|
730
|
+
};
|
|
731
|
+
// clean up object for anyInput
|
|
732
|
+
const cleanResultInner = (results) => {
|
|
733
|
+
if (Array.isArray(results)) {
|
|
734
|
+
return results.map((result) => cleanResultInner(result)).filter((result) => !isNull(result));
|
|
735
|
+
}
|
|
736
|
+
if (isObject(results)) {
|
|
737
|
+
return Object.keys(results).reduce((tmp, key) => {
|
|
738
|
+
const value = cleanResultInner(results[key]);
|
|
739
|
+
if (!isNull(value)) {
|
|
740
|
+
tmp[key] = value;
|
|
741
|
+
}
|
|
742
|
+
return tmp;
|
|
743
|
+
}, {});
|
|
744
|
+
}
|
|
745
|
+
return results;
|
|
746
|
+
};
|
|
747
|
+
const cleanResult = (results) => {
|
|
748
|
+
return Object.keys(results).reduce((tmp, key) => {
|
|
749
|
+
const value = cleanResultInner(results[key]);
|
|
750
|
+
if (!isNull(value)) {
|
|
751
|
+
tmp[key] = value;
|
|
752
|
+
}
|
|
753
|
+
return tmp;
|
|
754
|
+
}, {});
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const graphDataAttributeKeys = ["nodes", "concurrency", "agentId", "loop", "verbose", "version"];
|
|
758
|
+
const computedNodeAttributeKeys = [
|
|
759
|
+
"inputs",
|
|
760
|
+
"anyInput",
|
|
761
|
+
"params",
|
|
762
|
+
"retry",
|
|
763
|
+
"timeout",
|
|
764
|
+
"agent",
|
|
765
|
+
"graph",
|
|
766
|
+
"graphLoader",
|
|
767
|
+
"isResult",
|
|
768
|
+
"priority",
|
|
769
|
+
"if",
|
|
770
|
+
"unless",
|
|
771
|
+
"filterParams",
|
|
772
|
+
"console",
|
|
773
|
+
"passThrough",
|
|
774
|
+
];
|
|
775
|
+
const staticNodeAttributeKeys = ["value", "update", "isResult", "console"];
|
|
776
|
+
class ValidationError extends Error {
|
|
777
|
+
constructor(message) {
|
|
778
|
+
super(`\x1b[41m${message}\x1b[0m`); // Pass the message to the base Error class
|
|
779
|
+
// Set the prototype explicitly to ensure correct prototype chain
|
|
780
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const graphNodesValidator = (data) => {
|
|
785
|
+
if (data.nodes === undefined) {
|
|
786
|
+
throw new ValidationError("Invalid Graph Data: no nodes");
|
|
787
|
+
}
|
|
788
|
+
if (typeof data.nodes !== "object") {
|
|
789
|
+
throw new ValidationError("Invalid Graph Data: invalid nodes");
|
|
790
|
+
}
|
|
791
|
+
if (Array.isArray(data.nodes)) {
|
|
792
|
+
throw new ValidationError("Invalid Graph Data: nodes must be object");
|
|
793
|
+
}
|
|
794
|
+
if (Object.keys(data.nodes).length === 0) {
|
|
795
|
+
throw new ValidationError("Invalid Graph Data: nodes is empty");
|
|
796
|
+
}
|
|
797
|
+
Object.keys(data).forEach((key) => {
|
|
798
|
+
if (!graphDataAttributeKeys.includes(key)) {
|
|
799
|
+
throw new ValidationError("Graph Data does not allow " + key);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
};
|
|
803
|
+
const graphDataValidator = (data) => {
|
|
804
|
+
if (data.loop) {
|
|
805
|
+
if (data.loop.count === undefined && data.loop.while === undefined) {
|
|
806
|
+
throw new ValidationError("Loop: Either count or while is required in loop");
|
|
807
|
+
}
|
|
808
|
+
if (data.loop.count !== undefined && data.loop.while !== undefined) {
|
|
809
|
+
throw new ValidationError("Loop: Both count and while cannot be set");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (data.concurrency !== undefined) {
|
|
813
|
+
if (!Number.isInteger(data.concurrency)) {
|
|
814
|
+
throw new ValidationError("Concurrency must be an integer");
|
|
815
|
+
}
|
|
816
|
+
if (data.concurrency < 1) {
|
|
817
|
+
throw new ValidationError("Concurrency must be a positive integer");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
const nodeValidator = (nodeData) => {
|
|
823
|
+
if (nodeData.agent && nodeData.value) {
|
|
824
|
+
throw new ValidationError("Cannot set both agent and value");
|
|
825
|
+
}
|
|
826
|
+
if (!("agent" in nodeData) && !("value" in nodeData)) {
|
|
827
|
+
throw new ValidationError("Either agent or value is required");
|
|
828
|
+
}
|
|
829
|
+
return true;
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
const staticNodeValidator = (nodeData) => {
|
|
833
|
+
Object.keys(nodeData).forEach((key) => {
|
|
834
|
+
if (!staticNodeAttributeKeys.includes(key)) {
|
|
835
|
+
throw new ValidationError("Static node does not allow " + key);
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
return true;
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
const computedNodeValidator = (nodeData) => {
|
|
842
|
+
Object.keys(nodeData).forEach((key) => {
|
|
843
|
+
if (!computedNodeAttributeKeys.includes(key)) {
|
|
844
|
+
throw new ValidationError("Computed node does not allow " + key);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
return true;
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const relationValidator = (data, staticNodeIds, computedNodeIds) => {
|
|
851
|
+
const nodeIds = new Set(Object.keys(data.nodes));
|
|
852
|
+
const pendings = {};
|
|
853
|
+
const waitlist = {};
|
|
854
|
+
// validate input relation and set pendings and wait list
|
|
855
|
+
computedNodeIds.forEach((computedNodeId) => {
|
|
856
|
+
const nodeData = data.nodes[computedNodeId];
|
|
857
|
+
pendings[computedNodeId] = new Set();
|
|
858
|
+
const dataSourceValidator = (sourceType, sourceNodeIds) => {
|
|
859
|
+
sourceNodeIds.forEach((sourceNodeId) => {
|
|
860
|
+
if (sourceNodeId) {
|
|
861
|
+
if (!nodeIds.has(sourceNodeId)) {
|
|
862
|
+
throw new ValidationError(`${sourceType} not match: NodeId ${computedNodeId}, Inputs: ${sourceNodeId}`);
|
|
863
|
+
}
|
|
864
|
+
waitlist[sourceNodeId] === undefined && (waitlist[sourceNodeId] = new Set());
|
|
865
|
+
pendings[computedNodeId].add(sourceNodeId);
|
|
866
|
+
waitlist[sourceNodeId].add(computedNodeId);
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
};
|
|
870
|
+
if ("agent" in nodeData && nodeData) {
|
|
871
|
+
if (nodeData.inputs) {
|
|
872
|
+
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources(nodeData.inputs));
|
|
873
|
+
dataSourceValidator("Inputs", sourceNodeIds);
|
|
874
|
+
}
|
|
875
|
+
if (nodeData.if) {
|
|
876
|
+
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ if: nodeData.if }));
|
|
877
|
+
dataSourceValidator("If", sourceNodeIds);
|
|
878
|
+
}
|
|
879
|
+
if (nodeData.unless) {
|
|
880
|
+
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ unless: nodeData.unless }));
|
|
881
|
+
dataSourceValidator("Unless", sourceNodeIds);
|
|
882
|
+
}
|
|
883
|
+
if (nodeData.graph && typeof nodeData?.graph === "string") {
|
|
884
|
+
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ graph: nodeData.graph }));
|
|
885
|
+
dataSourceValidator("Graph", sourceNodeIds);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
// TODO. validate update
|
|
890
|
+
staticNodeIds.forEach((staticNodeId) => {
|
|
891
|
+
const nodeData = data.nodes[staticNodeId];
|
|
892
|
+
if ("value" in nodeData && nodeData.update) {
|
|
893
|
+
const update = nodeData.update;
|
|
894
|
+
const updateNodeId = parseNodeName(update).nodeId;
|
|
895
|
+
if (!updateNodeId) {
|
|
896
|
+
throw new ValidationError("Update it a literal");
|
|
897
|
+
}
|
|
898
|
+
if (!nodeIds.has(updateNodeId)) {
|
|
899
|
+
throw new ValidationError(`Update not match: NodeId ${staticNodeId}, update: ${update}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
const cycle = (possibles) => {
|
|
904
|
+
possibles.forEach((possobleNodeId) => {
|
|
905
|
+
(waitlist[possobleNodeId] || []).forEach((waitingNodeId) => {
|
|
906
|
+
pendings[waitingNodeId].delete(possobleNodeId);
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
const running = [];
|
|
910
|
+
Object.keys(pendings).forEach((pendingNodeId) => {
|
|
911
|
+
if (pendings[pendingNodeId].size === 0) {
|
|
912
|
+
running.push(pendingNodeId);
|
|
913
|
+
delete pendings[pendingNodeId];
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
return running;
|
|
917
|
+
};
|
|
918
|
+
let runningQueue = cycle(staticNodeIds);
|
|
919
|
+
if (runningQueue.length === 0) {
|
|
920
|
+
throw new ValidationError("No Initial Runnning Node");
|
|
921
|
+
}
|
|
922
|
+
do {
|
|
923
|
+
runningQueue = cycle(runningQueue);
|
|
924
|
+
} while (runningQueue.length > 0);
|
|
925
|
+
if (Object.keys(pendings).length > 0) {
|
|
926
|
+
throw new ValidationError("Some nodes are not executed: " + Object.keys(pendings).join(", "));
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const agentValidator = (graphAgentIds, agentIds) => {
|
|
931
|
+
graphAgentIds.forEach((agentId) => {
|
|
932
|
+
if (!agentIds.has(agentId)) {
|
|
933
|
+
throw new ValidationError("Invalid Agent : " + agentId + " is not in AgentFunctionInfoDictionary.");
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
return true;
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
const validateGraphData = (data, agentIds) => {
|
|
940
|
+
graphNodesValidator(data);
|
|
941
|
+
graphDataValidator(data);
|
|
942
|
+
const computedNodeIds = [];
|
|
943
|
+
const staticNodeIds = [];
|
|
944
|
+
const graphAgentIds = new Set();
|
|
945
|
+
Object.keys(data.nodes).forEach((nodeId) => {
|
|
946
|
+
const node = data.nodes[nodeId];
|
|
947
|
+
const isStaticNode = "value" in node;
|
|
948
|
+
nodeValidator(node);
|
|
949
|
+
const agentId = isStaticNode ? "" : node.agent;
|
|
950
|
+
isStaticNode && staticNodeValidator(node) && staticNodeIds.push(nodeId);
|
|
951
|
+
!isStaticNode && computedNodeValidator(node) && computedNodeIds.push(nodeId) && typeof agentId === "string" && graphAgentIds.add(agentId);
|
|
952
|
+
});
|
|
953
|
+
agentValidator(graphAgentIds, new Set(agentIds));
|
|
954
|
+
relationValidator(data, staticNodeIds, computedNodeIds);
|
|
955
|
+
return true;
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// TaskManage object controls the concurrency of ComputedNode execution.
|
|
959
|
+
//
|
|
960
|
+
// NOTE: A TaskManager instance will be shared between parent graph and its children
|
|
961
|
+
// when nested agents are involved.
|
|
962
|
+
class TaskManager {
|
|
963
|
+
constructor(concurrency) {
|
|
964
|
+
this.taskQueue = [];
|
|
965
|
+
this.runningNodes = new Set();
|
|
966
|
+
this.concurrency = concurrency;
|
|
967
|
+
}
|
|
968
|
+
// This internal method dequeus a task from the task queue
|
|
969
|
+
// and call the associated callback method, if the number of
|
|
970
|
+
// running task is lower than the spcified limit.
|
|
971
|
+
dequeueTaskIfPossible() {
|
|
972
|
+
if (this.runningNodes.size < this.concurrency) {
|
|
973
|
+
const task = this.taskQueue.shift();
|
|
974
|
+
if (task) {
|
|
975
|
+
this.runningNodes.add(task.node);
|
|
976
|
+
task.callback(task.node);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// Node will call this method to put itself in the execution queue.
|
|
981
|
+
// We call the associated callback function when it is dequeued.
|
|
982
|
+
addTask(node, graphId, callback) {
|
|
983
|
+
// Finder tasks in the queue, which has either the same or higher priority.
|
|
984
|
+
const count = this.taskQueue.filter((task) => {
|
|
985
|
+
return task.node.priority >= node.priority;
|
|
986
|
+
}).length;
|
|
987
|
+
assert(count <= this.taskQueue.length, "TaskManager.addTask: Something is really wrong.");
|
|
988
|
+
this.taskQueue.splice(count, 0, { node, graphId, callback });
|
|
989
|
+
this.dequeueTaskIfPossible();
|
|
990
|
+
}
|
|
991
|
+
isRunning(graphId) {
|
|
992
|
+
const count = [...this.runningNodes].filter((node) => {
|
|
993
|
+
return node.graphId == graphId;
|
|
994
|
+
}).length;
|
|
995
|
+
return count > 0 || Array.from(this.taskQueue).filter((data) => data.graphId === graphId).length > 0;
|
|
996
|
+
}
|
|
997
|
+
// Node MUST call this method once the execution of agent function is completed
|
|
998
|
+
// either successfully or not.
|
|
999
|
+
onComplete(node) {
|
|
1000
|
+
assert(this.runningNodes.has(node), `TaskManager.onComplete node(${node.nodeId}) is not in list`);
|
|
1001
|
+
this.runningNodes.delete(node);
|
|
1002
|
+
this.dequeueTaskIfPossible();
|
|
1003
|
+
}
|
|
1004
|
+
// Node will call this method before it hands the task manager from the graph
|
|
1005
|
+
// to a nested agent. We need to make it sure that there is enough room to run
|
|
1006
|
+
// computed nodes inside the nested graph to avoid a deadlock.
|
|
1007
|
+
prepareForNesting() {
|
|
1008
|
+
this.concurrency++;
|
|
1009
|
+
}
|
|
1010
|
+
restoreAfterNesting() {
|
|
1011
|
+
this.concurrency--;
|
|
1012
|
+
}
|
|
1013
|
+
getStatus(verbose = false) {
|
|
1014
|
+
const runningNodes = Array.from(this.runningNodes).map((node) => node.nodeId);
|
|
1015
|
+
const queuedNodes = this.taskQueue.map((task) => task.node.nodeId);
|
|
1016
|
+
const nodes = verbose ? { runningNodes, queuedNodes } : {};
|
|
1017
|
+
return {
|
|
1018
|
+
concurrency: this.concurrency,
|
|
1019
|
+
queue: this.taskQueue.length,
|
|
1020
|
+
running: this.runningNodes.size,
|
|
1021
|
+
...nodes,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const defaultConcurrency = 8;
|
|
1027
|
+
const graphDataLatestVersion = 0.5;
|
|
1028
|
+
class GraphAI {
|
|
1029
|
+
// This method is called when either the GraphAI obect was created,
|
|
1030
|
+
// or we are about to start n-th iteration (n>2).
|
|
1031
|
+
createNodes(data) {
|
|
1032
|
+
const nodes = Object.keys(data.nodes).reduce((_nodes, nodeId) => {
|
|
1033
|
+
const nodeData = data.nodes[nodeId];
|
|
1034
|
+
if ("value" in nodeData) {
|
|
1035
|
+
_nodes[nodeId] = new StaticNode(nodeId, nodeData, this);
|
|
1036
|
+
}
|
|
1037
|
+
else if ("agent" in nodeData) {
|
|
1038
|
+
_nodes[nodeId] = new ComputedNode(this.graphId, nodeId, nodeData, this);
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
throw new Error("Unknown node type (neither value nor agent): " + nodeId);
|
|
1042
|
+
}
|
|
1043
|
+
return _nodes;
|
|
1044
|
+
}, {});
|
|
1045
|
+
// Generate the waitlist for each node.
|
|
1046
|
+
Object.keys(nodes).forEach((nodeId) => {
|
|
1047
|
+
const node = nodes[nodeId];
|
|
1048
|
+
if (node.isComputedNode) {
|
|
1049
|
+
node.pendings.forEach((pending) => {
|
|
1050
|
+
if (nodes[pending]) {
|
|
1051
|
+
nodes[pending].waitlist.add(nodeId); // previousNode
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
throw new Error(`createNode: invalid input ${pending} for node, ${nodeId}`);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
return nodes;
|
|
1060
|
+
}
|
|
1061
|
+
getValueFromResults(source, results) {
|
|
1062
|
+
return getDataFromSource(source.nodeId ? results[source.nodeId] : undefined, source, this.propFunctions);
|
|
1063
|
+
}
|
|
1064
|
+
// for static
|
|
1065
|
+
initializeStaticNodes(enableConsoleLog = false) {
|
|
1066
|
+
// If the result property is specified, inject it.
|
|
1067
|
+
// If the previousResults exists (indicating we are in a loop),
|
|
1068
|
+
// process the update property (nodeId or nodeId.propId).
|
|
1069
|
+
Object.keys(this.data.nodes).forEach((nodeId) => {
|
|
1070
|
+
const node = this.nodes[nodeId];
|
|
1071
|
+
if (node?.isStaticNode) {
|
|
1072
|
+
const value = node?.value;
|
|
1073
|
+
if (value !== undefined) {
|
|
1074
|
+
this.injectValue(nodeId, value, nodeId);
|
|
1075
|
+
}
|
|
1076
|
+
if (enableConsoleLog) {
|
|
1077
|
+
node.consoleLog();
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
updateStaticNodes(previousResults, enableConsoleLog = false) {
|
|
1083
|
+
// If the result property is specified, inject it.
|
|
1084
|
+
// If the previousResults exists (indicating we are in a loop),
|
|
1085
|
+
// process the update property (nodeId or nodeId.propId).
|
|
1086
|
+
Object.keys(this.data.nodes).forEach((nodeId) => {
|
|
1087
|
+
const node = this.nodes[nodeId];
|
|
1088
|
+
if (node?.isStaticNode) {
|
|
1089
|
+
const update = node?.update;
|
|
1090
|
+
if (update && previousResults) {
|
|
1091
|
+
const result = this.getValueFromResults(update, previousResults);
|
|
1092
|
+
this.injectValue(nodeId, result, update.nodeId);
|
|
1093
|
+
}
|
|
1094
|
+
if (enableConsoleLog) {
|
|
1095
|
+
node.consoleLog();
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
constructor(data, agentFunctionInfoDictionary, options = {
|
|
1101
|
+
taskManager: undefined,
|
|
1102
|
+
agentFilters: [],
|
|
1103
|
+
bypassAgentIds: [],
|
|
1104
|
+
config: {},
|
|
1105
|
+
graphLoader: undefined,
|
|
1106
|
+
}) {
|
|
1107
|
+
this.logs = [];
|
|
1108
|
+
this.config = {};
|
|
1109
|
+
this.onLogCallback = (__log, __isUpdate) => { };
|
|
1110
|
+
this.repeatCount = 0;
|
|
1111
|
+
if (!data.version && !options.taskManager) {
|
|
1112
|
+
console.warn("------------ missing version number");
|
|
1113
|
+
}
|
|
1114
|
+
this.version = data.version ?? graphDataLatestVersion;
|
|
1115
|
+
if (this.version < graphDataLatestVersion) {
|
|
1116
|
+
console.warn(`------------ upgrade to ${graphDataLatestVersion}!`);
|
|
1117
|
+
}
|
|
1118
|
+
this.retryLimit = data.retry; // optional
|
|
1119
|
+
this.graphId = URL.createObjectURL(new Blob()).slice(-36);
|
|
1120
|
+
this.data = data;
|
|
1121
|
+
this.agentFunctionInfoDictionary = agentFunctionInfoDictionary;
|
|
1122
|
+
this.propFunctions = propFunctions;
|
|
1123
|
+
this.taskManager = options.taskManager ?? new TaskManager(data.concurrency ?? defaultConcurrency);
|
|
1124
|
+
this.agentFilters = options.agentFilters ?? [];
|
|
1125
|
+
this.bypassAgentIds = options.bypassAgentIds ?? [];
|
|
1126
|
+
this.config = options.config;
|
|
1127
|
+
this.graphLoader = options.graphLoader;
|
|
1128
|
+
this.loop = data.loop;
|
|
1129
|
+
this.verbose = data.verbose === true;
|
|
1130
|
+
this.onComplete = () => {
|
|
1131
|
+
throw new Error("SOMETHING IS WRONG: onComplete is called without run()");
|
|
1132
|
+
};
|
|
1133
|
+
validateGraphData(data, [...Object.keys(agentFunctionInfoDictionary), ...this.bypassAgentIds]);
|
|
1134
|
+
this.nodes = this.createNodes(data);
|
|
1135
|
+
this.initializeStaticNodes(true);
|
|
1136
|
+
}
|
|
1137
|
+
getAgentFunctionInfo(agentId) {
|
|
1138
|
+
if (agentId && this.agentFunctionInfoDictionary[agentId]) {
|
|
1139
|
+
return this.agentFunctionInfoDictionary[agentId];
|
|
1140
|
+
}
|
|
1141
|
+
if (agentId && this.bypassAgentIds.includes(agentId)) {
|
|
1142
|
+
return {
|
|
1143
|
+
agent: async () => {
|
|
1144
|
+
return null;
|
|
1145
|
+
},
|
|
1146
|
+
inputs: null,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
// We are not supposed to hit this error because the validator will catch it.
|
|
1150
|
+
throw new Error("No agent: " + agentId);
|
|
1151
|
+
}
|
|
1152
|
+
asString() {
|
|
1153
|
+
return Object.values(this.nodes)
|
|
1154
|
+
.map((node) => node.asString())
|
|
1155
|
+
.join("\n");
|
|
1156
|
+
}
|
|
1157
|
+
// Public API
|
|
1158
|
+
results(all) {
|
|
1159
|
+
return Object.keys(this.nodes)
|
|
1160
|
+
.filter((nodeId) => all || this.nodes[nodeId].isResult)
|
|
1161
|
+
.reduce((results, nodeId) => {
|
|
1162
|
+
const node = this.nodes[nodeId];
|
|
1163
|
+
if (node.result !== undefined) {
|
|
1164
|
+
results[nodeId] = node.result;
|
|
1165
|
+
}
|
|
1166
|
+
return results;
|
|
1167
|
+
}, {});
|
|
1168
|
+
}
|
|
1169
|
+
// Public API
|
|
1170
|
+
errors() {
|
|
1171
|
+
return Object.keys(this.nodes).reduce((errors, nodeId) => {
|
|
1172
|
+
const node = this.nodes[nodeId];
|
|
1173
|
+
if (node.isComputedNode) {
|
|
1174
|
+
if (node.error !== undefined) {
|
|
1175
|
+
errors[nodeId] = node.error;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return errors;
|
|
1179
|
+
}, {});
|
|
1180
|
+
}
|
|
1181
|
+
pushReadyNodesIntoQueue() {
|
|
1182
|
+
// Nodes without pending data should run immediately.
|
|
1183
|
+
Object.keys(this.nodes).forEach((nodeId) => {
|
|
1184
|
+
const node = this.nodes[nodeId];
|
|
1185
|
+
if (node.isComputedNode) {
|
|
1186
|
+
this.pushQueueIfReady(node);
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
pushQueueIfReady(node) {
|
|
1191
|
+
if (node.isReadyNode()) {
|
|
1192
|
+
this.pushQueue(node);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
pushQueueIfReadyAndRunning(node) {
|
|
1196
|
+
if (this.isRunning()) {
|
|
1197
|
+
this.pushQueueIfReady(node);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// for computed
|
|
1201
|
+
pushQueue(node) {
|
|
1202
|
+
node.beforeAddTask();
|
|
1203
|
+
this.taskManager.addTask(node, this.graphId, (_node) => {
|
|
1204
|
+
assert(node.nodeId === _node.nodeId, "GraphAI.pushQueue node mismatch");
|
|
1205
|
+
node.execute();
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
// Public API
|
|
1209
|
+
async run(all = false) {
|
|
1210
|
+
if (this.isRunning()) {
|
|
1211
|
+
throw new Error("This GraphUI instance is already running");
|
|
1212
|
+
}
|
|
1213
|
+
this.pushReadyNodesIntoQueue();
|
|
1214
|
+
if (!this.isRunning()) {
|
|
1215
|
+
console.warn("-- nothing to execute");
|
|
1216
|
+
return {};
|
|
1217
|
+
}
|
|
1218
|
+
return new Promise((resolve, reject) => {
|
|
1219
|
+
this.onComplete = () => {
|
|
1220
|
+
const errors = this.errors();
|
|
1221
|
+
const nodeIds = Object.keys(errors);
|
|
1222
|
+
if (nodeIds.length > 0) {
|
|
1223
|
+
reject(errors[nodeIds[0]]);
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
resolve(this.results(all));
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
// Public only for testing
|
|
1232
|
+
isRunning() {
|
|
1233
|
+
return this.taskManager.isRunning(this.graphId);
|
|
1234
|
+
}
|
|
1235
|
+
// callback from execute
|
|
1236
|
+
onExecutionComplete(node) {
|
|
1237
|
+
this.taskManager.onComplete(node);
|
|
1238
|
+
if (this.isRunning() || this.processLoopIfNecessary()) {
|
|
1239
|
+
return; // continue running
|
|
1240
|
+
}
|
|
1241
|
+
this.onComplete(); // Nothing to run. Finish it.
|
|
1242
|
+
}
|
|
1243
|
+
// Must be called only from onExecutionComplete righ after removeRunning
|
|
1244
|
+
// Check if there is any running computed nodes.
|
|
1245
|
+
// In case of no running computed note, start the another iteration if ncessary (loop)
|
|
1246
|
+
processLoopIfNecessary() {
|
|
1247
|
+
this.repeatCount++;
|
|
1248
|
+
const loop = this.loop;
|
|
1249
|
+
if (!loop) {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
// We need to update static nodes, before checking the condition
|
|
1253
|
+
const previousResults = this.results(true); // results from previous loop
|
|
1254
|
+
this.updateStaticNodes(previousResults);
|
|
1255
|
+
if (loop.count === undefined || this.repeatCount < loop.count) {
|
|
1256
|
+
if (loop.while) {
|
|
1257
|
+
const source = parseNodeName(loop.while);
|
|
1258
|
+
const value = this.getValueFromResults(source, this.results(true));
|
|
1259
|
+
// NOTE: We treat an empty array as false.
|
|
1260
|
+
if (!isLogicallyTrue(value)) {
|
|
1261
|
+
return false; // while condition is not met
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
this.nodes = this.createNodes(this.data);
|
|
1265
|
+
this.initializeStaticNodes();
|
|
1266
|
+
this.updateStaticNodes(previousResults, true);
|
|
1267
|
+
this.pushReadyNodesIntoQueue();
|
|
1268
|
+
return true; // Indicating that we are going to continue.
|
|
1269
|
+
}
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
setLoopLog(log) {
|
|
1273
|
+
log.isLoop = !!this.loop;
|
|
1274
|
+
log.repeatCount = this.repeatCount;
|
|
1275
|
+
}
|
|
1276
|
+
appendLog(log) {
|
|
1277
|
+
this.logs.push(log);
|
|
1278
|
+
this.onLogCallback(log, false);
|
|
1279
|
+
}
|
|
1280
|
+
updateLog(log) {
|
|
1281
|
+
this.onLogCallback(log, true);
|
|
1282
|
+
}
|
|
1283
|
+
// Public API
|
|
1284
|
+
transactionLogs() {
|
|
1285
|
+
return this.logs;
|
|
1286
|
+
}
|
|
1287
|
+
// Public API
|
|
1288
|
+
injectValue(nodeId, value, injectFrom) {
|
|
1289
|
+
const node = this.nodes[nodeId];
|
|
1290
|
+
if (node && node.isStaticNode) {
|
|
1291
|
+
node.injectValue(value, injectFrom);
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
throw new Error(`injectValue with Invalid nodeId, ${nodeId}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
resultsOf(inputs, anyInput = false) {
|
|
1298
|
+
const results = resultsOf(inputs ?? [], this.nodes, this.propFunctions);
|
|
1299
|
+
if (anyInput) {
|
|
1300
|
+
return cleanResult(results);
|
|
1301
|
+
}
|
|
1302
|
+
return results;
|
|
1303
|
+
}
|
|
1304
|
+
resultOf(source) {
|
|
1305
|
+
return resultOf(source, this.nodes, this.propFunctions);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
export { GraphAI, NodeState, ValidationError, agentInfoWrapper, assert, defaultAgentInfo, defaultConcurrency, defaultTestContext, graphDataLatestVersion, inputs2dataSources, isObject, parseNodeName, sleep, strIntentionalError };
|
|
1310
|
+
//# sourceMappingURL=bundle.esm.js.map
|