graphai 2.0.13 → 2.0.15
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/graphai.d.ts +1 -1
- package/lib/graphai.js +4 -1
- package/lib/node.js +9 -8
- package/lib/transaction_log.d.ts +2 -1
- package/lib/transaction_log.js +4 -1
- package/lib/utils/data_source.js +1 -1
- package/package.json +5 -7
- package/lib/bundle.cjs.js +0 -2
- package/lib/bundle.cjs.js.map +0 -1
- package/lib/bundle.esm.js +0 -1658
- package/lib/bundle.esm.js.map +0 -1
- package/lib/bundle.umd.js +0 -2
- package/lib/bundle.umd.js.map +0 -1
package/lib/bundle.esm.js
DELETED
|
@@ -1,1658 +0,0 @@
|
|
|
1
|
-
var NodeState;
|
|
2
|
-
(function (NodeState) {
|
|
3
|
-
NodeState["Waiting"] = "waiting";
|
|
4
|
-
NodeState["Queued"] = "queued";
|
|
5
|
-
NodeState["Executing"] = "executing";
|
|
6
|
-
NodeState["ExecutingServer"] = "executing-server";
|
|
7
|
-
NodeState["Failed"] = "failed";
|
|
8
|
-
NodeState["TimedOut"] = "timed-out";
|
|
9
|
-
NodeState["Abort"] = "abort";
|
|
10
|
-
NodeState["Completed"] = "completed";
|
|
11
|
-
NodeState["Injected"] = "injected";
|
|
12
|
-
NodeState["Skipped"] = "skipped";
|
|
13
|
-
})(NodeState || (NodeState = {}));
|
|
14
|
-
|
|
15
|
-
const enabledLevels = {
|
|
16
|
-
debug: true,
|
|
17
|
-
info: true,
|
|
18
|
-
log: true,
|
|
19
|
-
warn: true,
|
|
20
|
-
error: true,
|
|
21
|
-
};
|
|
22
|
-
let customLogger = null;
|
|
23
|
-
function setLevelEnabled(level, enabled) {
|
|
24
|
-
enabledLevels[level] = enabled;
|
|
25
|
-
}
|
|
26
|
-
function setLogger(logger) {
|
|
27
|
-
customLogger = logger;
|
|
28
|
-
}
|
|
29
|
-
function output(level, ...args) {
|
|
30
|
-
if (!enabledLevels[level])
|
|
31
|
-
return;
|
|
32
|
-
if (customLogger) {
|
|
33
|
-
customLogger(level, ...args);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
(console[level] || console.log)(...args);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function debug(...args) {
|
|
40
|
-
output("debug", ...args);
|
|
41
|
-
}
|
|
42
|
-
function info(...args) {
|
|
43
|
-
output("info", ...args);
|
|
44
|
-
}
|
|
45
|
-
function log(...args) {
|
|
46
|
-
output("log", ...args);
|
|
47
|
-
}
|
|
48
|
-
function warn(...args) {
|
|
49
|
-
output("warn", ...args);
|
|
50
|
-
}
|
|
51
|
-
function error(...args) {
|
|
52
|
-
output("error", ...args);
|
|
53
|
-
}
|
|
54
|
-
const GraphAILogger = {
|
|
55
|
-
setLevelEnabled,
|
|
56
|
-
setLogger,
|
|
57
|
-
debug,
|
|
58
|
-
info,
|
|
59
|
-
log,
|
|
60
|
-
warn,
|
|
61
|
-
error,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const propFunctionRegex = /^[a-zA-Z]+\([^)]*\)$/;
|
|
65
|
-
const propArrayFunction = (result, propId) => {
|
|
66
|
-
if (Array.isArray(result)) {
|
|
67
|
-
if (propId === "length()") {
|
|
68
|
-
return result.length;
|
|
69
|
-
}
|
|
70
|
-
if (propId === "flat()") {
|
|
71
|
-
return result.flat();
|
|
72
|
-
}
|
|
73
|
-
if (propId === "toJSON()") {
|
|
74
|
-
return JSON.stringify(result, null, 2);
|
|
75
|
-
}
|
|
76
|
-
if (propId === "isEmpty()") {
|
|
77
|
-
return result.length === 0;
|
|
78
|
-
}
|
|
79
|
-
if (propId === "average()" || propId === "mean()") {
|
|
80
|
-
return result.length ? result.reduce((a, b) => a + b, 0) / result.length : 0;
|
|
81
|
-
}
|
|
82
|
-
if (propId === "sum()") {
|
|
83
|
-
return result.reduce((a, b) => a + b, 0);
|
|
84
|
-
}
|
|
85
|
-
if (propId === "max()") {
|
|
86
|
-
return result.length ? Math.max(...result) : 0;
|
|
87
|
-
}
|
|
88
|
-
if (propId === "min()") {
|
|
89
|
-
return result.length ? Math.min(...result) : 0;
|
|
90
|
-
}
|
|
91
|
-
// array join
|
|
92
|
-
const matchJoin = propId.match(/^join\(([,-\s]?)\)$/);
|
|
93
|
-
if (matchJoin && Array.isArray(matchJoin)) {
|
|
94
|
-
return result.join(matchJoin[1] ?? "");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
};
|
|
99
|
-
const propObjectFunction = (result, propId) => {
|
|
100
|
-
if (isObject(result)) {
|
|
101
|
-
if (propId === "keys()") {
|
|
102
|
-
return Object.keys(result);
|
|
103
|
-
}
|
|
104
|
-
if (propId === "values()") {
|
|
105
|
-
return Object.values(result);
|
|
106
|
-
}
|
|
107
|
-
if (propId === "toJSON()") {
|
|
108
|
-
return JSON.stringify(result, null, 2);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return undefined;
|
|
112
|
-
};
|
|
113
|
-
const propStringFunction = (result, propId) => {
|
|
114
|
-
if (typeof result === "string") {
|
|
115
|
-
if (propId === "codeBlock()") {
|
|
116
|
-
const match = ("\n" + result).match(/\n```[a-zA-Z]*([\s\S]*?)\n```/);
|
|
117
|
-
if (match) {
|
|
118
|
-
return match[1];
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (propId === "codeBlockOrRaw()") {
|
|
122
|
-
const match = ("\n" + result).match(/\n```[a-zA-Z]*([\s\S]*?)\n```/);
|
|
123
|
-
if (match) {
|
|
124
|
-
return match[1];
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
}
|
|
128
|
-
if (propId === "jsonParse()") {
|
|
129
|
-
return JSON.parse(result);
|
|
130
|
-
}
|
|
131
|
-
if (propId === "toNumber()") {
|
|
132
|
-
const ret = Number(result);
|
|
133
|
-
if (!isNaN(ret)) {
|
|
134
|
-
return ret;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (propId === "trim()") {
|
|
138
|
-
return result.trim();
|
|
139
|
-
}
|
|
140
|
-
if (propId === "toLowerCase()") {
|
|
141
|
-
return result.toLowerCase();
|
|
142
|
-
}
|
|
143
|
-
if (propId === "toUpperCase()") {
|
|
144
|
-
return result.toUpperCase();
|
|
145
|
-
}
|
|
146
|
-
const equalMatch = propId.match(/^equal\(([A-Za-z0-9!#$%&()*+,\-./:;<=>?@]+)\)/);
|
|
147
|
-
if (equalMatch) {
|
|
148
|
-
return result === equalMatch[1];
|
|
149
|
-
}
|
|
150
|
-
const sliceMatch = propId.match(/^slice\((-?\d+)(?:,\s*(-?\d+))?\)/);
|
|
151
|
-
if (sliceMatch) {
|
|
152
|
-
if (sliceMatch[2] !== undefined) {
|
|
153
|
-
return result.slice(Number(sliceMatch[1]), Number(sliceMatch[2]));
|
|
154
|
-
}
|
|
155
|
-
if (sliceMatch[1] !== undefined) {
|
|
156
|
-
return result.slice(Number(sliceMatch[1]));
|
|
157
|
-
}
|
|
158
|
-
GraphAILogger.warn("slice is not valid format: " + sliceMatch);
|
|
159
|
-
}
|
|
160
|
-
const splitMatch = propId.match(/^split\(([-_:;.,\s\n]+)\)$/);
|
|
161
|
-
if (splitMatch) {
|
|
162
|
-
return result.split(splitMatch[1]);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return undefined;
|
|
166
|
-
};
|
|
167
|
-
const propNumberFunction = (result, propId) => {
|
|
168
|
-
if (result !== undefined && Number.isFinite(result)) {
|
|
169
|
-
if (propId === "toString()") {
|
|
170
|
-
return String(result);
|
|
171
|
-
}
|
|
172
|
-
const regex = /^add\((-?\d+)\)$/;
|
|
173
|
-
const match = propId.match(regex);
|
|
174
|
-
if (match) {
|
|
175
|
-
return Number(result) + Number(match[1]);
|
|
176
|
-
}
|
|
177
|
-
const equalMatch = propId.match(/^equal\(([A-Za-z0-9!#$%&()*+,\-/:;<=>?@]+)\)/);
|
|
178
|
-
if (equalMatch) {
|
|
179
|
-
return result === Number(equalMatch[1]);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return undefined;
|
|
183
|
-
};
|
|
184
|
-
const propBooleanFunction = (result, propId) => {
|
|
185
|
-
if (typeof result === "boolean") {
|
|
186
|
-
if (propId === "not()") {
|
|
187
|
-
return !result;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return undefined;
|
|
191
|
-
};
|
|
192
|
-
const propUndefinrdFunction = (result, propId) => {
|
|
193
|
-
if (result === undefined) {
|
|
194
|
-
const equalMatch = propId.match(/^default\(([A-Za-z0-9!#$%&()*+,\-/:;<=>?@]+)\)/);
|
|
195
|
-
if (equalMatch) {
|
|
196
|
-
if (equalMatch[1].match(/^[0-9-]+$/)) {
|
|
197
|
-
return Number(equalMatch[1]);
|
|
198
|
-
}
|
|
199
|
-
return equalMatch[1];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return undefined;
|
|
203
|
-
};
|
|
204
|
-
// TODO if (result === undefined) {default()}
|
|
205
|
-
const propFunctions = [propArrayFunction, propObjectFunction, propStringFunction, propNumberFunction, propBooleanFunction, propUndefinrdFunction];
|
|
206
|
-
const utilsFunctions = (input, nodes) => {
|
|
207
|
-
if (input === "@now" || input === "@now_ms") {
|
|
208
|
-
return Date.now();
|
|
209
|
-
}
|
|
210
|
-
if (input === "@now_s") {
|
|
211
|
-
return Math.floor(Date.now() / 1000);
|
|
212
|
-
}
|
|
213
|
-
if (input === "@loop") {
|
|
214
|
-
return nodes[loopCounterKey].result;
|
|
215
|
-
}
|
|
216
|
-
// If a placeholder does not match any key, replace it with an empty string.
|
|
217
|
-
GraphAILogger.warn("not match template utility function: ${" + input + "}");
|
|
218
|
-
return "";
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const sleep = async (milliseconds) => {
|
|
222
|
-
return await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
223
|
-
};
|
|
224
|
-
const parseNodeName = (inputNodeId, isSelfNode = false, nodes) => {
|
|
225
|
-
if (isSelfNode) {
|
|
226
|
-
if (typeof inputNodeId === "string" && inputNodeId[0] === ".") {
|
|
227
|
-
const parts = inputNodeId.split(".");
|
|
228
|
-
return { nodeId: "self", propIds: parts.slice(1) };
|
|
229
|
-
}
|
|
230
|
-
return { value: inputNodeId };
|
|
231
|
-
}
|
|
232
|
-
if (typeof inputNodeId === "string") {
|
|
233
|
-
const regex = /^:(.*)$/;
|
|
234
|
-
const match = inputNodeId.match(regex);
|
|
235
|
-
if (match) {
|
|
236
|
-
const parts = match[1].split(/(?<!\()\.(?!\))/);
|
|
237
|
-
if (parts.length == 1) {
|
|
238
|
-
return { nodeId: parts[0] };
|
|
239
|
-
}
|
|
240
|
-
return { nodeId: parts[0], propIds: parts.slice(1) };
|
|
241
|
-
}
|
|
242
|
-
const regexUtil = /^@(.*)$/;
|
|
243
|
-
const matchUtil = inputNodeId.match(regexUtil);
|
|
244
|
-
// Only when just called from resultsOfInner
|
|
245
|
-
if (nodes && matchUtil) {
|
|
246
|
-
return { value: utilsFunctions(inputNodeId, nodes) };
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return { value: inputNodeId }; // non-string literal
|
|
250
|
-
};
|
|
251
|
-
function assert(condition, message, isWarn = false) {
|
|
252
|
-
if (!condition) {
|
|
253
|
-
if (!isWarn) {
|
|
254
|
-
throw new Error(message);
|
|
255
|
-
}
|
|
256
|
-
GraphAILogger.warn("warn: " + message);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const isObject = (x) => {
|
|
260
|
-
return x !== null && typeof x === "object";
|
|
261
|
-
};
|
|
262
|
-
const isNull = (data) => {
|
|
263
|
-
return data === null || data === undefined;
|
|
264
|
-
};
|
|
265
|
-
const strIntentionalError = "Intentional Error for Debugging";
|
|
266
|
-
const defaultAgentInfo = {
|
|
267
|
-
name: "defaultAgentInfo",
|
|
268
|
-
samples: [
|
|
269
|
-
{
|
|
270
|
-
inputs: [],
|
|
271
|
-
params: {},
|
|
272
|
-
result: {},
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
description: "",
|
|
276
|
-
category: [],
|
|
277
|
-
author: "",
|
|
278
|
-
repository: "",
|
|
279
|
-
license: "",
|
|
280
|
-
};
|
|
281
|
-
const agentInfoWrapper = (agent) => {
|
|
282
|
-
return {
|
|
283
|
-
agent,
|
|
284
|
-
mock: agent,
|
|
285
|
-
...defaultAgentInfo,
|
|
286
|
-
};
|
|
287
|
-
};
|
|
288
|
-
const objectToKeyArray = (innerData) => {
|
|
289
|
-
const ret = [];
|
|
290
|
-
Object.keys(innerData).forEach((key) => {
|
|
291
|
-
ret.push([key]);
|
|
292
|
-
if (Object.keys(innerData[key]).length > 0) {
|
|
293
|
-
objectToKeyArray(innerData[key]).forEach((tmp) => {
|
|
294
|
-
ret.push([key, ...tmp]);
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
return ret;
|
|
299
|
-
};
|
|
300
|
-
const debugResultKey = (agentId, result) => {
|
|
301
|
-
return objectToKeyArray({ [agentId]: debugResultKeyInner(result) }).map((objectKeys) => {
|
|
302
|
-
return ":" + objectKeys.join(".");
|
|
303
|
-
});
|
|
304
|
-
};
|
|
305
|
-
const debugResultKeyInner = (result) => {
|
|
306
|
-
if (result === null || result === undefined) {
|
|
307
|
-
return {};
|
|
308
|
-
}
|
|
309
|
-
if (typeof result === "string") {
|
|
310
|
-
return {};
|
|
311
|
-
}
|
|
312
|
-
if (Array.isArray(result)) {
|
|
313
|
-
return Array.from(result.keys()).reduce((tmp, index) => {
|
|
314
|
-
tmp["$" + String(index)] = debugResultKeyInner(result[index]);
|
|
315
|
-
return tmp;
|
|
316
|
-
}, {});
|
|
317
|
-
}
|
|
318
|
-
return Object.keys(result).reduce((tmp, key) => {
|
|
319
|
-
tmp[key] = debugResultKeyInner(result[key]);
|
|
320
|
-
return tmp;
|
|
321
|
-
}, {});
|
|
322
|
-
};
|
|
323
|
-
const isLogicallyTrue = (value) => {
|
|
324
|
-
// Notice that empty aray is not true under GraphAI
|
|
325
|
-
if (Array.isArray(value) ? value.length === 0 : !value) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
return true;
|
|
329
|
-
};
|
|
330
|
-
const defaultTestContext = {
|
|
331
|
-
debugInfo: {
|
|
332
|
-
nodeId: "test",
|
|
333
|
-
retry: 0,
|
|
334
|
-
verbose: true,
|
|
335
|
-
state: NodeState.Executing,
|
|
336
|
-
subGraphs: new Map(),
|
|
337
|
-
},
|
|
338
|
-
params: {},
|
|
339
|
-
filterParams: {},
|
|
340
|
-
agents: {},
|
|
341
|
-
log: [],
|
|
342
|
-
};
|
|
343
|
-
const isNamedInputs = (namedInputs) => {
|
|
344
|
-
return isObject(namedInputs) && !Array.isArray(namedInputs) && Object.keys(namedInputs || {}).length > 0;
|
|
345
|
-
};
|
|
346
|
-
const isComputedNodeData = (node) => {
|
|
347
|
-
return "agent" in node;
|
|
348
|
-
};
|
|
349
|
-
const isStaticNodeData = (node) => {
|
|
350
|
-
return !("agent" in node);
|
|
351
|
-
};
|
|
352
|
-
const loopCounterKey = "__loopIndex";
|
|
353
|
-
|
|
354
|
-
// for dataSource
|
|
355
|
-
const inputs2dataSources = (inputs) => {
|
|
356
|
-
if (Array.isArray(inputs)) {
|
|
357
|
-
return inputs.map((inp) => inputs2dataSources(inp)).flat();
|
|
358
|
-
}
|
|
359
|
-
if (isObject(inputs)) {
|
|
360
|
-
return Object.values(inputs)
|
|
361
|
-
.map((input) => inputs2dataSources(input))
|
|
362
|
-
.flat();
|
|
363
|
-
}
|
|
364
|
-
if (typeof inputs === "string") {
|
|
365
|
-
const templateMatch = [...inputs.matchAll(/\${(:[^}]+)}/g)].map((m) => m[1]);
|
|
366
|
-
if (templateMatch.length > 0) {
|
|
367
|
-
return inputs2dataSources(templateMatch);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return parseNodeName(inputs);
|
|
371
|
-
};
|
|
372
|
-
// TODO: Maybe it's a remnant of old array inputs. Check and delete.
|
|
373
|
-
const dataSourceNodeIds = (sources) => {
|
|
374
|
-
if (!Array.isArray(sources)) {
|
|
375
|
-
throw new Error("sources must be array!! maybe inputs is invalid");
|
|
376
|
-
}
|
|
377
|
-
return sources.filter((source) => source.nodeId).map((source) => source.nodeId);
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
class TransactionLog {
|
|
381
|
-
constructor(nodeId, mapIndex) {
|
|
382
|
-
this.nodeId = nodeId;
|
|
383
|
-
this.state = NodeState.Waiting;
|
|
384
|
-
this.mapIndex = mapIndex;
|
|
385
|
-
}
|
|
386
|
-
initForComputedNode(node, graph) {
|
|
387
|
-
this.agentId = node.getAgentId();
|
|
388
|
-
this.params = node.params;
|
|
389
|
-
graph.appendLog(this);
|
|
390
|
-
}
|
|
391
|
-
onInjected(node, graph, injectFrom) {
|
|
392
|
-
const isUpdating = "endTime" in this;
|
|
393
|
-
this.result = node.result;
|
|
394
|
-
this.state = node.state;
|
|
395
|
-
this.endTime = Date.now();
|
|
396
|
-
this.injectFrom = injectFrom;
|
|
397
|
-
graph.setLoopLog(this);
|
|
398
|
-
// console.log(this)
|
|
399
|
-
if (isUpdating) {
|
|
400
|
-
graph.updateLog(this);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
graph.appendLog(this);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
onComplete(node, graph, localLog) {
|
|
407
|
-
this.result = node.result;
|
|
408
|
-
this.resultKeys = debugResultKey(this.agentId || "", node.result);
|
|
409
|
-
this.state = node.state;
|
|
410
|
-
this.endTime = Date.now();
|
|
411
|
-
graph.setLoopLog(this);
|
|
412
|
-
if (localLog.length > 0) {
|
|
413
|
-
this.log = localLog;
|
|
414
|
-
}
|
|
415
|
-
graph.updateLog(this);
|
|
416
|
-
}
|
|
417
|
-
beforeExecute(node, graph, transactionId, inputs) {
|
|
418
|
-
this.state = node.state;
|
|
419
|
-
this.retryCount = node.retryCount > 0 ? node.retryCount : undefined;
|
|
420
|
-
this.startTime = transactionId;
|
|
421
|
-
this.inputs = dataSourceNodeIds(node.dataSources);
|
|
422
|
-
this.inputsData = inputs.length > 0 ? inputs : undefined;
|
|
423
|
-
graph.setLoopLog(this);
|
|
424
|
-
graph.appendLog(this);
|
|
425
|
-
}
|
|
426
|
-
beforeAddTask(node, graph) {
|
|
427
|
-
this.state = node.state;
|
|
428
|
-
graph.setLoopLog(this);
|
|
429
|
-
graph.appendLog(this);
|
|
430
|
-
}
|
|
431
|
-
onError(node, graph, errorMessage) {
|
|
432
|
-
this.state = node.state;
|
|
433
|
-
this.errorMessage = errorMessage;
|
|
434
|
-
this.endTime = Date.now();
|
|
435
|
-
graph.setLoopLog(this);
|
|
436
|
-
graph.updateLog(this);
|
|
437
|
-
}
|
|
438
|
-
onSkipped(node, graph) {
|
|
439
|
-
this.state = node.state;
|
|
440
|
-
graph.setLoopLog(this);
|
|
441
|
-
graph.updateLog(this);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const getNestedData = (result, propId, propFunctions) => {
|
|
446
|
-
const match = propId.match(propFunctionRegex);
|
|
447
|
-
if (match) {
|
|
448
|
-
for (const propFunction of propFunctions) {
|
|
449
|
-
const ret = propFunction(result, propId);
|
|
450
|
-
if (!isNull(ret)) {
|
|
451
|
-
return ret;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
// for array.
|
|
456
|
-
if (Array.isArray(result)) {
|
|
457
|
-
// $0, $1. array value.
|
|
458
|
-
const regex = /^\$(\d+)$/;
|
|
459
|
-
const match = propId.match(regex);
|
|
460
|
-
if (match) {
|
|
461
|
-
const index = parseInt(match[1], 10);
|
|
462
|
-
return result[index];
|
|
463
|
-
}
|
|
464
|
-
if (propId === "$last") {
|
|
465
|
-
return result[result.length - 1];
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
else if (isObject(result)) {
|
|
469
|
-
if (propId in result) {
|
|
470
|
-
return result[propId];
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
return undefined;
|
|
474
|
-
};
|
|
475
|
-
const innerGetDataFromSource = (result, propIds, propFunctions) => {
|
|
476
|
-
if (propIds && propIds.length > 0) {
|
|
477
|
-
const propId = propIds[0];
|
|
478
|
-
const ret = getNestedData(result, propId, propFunctions);
|
|
479
|
-
if (ret === undefined) {
|
|
480
|
-
GraphAILogger.error(`prop: ${propIds.join(".")} is not hit`);
|
|
481
|
-
}
|
|
482
|
-
if (propIds.length > 1) {
|
|
483
|
-
return innerGetDataFromSource(ret, propIds.slice(1), propFunctions);
|
|
484
|
-
}
|
|
485
|
-
return ret;
|
|
486
|
-
}
|
|
487
|
-
return result;
|
|
488
|
-
};
|
|
489
|
-
const getDataFromSource = (result, source, propFunctions = []) => {
|
|
490
|
-
if (!source.nodeId) {
|
|
491
|
-
return source.value;
|
|
492
|
-
}
|
|
493
|
-
return innerGetDataFromSource(result, source.propIds, propFunctions);
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
const replaceTemplatePlaceholders = (input, templateMatch, nodes, propFunctions, isSelfNode) => {
|
|
497
|
-
// GOD format ${:node.prop1.prop2}
|
|
498
|
-
const godResults = resultsOfInner(templateMatch.filter((text) => text.startsWith(":")), nodes, propFunctions, isSelfNode);
|
|
499
|
-
// utilsFunctions ${@now}
|
|
500
|
-
const utilsFuncResult = templateMatch
|
|
501
|
-
.filter((text) => text.startsWith("@"))
|
|
502
|
-
.reduce((tmp, key) => {
|
|
503
|
-
tmp[key] = utilsFunctions(key, nodes);
|
|
504
|
-
return tmp;
|
|
505
|
-
}, {});
|
|
506
|
-
return Array.from(templateMatch.keys()).reduce((tmp, key) => {
|
|
507
|
-
if (templateMatch[key].startsWith(":")) {
|
|
508
|
-
return tmp.replaceAll("${" + templateMatch[key] + "}", godResults[key]);
|
|
509
|
-
}
|
|
510
|
-
return tmp.replaceAll("${" + templateMatch[key] + "}", utilsFuncResult[templateMatch[key]]);
|
|
511
|
-
}, input);
|
|
512
|
-
};
|
|
513
|
-
const resultsOfInner = (input, nodes, propFunctions, isSelfNode = false) => {
|
|
514
|
-
if (Array.isArray(input)) {
|
|
515
|
-
return input.map((inp) => resultsOfInner(inp, nodes, propFunctions, isSelfNode));
|
|
516
|
-
}
|
|
517
|
-
if (isNamedInputs(input)) {
|
|
518
|
-
return resultsOf(input, nodes, propFunctions, isSelfNode);
|
|
519
|
-
}
|
|
520
|
-
if (typeof input === "string") {
|
|
521
|
-
const templateMatch = [...input.matchAll(/\${([:@][^}]+)}/g)].map((m) => m[1]);
|
|
522
|
-
if (templateMatch.length > 0) {
|
|
523
|
-
return replaceTemplatePlaceholders(input, templateMatch, nodes, propFunctions, isSelfNode);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
// :node.prod
|
|
527
|
-
return resultOf(parseNodeName(input, isSelfNode, nodes), nodes, propFunctions);
|
|
528
|
-
};
|
|
529
|
-
const resultsOf = (inputs, nodes, propFunctions, isSelfNode = false) => {
|
|
530
|
-
return Object.keys(inputs).reduce((tmp, key) => {
|
|
531
|
-
const input = inputs[key];
|
|
532
|
-
tmp[key] = isNamedInputs(input) ? resultsOf(input, nodes, propFunctions, isSelfNode) : resultsOfInner(input, nodes, propFunctions, isSelfNode);
|
|
533
|
-
return tmp;
|
|
534
|
-
}, {});
|
|
535
|
-
};
|
|
536
|
-
const resultOf = (source, nodes, propFunctions) => {
|
|
537
|
-
const { result } = source.nodeId ? nodes[source.nodeId] : { result: undefined };
|
|
538
|
-
return getDataFromSource(result, source, propFunctions);
|
|
539
|
-
};
|
|
540
|
-
// clean up object for anyInput
|
|
541
|
-
const cleanResultInner = (results) => {
|
|
542
|
-
if (Array.isArray(results)) {
|
|
543
|
-
return results.map((result) => cleanResultInner(result)).filter((result) => !isNull(result));
|
|
544
|
-
}
|
|
545
|
-
if (isObject(results)) {
|
|
546
|
-
return Object.keys(results).reduce((tmp, key) => {
|
|
547
|
-
const value = cleanResultInner(results[key]);
|
|
548
|
-
if (!isNull(value)) {
|
|
549
|
-
tmp[key] = value;
|
|
550
|
-
}
|
|
551
|
-
return tmp;
|
|
552
|
-
}, {});
|
|
553
|
-
}
|
|
554
|
-
return results;
|
|
555
|
-
};
|
|
556
|
-
const cleanResult = (results) => {
|
|
557
|
-
return Object.keys(results).reduce((tmp, key) => {
|
|
558
|
-
const value = cleanResultInner(results[key]);
|
|
559
|
-
if (!isNull(value)) {
|
|
560
|
-
tmp[key] = value;
|
|
561
|
-
}
|
|
562
|
-
return tmp;
|
|
563
|
-
}, {});
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
class Node {
|
|
567
|
-
constructor(nodeId, graph) {
|
|
568
|
-
this.waitlist = new Set(); // List of nodes which need data from this node.
|
|
569
|
-
this.state = NodeState.Waiting;
|
|
570
|
-
this.result = undefined;
|
|
571
|
-
this.nodeId = nodeId;
|
|
572
|
-
this.graph = graph;
|
|
573
|
-
this.log = new TransactionLog(nodeId, this.graph.mapIndex);
|
|
574
|
-
this.console = {};
|
|
575
|
-
}
|
|
576
|
-
asString() {
|
|
577
|
-
return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
|
|
578
|
-
}
|
|
579
|
-
// This method is called either as the result of computation (computed node) or
|
|
580
|
-
// injection (static node).
|
|
581
|
-
onSetResult() {
|
|
582
|
-
this.waitlist.forEach((waitingNodeId) => {
|
|
583
|
-
const waitingNode = this.graph.nodes[waitingNodeId];
|
|
584
|
-
if (waitingNode.isComputedNode) {
|
|
585
|
-
waitingNode.removePending(this.nodeId);
|
|
586
|
-
this.graph.pushQueueIfReadyAndRunning(waitingNode);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
afterConsoleLog(result) {
|
|
591
|
-
if (this.console === false) {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
else if (this.console === true || this.console.after === true) {
|
|
595
|
-
GraphAILogger.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
596
|
-
}
|
|
597
|
-
else if (this.console.after) {
|
|
598
|
-
if (isObject(this.console.after)) {
|
|
599
|
-
GraphAILogger.log(JSON.stringify(resultsOf(this.console.after, { self: { result } }, this.graph.propFunctions, true), null, 2));
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
GraphAILogger.log(this.console.after);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
class ComputedNode extends Node {
|
|
608
|
-
constructor(graphId, nodeId, data, graph) {
|
|
609
|
-
super(nodeId, graph);
|
|
610
|
-
this.retryCount = 0;
|
|
611
|
-
this.dataSources = []; // no longer needed. This is for transaction log.
|
|
612
|
-
this.isSkip = false;
|
|
613
|
-
this.isStaticNode = false;
|
|
614
|
-
this.isComputedNode = true;
|
|
615
|
-
this.graphId = graphId;
|
|
616
|
-
this.params = data.params ?? {};
|
|
617
|
-
this.console = data.console ?? {};
|
|
618
|
-
this.filterParams = data.filterParams ?? {};
|
|
619
|
-
this.passThrough = data.passThrough;
|
|
620
|
-
this.retryLimit = data.retry ?? graph.retryLimit ?? 0;
|
|
621
|
-
this.repeatUntil = data.repeatUntil;
|
|
622
|
-
this.timeout = data.timeout;
|
|
623
|
-
this.isResult = data.isResult ?? false;
|
|
624
|
-
this.priority = data.priority ?? 0;
|
|
625
|
-
assert(["function", "string"].includes(typeof data.agent), "agent must be either string or function");
|
|
626
|
-
if (typeof data.agent === "string") {
|
|
627
|
-
this.agentId = data.agent;
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
const agent = data.agent;
|
|
631
|
-
this.agentFunction = async ({ namedInputs, params }) => agent(namedInputs, params);
|
|
632
|
-
}
|
|
633
|
-
this.anyInput = data.anyInput ?? false;
|
|
634
|
-
this.inputs = data.inputs;
|
|
635
|
-
this.output = data.output;
|
|
636
|
-
this.dataSources = [
|
|
637
|
-
...(data.inputs ? inputs2dataSources(data.inputs).flat(10) : []),
|
|
638
|
-
// ...(data.params ? inputs2dataSources(data.params).flat(10) : []),
|
|
639
|
-
...(this.agentId ? [parseNodeName(this.agentId)] : []),
|
|
640
|
-
...(data.passThrough ? inputs2dataSources(data.passThrough).flat(10) : []),
|
|
641
|
-
];
|
|
642
|
-
if (data.inputs && Array.isArray(data.inputs)) {
|
|
643
|
-
throw new Error(`array inputs have been deprecated. nodeId: ${nodeId}: see https://github.com/receptron/graphai/blob/main/docs/NamedInputs.md`);
|
|
644
|
-
}
|
|
645
|
-
this.pendings = new Set(dataSourceNodeIds(this.dataSources));
|
|
646
|
-
if (data.graph) {
|
|
647
|
-
this.nestedGraph = typeof data.graph === "string" ? this.addPendingNode(data.graph) : data.graph;
|
|
648
|
-
}
|
|
649
|
-
if (data.graphLoader && graph.graphLoader) {
|
|
650
|
-
this.nestedGraph = graph.graphLoader(data.graphLoader);
|
|
651
|
-
}
|
|
652
|
-
if (data.if) {
|
|
653
|
-
this.ifSource = this.addPendingNode(data.if);
|
|
654
|
-
}
|
|
655
|
-
if (data.unless) {
|
|
656
|
-
this.unlessSource = this.addPendingNode(data.unless);
|
|
657
|
-
}
|
|
658
|
-
if (data.defaultValue) {
|
|
659
|
-
this.defaultValue = data.defaultValue;
|
|
660
|
-
}
|
|
661
|
-
this.isSkip = false;
|
|
662
|
-
this.log.initForComputedNode(this, graph);
|
|
663
|
-
}
|
|
664
|
-
getAgentId() {
|
|
665
|
-
return this.agentId ?? "__custom__function"; // only for display purpose in the log.
|
|
666
|
-
}
|
|
667
|
-
getConfig(hasGraphData, agentId) {
|
|
668
|
-
if (agentId) {
|
|
669
|
-
if (hasGraphData) {
|
|
670
|
-
return this.graph.config;
|
|
671
|
-
}
|
|
672
|
-
const config = this.graph.config ?? {};
|
|
673
|
-
return {
|
|
674
|
-
...(config["global"] ?? {}),
|
|
675
|
-
...(config[agentId] ?? {}),
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
return {};
|
|
679
|
-
}
|
|
680
|
-
addPendingNode(nodeId) {
|
|
681
|
-
const source = parseNodeName(nodeId);
|
|
682
|
-
assert(!!source.nodeId, `Invalid data source ${nodeId}`);
|
|
683
|
-
this.pendings.add(source.nodeId);
|
|
684
|
-
return source;
|
|
685
|
-
}
|
|
686
|
-
updateState(state) {
|
|
687
|
-
this.state = state;
|
|
688
|
-
if (this.debugInfo) {
|
|
689
|
-
this.debugInfo.state = state;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
resetPending() {
|
|
693
|
-
this.pendings.clear();
|
|
694
|
-
if (this.state === NodeState.Executing) {
|
|
695
|
-
this.updateState(NodeState.Abort);
|
|
696
|
-
}
|
|
697
|
-
if (this.debugInfo && this.debugInfo.subGraphs) {
|
|
698
|
-
this.debugInfo.subGraphs.forEach((graph) => graph.abort(true));
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
isReadyNode() {
|
|
702
|
-
if (this.state !== NodeState.Waiting || this.pendings.size !== 0) {
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
this.isSkip = !!((this.ifSource && !isLogicallyTrue(this.graph.resultOf(this.ifSource))) ||
|
|
706
|
-
(this.unlessSource && isLogicallyTrue(this.graph.resultOf(this.unlessSource))));
|
|
707
|
-
if (this.isSkip && this.defaultValue === undefined) {
|
|
708
|
-
this.updateState(NodeState.Skipped);
|
|
709
|
-
this.log.onSkipped(this, this.graph);
|
|
710
|
-
return false;
|
|
711
|
-
}
|
|
712
|
-
return true;
|
|
713
|
-
}
|
|
714
|
-
// This private method (only called while executing execute()) performs
|
|
715
|
-
// the "retry" if specified. The transaction log must be updated before
|
|
716
|
-
// callling this method.
|
|
717
|
-
retry(state, error) {
|
|
718
|
-
this.updateState(state); // this.execute() will update to NodeState.Executing
|
|
719
|
-
this.log.onError(this, this.graph, error.message);
|
|
720
|
-
if (this.retryCount < this.retryLimit) {
|
|
721
|
-
this.retryCount++;
|
|
722
|
-
this.execute();
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
725
|
-
this.result = undefined;
|
|
726
|
-
this.error = error;
|
|
727
|
-
this.transactionId = undefined; // This is necessary for timeout case
|
|
728
|
-
this.graph.onExecutionComplete(this);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
checkDataAvailability() {
|
|
732
|
-
return Object.values(this.graph.resultsOf(this.inputs))
|
|
733
|
-
.flat()
|
|
734
|
-
.some((result) => result !== undefined);
|
|
735
|
-
}
|
|
736
|
-
// This method is called right before the Graph add this node to the task manager.
|
|
737
|
-
beforeAddTask() {
|
|
738
|
-
this.updateState(NodeState.Queued);
|
|
739
|
-
this.log.beforeAddTask(this, this.graph);
|
|
740
|
-
}
|
|
741
|
-
// This method is called when the data became available on one of nodes,
|
|
742
|
-
// which this node needs data from.
|
|
743
|
-
removePending(nodeId) {
|
|
744
|
-
if (this.anyInput) {
|
|
745
|
-
if (this.checkDataAvailability()) {
|
|
746
|
-
this.pendings.clear();
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
else {
|
|
750
|
-
this.pendings.delete(nodeId);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
isCurrentTransaction(transactionId) {
|
|
754
|
-
return this.transactionId === transactionId;
|
|
755
|
-
}
|
|
756
|
-
// This private method (called only fro execute) checks if the callback from
|
|
757
|
-
// the timer came before the completion of agent function call, record it
|
|
758
|
-
// and attempt to retry (if specified).
|
|
759
|
-
executeTimeout(transactionId) {
|
|
760
|
-
if (this.state === NodeState.Executing && this.isCurrentTransaction(transactionId)) {
|
|
761
|
-
GraphAILogger.warn(`-- timeout ${this.timeout} with ${this.nodeId}`);
|
|
762
|
-
this.retry(NodeState.TimedOut, Error("Timeout"));
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
// Check if we need to apply this filter to this node or not.
|
|
766
|
-
shouldApplyAgentFilter(agentFilter, agentId) {
|
|
767
|
-
if (agentFilter.agentIds && Array.isArray(agentFilter.agentIds) && agentFilter.agentIds.length > 0) {
|
|
768
|
-
if (agentId && agentFilter.agentIds.includes(agentId)) {
|
|
769
|
-
return true;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
if (agentFilter.nodeIds && Array.isArray(agentFilter.nodeIds) && agentFilter.nodeIds.length > 0) {
|
|
773
|
-
if (agentFilter.nodeIds.includes(this.nodeId)) {
|
|
774
|
-
return true;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
return !agentFilter.agentIds && !agentFilter.nodeIds;
|
|
778
|
-
}
|
|
779
|
-
agentFilterHandler(context, agentFunction, agentId) {
|
|
780
|
-
let index = 0;
|
|
781
|
-
const next = (innerContext) => {
|
|
782
|
-
const agentFilter = this.graph.agentFilters[index++];
|
|
783
|
-
if (agentFilter) {
|
|
784
|
-
if (this.shouldApplyAgentFilter(agentFilter, agentId)) {
|
|
785
|
-
if (agentFilter.filterParams) {
|
|
786
|
-
innerContext.filterParams = { ...agentFilter.filterParams, ...innerContext.filterParams };
|
|
787
|
-
}
|
|
788
|
-
return agentFilter.agent(innerContext, next);
|
|
789
|
-
}
|
|
790
|
-
return next(innerContext);
|
|
791
|
-
}
|
|
792
|
-
return agentFunction(innerContext);
|
|
793
|
-
};
|
|
794
|
-
return next(context);
|
|
795
|
-
}
|
|
796
|
-
// This method is called when this computed node became ready to run.
|
|
797
|
-
// It asynchronously calls the associated with agent function and set the result,
|
|
798
|
-
// then it removes itself from the "running node" list of the graph.
|
|
799
|
-
// Notice that setting the result of this node may make other nodes ready to run.
|
|
800
|
-
async execute() {
|
|
801
|
-
if (this.isSkip) {
|
|
802
|
-
this.afterExecute(this.defaultValue, []);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
const previousResults = this.graph.resultsOf(this.inputs, this.anyInput);
|
|
806
|
-
const agentId = this.agentId ? this.graph.resultOf(parseNodeName(this.agentId)) : this.agentId;
|
|
807
|
-
if (typeof agentId === "function") {
|
|
808
|
-
this.agentFunction = agentId;
|
|
809
|
-
}
|
|
810
|
-
const hasNestedGraph = Boolean(this.nestedGraph) || Boolean(agentId && this.graph.getAgentFunctionInfo(agentId).hasGraphData);
|
|
811
|
-
const config = this.getConfig(hasNestedGraph, agentId);
|
|
812
|
-
const transactionId = Date.now();
|
|
813
|
-
this.prepareExecute(transactionId, Object.values(previousResults));
|
|
814
|
-
if (this.timeout && this.timeout > 0) {
|
|
815
|
-
setTimeout(() => {
|
|
816
|
-
this.executeTimeout(transactionId);
|
|
817
|
-
}, this.timeout);
|
|
818
|
-
}
|
|
819
|
-
try {
|
|
820
|
-
const agentFunction = this.agentFunction ?? this.graph.getAgentFunctionInfo(agentId).agent;
|
|
821
|
-
const localLog = [];
|
|
822
|
-
const context = this.getContext(previousResults, localLog, agentId, config);
|
|
823
|
-
// NOTE: We use the existence of graph object in the agent-specific params to determine
|
|
824
|
-
// if this is a nested agent or not.
|
|
825
|
-
if (hasNestedGraph) {
|
|
826
|
-
this.graph.taskManager.prepareForNesting();
|
|
827
|
-
context.forNestedGraph = {
|
|
828
|
-
graphData: this.nestedGraph
|
|
829
|
-
? "nodes" in this.nestedGraph
|
|
830
|
-
? this.nestedGraph
|
|
831
|
-
: this.graph.resultOf(this.nestedGraph) // HACK: compiler work-around
|
|
832
|
-
: { version: 0, nodes: {} },
|
|
833
|
-
agents: this.graph.agentFunctionInfoDictionary,
|
|
834
|
-
graphOptions: {
|
|
835
|
-
agentFilters: this.graph.agentFilters,
|
|
836
|
-
taskManager: this.graph.taskManager,
|
|
837
|
-
bypassAgentIds: this.graph.bypassAgentIds,
|
|
838
|
-
config,
|
|
839
|
-
graphLoader: this.graph.graphLoader,
|
|
840
|
-
},
|
|
841
|
-
onLogCallback: this.graph.onLogCallback,
|
|
842
|
-
callbacks: this.graph.callbacks,
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
this.beforeConsoleLog(context);
|
|
846
|
-
const result = await this.agentFilterHandler(context, agentFunction, agentId);
|
|
847
|
-
this.afterConsoleLog(result);
|
|
848
|
-
if (hasNestedGraph) {
|
|
849
|
-
this.graph.taskManager.restoreAfterNesting();
|
|
850
|
-
}
|
|
851
|
-
if (!this.isCurrentTransaction(transactionId)) {
|
|
852
|
-
// This condition happens when the agent function returns
|
|
853
|
-
// after the timeout (either retried or not).
|
|
854
|
-
GraphAILogger.log(`-- transactionId mismatch with ${this.nodeId} (probably timeout)`);
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
if (this.repeatUntil?.exists) {
|
|
858
|
-
const dummyResult = { self: { result: this.getResult(result) } };
|
|
859
|
-
const repeatResult = resultsOf({ data: this.repeatUntil?.exists }, dummyResult, [], true);
|
|
860
|
-
if (isNull(repeatResult?.data)) {
|
|
861
|
-
this.retry(NodeState.Failed, Error("Repeat Until"));
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
// after process
|
|
866
|
-
this.afterExecute(result, localLog);
|
|
867
|
-
}
|
|
868
|
-
catch (error) {
|
|
869
|
-
this.errorProcess(error, transactionId, previousResults);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
afterExecute(result, localLog) {
|
|
873
|
-
if (this.state == NodeState.Abort) {
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
this.updateState(NodeState.Completed);
|
|
877
|
-
this.result = this.getResult(result);
|
|
878
|
-
if (this.output) {
|
|
879
|
-
this.result = resultsOf(this.output, { self: this }, this.graph.propFunctions, true);
|
|
880
|
-
if (this.passThrough) {
|
|
881
|
-
this.result = { ...this.result, ...this.graph.resultsOf(this.passThrough) };
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
this.log.onComplete(this, this.graph, localLog);
|
|
885
|
-
this.onSetResult();
|
|
886
|
-
this.graph.onExecutionComplete(this);
|
|
887
|
-
}
|
|
888
|
-
// This private method (called only by execute()) prepares the ComputedNode object
|
|
889
|
-
// for execution, and create a new transaction to record it.
|
|
890
|
-
prepareExecute(transactionId, inputs) {
|
|
891
|
-
this.updateState(NodeState.Executing);
|
|
892
|
-
this.log.beforeExecute(this, this.graph, transactionId, inputs);
|
|
893
|
-
this.transactionId = transactionId;
|
|
894
|
-
}
|
|
895
|
-
// This private method (called only by execute) processes an error received from
|
|
896
|
-
// the agent function. It records the error in the transaction log and handles
|
|
897
|
-
// the retry if specified.
|
|
898
|
-
errorProcess(error, transactionId, namedInputs) {
|
|
899
|
-
if (error instanceof Error && error.message !== strIntentionalError) {
|
|
900
|
-
GraphAILogger.error(`<-- NodeId: ${this.nodeId}, Agent: ${this.agentId}`);
|
|
901
|
-
GraphAILogger.error({ namedInputs });
|
|
902
|
-
GraphAILogger.error(error);
|
|
903
|
-
GraphAILogger.error("-->");
|
|
904
|
-
}
|
|
905
|
-
if (!this.isCurrentTransaction(transactionId)) {
|
|
906
|
-
GraphAILogger.warn(`-- transactionId mismatch with ${this.nodeId} (not timeout)`);
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
if (error instanceof Error) {
|
|
910
|
-
this.retry(NodeState.Failed, error);
|
|
911
|
-
}
|
|
912
|
-
else {
|
|
913
|
-
GraphAILogger.error(`-- NodeId: ${this.nodeId}: Unknown error was caught`);
|
|
914
|
-
this.retry(NodeState.Failed, Error("Unknown"));
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
getContext(previousResults, localLog, agentId, config) {
|
|
918
|
-
// Pass debugInfo by reference, and the state of this node will be received by agent/agentFilter.
|
|
919
|
-
// From graphAgent(nested, map), set the instance of graphai, and use abort on the child graphai.
|
|
920
|
-
this.debugInfo = this.getDebugInfo(agentId);
|
|
921
|
-
const params = {
|
|
922
|
-
...(this.params ?? {}),
|
|
923
|
-
...(isNamedInputs(previousResults?.params) ? previousResults?.params : {}),
|
|
924
|
-
};
|
|
925
|
-
const context = {
|
|
926
|
-
//params: this.graph.resultsOf(this.params),
|
|
927
|
-
params,
|
|
928
|
-
namedInputs: previousResults,
|
|
929
|
-
inputSchema: this.agentFunction ? undefined : this.graph.getAgentFunctionInfo(agentId)?.inputs,
|
|
930
|
-
debugInfo: this.debugInfo,
|
|
931
|
-
cacheType: this.agentFunction ? undefined : this.graph.getAgentFunctionInfo(agentId)?.cacheType,
|
|
932
|
-
filterParams: this.filterParams,
|
|
933
|
-
config,
|
|
934
|
-
log: localLog,
|
|
935
|
-
};
|
|
936
|
-
return context;
|
|
937
|
-
}
|
|
938
|
-
getResult(result) {
|
|
939
|
-
if (result && this.passThrough) {
|
|
940
|
-
if (isObject(result) && !Array.isArray(result)) {
|
|
941
|
-
return { ...result, ...this.graph.resultsOf(this.passThrough) };
|
|
942
|
-
}
|
|
943
|
-
else if (Array.isArray(result)) {
|
|
944
|
-
return result.map((r) => (isObject(r) && !Array.isArray(r) ? { ...r, ...this.graph.resultsOf(this.passThrough) } : r));
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
return result;
|
|
948
|
-
}
|
|
949
|
-
getDebugInfo(agentId) {
|
|
950
|
-
return {
|
|
951
|
-
nodeId: this.nodeId,
|
|
952
|
-
agentId,
|
|
953
|
-
retry: this.retryCount,
|
|
954
|
-
state: this.state,
|
|
955
|
-
subGraphs: new Map(),
|
|
956
|
-
verbose: this.graph.verbose,
|
|
957
|
-
version: this.graph.version,
|
|
958
|
-
isResult: this.isResult,
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
beforeConsoleLog(context) {
|
|
962
|
-
if (this.console === false) {
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
else if (this.console === true || this.console.before === true) {
|
|
966
|
-
GraphAILogger.log(JSON.stringify(context.namedInputs, null, 2));
|
|
967
|
-
}
|
|
968
|
-
else if (this.console.before) {
|
|
969
|
-
GraphAILogger.log(this.console.before);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
class StaticNode extends Node {
|
|
974
|
-
constructor(nodeId, data, graph) {
|
|
975
|
-
super(nodeId, graph);
|
|
976
|
-
this.isStaticNode = true;
|
|
977
|
-
this.isComputedNode = false;
|
|
978
|
-
this.value = data.value;
|
|
979
|
-
this.update = data.update ? parseNodeName(data.update) : undefined;
|
|
980
|
-
this.isResult = data.isResult ?? false;
|
|
981
|
-
this.console = data.console ?? {};
|
|
982
|
-
}
|
|
983
|
-
updateValue(value, injectFrom) {
|
|
984
|
-
this.value = value;
|
|
985
|
-
this.log.onInjected(this, this.graph, injectFrom);
|
|
986
|
-
}
|
|
987
|
-
setResultValue(injectFrom) {
|
|
988
|
-
this.state = NodeState.Injected;
|
|
989
|
-
this.result = this.value;
|
|
990
|
-
this.log.onInjected(this, this.graph, injectFrom);
|
|
991
|
-
this.onSetResult();
|
|
992
|
-
}
|
|
993
|
-
consoleLog() {
|
|
994
|
-
this.afterConsoleLog(this.result);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
const graphDataAttributeKeys = ["nodes", "concurrency", "agentId", "loop", "verbose", "version", "metadata"];
|
|
999
|
-
const computedNodeAttributeKeys = [
|
|
1000
|
-
"inputs",
|
|
1001
|
-
"output",
|
|
1002
|
-
"anyInput",
|
|
1003
|
-
"params",
|
|
1004
|
-
"retry",
|
|
1005
|
-
"repeatUntil",
|
|
1006
|
-
"timeout",
|
|
1007
|
-
"agent",
|
|
1008
|
-
"graph",
|
|
1009
|
-
"graphLoader",
|
|
1010
|
-
"isResult",
|
|
1011
|
-
"priority",
|
|
1012
|
-
"if",
|
|
1013
|
-
"unless",
|
|
1014
|
-
"defaultValue",
|
|
1015
|
-
"filterParams",
|
|
1016
|
-
"console",
|
|
1017
|
-
"passThrough",
|
|
1018
|
-
];
|
|
1019
|
-
const staticNodeAttributeKeys = ["value", "update", "isResult", "console"];
|
|
1020
|
-
class ValidationError extends Error {
|
|
1021
|
-
constructor(message) {
|
|
1022
|
-
super(`\x1b[41m${message}\x1b[0m`); // Pass the message to the base Error class
|
|
1023
|
-
// Set the prototype explicitly to ensure correct prototype chain
|
|
1024
|
-
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
const graphNodesValidator = (data) => {
|
|
1029
|
-
if (data.nodes === undefined) {
|
|
1030
|
-
throw new ValidationError("Invalid Graph Data: no nodes");
|
|
1031
|
-
}
|
|
1032
|
-
if (typeof data.nodes !== "object") {
|
|
1033
|
-
throw new ValidationError("Invalid Graph Data: invalid nodes");
|
|
1034
|
-
}
|
|
1035
|
-
if (Array.isArray(data.nodes)) {
|
|
1036
|
-
throw new ValidationError("Invalid Graph Data: nodes must be object");
|
|
1037
|
-
}
|
|
1038
|
-
if (Object.keys(data.nodes).length === 0) {
|
|
1039
|
-
throw new ValidationError("Invalid Graph Data: nodes is empty");
|
|
1040
|
-
}
|
|
1041
|
-
Object.keys(data).forEach((key) => {
|
|
1042
|
-
if (!graphDataAttributeKeys.includes(key)) {
|
|
1043
|
-
throw new ValidationError("Graph Data does not allow " + key);
|
|
1044
|
-
}
|
|
1045
|
-
});
|
|
1046
|
-
};
|
|
1047
|
-
const graphDataValidator = (data) => {
|
|
1048
|
-
if (data.loop) {
|
|
1049
|
-
if (data.loop.count === undefined && data.loop.while === undefined) {
|
|
1050
|
-
throw new ValidationError("Loop: Either count or while is required in loop");
|
|
1051
|
-
}
|
|
1052
|
-
if (data.loop.count !== undefined && data.loop.while !== undefined) {
|
|
1053
|
-
throw new ValidationError("Loop: Both count and while cannot be set");
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
if (data.concurrency !== undefined) {
|
|
1057
|
-
if (!Number.isInteger(data.concurrency)) {
|
|
1058
|
-
throw new ValidationError("Concurrency must be an integer");
|
|
1059
|
-
}
|
|
1060
|
-
if (data.concurrency < 1) {
|
|
1061
|
-
throw new ValidationError("Concurrency must be a positive integer");
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
const nodeValidator = (nodeData) => {
|
|
1067
|
-
if (nodeData.agent && nodeData.value) {
|
|
1068
|
-
throw new ValidationError("Cannot set both agent and value");
|
|
1069
|
-
}
|
|
1070
|
-
// if (!("agent" in nodeData) && !("value" in nodeData)) {
|
|
1071
|
-
// throw new ValidationError("Either agent or value is required");
|
|
1072
|
-
// }
|
|
1073
|
-
return true;
|
|
1074
|
-
};
|
|
1075
|
-
|
|
1076
|
-
const staticNodeValidator = (nodeData) => {
|
|
1077
|
-
Object.keys(nodeData).forEach((key) => {
|
|
1078
|
-
if (!staticNodeAttributeKeys.includes(key)) {
|
|
1079
|
-
throw new ValidationError("Static node does not allow " + key);
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
return true;
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1085
|
-
const computedNodeValidator = (nodeData) => {
|
|
1086
|
-
Object.keys(nodeData).forEach((key) => {
|
|
1087
|
-
if (!computedNodeAttributeKeys.includes(key)) {
|
|
1088
|
-
throw new ValidationError("Computed node does not allow " + key);
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
|
-
return true;
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
const relationValidator = (graphData, staticNodeIds, computedNodeIds) => {
|
|
1095
|
-
const nodeIds = new Set(Object.keys(graphData.nodes));
|
|
1096
|
-
const pendings = {};
|
|
1097
|
-
const waitlist = {};
|
|
1098
|
-
// validate input relation and set pendings and wait list
|
|
1099
|
-
computedNodeIds.forEach((computedNodeId) => {
|
|
1100
|
-
const nodeData = graphData.nodes[computedNodeId];
|
|
1101
|
-
pendings[computedNodeId] = new Set();
|
|
1102
|
-
const dataSourceValidator = (sourceType, sourceNodeIds) => {
|
|
1103
|
-
sourceNodeIds.forEach((sourceNodeId) => {
|
|
1104
|
-
if (sourceNodeId) {
|
|
1105
|
-
if (!nodeIds.has(sourceNodeId)) {
|
|
1106
|
-
throw new ValidationError(`${sourceType} not match: NodeId ${computedNodeId}, Inputs: ${sourceNodeId}`);
|
|
1107
|
-
}
|
|
1108
|
-
waitlist[sourceNodeId] === undefined && (waitlist[sourceNodeId] = new Set());
|
|
1109
|
-
pendings[computedNodeId].add(sourceNodeId);
|
|
1110
|
-
waitlist[sourceNodeId].add(computedNodeId);
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
};
|
|
1114
|
-
if (nodeData && isComputedNodeData(nodeData)) {
|
|
1115
|
-
if (nodeData.inputs) {
|
|
1116
|
-
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources(nodeData.inputs));
|
|
1117
|
-
dataSourceValidator("Inputs", sourceNodeIds);
|
|
1118
|
-
}
|
|
1119
|
-
/*
|
|
1120
|
-
if (nodeData.params) {
|
|
1121
|
-
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources(nodeData.params));
|
|
1122
|
-
dataSourceValidator("Params", sourceNodeIds);
|
|
1123
|
-
}
|
|
1124
|
-
*/
|
|
1125
|
-
if (nodeData.if) {
|
|
1126
|
-
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ if: nodeData.if }));
|
|
1127
|
-
dataSourceValidator("If", sourceNodeIds);
|
|
1128
|
-
}
|
|
1129
|
-
if (nodeData.unless) {
|
|
1130
|
-
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ unless: nodeData.unless }));
|
|
1131
|
-
dataSourceValidator("Unless", sourceNodeIds);
|
|
1132
|
-
}
|
|
1133
|
-
if (nodeData.graph && typeof nodeData?.graph === "string") {
|
|
1134
|
-
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ graph: nodeData.graph }));
|
|
1135
|
-
dataSourceValidator("Graph", sourceNodeIds);
|
|
1136
|
-
}
|
|
1137
|
-
if (typeof nodeData.agent === "string" && nodeData.agent[0] === ":") {
|
|
1138
|
-
const sourceNodeIds = dataSourceNodeIds(inputs2dataSources({ agent: nodeData.agent }));
|
|
1139
|
-
dataSourceValidator("Agent", sourceNodeIds);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
});
|
|
1143
|
-
// TODO. validate update
|
|
1144
|
-
staticNodeIds.forEach((staticNodeId) => {
|
|
1145
|
-
const nodeData = graphData.nodes[staticNodeId];
|
|
1146
|
-
if (isStaticNodeData(nodeData) && nodeData.update) {
|
|
1147
|
-
const update = nodeData.update;
|
|
1148
|
-
const updateNodeId = parseNodeName(update).nodeId;
|
|
1149
|
-
if (!updateNodeId) {
|
|
1150
|
-
throw new ValidationError("Update it a literal");
|
|
1151
|
-
}
|
|
1152
|
-
if (!nodeIds.has(updateNodeId)) {
|
|
1153
|
-
throw new ValidationError(`Update not match: NodeId ${staticNodeId}, update: ${update}`);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
});
|
|
1157
|
-
const cycle = (possibles) => {
|
|
1158
|
-
possibles.forEach((possobleNodeId) => {
|
|
1159
|
-
(waitlist[possobleNodeId] || []).forEach((waitingNodeId) => {
|
|
1160
|
-
pendings[waitingNodeId].delete(possobleNodeId);
|
|
1161
|
-
});
|
|
1162
|
-
});
|
|
1163
|
-
const running = [];
|
|
1164
|
-
Object.keys(pendings).forEach((pendingNodeId) => {
|
|
1165
|
-
if (pendings[pendingNodeId].size === 0) {
|
|
1166
|
-
running.push(pendingNodeId);
|
|
1167
|
-
delete pendings[pendingNodeId];
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
return running;
|
|
1171
|
-
};
|
|
1172
|
-
let runningQueue = cycle(staticNodeIds);
|
|
1173
|
-
if (runningQueue.length === 0) {
|
|
1174
|
-
throw new ValidationError("No Initial Runnning Node");
|
|
1175
|
-
}
|
|
1176
|
-
do {
|
|
1177
|
-
runningQueue = cycle(runningQueue);
|
|
1178
|
-
} while (runningQueue.length > 0);
|
|
1179
|
-
if (Object.keys(pendings).length > 0) {
|
|
1180
|
-
throw new ValidationError("Some nodes are not executed: " + Object.keys(pendings).join(", "));
|
|
1181
|
-
}
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
const agentValidator = (graphAgentIds, agentIds) => {
|
|
1185
|
-
graphAgentIds.forEach((agentId) => {
|
|
1186
|
-
// agentId or dynamic agentId
|
|
1187
|
-
if (!agentIds.has(agentId) && agentId[0] !== ":") {
|
|
1188
|
-
throw new ValidationError("Invalid Agent : " + agentId + " is not in AgentFunctionInfoDictionary.");
|
|
1189
|
-
}
|
|
1190
|
-
});
|
|
1191
|
-
return true;
|
|
1192
|
-
};
|
|
1193
|
-
|
|
1194
|
-
const validateGraphData = (data, agentIds) => {
|
|
1195
|
-
graphNodesValidator(data);
|
|
1196
|
-
graphDataValidator(data);
|
|
1197
|
-
const computedNodeIds = [];
|
|
1198
|
-
const staticNodeIds = [];
|
|
1199
|
-
const graphAgentIds = new Set();
|
|
1200
|
-
Object.keys(data.nodes).forEach((nodeId) => {
|
|
1201
|
-
const node = data.nodes[nodeId];
|
|
1202
|
-
const isStaticNode = isStaticNodeData(node);
|
|
1203
|
-
nodeValidator(node);
|
|
1204
|
-
const agentId = isStaticNode ? "" : node.agent;
|
|
1205
|
-
isStaticNode && staticNodeValidator(node) && staticNodeIds.push(nodeId);
|
|
1206
|
-
!isStaticNode && computedNodeValidator(node) && computedNodeIds.push(nodeId) && typeof agentId === "string" && graphAgentIds.add(agentId);
|
|
1207
|
-
});
|
|
1208
|
-
agentValidator(graphAgentIds, new Set(agentIds));
|
|
1209
|
-
relationValidator(data, staticNodeIds, computedNodeIds);
|
|
1210
|
-
return true;
|
|
1211
|
-
};
|
|
1212
|
-
const validateAgent = (agentFunctionInfoDictionary) => {
|
|
1213
|
-
Object.keys(agentFunctionInfoDictionary).forEach((agentId) => {
|
|
1214
|
-
if (agentId !== "default") {
|
|
1215
|
-
const agentInfo = agentFunctionInfoDictionary[agentId];
|
|
1216
|
-
if (!agentInfo || !agentInfo.agent) {
|
|
1217
|
-
throw new ValidationError("No Agent: " + agentId + " is not in AgentFunctionInfoDictionary.");
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
};
|
|
1222
|
-
|
|
1223
|
-
// TaskManage object controls the concurrency of ComputedNode execution.
|
|
1224
|
-
//
|
|
1225
|
-
// NOTE: A TaskManager instance will be shared between parent graph and its children
|
|
1226
|
-
// when nested agents are involved.
|
|
1227
|
-
class TaskManager {
|
|
1228
|
-
constructor(concurrency) {
|
|
1229
|
-
this.taskQueue = [];
|
|
1230
|
-
this.runningNodes = new Set();
|
|
1231
|
-
this.concurrency = concurrency;
|
|
1232
|
-
}
|
|
1233
|
-
// This internal method dequeus a task from the task queue
|
|
1234
|
-
// and call the associated callback method, if the number of
|
|
1235
|
-
// running task is lower than the spcified limit.
|
|
1236
|
-
dequeueTaskIfPossible() {
|
|
1237
|
-
if (this.runningNodes.size < this.concurrency) {
|
|
1238
|
-
const task = this.taskQueue.shift();
|
|
1239
|
-
if (task) {
|
|
1240
|
-
this.runningNodes.add(task.node);
|
|
1241
|
-
task.callback(task.node);
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
// Node will call this method to put itself in the execution queue.
|
|
1246
|
-
// We call the associated callback function when it is dequeued.
|
|
1247
|
-
addTask(node, graphId, callback) {
|
|
1248
|
-
// Finder tasks in the queue, which has either the same or higher priority.
|
|
1249
|
-
const count = this.taskQueue.filter((task) => {
|
|
1250
|
-
return task.node.priority >= node.priority;
|
|
1251
|
-
}).length;
|
|
1252
|
-
assert(count <= this.taskQueue.length, "TaskManager.addTask: Something is really wrong.");
|
|
1253
|
-
this.taskQueue.splice(count, 0, { node, graphId, callback });
|
|
1254
|
-
this.dequeueTaskIfPossible();
|
|
1255
|
-
}
|
|
1256
|
-
isRunning(graphId) {
|
|
1257
|
-
const count = [...this.runningNodes].filter((node) => {
|
|
1258
|
-
return node.graphId == graphId;
|
|
1259
|
-
}).length;
|
|
1260
|
-
return count > 0 || Array.from(this.taskQueue).filter((data) => data.graphId === graphId).length > 0;
|
|
1261
|
-
}
|
|
1262
|
-
// Node MUST call this method once the execution of agent function is completed
|
|
1263
|
-
// either successfully or not.
|
|
1264
|
-
onComplete(node) {
|
|
1265
|
-
assert(this.runningNodes.has(node), `TaskManager.onComplete node(${node.nodeId}) is not in list`);
|
|
1266
|
-
this.runningNodes.delete(node);
|
|
1267
|
-
this.dequeueTaskIfPossible();
|
|
1268
|
-
}
|
|
1269
|
-
// Node will call this method before it hands the task manager from the graph
|
|
1270
|
-
// to a nested agent. We need to make it sure that there is enough room to run
|
|
1271
|
-
// computed nodes inside the nested graph to avoid a deadlock.
|
|
1272
|
-
prepareForNesting() {
|
|
1273
|
-
this.concurrency++;
|
|
1274
|
-
}
|
|
1275
|
-
restoreAfterNesting() {
|
|
1276
|
-
this.concurrency--;
|
|
1277
|
-
}
|
|
1278
|
-
getStatus(verbose = false) {
|
|
1279
|
-
const runningNodes = Array.from(this.runningNodes).map((node) => node.nodeId);
|
|
1280
|
-
const queuedNodes = this.taskQueue.map((task) => task.node.nodeId);
|
|
1281
|
-
const nodes = verbose ? { runningNodes, queuedNodes } : {};
|
|
1282
|
-
return {
|
|
1283
|
-
concurrency: this.concurrency,
|
|
1284
|
-
queue: this.taskQueue.length,
|
|
1285
|
-
running: this.runningNodes.size,
|
|
1286
|
-
...nodes,
|
|
1287
|
-
};
|
|
1288
|
-
}
|
|
1289
|
-
reset() {
|
|
1290
|
-
this.taskQueue.length = 0;
|
|
1291
|
-
this.runningNodes.clear();
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
const defaultConcurrency = 8;
|
|
1296
|
-
const graphDataLatestVersion = 0.5;
|
|
1297
|
-
class GraphAI {
|
|
1298
|
-
// This method is called when either the GraphAI obect was created,
|
|
1299
|
-
// or we are about to start n-th iteration (n>2).
|
|
1300
|
-
createNodes(graphData) {
|
|
1301
|
-
const nodes = Object.keys(graphData.nodes).reduce((_nodes, nodeId) => {
|
|
1302
|
-
const nodeData = graphData.nodes[nodeId];
|
|
1303
|
-
if (isComputedNodeData(nodeData)) {
|
|
1304
|
-
_nodes[nodeId] = new ComputedNode(this.graphId, nodeId, nodeData, this);
|
|
1305
|
-
}
|
|
1306
|
-
else {
|
|
1307
|
-
const updateValue = this.staticNodeInitData[nodeId];
|
|
1308
|
-
_nodes[nodeId] = new StaticNode(nodeId, updateValue !== undefined ? { ...nodeData, value: updateValue } : nodeData, this);
|
|
1309
|
-
}
|
|
1310
|
-
return _nodes;
|
|
1311
|
-
}, {});
|
|
1312
|
-
// Generate the waitlist for each node.
|
|
1313
|
-
Object.keys(nodes).forEach((nodeId) => {
|
|
1314
|
-
const node = nodes[nodeId];
|
|
1315
|
-
if (node.isComputedNode) {
|
|
1316
|
-
node.pendings.forEach((pending) => {
|
|
1317
|
-
if (nodes[pending]) {
|
|
1318
|
-
nodes[pending].waitlist.add(nodeId); // previousNode
|
|
1319
|
-
}
|
|
1320
|
-
else {
|
|
1321
|
-
throw new Error(`createNode: invalid input ${pending} for node, ${nodeId}`);
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
});
|
|
1326
|
-
return nodes;
|
|
1327
|
-
}
|
|
1328
|
-
getValueFromResults(source, results) {
|
|
1329
|
-
return getDataFromSource(source.nodeId ? results[source.nodeId] : undefined, source, this.propFunctions);
|
|
1330
|
-
}
|
|
1331
|
-
// for static
|
|
1332
|
-
setStaticNodeResults(enableConsoleLog = false) {
|
|
1333
|
-
// If the result property is specified, inject it.
|
|
1334
|
-
// If the previousResults exists (indicating we are in a loop),
|
|
1335
|
-
// process the update property (nodeId or nodeId.propId).
|
|
1336
|
-
Object.keys(this.graphData.nodes).forEach((nodeId) => {
|
|
1337
|
-
const node = this.nodes[nodeId];
|
|
1338
|
-
if (node?.isStaticNode) {
|
|
1339
|
-
const value = node?.value;
|
|
1340
|
-
if (value !== undefined) {
|
|
1341
|
-
node.setResultValue(nodeId);
|
|
1342
|
-
}
|
|
1343
|
-
if (enableConsoleLog) {
|
|
1344
|
-
node.consoleLog();
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
updateStaticNodes(previousResults, enableConsoleLog = false) {
|
|
1350
|
-
// If the result property is specified, inject it.
|
|
1351
|
-
// If the previousResults exists (indicating we are in a loop),
|
|
1352
|
-
// process the update property (nodeId or nodeId.propId).
|
|
1353
|
-
Object.keys(this.graphData.nodes).forEach((nodeId) => {
|
|
1354
|
-
const node = this.nodes[nodeId];
|
|
1355
|
-
if (node?.isStaticNode) {
|
|
1356
|
-
const update = node?.update;
|
|
1357
|
-
if (update && previousResults) {
|
|
1358
|
-
const result = this.getValueFromResults(update, previousResults);
|
|
1359
|
-
this.updateStaticNodeValue(nodeId, result, update.nodeId);
|
|
1360
|
-
}
|
|
1361
|
-
if (enableConsoleLog) {
|
|
1362
|
-
node.consoleLog();
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
constructor(graphData, agentFunctionInfoDictionary, options = {
|
|
1368
|
-
taskManager: undefined,
|
|
1369
|
-
agentFilters: [],
|
|
1370
|
-
bypassAgentIds: [],
|
|
1371
|
-
config: {},
|
|
1372
|
-
graphLoader: undefined,
|
|
1373
|
-
forceLoop: false,
|
|
1374
|
-
mapIndex: undefined,
|
|
1375
|
-
}) {
|
|
1376
|
-
this.staticNodeInitData = {};
|
|
1377
|
-
this.logs = [];
|
|
1378
|
-
this.config = {};
|
|
1379
|
-
this.onLogCallback = (__log, __isUpdate) => { };
|
|
1380
|
-
this.callbacks = [];
|
|
1381
|
-
this.repeatCount = 0;
|
|
1382
|
-
if (!graphData.version && !options.taskManager) {
|
|
1383
|
-
GraphAILogger.warn("------------ missing version number");
|
|
1384
|
-
}
|
|
1385
|
-
this.version = graphData.version ?? graphDataLatestVersion;
|
|
1386
|
-
if (this.version < graphDataLatestVersion) {
|
|
1387
|
-
GraphAILogger.warn(`------------ upgrade to ${graphDataLatestVersion}!`);
|
|
1388
|
-
}
|
|
1389
|
-
this.retryLimit = graphData.retry; // optional
|
|
1390
|
-
this.graphId = `${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}`; // URL.createObjectURL(new Blob()).slice(-36);
|
|
1391
|
-
this.agentFunctionInfoDictionary = agentFunctionInfoDictionary;
|
|
1392
|
-
this.propFunctions = propFunctions;
|
|
1393
|
-
this.taskManager = options.taskManager ?? new TaskManager(graphData.concurrency ?? defaultConcurrency);
|
|
1394
|
-
this.agentFilters = options.agentFilters ?? [];
|
|
1395
|
-
this.bypassAgentIds = options.bypassAgentIds ?? [];
|
|
1396
|
-
this.config = options.config;
|
|
1397
|
-
this.graphLoader = options.graphLoader;
|
|
1398
|
-
this.forceLoop = options.forceLoop ?? false;
|
|
1399
|
-
this.mapIndex = options.mapIndex;
|
|
1400
|
-
this.loop = graphData.loop;
|
|
1401
|
-
this.verbose = graphData.verbose === true;
|
|
1402
|
-
this.onComplete = (__isAbort) => {
|
|
1403
|
-
throw new Error("SOMETHING IS WRONG: onComplete is called without run()");
|
|
1404
|
-
};
|
|
1405
|
-
validateGraphData(graphData, [...Object.keys(agentFunctionInfoDictionary), ...this.bypassAgentIds]);
|
|
1406
|
-
validateAgent(agentFunctionInfoDictionary);
|
|
1407
|
-
this.graphData = {
|
|
1408
|
-
...graphData,
|
|
1409
|
-
nodes: {
|
|
1410
|
-
...graphData.nodes,
|
|
1411
|
-
[loopCounterKey]: { value: 0, update: `:${loopCounterKey}.add(1)` },
|
|
1412
|
-
},
|
|
1413
|
-
};
|
|
1414
|
-
this.nodes = this.createNodes(this.graphData);
|
|
1415
|
-
}
|
|
1416
|
-
getAgentFunctionInfo(agentId) {
|
|
1417
|
-
if (agentId && this.agentFunctionInfoDictionary[agentId]) {
|
|
1418
|
-
return this.agentFunctionInfoDictionary[agentId];
|
|
1419
|
-
}
|
|
1420
|
-
if (agentId && this.bypassAgentIds.includes(agentId)) {
|
|
1421
|
-
return {
|
|
1422
|
-
agent: async () => {
|
|
1423
|
-
return null;
|
|
1424
|
-
},
|
|
1425
|
-
hasGraphData: false,
|
|
1426
|
-
inputs: null,
|
|
1427
|
-
cacheType: undefined, // for node.getContext
|
|
1428
|
-
};
|
|
1429
|
-
}
|
|
1430
|
-
// We are not supposed to hit this error because the validator will catch it.
|
|
1431
|
-
throw new Error("No agent: " + agentId);
|
|
1432
|
-
}
|
|
1433
|
-
asString() {
|
|
1434
|
-
return Object.values(this.nodes)
|
|
1435
|
-
.map((node) => node.asString())
|
|
1436
|
-
.join("\n");
|
|
1437
|
-
}
|
|
1438
|
-
// Public API
|
|
1439
|
-
results(all, internalUse = false) {
|
|
1440
|
-
return Object.keys(this.nodes)
|
|
1441
|
-
.filter((nodeId) => (all && (internalUse || nodeId !== loopCounterKey)) || this.nodes[nodeId].isResult)
|
|
1442
|
-
.reduce((results, nodeId) => {
|
|
1443
|
-
const node = this.nodes[nodeId];
|
|
1444
|
-
if (node.result !== undefined) {
|
|
1445
|
-
results[nodeId] = node.result;
|
|
1446
|
-
}
|
|
1447
|
-
return results;
|
|
1448
|
-
}, {});
|
|
1449
|
-
}
|
|
1450
|
-
// Public API
|
|
1451
|
-
errors() {
|
|
1452
|
-
return Object.keys(this.nodes).reduce((errors, nodeId) => {
|
|
1453
|
-
const node = this.nodes[nodeId];
|
|
1454
|
-
if (node.isComputedNode) {
|
|
1455
|
-
if (node.error !== undefined) {
|
|
1456
|
-
errors[nodeId] = node.error;
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
return errors;
|
|
1460
|
-
}, {});
|
|
1461
|
-
}
|
|
1462
|
-
pushReadyNodesIntoQueue() {
|
|
1463
|
-
// Nodes without pending data should run immediately.
|
|
1464
|
-
Object.keys(this.nodes).forEach((nodeId) => {
|
|
1465
|
-
const node = this.nodes[nodeId];
|
|
1466
|
-
if (node.isComputedNode) {
|
|
1467
|
-
this.pushQueueIfReady(node);
|
|
1468
|
-
}
|
|
1469
|
-
});
|
|
1470
|
-
}
|
|
1471
|
-
pushQueueIfReady(node) {
|
|
1472
|
-
if (node.isReadyNode()) {
|
|
1473
|
-
this.pushQueue(node);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
pushQueueIfReadyAndRunning(node) {
|
|
1477
|
-
if (this.isRunning()) {
|
|
1478
|
-
this.pushQueueIfReady(node);
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
// for computed
|
|
1482
|
-
pushQueue(node) {
|
|
1483
|
-
node.beforeAddTask();
|
|
1484
|
-
this.taskManager.addTask(node, this.graphId, (_node) => {
|
|
1485
|
-
assert(node.nodeId === _node.nodeId, "GraphAI.pushQueue node mismatch");
|
|
1486
|
-
node.execute();
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
// Public API
|
|
1490
|
-
async run(all = false) {
|
|
1491
|
-
this.setStaticNodeResults();
|
|
1492
|
-
const invalidStaticNodes = Object.values(this.nodes)
|
|
1493
|
-
.filter((node) => node.isStaticNode)
|
|
1494
|
-
.filter((node) => node.result === undefined && node.update === undefined);
|
|
1495
|
-
if (invalidStaticNodes.length > 0) {
|
|
1496
|
-
const nodeIds = invalidStaticNodes.map((node) => node.nodeId).join(", ");
|
|
1497
|
-
throw new Error(`Static node(s) must have value. Set value, injectValue, or set update. Affected nodeIds: ${nodeIds}`);
|
|
1498
|
-
}
|
|
1499
|
-
if (this.isRunning()) {
|
|
1500
|
-
throw new Error("This GraphAI instance is already running");
|
|
1501
|
-
}
|
|
1502
|
-
this.pushReadyNodesIntoQueue();
|
|
1503
|
-
if (!this.isRunning()) {
|
|
1504
|
-
GraphAILogger.warn("-- nothing to execute");
|
|
1505
|
-
return {};
|
|
1506
|
-
}
|
|
1507
|
-
return new Promise((resolve, reject) => {
|
|
1508
|
-
this.onComplete = (isAbort = false) => {
|
|
1509
|
-
const errors = this.errors();
|
|
1510
|
-
const errorNodeIds = Object.keys(errors);
|
|
1511
|
-
if (errorNodeIds.length > 0 || isAbort) {
|
|
1512
|
-
reject(errors[errorNodeIds[0]]);
|
|
1513
|
-
}
|
|
1514
|
-
else {
|
|
1515
|
-
resolve(this.results(all));
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
abort(isChild = false) {
|
|
1521
|
-
if (this.isRunning()) {
|
|
1522
|
-
this.resetPending();
|
|
1523
|
-
// Stop All Running node.
|
|
1524
|
-
}
|
|
1525
|
-
// For an agent like an event agent, where an external promise remains unresolved,
|
|
1526
|
-
// aborting and then retrying can cause nodes or the graph to execute again.
|
|
1527
|
-
// To prevent this, the transactionId is updated to ensure the retry fails.
|
|
1528
|
-
Object.values(this.nodes).forEach((node) => node.isComputedNode && (node.transactionId = undefined));
|
|
1529
|
-
if (!isChild) {
|
|
1530
|
-
this.taskManager.reset();
|
|
1531
|
-
}
|
|
1532
|
-
this.onComplete(this.isRunning());
|
|
1533
|
-
}
|
|
1534
|
-
resetPending() {
|
|
1535
|
-
Object.values(this.nodes).map((node) => {
|
|
1536
|
-
if (node.isComputedNode) {
|
|
1537
|
-
node.resetPending();
|
|
1538
|
-
}
|
|
1539
|
-
});
|
|
1540
|
-
}
|
|
1541
|
-
// Public only for testing
|
|
1542
|
-
isRunning() {
|
|
1543
|
-
return this.taskManager.isRunning(this.graphId);
|
|
1544
|
-
}
|
|
1545
|
-
// callback from execute
|
|
1546
|
-
onExecutionComplete(node) {
|
|
1547
|
-
this.taskManager.onComplete(node);
|
|
1548
|
-
if (this.isRunning() || this.processLoopIfNecessary()) {
|
|
1549
|
-
return; // continue running
|
|
1550
|
-
}
|
|
1551
|
-
if (this.verbose) {
|
|
1552
|
-
const notExecutedNodes = Object.values(this.nodes).filter((node) => node.isComputedNode && node.state !== "completed");
|
|
1553
|
-
if (notExecutedNodes.length > 0) {
|
|
1554
|
-
console.log("Those nodes are not running. " + notExecutedNodes.map((node) => `${node.nodeId}: ${node.state}`).join(", "));
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
this.onComplete(false); // Nothing to run. Finish it.
|
|
1558
|
-
}
|
|
1559
|
-
// Must be called only from onExecutionComplete righ after removeRunning
|
|
1560
|
-
// Check if there is any running computed nodes.
|
|
1561
|
-
// In case of no running computed note, start the another iteration if ncessary (loop)
|
|
1562
|
-
processLoopIfNecessary() {
|
|
1563
|
-
//
|
|
1564
|
-
if (!this.forceLoop && Object.keys(this.errors()).length > 0) {
|
|
1565
|
-
return false;
|
|
1566
|
-
}
|
|
1567
|
-
this.repeatCount++;
|
|
1568
|
-
const loop = this.loop;
|
|
1569
|
-
if (!loop) {
|
|
1570
|
-
return false;
|
|
1571
|
-
}
|
|
1572
|
-
// We need to update static nodes, before checking the condition
|
|
1573
|
-
const previousResults = this.results(true, true); // results from previous loop
|
|
1574
|
-
this.updateStaticNodes(previousResults);
|
|
1575
|
-
this.setStaticNodeResults();
|
|
1576
|
-
if (loop.count === undefined || this.repeatCount < loop.count) {
|
|
1577
|
-
if (loop.while) {
|
|
1578
|
-
const source = parseNodeName(loop.while);
|
|
1579
|
-
const value = this.getValueFromResults(source, this.results(true, true));
|
|
1580
|
-
// NOTE: We treat an empty array as false.
|
|
1581
|
-
if (!isLogicallyTrue(value)) {
|
|
1582
|
-
return false; // while condition is not met
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
this.nodes = this.createNodes(this.graphData);
|
|
1586
|
-
this.updateStaticNodes(previousResults, true);
|
|
1587
|
-
this.setStaticNodeResults();
|
|
1588
|
-
this.pushReadyNodesIntoQueue();
|
|
1589
|
-
return true; // Indicating that we are going to continue.
|
|
1590
|
-
}
|
|
1591
|
-
return false;
|
|
1592
|
-
}
|
|
1593
|
-
initializeGraphAI() {
|
|
1594
|
-
if (this.isRunning()) {
|
|
1595
|
-
throw new Error("This GraphAI instance is running");
|
|
1596
|
-
}
|
|
1597
|
-
this.nodes = this.createNodes(this.graphData);
|
|
1598
|
-
this.setStaticNodeResults();
|
|
1599
|
-
}
|
|
1600
|
-
setPreviousResults(previousResults) {
|
|
1601
|
-
this.updateStaticNodes(previousResults);
|
|
1602
|
-
}
|
|
1603
|
-
setLoopLog(log) {
|
|
1604
|
-
log.isLoop = !!this.loop;
|
|
1605
|
-
log.repeatCount = this.repeatCount;
|
|
1606
|
-
}
|
|
1607
|
-
appendLog(log) {
|
|
1608
|
-
this.logs.push(log);
|
|
1609
|
-
this.onLogCallback(log, false);
|
|
1610
|
-
this.callbacks.forEach((callback) => callback(log, false));
|
|
1611
|
-
}
|
|
1612
|
-
updateLog(log) {
|
|
1613
|
-
this.onLogCallback(log, true);
|
|
1614
|
-
this.callbacks.forEach((callback) => callback(log, false));
|
|
1615
|
-
}
|
|
1616
|
-
registerCallback(callback) {
|
|
1617
|
-
this.callbacks.push(callback);
|
|
1618
|
-
}
|
|
1619
|
-
clearCallbacks() {
|
|
1620
|
-
this.callbacks = [];
|
|
1621
|
-
}
|
|
1622
|
-
// Public API
|
|
1623
|
-
transactionLogs() {
|
|
1624
|
-
return this.logs;
|
|
1625
|
-
}
|
|
1626
|
-
// Public API
|
|
1627
|
-
injectValue(nodeId, value, injectFrom) {
|
|
1628
|
-
this.staticNodeInitData[nodeId] = value;
|
|
1629
|
-
this.updateStaticNodeValue(nodeId, value, injectFrom);
|
|
1630
|
-
}
|
|
1631
|
-
setLoopCount(count) {
|
|
1632
|
-
this.loop = {
|
|
1633
|
-
count,
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
updateStaticNodeValue(nodeId, value, injectFrom) {
|
|
1637
|
-
const node = this.nodes[nodeId];
|
|
1638
|
-
if (node && node.isStaticNode) {
|
|
1639
|
-
node.updateValue(value, injectFrom);
|
|
1640
|
-
}
|
|
1641
|
-
else {
|
|
1642
|
-
throw new Error(`injectValue with Invalid nodeId, ${nodeId}`);
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
resultsOf(inputs, anyInput = false) {
|
|
1646
|
-
const results = resultsOf(inputs ?? {}, this.nodes, this.propFunctions);
|
|
1647
|
-
if (anyInput) {
|
|
1648
|
-
return cleanResult(results);
|
|
1649
|
-
}
|
|
1650
|
-
return results;
|
|
1651
|
-
}
|
|
1652
|
-
resultOf(source) {
|
|
1653
|
-
return resultOf(source, this.nodes, this.propFunctions);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
export { GraphAI, GraphAILogger, NodeState, TaskManager, ValidationError, agentInfoWrapper, assert, debugResultKey, defaultAgentInfo, defaultConcurrency, defaultTestContext, graphDataLatestVersion, inputs2dataSources, isComputedNodeData, isNull, isObject, isStaticNodeData, parseNodeName, sleep, strIntentionalError };
|
|
1658
|
-
//# sourceMappingURL=bundle.esm.js.map
|