graphai 0.0.12 → 0.1.0

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/README.md CHANGED
@@ -9,6 +9,7 @@ You just need to describe dependencies among those API calls in a single data fl
9
9
  Here is an example:
10
10
 
11
11
  ```YAML
12
+ agentId: sample
12
13
  nodes:
13
14
  taskA:
14
15
  params:
@@ -37,7 +38,7 @@ const sampleAgentFunction = async (context: AgentFunctionContext) => {
37
38
  ...
38
39
  const file = fs.readFileSync(pathToYamlFile, "utf8");
39
40
  const graphData = YAML.parse(file);
40
- const graph = new GraphAI(graphData, sampleAgentFunction);
41
+ const graph = new GraphAI(graphData, { sample: sampleAgentFunction });
41
42
  const results = await graph.run();
42
43
  return results["taskC"];
43
44
  ```
@@ -48,7 +49,7 @@ As Andrew Ng has described in his article, "[The batch: Issue 242](https://www.d
48
49
 
49
50
  Building applications that employ these workflows, however, is challenging due to the complexities of managing multiple asynchronous API calls, including error handling, timeouts, retries, and logging.
50
51
 
51
- GraphAI is designed to simplify this process by decoupling the complexity of multiple asynchronous calls from the application's core logic. It enables developers to model these calls and their dependencies within a single Data Flow Graph, enhancing development and debugging processes.
52
+ GraphAI is designed to simplify this process by decoupling the complexity of multiple asynchronous calls from the application's core logic. It enables developers to model these calls and their dependencies within a single acyclic Data Flow Graph, enhancing development and debugging processes.
52
53
 
53
54
  Furthermore, GraphAI's robust mechanisms for error handling, retry strategies, timeouts, and logging empower developers to concentrate on refining the application logic.
54
55
 
@@ -114,7 +115,9 @@ Key principles:
114
115
 
115
116
  A Data Flow Graph (DFG) is a JavaScript object, which defines the flow of data. It is typically described in YAML file and loaded at runtime.
116
117
 
117
- A DFG consists of a collection of 'nodes', which contains a series of nested keys representing individual nodes in the data flow. Each node is identified by a unique key (e.g., node1, node2) and can contain several predefined keys (params, inputs, outputs, retry, timeout, source, agentId, fork, value) that dictate the node's behavior and its relationship with other nodes.
118
+ A DFG consists of a collection of 'nodes', which contains a series of nested properties representing individual nodes in the data flow. Each node is identified by a unique key, *nodeId* (e.g., node1, node2) and can contain several predefined properties (params, inputs, retry, timeout, agentId, fork, value, update) that dictate the node's behavior and its relationship with other nodes.
119
+
120
+ Connections between nodes will be established by references from one not to another, using either its "inputs" or "update" property. The values of those properties are *data sources*. A *data souce* is specified by either the nodeId (e.g., "node1"), or nodeId + propertyId ("node1.item").
118
121
 
119
122
  ### DFG Structure
120
123
 
@@ -132,10 +135,10 @@ nodes:
132
135
  - Steve Jobs
133
136
  - Elon Musk
134
137
  - Nikola Tesla
135
- next: retriever.array
138
+ update: retriever.array
136
139
  result:
137
140
  value: []
138
- next: reducer
141
+ update: reducer
139
142
  retriever:
140
143
  agentId: shift
141
144
  inputs: [people]
@@ -172,12 +175,11 @@ A *computed node* have following properties.
172
175
  - 'timeout': An optional number, which specifies the maximum waittime in msec. If the associated agent function does not return the value in time, the "Timeout" error will be recorded and the returned value will be discarded.
173
176
  - 'params': An optional property to the associated agent function, which are agent specific.
174
177
  - 'fork': An optional paramter, which specifies the number of concurrent transactions to be created for the current node.
175
- - 'outputs' (MAY BECOME OBSOLETE): An optinal property, which specifies the mapping from outputId to nodeId. If this property is set, the node become a special node called *dispatcher*. A *dispatcher* node injects result(s) into specified static nodes, enabling the dynamic flow of data.
176
178
 
177
179
  A *static* node have following properties.
178
180
 
179
181
  - 'value': An optional property, which specifies the value of this static node (equivalent to calling the injectValue method from outside).
180
- - 'next': An optional property, which specifies the *data source* after each iteration.
182
+ - 'update': An optional property, which specifies the *data source* after each iteration.
181
183
 
182
184
  ## GraphAI class
183
185
 
package/lib/graphai.d.ts CHANGED
@@ -1,94 +1,7 @@
1
- export declare enum NodeState {
2
- Waiting = "waiting",
3
- Executing = "executing",
4
- Failed = "failed",
5
- TimedOut = "timed-out",
6
- Completed = "completed",
7
- Injected = "injected",
8
- Dispatched = "dispatched"
9
- }
10
- type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
11
- type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
12
- export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
13
- type NodeData = {
14
- inputs?: Array<string>;
15
- params?: NodeDataParams;
16
- retry?: number;
17
- timeout?: number;
18
- agentId?: string;
19
- fork?: number;
20
- source?: boolean;
21
- value?: ResultData;
22
- next?: string;
23
- outputs?: Record<string, string>;
24
- };
25
- type LoopData = {
26
- count?: number;
27
- while?: string;
28
- };
29
- export type GraphData = {
30
- agentId?: string;
31
- nodes: Record<string, NodeData>;
32
- concurrency?: number;
33
- loop?: LoopData;
34
- verbose?: boolean;
35
- };
36
- export type TransactionLog = {
37
- nodeId: string;
38
- state: NodeState;
39
- startTime: number;
40
- endTime?: number;
41
- retryCount?: number;
42
- agentId?: string;
43
- params?: NodeDataParams;
44
- inputs?: Array<ResultData>;
45
- errorMessage?: string;
46
- result?: ResultData;
47
- log?: TransactionLog[];
48
- };
49
- export type AgentFunctionContext<ParamsType, PreviousResultType> = {
50
- nodeId: string;
51
- forkIndex?: number;
52
- retry: number;
53
- params: NodeDataParams<ParamsType>;
54
- inputs: Array<PreviousResultType>;
55
- verbose: boolean;
56
- agents: CallbackDictonaryArgs;
57
- log: TransactionLog[];
58
- };
59
- export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, PreviousResultType>) => Promise<ResultData<ResultType>>;
60
- export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
61
- declare class Node {
62
- nodeId: string;
63
- params: NodeDataParams;
64
- inputs: Array<string>;
65
- inputProps: Record<string, string>;
66
- pendings: Set<string>;
67
- waitlist: Set<string>;
68
- state: NodeState;
69
- agentId?: string;
70
- fork?: number;
71
- forkIndex?: number;
72
- result: ResultData;
73
- retryLimit: number;
74
- retryCount: number;
75
- transactionId: undefined | number;
76
- timeout?: number;
77
- error?: Error;
78
- source: boolean;
79
- outputs?: Record<string, string>;
80
- private graph;
81
- constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
82
- asString(): string;
83
- private retry;
84
- removePending(nodeId: string): void;
85
- pushQueueIfReady(): void;
86
- injectValue(value: ResultData): void;
87
- private setResult;
88
- execute(): Promise<void>;
89
- }
90
- type GraphNodes = Record<string, Node>;
91
- export type CallbackDictonaryArgs = AgentFunctionDictonary;
1
+ export { AgentFunction, AgentFunctionDictonary, GraphData } from "./type";
2
+ import { AgentFunctionDictonary, GraphData, DataSource, TransactionLog, ResultDataDictonary, ResultData, CallbackDictonaryArgs } from "./type";
3
+ import { ComputedNode, StaticNode } from "./node";
4
+ type GraphNodes = Record<string, ComputedNode | StaticNode>;
92
5
  export declare class GraphAI {
93
6
  private data;
94
7
  nodes: GraphNodes;
@@ -107,18 +20,17 @@ export declare class GraphAI {
107
20
  private getValueFromResults;
108
21
  private initializeNodes;
109
22
  constructor(data: GraphData, callbackDictonary: CallbackDictonaryArgs);
110
- getCallback(agentId?: string): AgentFunction<any, any, any>;
23
+ getCallback(agentId?: string): import("./type").AgentFunction<any, any, any>;
111
24
  asString(): string;
112
- results(): ResultDataDictonary<Record<string, any>>;
25
+ results(): ResultDataDictonary;
113
26
  errors(): Record<string, Error>;
114
27
  private pushReadyNodesIntoQueue;
115
28
  run(): Promise<ResultDataDictonary>;
116
29
  private runNode;
117
- pushQueue(node: Node): void;
118
- removeRunning(node: Node): void;
30
+ pushQueue(node: ComputedNode): void;
31
+ removeRunning(node: ComputedNode): void;
119
32
  appendLog(log: TransactionLog): void;
120
33
  transactionLogs(): TransactionLog[];
121
34
  injectValue(nodeId: string, value: ResultData): void;
122
- resultsOf(nodeIds: Array<string>): ResultData<Record<string, any>>[];
35
+ resultsOf(sources: Array<DataSource>): any[];
123
36
  }
124
- export {};
package/lib/graphai.js CHANGED
@@ -1,227 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GraphAI = exports.NodeState = void 0;
4
- var NodeState;
5
- (function (NodeState) {
6
- NodeState["Waiting"] = "waiting";
7
- NodeState["Executing"] = "executing";
8
- NodeState["Failed"] = "failed";
9
- NodeState["TimedOut"] = "timed-out";
10
- NodeState["Completed"] = "completed";
11
- NodeState["Injected"] = "injected";
12
- NodeState["Dispatched"] = "dispatched";
13
- })(NodeState || (exports.NodeState = NodeState = {}));
14
- const parseNodeName = (name) => {
15
- const parts = name.split(".");
16
- if (parts.length == 1) {
17
- return { sourceNodeId: parts[0] };
18
- }
19
- else {
20
- return { sourceNodeId: parts[0], propId: parts[1] };
21
- }
22
- };
23
- class Node {
24
- constructor(nodeId, forkIndex, data, graph) {
25
- this.inputProps = {}; // optional properties for input
26
- this.waitlist = new Set(); // List of nodes which need data from this node.
27
- this.state = NodeState.Waiting;
28
- this.result = undefined;
29
- this.retryCount = 0;
30
- this.nodeId = nodeId;
31
- this.forkIndex = forkIndex;
32
- this.inputs = (data.inputs ?? []).map((input) => {
33
- const { sourceNodeId, propId } = parseNodeName(input);
34
- if (propId) {
35
- this.inputProps[sourceNodeId] = propId;
36
- }
37
- return sourceNodeId;
38
- });
39
- this.pendings = new Set(this.inputs);
40
- this.params = data.params ?? {};
41
- this.agentId = data.agentId ?? graph.agentId;
42
- this.fork = data.fork;
43
- this.retryLimit = data.retry ?? 0;
44
- this.timeout = data.timeout;
45
- this.source = this.agentId === undefined;
46
- this.outputs = data.outputs;
47
- this.graph = graph;
48
- }
49
- asString() {
50
- return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
51
- }
52
- retry(state, error) {
53
- if (this.retryCount < this.retryLimit) {
54
- this.retryCount++;
55
- this.execute();
56
- }
57
- else {
58
- this.state = state;
59
- this.result = undefined;
60
- this.error = error;
61
- this.transactionId = undefined; // This is necessary for timeout case
62
- this.graph.removeRunning(this);
63
- }
64
- }
65
- removePending(nodeId) {
66
- this.pendings.delete(nodeId);
67
- if (this.graph.isRunning) {
68
- this.pushQueueIfReady();
69
- }
70
- }
71
- pushQueueIfReady() {
72
- if (this.pendings.size === 0 && !this.source) {
73
- // If input property is specified, we need to ensure that the property value exists.
74
- Object.keys(this.inputProps).forEach((nodeId) => {
75
- const [result] = this.graph.resultsOf([nodeId]);
76
- const propId = this.inputProps[nodeId];
77
- if (!result || !(propId in result)) {
78
- return;
79
- }
80
- });
81
- this.graph.pushQueue(this);
82
- }
83
- }
84
- injectValue(value) {
85
- if (this.source) {
86
- const log = {
87
- nodeId: this.nodeId,
88
- retryCount: this.retryCount,
89
- state: NodeState.Injected,
90
- startTime: Date.now(),
91
- endTime: Date.now(),
92
- result: value,
93
- };
94
- this.graph.appendLog(log);
95
- this.setResult(value, NodeState.Injected);
96
- }
97
- else {
98
- console.error("- injectValue called on non-source node.", this.nodeId);
99
- }
100
- }
101
- setResult(result, state) {
102
- this.state = state;
103
- this.result = result;
104
- this.waitlist.forEach((nodeId) => {
105
- const node = this.graph.nodes[nodeId];
106
- // Todo: Avoid running before Run()
107
- node.removePending(this.nodeId);
108
- });
109
- }
110
- async execute() {
111
- const results = this.graph.resultsOf(this.inputs);
112
- this.inputs.forEach((nodeId, index) => {
113
- const propId = this.inputProps[nodeId];
114
- if (propId) {
115
- results[index] = results[index][propId];
116
- }
117
- });
118
- const transactionId = Date.now();
119
- const log = {
120
- nodeId: this.nodeId,
121
- retryCount: this.retryCount > 0 ? this.retryCount : undefined,
122
- state: NodeState.Executing,
123
- startTime: transactionId,
124
- agentId: this.agentId,
125
- params: this.params,
126
- inputs: results.length > 0 ? results : undefined,
127
- };
128
- this.graph.appendLog(log);
129
- this.state = NodeState.Executing;
130
- this.transactionId = transactionId;
131
- if (this.timeout && this.timeout > 0) {
132
- setTimeout(() => {
133
- if (this.state === NodeState.Executing && this.transactionId === transactionId) {
134
- console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
135
- log.errorMessage = "Timeout";
136
- log.state = NodeState.TimedOut;
137
- log.endTime = Date.now();
138
- this.retry(NodeState.TimedOut, Error("Timeout"));
139
- }
140
- }, this.timeout);
141
- }
142
- try {
143
- const callback = this.graph.getCallback(this.agentId);
144
- const localLog = [];
145
- const result = await callback({
146
- nodeId: this.nodeId,
147
- retry: this.retryCount,
148
- params: this.params,
149
- inputs: results,
150
- forkIndex: this.forkIndex,
151
- verbose: this.graph.verbose,
152
- agents: this.graph.callbackDictonary,
153
- log: localLog,
154
- });
155
- if (this.transactionId !== transactionId) {
156
- console.log(`-- ${this.nodeId}: transactionId mismatch`);
157
- return;
158
- }
159
- log.endTime = Date.now();
160
- log.result = result;
161
- if (localLog.length > 0) {
162
- log.log = localLog;
163
- }
164
- const outputs = this.outputs;
165
- if (outputs !== undefined) {
166
- Object.keys(outputs).forEach((outputId) => {
167
- const nodeId = outputs[outputId];
168
- const value = result[outputId];
169
- if (value) {
170
- this.graph.injectValue(nodeId, value);
171
- }
172
- else {
173
- console.error("-- Invalid outputId", outputId, result);
174
- }
175
- });
176
- log.state = NodeState.Dispatched;
177
- this.state = NodeState.Dispatched;
178
- this.graph.removeRunning(this);
179
- return;
180
- }
181
- log.state = NodeState.Completed;
182
- this.setResult(result, NodeState.Completed);
183
- this.graph.removeRunning(this);
184
- }
185
- catch (error) {
186
- if (this.transactionId !== transactionId) {
187
- console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
188
- return;
189
- }
190
- log.state = NodeState.Failed;
191
- log.endTime = Date.now();
192
- if (error instanceof Error) {
193
- log.errorMessage = error.message;
194
- this.retry(NodeState.Failed, error);
195
- }
196
- else {
197
- console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
198
- log.errorMessage = "Unknown";
199
- this.retry(NodeState.Failed, Error("Unknown"));
200
- }
201
- }
202
- }
203
- }
3
+ exports.GraphAI = void 0;
4
+ const node_1 = require("./node");
5
+ const utils_1 = require("./utils/utils");
204
6
  const defaultConcurrency = 8;
205
7
  class GraphAI {
206
8
  createNodes(data) {
207
9
  const nodeId2forkedNodeIds = {};
208
10
  const forkedNodeId2Index = {};
209
- const nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
11
+ const forkedNodeId2NodeId = {}; // for sources
12
+ const nodes = Object.keys(data.nodes).reduce((_nodes, nodeId) => {
210
13
  const fork = data.nodes[nodeId].fork;
14
+ const isStaticNode = (data.nodes[nodeId].agentId ?? data.agentId) === undefined;
15
+ const node = isStaticNode ? node_1.StaticNode : node_1.ComputedNode;
211
16
  if (fork) {
212
17
  // For fork, change the nodeId and increase the node
213
18
  nodeId2forkedNodeIds[nodeId] = new Array(fork).fill(undefined).map((_, i) => {
214
19
  const forkedNodeId = `${nodeId}_${i}`;
215
- nodes[forkedNodeId] = new Node(forkedNodeId, i, data.nodes[nodeId], this);
20
+ _nodes[forkedNodeId] = new node(forkedNodeId, i, data.nodes[nodeId], this);
216
21
  // Data for pending and waiting
217
22
  forkedNodeId2Index[forkedNodeId] = i;
23
+ forkedNodeId2NodeId[forkedNodeId] = nodeId;
218
24
  return forkedNodeId;
219
25
  });
220
26
  }
221
27
  else {
222
- nodes[nodeId] = new Node(nodeId, undefined, data.nodes[nodeId], this);
28
+ _nodes[nodeId] = new node(nodeId, undefined, data.nodes[nodeId], this);
223
29
  }
224
- return nodes;
30
+ return _nodes;
225
31
  }, {});
226
32
  // Generate the waitlist for each node, and update the pendings in case of forked node.
227
33
  Object.keys(nodes).forEach((nodeId) => {
@@ -255,28 +61,37 @@ class GraphAI {
255
61
  }
256
62
  });
257
63
  node.inputs = Array.from(node.pendings); // for fork.
64
+ node.sources = node.inputs.reduce((sources, input) => {
65
+ const refNodeId = forkedNodeId2NodeId[input] ?? input;
66
+ sources[input] = { nodeId: input, propId: node.sources[refNodeId].propId };
67
+ return sources;
68
+ }, {});
258
69
  });
259
70
  return nodes;
260
71
  }
261
72
  getValueFromResults(key, results) {
262
- const { sourceNodeId, propId } = parseNodeName(key);
263
- const result = results[sourceNodeId];
264
- return result ? (propId ? result[propId] : result) : undefined;
73
+ const source = (0, utils_1.parseNodeName)(key);
74
+ const result = results[source.nodeId];
75
+ return result ? (source.propId ? result[source.propId] : result) : undefined;
265
76
  }
77
+ // for static
266
78
  initializeNodes(previousResults) {
267
79
  // If the result property is specified, inject it.
268
80
  // If the previousResults exists (indicating we are in a loop),
269
- // process the next property (nodeId or nodeId.propId).
81
+ // process the update property (nodeId or nodeId.propId).
270
82
  Object.keys(this.data.nodes).forEach((nodeId) => {
271
- const node = this.data.nodes[nodeId];
272
- const { value, next } = node;
273
- if (value) {
274
- this.injectValue(nodeId, value);
275
- }
276
- if (next && previousResults) {
277
- const result = this.getValueFromResults(next, previousResults);
278
- if (result) {
279
- this.injectValue(nodeId, result);
83
+ const node = this.nodes[nodeId];
84
+ if (node?.isStaticNode) {
85
+ const value = node?.value;
86
+ const update = node?.update;
87
+ if (value) {
88
+ this.injectValue(nodeId, value);
89
+ }
90
+ if (update && previousResults) {
91
+ const result = this.getValueFromResults(update, previousResults);
92
+ if (result) {
93
+ this.injectValue(nodeId, result);
94
+ }
280
95
  }
281
96
  }
282
97
  });
@@ -284,7 +99,7 @@ class GraphAI {
284
99
  constructor(data, callbackDictonary) {
285
100
  this.isRunning = false;
286
101
  this.runningNodes = new Set();
287
- this.nodeQueue = [];
102
+ this.nodeQueue = []; // for Computed Node
288
103
  this.repeatCount = 0;
289
104
  this.logs = [];
290
105
  this.data = data;
@@ -324,8 +139,10 @@ class GraphAI {
324
139
  errors() {
325
140
  return Object.keys(this.nodes).reduce((errors, nodeId) => {
326
141
  const node = this.nodes[nodeId];
327
- if (node.error !== undefined) {
328
- errors[nodeId] = node.error;
142
+ if (node.isComputedNode) {
143
+ if (node.error !== undefined) {
144
+ errors[nodeId] = node.error;
145
+ }
329
146
  }
330
147
  return errors;
331
148
  }, {});
@@ -334,7 +151,9 @@ class GraphAI {
334
151
  // Nodes without pending data should run immediately.
335
152
  Object.keys(this.nodes).forEach((nodeId) => {
336
153
  const node = this.nodes[nodeId];
337
- node.pushQueueIfReady();
154
+ if (node.isComputedNode) {
155
+ node.pushQueueIfReady();
156
+ }
338
157
  });
339
158
  }
340
159
  async run() {
@@ -357,10 +176,12 @@ class GraphAI {
357
176
  };
358
177
  });
359
178
  }
179
+ // for computed
360
180
  runNode(node) {
361
181
  this.runningNodes.add(node.nodeId);
362
182
  node.execute();
363
183
  }
184
+ // for computed
364
185
  pushQueue(node) {
365
186
  if (this.runningNodes.size < this.concurrency) {
366
187
  this.runNode(node);
@@ -369,6 +190,7 @@ class GraphAI {
369
190
  this.nodeQueue.push(node);
370
191
  }
371
192
  }
193
+ // for completed
372
194
  removeRunning(node) {
373
195
  this.runningNodes.delete(node.nodeId);
374
196
  if (this.nodeQueue.length > 0) {
@@ -410,16 +232,17 @@ class GraphAI {
410
232
  }
411
233
  injectValue(nodeId, value) {
412
234
  const node = this.nodes[nodeId];
413
- if (node) {
235
+ if (node && node.isStaticNode) {
414
236
  node.injectValue(value);
415
237
  }
416
238
  else {
417
239
  console.error("-- Invalid nodeId", nodeId);
418
240
  }
419
241
  }
420
- resultsOf(nodeIds) {
421
- return nodeIds.map((nodeId) => {
422
- return this.nodes[nodeId].result;
242
+ resultsOf(sources) {
243
+ return sources.map((source) => {
244
+ const result = this.nodes[source.nodeId].result;
245
+ return result && source.propId ? result[source.propId] : result;
423
246
  });
424
247
  }
425
248
  }
package/lib/log.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { ResultData, TransactionLog, NodeDataParams } from "./type";
2
+ export declare const injectValueLog: (nodeId: string, value: ResultData) => TransactionLog;
3
+ export declare const executeLog: (nodeId: string, retryCount: number, transactionId: number, agentId: string | undefined, params: NodeDataParams, results: ResultData[]) => TransactionLog;
4
+ export declare const timeoutLog: (log: TransactionLog) => void;
5
+ export declare const callbackLog: (log: TransactionLog, result: ResultData, localLog: TransactionLog[]) => void;
6
+ export declare const errorLog: (log: TransactionLog, errorMessage: string) => void;
package/lib/log.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.errorLog = exports.callbackLog = exports.timeoutLog = exports.executeLog = exports.injectValueLog = void 0;
4
+ const type_1 = require("./type");
5
+ const injectValueLog = (nodeId, value) => {
6
+ const log = {
7
+ nodeId,
8
+ state: type_1.NodeState.Injected,
9
+ startTime: Date.now(),
10
+ endTime: Date.now(),
11
+ result: value,
12
+ };
13
+ return log;
14
+ };
15
+ exports.injectValueLog = injectValueLog;
16
+ const executeLog = (nodeId, retryCount, transactionId, agentId, params, results) => {
17
+ const log = {
18
+ nodeId,
19
+ retryCount: retryCount > 0 ? retryCount : undefined,
20
+ state: type_1.NodeState.Executing,
21
+ startTime: transactionId,
22
+ agentId,
23
+ params,
24
+ inputs: results.length > 0 ? results : undefined,
25
+ };
26
+ return log;
27
+ };
28
+ exports.executeLog = executeLog;
29
+ const timeoutLog = (log) => {
30
+ log.errorMessage = "Timeout";
31
+ log.state = type_1.NodeState.TimedOut;
32
+ log.endTime = Date.now();
33
+ };
34
+ exports.timeoutLog = timeoutLog;
35
+ const callbackLog = (log, result, localLog) => {
36
+ log.endTime = Date.now();
37
+ log.result = result;
38
+ if (localLog.length > 0) {
39
+ log.log = localLog;
40
+ }
41
+ };
42
+ exports.callbackLog = callbackLog;
43
+ const errorLog = (log, errorMessage) => {
44
+ log.state = type_1.NodeState.Failed;
45
+ log.endTime = Date.now();
46
+ log.errorMessage = errorMessage;
47
+ };
48
+ exports.errorLog = errorLog;
package/lib/node.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { NodeDataParams, ResultData, DataSource, NodeData } from "./type";
2
+ import type { GraphAI } from "./graphai";
3
+ import { NodeState } from "./type";
4
+ export declare class Node {
5
+ nodeId: string;
6
+ sources: Record<string, DataSource>;
7
+ anyInput: boolean;
8
+ inputs: Array<string>;
9
+ pendings: Set<string>;
10
+ waitlist: Set<string>;
11
+ state: NodeState;
12
+ fork?: number;
13
+ forkIndex?: number;
14
+ result: ResultData;
15
+ transactionId: undefined | number;
16
+ protected graph: GraphAI;
17
+ constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
18
+ asString(): string;
19
+ removePending(nodeId: string): void;
20
+ protected setResult(result: ResultData, state: NodeState): void;
21
+ }
22
+ export declare class ComputedNode extends Node {
23
+ params: NodeDataParams;
24
+ retryLimit: number;
25
+ retryCount: number;
26
+ agentId?: string;
27
+ timeout?: number;
28
+ error?: Error;
29
+ readonly isStaticNode = false;
30
+ readonly isComputedNode = true;
31
+ constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
32
+ pushQueueIfReady(): void;
33
+ private retry;
34
+ removePending(nodeId: string): void;
35
+ execute(): Promise<void>;
36
+ }
37
+ export declare class StaticNode extends Node {
38
+ value?: ResultData;
39
+ update?: string;
40
+ readonly isStaticNode = true;
41
+ readonly isComputedNode = false;
42
+ constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
43
+ injectValue(value: ResultData): void;
44
+ }
package/lib/node.js ADDED
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StaticNode = exports.ComputedNode = exports.Node = void 0;
4
+ const type_1 = require("./type");
5
+ const utils_1 = require("./utils/utils");
6
+ const log_1 = require("./log");
7
+ class Node {
8
+ constructor(nodeId, forkIndex, data, graph) {
9
+ this.sources = {}; // data sources.
10
+ this.waitlist = new Set(); // List of nodes which need data from this node.
11
+ this.state = type_1.NodeState.Waiting;
12
+ this.result = undefined;
13
+ this.nodeId = nodeId;
14
+ this.forkIndex = forkIndex;
15
+ this.anyInput = data.anyInput ?? false;
16
+ this.inputs = (data.inputs ?? []).map((input) => {
17
+ const source = (0, utils_1.parseNodeName)(input);
18
+ this.sources[source.nodeId] = source;
19
+ return source.nodeId;
20
+ });
21
+ this.pendings = new Set(this.inputs);
22
+ this.fork = data.fork;
23
+ this.graph = graph;
24
+ }
25
+ asString() {
26
+ return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
27
+ }
28
+ removePending(nodeId) {
29
+ if (this.anyInput) {
30
+ const [result] = this.graph.resultsOf([this.sources[nodeId]]);
31
+ if (result) {
32
+ this.pendings.clear();
33
+ }
34
+ }
35
+ else {
36
+ this.pendings.delete(nodeId);
37
+ }
38
+ }
39
+ setResult(result, state) {
40
+ this.state = state;
41
+ this.result = result;
42
+ this.waitlist.forEach((nodeId) => {
43
+ const node = this.graph.nodes[nodeId];
44
+ // Todo: Avoid running before Run()
45
+ node.removePending(this.nodeId);
46
+ });
47
+ }
48
+ }
49
+ exports.Node = Node;
50
+ class ComputedNode extends Node {
51
+ constructor(nodeId, forkIndex, data, graph) {
52
+ super(nodeId, forkIndex, data, graph);
53
+ this.retryCount = 0;
54
+ this.isStaticNode = false;
55
+ this.isComputedNode = true;
56
+ this.params = data.params ?? {};
57
+ this.agentId = data.agentId ?? graph.agentId;
58
+ this.retryLimit = data.retry ?? 0;
59
+ this.timeout = data.timeout;
60
+ }
61
+ // for completed
62
+ pushQueueIfReady() {
63
+ if (this.pendings.size === 0) {
64
+ // If input property is specified, we need to ensure that the property value exists.
65
+ const count = this.inputs.reduce((count, nodeId) => {
66
+ const source = this.sources[nodeId];
67
+ if (source.propId) {
68
+ const [result] = this.graph.resultsOf([source]);
69
+ if (!result) {
70
+ return count;
71
+ }
72
+ }
73
+ return count + 1;
74
+ }, 0);
75
+ if ((this.anyInput && count > 0) || count == this.inputs.length) {
76
+ this.graph.pushQueue(this);
77
+ }
78
+ }
79
+ }
80
+ // for computed
81
+ retry(state, error) {
82
+ if (this.retryCount < this.retryLimit) {
83
+ this.retryCount++;
84
+ this.execute();
85
+ }
86
+ else {
87
+ this.state = state;
88
+ this.result = undefined;
89
+ this.error = error;
90
+ this.transactionId = undefined; // This is necessary for timeout case
91
+ this.graph.removeRunning(this);
92
+ }
93
+ }
94
+ removePending(nodeId) {
95
+ super.removePending(nodeId);
96
+ if (this.graph.isRunning) {
97
+ this.pushQueueIfReady();
98
+ }
99
+ }
100
+ async execute() {
101
+ const results = this.graph
102
+ .resultsOf(this.inputs.map((input) => {
103
+ return this.sources[input];
104
+ }))
105
+ .filter((result) => {
106
+ // Remove undefined if anyInput flag is set.
107
+ return !this.anyInput || result !== undefined;
108
+ });
109
+ const transactionId = Date.now();
110
+ const log = (0, log_1.executeLog)(this.nodeId, this.retryCount, transactionId, this.agentId, this.params, results);
111
+ this.graph.appendLog(log);
112
+ this.state = type_1.NodeState.Executing;
113
+ this.transactionId = transactionId;
114
+ if (this.timeout && this.timeout > 0) {
115
+ setTimeout(() => {
116
+ if (this.state === type_1.NodeState.Executing && this.transactionId === transactionId) {
117
+ console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
118
+ (0, log_1.timeoutLog)(log);
119
+ this.retry(type_1.NodeState.TimedOut, Error("Timeout"));
120
+ }
121
+ }, this.timeout);
122
+ }
123
+ try {
124
+ const callback = this.graph.getCallback(this.agentId);
125
+ const localLog = [];
126
+ const result = await callback({
127
+ nodeId: this.nodeId,
128
+ retry: this.retryCount,
129
+ params: this.params,
130
+ inputs: results,
131
+ forkIndex: this.forkIndex,
132
+ verbose: this.graph.verbose,
133
+ agents: this.graph.callbackDictonary,
134
+ log: localLog,
135
+ });
136
+ if (this.transactionId !== transactionId) {
137
+ console.log(`-- ${this.nodeId}: transactionId mismatch`);
138
+ return;
139
+ }
140
+ (0, log_1.callbackLog)(log, result, localLog);
141
+ log.state = type_1.NodeState.Completed;
142
+ this.setResult(result, type_1.NodeState.Completed);
143
+ this.graph.removeRunning(this);
144
+ }
145
+ catch (error) {
146
+ if (this.transactionId !== transactionId) {
147
+ console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
148
+ return;
149
+ }
150
+ const isError = error instanceof Error;
151
+ (0, log_1.errorLog)(log, isError ? error.message : "Unknown");
152
+ if (isError) {
153
+ this.retry(type_1.NodeState.Failed, error);
154
+ }
155
+ else {
156
+ console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
157
+ this.retry(type_1.NodeState.Failed, Error("Unknown"));
158
+ }
159
+ }
160
+ }
161
+ }
162
+ exports.ComputedNode = ComputedNode;
163
+ class StaticNode extends Node {
164
+ constructor(nodeId, forkIndex, data, graph) {
165
+ super(nodeId, forkIndex, data, graph);
166
+ this.isStaticNode = true;
167
+ this.isComputedNode = false;
168
+ this.value = data.value;
169
+ this.update = data.update;
170
+ }
171
+ // for static
172
+ injectValue(value) {
173
+ const log = (0, log_1.injectValueLog)(this.nodeId, value);
174
+ this.graph.appendLog(log);
175
+ this.setResult(value, type_1.NodeState.Injected);
176
+ //console.error("- injectValue called on non-source node.", this.nodeId);
177
+ }
178
+ }
179
+ exports.StaticNode = StaticNode;
package/lib/type.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ export declare enum NodeState {
2
+ Waiting = "waiting",
3
+ Executing = "executing",
4
+ Failed = "failed",
5
+ TimedOut = "timed-out",
6
+ Completed = "completed",
7
+ Injected = "injected",
8
+ Dispatched = "dispatched"
9
+ }
10
+ export type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
11
+ export type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
12
+ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
13
+ export type DataSource = {
14
+ nodeId: string;
15
+ propId?: string;
16
+ };
17
+ export type NodeData = {
18
+ inputs?: Array<string>;
19
+ anyInput?: boolean;
20
+ params?: NodeDataParams;
21
+ retry?: number;
22
+ timeout?: number;
23
+ agentId?: string;
24
+ fork?: number;
25
+ value?: ResultData;
26
+ update?: string;
27
+ };
28
+ export type LoopData = {
29
+ count?: number;
30
+ while?: string;
31
+ };
32
+ export type GraphData = {
33
+ agentId?: string;
34
+ nodes: Record<string, NodeData>;
35
+ concurrency?: number;
36
+ loop?: LoopData;
37
+ verbose?: boolean;
38
+ };
39
+ export type TransactionLog = {
40
+ nodeId: string;
41
+ state: NodeState;
42
+ startTime: number;
43
+ endTime?: number;
44
+ retryCount?: number;
45
+ agentId?: string;
46
+ params?: NodeDataParams;
47
+ inputs?: Array<ResultData>;
48
+ errorMessage?: string;
49
+ result?: ResultData;
50
+ log?: TransactionLog[];
51
+ };
52
+ export type AgentFunctionContext<ParamsType, PreviousResultType> = {
53
+ nodeId: string;
54
+ forkIndex?: number;
55
+ retry: number;
56
+ params: NodeDataParams<ParamsType>;
57
+ inputs: Array<PreviousResultType>;
58
+ verbose: boolean;
59
+ agents: CallbackDictonaryArgs;
60
+ log: TransactionLog[];
61
+ };
62
+ export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, PreviousResultType>) => Promise<ResultData<ResultType>>;
63
+ export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
64
+ export type CallbackDictonaryArgs = AgentFunctionDictonary;
package/lib/type.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeState = void 0;
4
+ var NodeState;
5
+ (function (NodeState) {
6
+ NodeState["Waiting"] = "waiting";
7
+ NodeState["Executing"] = "executing";
8
+ NodeState["Failed"] = "failed";
9
+ NodeState["TimedOut"] = "timed-out";
10
+ NodeState["Completed"] = "completed";
11
+ NodeState["Injected"] = "injected";
12
+ NodeState["Dispatched"] = "dispatched";
13
+ })(NodeState || (exports.NodeState = NodeState = {}));
@@ -1 +1,3 @@
1
+ import { DataSource } from "../type";
1
2
  export declare const sleep: (milliseconds: number) => Promise<unknown>;
3
+ export declare const parseNodeName: (name: string) => DataSource;
@@ -1,7 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sleep = void 0;
3
+ exports.parseNodeName = exports.sleep = void 0;
4
4
  const sleep = async (milliseconds) => {
5
5
  return await new Promise((resolve) => setTimeout(resolve, milliseconds));
6
6
  };
7
7
  exports.sleep = sleep;
8
+ const parseNodeName = (name) => {
9
+ const parts = name.split(".");
10
+ if (parts.length == 1) {
11
+ return { nodeId: parts[0] };
12
+ }
13
+ else {
14
+ return { nodeId: parts[0], propId: parts[1] };
15
+ }
16
+ };
17
+ exports.parseNodeName = parseNodeName;
package/lib/utils.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare const parseNodeName: (name: string) => {
2
+ sourceNodeId: string;
3
+ propId?: undefined;
4
+ } | {
5
+ sourceNodeId: string;
6
+ propId: string;
7
+ };
package/lib/utils.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseNodeName = void 0;
4
+ const parseNodeName = (name) => {
5
+ const parts = name.split(".");
6
+ if (parts.length == 1) {
7
+ return { sourceNodeId: parts[0] };
8
+ }
9
+ else {
10
+ return { sourceNodeId: parts[0], propId: parts[1] };
11
+ }
12
+ };
13
+ exports.parseNodeName = parseNodeName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphai",
3
- "version": "0.0.12",
3
+ "version": "0.1.0",
4
4
  "description": "Asynchronous data flow execution engine to make it simple to build LLM apps.",
5
5
  "main": "lib/index.js",
6
6
  "bin": {