graphai 0.0.4 → 0.0.5

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
@@ -1,6 +1,6 @@
1
1
  # GraphAI
2
2
 
3
- GraphAI is a TypeScript library, which makes it easy to create AI applications that need to make asynchronous AI API calls multiple times with some dependencies among them, such as giving the answer from one LLM call to another LLM call as a prompt.
3
+ GraphAI is an asynchronous data flow execution engine, which makes it easy to create AI applications that need to make asynchronous AI API calls multiple times with some dependencies among them, such as giving the answer from one LLM call to another LLM call as a prompt.
4
4
 
5
5
  You just need to describe dependencies among those API calls in a single graph definition file (in JSON or YAML), create a GraphAI object with it, and run it.
6
6
 
@@ -38,7 +38,6 @@ const nodeExecute = async (context: NodeExecuteContext) => {
38
38
  const graph = new GraphAI(graph_data, nodeExecute);
39
39
  const results = await graph.run();
40
40
  return results["taskC"];
41
-
42
41
  ```
43
42
 
44
43
 
package/lib/graphai.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare enum NodeState {
7
7
  }
8
8
  type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
9
9
  type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
10
- export type NodeDataParams = Record<string, any>;
10
+ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
11
11
  type NodeData = {
12
12
  inputs: undefined | Array<string>;
13
13
  params: NodeDataParams;
@@ -19,14 +19,23 @@ type GraphData = {
19
19
  nodes: Record<string, NodeData>;
20
20
  concurrency: number;
21
21
  };
22
- export type NodeExecuteContext<ResultType> = {
22
+ type NodeExecuteContext<ResultType, ParamsType> = {
23
23
  nodeId: string;
24
24
  retry: number;
25
- params: NodeDataParams;
25
+ params: NodeDataParams<ParamsType>;
26
26
  payload: ResultDataDictonary<ResultType>;
27
27
  };
28
- type NodeExecute<ResultType> = (context: NodeExecuteContext<ResultType>) => Promise<ResultData<ResultType>>;
29
- declare class Node<ResultType = Record<string, any>> {
28
+ export type TransactionLog = {
29
+ nodeId: string;
30
+ state: NodeState;
31
+ startTime: undefined | number;
32
+ endTime: undefined | number;
33
+ retryCount: number;
34
+ error: undefined | Error;
35
+ result?: ResultData;
36
+ };
37
+ export type NodeExecute<ResultType = Record<string, any>, ParamsType = Record<string, any>> = (context: NodeExecuteContext<ResultType, ParamsType>) => Promise<ResultData<ResultType>>;
38
+ declare class Node {
30
39
  nodeId: string;
31
40
  params: NodeDataParams;
32
41
  inputs: Array<string>;
@@ -34,35 +43,41 @@ declare class Node<ResultType = Record<string, any>> {
34
43
  waitlist: Set<string>;
35
44
  state: NodeState;
36
45
  functionName: string;
37
- result: ResultData<ResultType>;
46
+ result: ResultData;
38
47
  retryLimit: number;
39
48
  retryCount: number;
40
49
  transactionId: undefined | number;
41
50
  timeout: number;
51
+ error: undefined | Error;
42
52
  private graph;
43
- constructor(nodeId: string, data: NodeData, graph: GraphAI<ResultType>);
53
+ constructor(nodeId: string, data: NodeData, graph: GraphAI);
44
54
  asString(): string;
45
55
  private retry;
46
56
  removePending(nodeId: string): void;
47
- payload(): ResultDataDictonary<ResultType>;
57
+ payload(): ResultDataDictonary<Record<string, any>>;
48
58
  pushQueueIfReady(): void;
49
59
  execute(): Promise<void>;
50
60
  }
51
- type GraphNodes<ResultType> = Record<string, Node<ResultType>>;
52
- type NodeExecuteDictonary<ResultType> = Record<string, NodeExecute<ResultType>>;
53
- export declare class GraphAI<ResultType = Record<string, any>> {
54
- nodes: GraphNodes<ResultType>;
55
- callbackDictonary: NodeExecuteDictonary<ResultType>;
61
+ type GraphNodes = Record<string, Node>;
62
+ type NodeExecuteDictonary = Record<string, NodeExecute>;
63
+ export declare class GraphAI {
64
+ nodes: GraphNodes;
65
+ callbackDictonary: NodeExecuteDictonary;
56
66
  private runningNodes;
57
67
  private nodeQueue;
58
68
  private onComplete;
59
69
  private concurrency;
60
- constructor(data: GraphData, callbackDictonary: NodeExecuteDictonary<ResultType> | NodeExecute<ResultType>);
61
- getCallback(functionName: string): NodeExecute<ResultType>;
70
+ private logs;
71
+ constructor(data: GraphData, callbackDictonary: NodeExecuteDictonary | NodeExecute);
72
+ getCallback(functionName: string): NodeExecute<Record<string, any>, Record<string, any>>;
62
73
  asString(): string;
74
+ results(): ResultDataDictonary<Record<string, any>>;
75
+ errors(): Record<string, Error>;
63
76
  run(): Promise<unknown>;
64
77
  private runNode;
65
- pushQueue(node: Node<ResultType>): void;
66
- removeRunning(node: Node<ResultType>): void;
78
+ pushQueue(node: Node): void;
79
+ removeRunning(node: Node): void;
80
+ appendLog(log: TransactionLog): void;
81
+ transactionLogs(): TransactionLog[];
67
82
  }
68
83
  export {};
package/lib/graphai.js CHANGED
@@ -27,14 +27,16 @@ class Node {
27
27
  asString() {
28
28
  return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
29
29
  }
30
- retry(state, result) {
30
+ retry(state, error) {
31
31
  if (this.retryCount < this.retryLimit) {
32
32
  this.retryCount++;
33
33
  this.execute();
34
34
  }
35
35
  else {
36
36
  this.state = state;
37
- this.result = result;
37
+ this.result = undefined;
38
+ this.error = error;
39
+ this.transactionId = 0; // This is necessary for timeout case
38
40
  this.graph.removeRunning(this);
39
41
  }
40
42
  }
@@ -54,14 +56,26 @@ class Node {
54
56
  }
55
57
  }
56
58
  async execute() {
59
+ const log = {
60
+ nodeId: this.nodeId,
61
+ retryCount: this.retryCount,
62
+ state: NodeState.Executing,
63
+ startTime: Date.now(),
64
+ endTime: undefined,
65
+ error: undefined,
66
+ };
67
+ this.graph.appendLog(log);
57
68
  this.state = NodeState.Executing;
58
- const transactionId = Date.now();
69
+ const transactionId = log.startTime;
59
70
  this.transactionId = transactionId;
60
71
  if (this.timeout > 0) {
61
72
  setTimeout(() => {
62
73
  if (this.state === NodeState.Executing && this.transactionId === transactionId) {
63
- console.log("*** timeout", this.timeout);
64
- this.retry(NodeState.TimedOut, undefined);
74
+ console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
75
+ log.error = Error("Timeout");
76
+ log.state = NodeState.TimedOut;
77
+ log.endTime = Date.now();
78
+ this.retry(NodeState.TimedOut, Error("Timeout"));
65
79
  }
66
80
  }, this.timeout);
67
81
  }
@@ -74,9 +88,12 @@ class Node {
74
88
  payload: this.payload(),
75
89
  });
76
90
  if (this.transactionId !== transactionId) {
77
- console.log("****** transactionId mismatch (success)");
91
+ console.log(`-- ${this.nodeId}: transactionId mismatch`);
78
92
  return;
79
93
  }
94
+ log.state = NodeState.Completed;
95
+ log.endTime = Date.now();
96
+ log.result = result;
80
97
  this.state = NodeState.Completed;
81
98
  this.result = result;
82
99
  this.waitlist.forEach((nodeId) => {
@@ -85,22 +102,34 @@ class Node {
85
102
  });
86
103
  this.graph.removeRunning(this);
87
104
  }
88
- catch (e) {
105
+ catch (error) {
89
106
  if (this.transactionId !== transactionId) {
90
- console.log("****** transactionId mismatch (failed)");
107
+ console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
91
108
  return;
92
109
  }
93
- this.retry(NodeState.Failed, undefined);
110
+ log.state = NodeState.Failed;
111
+ log.endTime = Date.now();
112
+ if (error instanceof Error) {
113
+ log.error = error;
114
+ this.retry(NodeState.Failed, error);
115
+ }
116
+ else {
117
+ console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
118
+ log.error = Error("Unknown");
119
+ this.retry(NodeState.Failed, Error("Unknown"));
120
+ }
94
121
  }
95
122
  }
96
123
  }
124
+ const defaultConcurrency = 8;
97
125
  class GraphAI {
98
126
  constructor(data, callbackDictonary) {
127
+ this.logs = [];
99
128
  this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
100
129
  if (this.callbackDictonary["default"] === undefined) {
101
130
  throw new Error("No default function");
102
131
  }
103
- this.concurrency = data.concurrency ?? 2;
132
+ this.concurrency = data.concurrency ?? defaultConcurrency;
104
133
  this.runningNodes = new Set();
105
134
  this.nodeQueue = [];
106
135
  this.onComplete = () => { };
@@ -130,6 +159,24 @@ class GraphAI {
130
159
  })
131
160
  .join("\n");
132
161
  }
162
+ results() {
163
+ return Object.keys(this.nodes).reduce((results, nodeId) => {
164
+ const node = this.nodes[nodeId];
165
+ if (node.result !== undefined) {
166
+ results[nodeId] = node.result;
167
+ }
168
+ return results;
169
+ }, {});
170
+ }
171
+ errors() {
172
+ return Object.keys(this.nodes).reduce((errors, nodeId) => {
173
+ const node = this.nodes[nodeId];
174
+ if (node.error !== undefined) {
175
+ errors[nodeId] = node.error;
176
+ }
177
+ return errors;
178
+ }, {});
179
+ }
133
180
  async run() {
134
181
  // Nodes without pending data should run immediately.
135
182
  Object.keys(this.nodes).forEach((nodeId) => {
@@ -138,11 +185,14 @@ class GraphAI {
138
185
  });
139
186
  return new Promise((resolve, reject) => {
140
187
  this.onComplete = () => {
141
- const results = Object.keys(this.nodes).reduce((results, nodeId) => {
142
- results[nodeId] = this.nodes[nodeId].result;
143
- return results;
144
- }, {});
145
- resolve(results);
188
+ const errors = this.errors();
189
+ const nodeIds = Object.keys(errors);
190
+ if (nodeIds.length > 0) {
191
+ reject(errors[nodeIds[0]]);
192
+ }
193
+ else {
194
+ resolve(this.results());
195
+ }
146
196
  };
147
197
  });
148
198
  }
@@ -170,5 +220,11 @@ class GraphAI {
170
220
  this.onComplete();
171
221
  }
172
222
  }
223
+ appendLog(log) {
224
+ this.logs.push(log);
225
+ }
226
+ transactionLogs() {
227
+ return this.logs;
228
+ }
173
229
  }
174
230
  exports.GraphAI = GraphAI;
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "graphai",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Asynchronous data flow execution engine to make it simple to build LLM apps.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
7
  "build": "tsc",
8
8
  "eslint": "eslint --fix --ext src/**/*.{ts} tests/**/*.ts",
9
9
  "format": "prettier --write '{src,tests}/**/*.ts'",
10
- "test": "node --test --require ts-node/register ./tests/test_*.ts"
10
+ "test": "node --test --require ts-node/register ./tests/test_*.ts",
11
+ "gpt": "npx ts-node ./samples/sample_gpt.ts"
11
12
  },
12
13
  "repository": {
13
14
  "type": "git",
@@ -27,7 +28,7 @@
27
28
  "eslint-plugin-import": "^2.25.2",
28
29
  "openai": "^4.12.4",
29
30
  "prettier": "^3.0.3",
30
- "slashgpt": "^0.0.6",
31
+ "slashgpt": "^0.0.8",
31
32
  "ts-node": "^10.9.1",
32
33
  "typescript": "^5.2.2"
33
34
  },
@@ -1,12 +1,12 @@
1
1
  import path from "path";
2
- import { GraphAI, NodeExecuteContext } from "../src/graphai";
2
+ import { GraphAI, NodeExecute } from "../src/graphai";
3
3
  import { ChatSession, ChatConfig } from "slashgpt";
4
- import { readManifestData } from "./file_utils";
4
+ import { readManifestData } from "../tests/file_utils";
5
5
 
6
6
  const config = new ChatConfig(path.resolve(__dirname));
7
7
 
8
- const testFunction = async (context: NodeExecuteContext<Record<string, string>>) => {
9
- console.log("executing", context.nodeId, context.params, context.payload);
8
+ const testFunction: NodeExecute<Record<string, string>> = async (context) => {
9
+ console.log("executing", context.nodeId, context.params);
10
10
  const session = new ChatSession(config, context.params.manifest ?? {});
11
11
  const prompt = Object.keys(context.payload).reduce((prompt, key) => {
12
12
  return prompt.replace("${" + key + "}", context.payload[key]!["answer"]);
@@ -31,7 +31,7 @@ const test = async (file: string) => {
31
31
  };
32
32
 
33
33
  const main = async () => {
34
- await test("/graphs/sample3.yml");
34
+ await test("/graphs/slash_gpt.yml");
35
35
  console.log("COMPLETE 1");
36
36
  };
37
37
  main();
package/src/graphai.ts CHANGED
@@ -10,7 +10,7 @@ export enum NodeState {
10
10
  type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
11
11
  type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
12
12
 
13
- export type NodeDataParams = Record<string, any>; // App-specific parameters
13
+ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType; // App-specific parameters
14
14
 
15
15
  type NodeData = {
16
16
  inputs: undefined | Array<string>;
@@ -25,16 +25,28 @@ type GraphData = {
25
25
  concurrency: number;
26
26
  };
27
27
 
28
- export type NodeExecuteContext<ResultType> = {
28
+ type NodeExecuteContext<ResultType, ParamsType> = {
29
29
  nodeId: string;
30
30
  retry: number;
31
- params: NodeDataParams;
31
+ params: NodeDataParams<ParamsType>;
32
32
  payload: ResultDataDictonary<ResultType>;
33
33
  };
34
34
 
35
- type NodeExecute<ResultType> = (context: NodeExecuteContext<ResultType>) => Promise<ResultData<ResultType>>;
35
+ export type TransactionLog = {
36
+ nodeId: string;
37
+ state: NodeState;
38
+ startTime: undefined | number;
39
+ endTime: undefined | number;
40
+ retryCount: number;
41
+ error: undefined | Error;
42
+ result?: ResultData;
43
+ };
44
+
45
+ export type NodeExecute<ResultType = Record<string, any>, ParamsType = Record<string, any>> = (
46
+ context: NodeExecuteContext<ResultType, ParamsType>,
47
+ ) => Promise<ResultData<ResultType>>;
36
48
 
37
- class Node<ResultType = Record<string, any>> {
49
+ class Node {
38
50
  public nodeId: string;
39
51
  public params: NodeDataParams; // App-specific parameters
40
52
  public inputs: Array<string>; // List of nodes this node needs data from.
@@ -42,15 +54,16 @@ class Node<ResultType = Record<string, any>> {
42
54
  public waitlist: Set<string>; // List of nodes which need data from this node.
43
55
  public state: NodeState;
44
56
  public functionName: string;
45
- public result: ResultData<ResultType>;
57
+ public result: ResultData;
46
58
  public retryLimit: number;
47
59
  public retryCount: number;
48
60
  public transactionId: undefined | number; // To reject callbacks from timed-out transactions
49
61
  public timeout: number; // msec
62
+ public error: undefined | Error;
50
63
 
51
- private graph: GraphAI<ResultType>;
64
+ private graph: GraphAI;
52
65
 
53
- constructor(nodeId: string, data: NodeData, graph: GraphAI<ResultType>) {
66
+ constructor(nodeId: string, data: NodeData, graph: GraphAI) {
54
67
  this.nodeId = nodeId;
55
68
  this.inputs = data.inputs ?? [];
56
69
  this.pendings = new Set(this.inputs);
@@ -70,13 +83,15 @@ class Node<ResultType = Record<string, any>> {
70
83
  return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
71
84
  }
72
85
 
73
- private retry(state: NodeState, result: ResultData<ResultType>) {
86
+ private retry(state: NodeState, error: Error) {
74
87
  if (this.retryCount < this.retryLimit) {
75
88
  this.retryCount++;
76
89
  this.execute();
77
90
  } else {
78
91
  this.state = state;
79
- this.result = result;
92
+ this.result = undefined;
93
+ this.error = error;
94
+ this.transactionId = 0; // This is necessary for timeout case
80
95
  this.graph.removeRunning(this);
81
96
  }
82
97
  }
@@ -87,7 +102,7 @@ class Node<ResultType = Record<string, any>> {
87
102
  }
88
103
 
89
104
  public payload() {
90
- return this.inputs.reduce((results: ResultDataDictonary<ResultType>, nodeId) => {
105
+ return this.inputs.reduce((results: ResultDataDictonary, nodeId) => {
91
106
  results[nodeId] = this.graph.nodes[nodeId].result;
92
107
  return results;
93
108
  }, {});
@@ -100,15 +115,27 @@ class Node<ResultType = Record<string, any>> {
100
115
  }
101
116
 
102
117
  public async execute() {
118
+ const log: TransactionLog = {
119
+ nodeId: this.nodeId,
120
+ retryCount: this.retryCount,
121
+ state: NodeState.Executing,
122
+ startTime: Date.now(),
123
+ endTime: undefined,
124
+ error: undefined,
125
+ };
126
+ this.graph.appendLog(log);
103
127
  this.state = NodeState.Executing;
104
- const transactionId = Date.now();
128
+ const transactionId = log.startTime;
105
129
  this.transactionId = transactionId;
106
130
 
107
131
  if (this.timeout > 0) {
108
132
  setTimeout(() => {
109
133
  if (this.state === NodeState.Executing && this.transactionId === transactionId) {
110
- console.log("*** timeout", this.timeout);
111
- this.retry(NodeState.TimedOut, undefined);
134
+ console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
135
+ log.error = Error("Timeout");
136
+ log.state = NodeState.TimedOut;
137
+ log.endTime = Date.now();
138
+ this.retry(NodeState.TimedOut, Error("Timeout"));
112
139
  }
113
140
  }, this.timeout);
114
141
  }
@@ -122,9 +149,12 @@ class Node<ResultType = Record<string, any>> {
122
149
  payload: this.payload(),
123
150
  });
124
151
  if (this.transactionId !== transactionId) {
125
- console.log("****** transactionId mismatch (success)");
152
+ console.log(`-- ${this.nodeId}: transactionId mismatch`);
126
153
  return;
127
154
  }
155
+ log.state = NodeState.Completed;
156
+ log.endTime = Date.now();
157
+ log.result = result;
128
158
  this.state = NodeState.Completed;
129
159
  this.result = result;
130
160
  this.waitlist.forEach((nodeId) => {
@@ -132,39 +162,51 @@ class Node<ResultType = Record<string, any>> {
132
162
  node.removePending(this.nodeId);
133
163
  });
134
164
  this.graph.removeRunning(this);
135
- } catch (e) {
165
+ } catch (error) {
136
166
  if (this.transactionId !== transactionId) {
137
- console.log("****** transactionId mismatch (failed)");
167
+ console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
138
168
  return;
139
169
  }
140
- this.retry(NodeState.Failed, undefined);
170
+ log.state = NodeState.Failed;
171
+ log.endTime = Date.now();
172
+ if (error instanceof Error) {
173
+ log.error = error;
174
+ this.retry(NodeState.Failed, error);
175
+ } else {
176
+ console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
177
+ log.error = Error("Unknown");
178
+ this.retry(NodeState.Failed, Error("Unknown"));
179
+ }
141
180
  }
142
181
  }
143
182
  }
144
183
 
145
- type GraphNodes<ResultType> = Record<string, Node<ResultType>>;
184
+ type GraphNodes = Record<string, Node>;
146
185
 
147
- type NodeExecuteDictonary<ResultType> = Record<string, NodeExecute<ResultType>>;
186
+ type NodeExecuteDictonary = Record<string, NodeExecute>;
148
187
 
149
- export class GraphAI<ResultType = Record<string, any>> {
150
- public nodes: GraphNodes<ResultType>;
151
- public callbackDictonary: NodeExecuteDictonary<ResultType>;
188
+ const defaultConcurrency = 8;
189
+
190
+ export class GraphAI {
191
+ public nodes: GraphNodes;
192
+ public callbackDictonary: NodeExecuteDictonary;
152
193
  private runningNodes: Set<string>;
153
- private nodeQueue: Array<Node<ResultType>>;
194
+ private nodeQueue: Array<Node>;
154
195
  private onComplete: () => void;
155
196
  private concurrency: number;
197
+ private logs: Array<TransactionLog> = [];
156
198
 
157
- constructor(data: GraphData, callbackDictonary: NodeExecuteDictonary<ResultType> | NodeExecute<ResultType>) {
199
+ constructor(data: GraphData, callbackDictonary: NodeExecuteDictonary | NodeExecute) {
158
200
  this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
159
201
  if (this.callbackDictonary["default"] === undefined) {
160
202
  throw new Error("No default function");
161
203
  }
162
- this.concurrency = data.concurrency ?? 2;
204
+ this.concurrency = data.concurrency ?? defaultConcurrency;
163
205
  this.runningNodes = new Set<string>();
164
206
  this.nodeQueue = [];
165
207
  this.onComplete = () => {};
166
- this.nodes = Object.keys(data.nodes).reduce((nodes: GraphNodes<ResultType>, nodeId: string) => {
167
- nodes[nodeId] = new Node<ResultType>(nodeId, data.nodes[nodeId], this);
208
+ this.nodes = Object.keys(data.nodes).reduce((nodes: GraphNodes, nodeId: string) => {
209
+ nodes[nodeId] = new Node(nodeId, data.nodes[nodeId], this);
168
210
  return nodes;
169
211
  }, {});
170
212
 
@@ -193,6 +235,26 @@ export class GraphAI<ResultType = Record<string, any>> {
193
235
  .join("\n");
194
236
  }
195
237
 
238
+ public results() {
239
+ return Object.keys(this.nodes).reduce((results: ResultDataDictonary, nodeId) => {
240
+ const node = this.nodes[nodeId];
241
+ if (node.result !== undefined) {
242
+ results[nodeId] = node.result;
243
+ }
244
+ return results;
245
+ }, {});
246
+ }
247
+
248
+ public errors() {
249
+ return Object.keys(this.nodes).reduce((errors: Record<string, Error>, nodeId) => {
250
+ const node = this.nodes[nodeId];
251
+ if (node.error !== undefined) {
252
+ errors[nodeId] = node.error;
253
+ }
254
+ return errors;
255
+ }, {});
256
+ }
257
+
196
258
  public async run() {
197
259
  // Nodes without pending data should run immediately.
198
260
  Object.keys(this.nodes).forEach((nodeId) => {
@@ -202,21 +264,23 @@ export class GraphAI<ResultType = Record<string, any>> {
202
264
 
203
265
  return new Promise((resolve, reject) => {
204
266
  this.onComplete = () => {
205
- const results = Object.keys(this.nodes).reduce((results: ResultDataDictonary<ResultType>, nodeId) => {
206
- results[nodeId] = this.nodes[nodeId].result;
207
- return results;
208
- }, {});
209
- resolve(results);
267
+ const errors = this.errors();
268
+ const nodeIds = Object.keys(errors);
269
+ if (nodeIds.length > 0) {
270
+ reject(errors[nodeIds[0]]);
271
+ } else {
272
+ resolve(this.results());
273
+ }
210
274
  };
211
275
  });
212
276
  }
213
277
 
214
- private runNode(node: Node<ResultType>) {
278
+ private runNode(node: Node) {
215
279
  this.runningNodes.add(node.nodeId);
216
280
  node.execute();
217
281
  }
218
282
 
219
- public pushQueue(node: Node<ResultType>) {
283
+ public pushQueue(node: Node) {
220
284
  if (this.runningNodes.size < this.concurrency) {
221
285
  this.runNode(node);
222
286
  } else {
@@ -224,7 +288,7 @@ export class GraphAI<ResultType = Record<string, any>> {
224
288
  }
225
289
  }
226
290
 
227
- public removeRunning(node: Node<ResultType>) {
291
+ public removeRunning(node: Node) {
228
292
  this.runningNodes.delete(node.nodeId);
229
293
  if (this.nodeQueue.length > 0) {
230
294
  const n = this.nodeQueue.shift();
@@ -236,4 +300,12 @@ export class GraphAI<ResultType = Record<string, any>> {
236
300
  this.onComplete();
237
301
  }
238
302
  }
303
+
304
+ public appendLog(log: TransactionLog) {
305
+ this.logs.push(log);
306
+ }
307
+
308
+ public transactionLogs() {
309
+ return this.logs;
310
+ }
239
311
  }
@@ -0,0 +1,22 @@
1
+ nodes:
2
+ node1:
3
+ params:
4
+ delay: 500
5
+ node2:
6
+ params:
7
+ delay: 100
8
+ node3:
9
+ params:
10
+ delay: 500
11
+ fail: true
12
+ inputs: [node1, node2]
13
+ node4:
14
+ timeout: 200
15
+ retry: 2
16
+ params:
17
+ delay: 300
18
+ inputs: [node3]
19
+ node5:
20
+ params:
21
+ delay: 100
22
+ inputs: [node4]
@@ -19,3 +19,9 @@ nodes:
19
19
  delay: 500
20
20
  inputs: [node2, node4]
21
21
  functionName: test2
22
+ node6:
23
+ params:
24
+ delay: 100
25
+ number: 10
26
+ inputs: [node4]
27
+ functionName: numberTestFunction
@@ -0,0 +1,22 @@
1
+ nodes:
2
+ node1:
3
+ params:
4
+ delay: 500
5
+ node2:
6
+ params:
7
+ delay: 100
8
+ node3:
9
+ params:
10
+ delay: 500
11
+ fail: true
12
+ inputs: [node1, node2]
13
+ retry: 2
14
+ node4:
15
+ timeout: 200
16
+ params:
17
+ delay: 300
18
+ inputs: [node3]
19
+ node5:
20
+ params:
21
+ delay: 100
22
+ inputs: [node4]
@@ -1,12 +1,12 @@
1
1
  import path from "path";
2
- import { GraphAI, NodeExecuteContext } from "../src/graphai";
3
- import { readManifestData } from "../src/file_utils";
2
+ import { GraphAI, NodeExecute } from "../src/graphai";
3
+ import { readManifestData } from "./file_utils";
4
4
  import { sleep } from "./utils";
5
5
 
6
6
  import test from "node:test";
7
7
  import assert from "node:assert";
8
8
 
9
- const testFunction1 = async (context: NodeExecuteContext<Record<string, string>>) => {
9
+ const testFunction1: NodeExecute<Record<string, string>> = async (context) => {
10
10
  const { nodeId, retry, params } = context;
11
11
  console.log("executing", nodeId, params);
12
12
 
@@ -15,7 +15,7 @@ const testFunction1 = async (context: NodeExecuteContext<Record<string, string>>
15
15
  return result;
16
16
  };
17
17
 
18
- const testFunction2 = async (context: NodeExecuteContext<Record<string, string>>) => {
18
+ const testFunction2: NodeExecute<Record<string, string>> = async (context) => {
19
19
  const { nodeId, retry, params } = context;
20
20
  console.log("executing", nodeId, params);
21
21
 
@@ -24,11 +24,20 @@ const testFunction2 = async (context: NodeExecuteContext<Record<string, string>>
24
24
  return result;
25
25
  };
26
26
 
27
+ const numberTestFunction: NodeExecute<Record<string, number>, Record<"number", number>> = async (context) => {
28
+ const { nodeId, retry, params } = context;
29
+ console.log("executing", nodeId, params);
30
+
31
+ const result = { [nodeId]: params.number };
32
+ console.log("completing", nodeId, result);
33
+ return result;
34
+ };
35
+
27
36
  const runTest = async (file: string) => {
28
37
  const file_path = path.resolve(__dirname) + file;
29
38
  const graph_data = readManifestData(file_path);
30
39
 
31
- const graph = new GraphAI(graph_data, { default: testFunction1, test2: testFunction2 });
40
+ const graph = new GraphAI(graph_data, { default: testFunction1, test2: testFunction2, numberTestFunction });
32
41
 
33
42
  const results = await graph.run();
34
43
  console.log(results);
@@ -43,5 +52,6 @@ test("test sample1", async () => {
43
52
  node3: { node3: "output 2" },
44
53
  node4: { node4: "output 1" },
45
54
  node5: { node5: "output 2" },
55
+ node6: { node6: 10 },
46
56
  });
47
57
  });
@@ -1,19 +1,19 @@
1
1
  import path from "path";
2
- import { GraphAI, NodeExecuteContext } from "../src/graphai";
2
+ import { GraphAI, NodeExecute } from "../src/graphai";
3
3
  import { readManifestData } from "./file_utils";
4
4
  import { sleep } from "./utils";
5
5
 
6
6
  import test from "node:test";
7
7
  import assert from "node:assert";
8
8
 
9
- const testFunction = async (context: NodeExecuteContext<Record<string, string>>) => {
9
+ const testFunction: NodeExecute<Record<string, string>> = async (context) => {
10
10
  const { nodeId, retry, params, payload } = context;
11
- console.log("executing", nodeId, params);
11
+ console.log("executing", nodeId);
12
12
  await sleep(params.delay / (retry + 1));
13
13
 
14
14
  if (params.fail && retry < 2) {
15
15
  const result = { [nodeId]: "failed" };
16
- console.log("failed", nodeId, result, retry);
16
+ console.log("failed (intentional)", nodeId, retry);
17
17
  throw new Error("Intentional Failure");
18
18
  } else {
19
19
  const result = Object.keys(payload).reduce(
@@ -23,7 +23,7 @@ const testFunction = async (context: NodeExecuteContext<Record<string, string>>)
23
23
  },
24
24
  { [nodeId]: "output" },
25
25
  );
26
- console.log("completing", nodeId, result);
26
+ console.log("completing", nodeId);
27
27
  return result;
28
28
  }
29
29
  };
@@ -34,13 +34,21 @@ const runTest = async (file: string) => {
34
34
 
35
35
  const graph = new GraphAI(graph_data, testFunction);
36
36
 
37
- const results = await graph.run();
38
- console.log(results);
39
- return results;
37
+ try {
38
+ const results = await graph.run();
39
+ console.log(graph.transactionLogs());
40
+ return results;
41
+ } catch (error) {
42
+ if (error instanceof Error) {
43
+ console.log("Error:", error.message);
44
+ }
45
+ console.log(graph.transactionLogs());
46
+ return graph.results();
47
+ }
40
48
  };
41
49
 
42
- test("test sample1", async () => {
43
- const result = await runTest("/graphs/sample1.yml");
50
+ test("test base", async () => {
51
+ const result = await runTest("/graphs/test_base.yml");
44
52
  assert.deepStrictEqual(result, {
45
53
  node1: { node1: "output" },
46
54
  node2: { node2: "output" },
@@ -50,8 +58,8 @@ test("test sample1", async () => {
50
58
  });
51
59
  });
52
60
 
53
- test("test sample1", async () => {
54
- const result = await runTest("/graphs/sample2.yml");
61
+ test("test retry", async () => {
62
+ const result = await runTest("/graphs/test_retry.yml");
55
63
  assert.deepStrictEqual(result, {
56
64
  node1: { node1: "output" },
57
65
  node2: { node2: "output" },
@@ -59,5 +67,21 @@ test("test sample1", async () => {
59
67
  node4: { node4: "output", node3: "output", node1: "output", node2: "output" },
60
68
  node5: { node5: "output", node4: "output", node3: "output", node1: "output", node2: "output" },
61
69
  });
62
- console.log("COMPLETE 2");
70
+ });
71
+
72
+ test("test error", async () => {
73
+ const result = await runTest("/graphs/test_error.yml");
74
+ assert.deepStrictEqual(result, {
75
+ node1: { node1: "output" },
76
+ node2: { node2: "output" },
77
+ });
78
+ });
79
+
80
+ test("test timeout", async () => {
81
+ const result = await runTest("/graphs/test_timeout.yml");
82
+ assert.deepStrictEqual(result, {
83
+ node1: { node1: "output" },
84
+ node2: { node2: "output" },
85
+ node3: { node3: "output", node1: "output", node2: "output" },
86
+ });
63
87
  });
File without changes
File without changes