graphai 0.0.4 → 0.0.6

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.
Files changed (35) hide show
  1. package/.eslintrc.js +27 -46
  2. package/.github/workflows/node.js.yml +1 -0
  3. package/README.md +42 -10
  4. package/lib/graphai.d.ts +57 -27
  5. package/lib/graphai.js +157 -39
  6. package/package.json +11 -5
  7. package/samples/agents/arxiv_agent.ts +46 -0
  8. package/samples/agents/slashgpt_agent.ts +21 -0
  9. package/samples/express.ts +47 -0
  10. package/samples/graphs/arxiv.yml +29 -0
  11. package/{tests → samples}/sample_gpt.ts +11 -11
  12. package/samples/sample_paper_ai.ts +26 -0
  13. package/src/graphai.ts +213 -77
  14. package/tests/agents/agents.ts +24 -0
  15. package/tests/graphai/test_dispatch.ts +42 -0
  16. package/tests/graphai/test_http_client.ts +40 -0
  17. package/tests/{test_multiple_functions.ts → graphai/test_multiple_functions.ts} +13 -14
  18. package/tests/graphai/test_sample_flow.ts +72 -0
  19. package/tests/graphs/test_dispatch.yml +24 -0
  20. package/tests/graphs/test_error.yml +22 -0
  21. package/tests/graphs/test_multiple_functions_1.yml +8 -2
  22. package/tests/graphs/test_source.yml +18 -0
  23. package/tests/graphs/test_source2.yml +17 -0
  24. package/tests/graphs/test_timeout.yml +22 -0
  25. package/tests/http-server/README.md +10 -0
  26. package/tests/http-server/docs/llm.json +4 -0
  27. package/tests/http-server/docs/llm2.json +4 -0
  28. package/tests/{file_utils.ts → utils/file_utils.ts} +10 -1
  29. package/tests/utils/runner.ts +40 -0
  30. package/tsconfig.json +5 -2
  31. package/tests/test_sample_flow.ts +0 -63
  32. /package/{tests/graphs/sample3.yml → samples/graphs/slash_gpt.yml} +0 -0
  33. /package/tests/graphs/{sample1.yml → test_base.yml} +0 -0
  34. /package/tests/graphs/{sample2.yml → test_retry.yml} +0 -0
  35. /package/tests/{utils.ts → utils/utils.ts} +0 -0
package/.eslintrc.js CHANGED
@@ -1,49 +1,30 @@
1
1
  module.exports = {
2
- "env": {
3
- "es2021": true,
4
- "node": true
2
+ env: {
3
+ es2021: true,
4
+ node: true,
5
+ },
6
+ extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
7
+ overrides: [
8
+ {
9
+ env: {
10
+ node: true,
11
+ },
12
+ files: [".eslintrc.{js,cjs}"],
13
+ parserOptions: {
14
+ sourceType: "script",
15
+ },
5
16
  },
6
- "extends": [
7
- "eslint:recommended",
8
- "plugin:@typescript-eslint/recommended"
9
- ],
10
- "overrides": [
11
- {
12
- "env": {
13
- "node": true
14
- },
15
- "files": [
16
- ".eslintrc.{js,cjs}"
17
- ],
18
- "parserOptions": {
19
- "sourceType": "script"
20
- }
21
- }
22
- ],
23
- "parser": "@typescript-eslint/parser",
24
- "parserOptions": {
25
- "ecmaVersion": "latest",
26
- "sourceType": "module"
27
- },
28
- "plugins": [
29
- "@typescript-eslint"
30
- ],
31
- "rules": {
32
- "indent": [
33
- "error",
34
- 2
35
- ],
36
- "linebreak-style": [
37
- "error",
38
- "unix"
39
- ],
40
- "quotes": [
41
- "error",
42
- "double"
43
- ],
44
- "semi": [
45
- "error",
46
- "always"
47
- ]
48
- }
17
+ ],
18
+ parser: "@typescript-eslint/parser",
19
+ parserOptions: {
20
+ ecmaVersion: "latest",
21
+ sourceType: "module",
22
+ },
23
+ plugins: ["@typescript-eslint"],
24
+ rules: {
25
+ indent: ["error", 2],
26
+ "linebreak-style": ["error", "unix"],
27
+ quotes: ["error", "double"],
28
+ semi: ["error", "always"],
29
+ },
49
30
  };
@@ -24,4 +24,5 @@ jobs:
24
24
  node-version: ${{ matrix.node-version }}
25
25
  cache: 'npm'
26
26
  - run: yarn install
27
+ - run: cd tests/http-server/docs/ && npx http-server &
27
28
  - run: yarn test
package/README.md CHANGED
@@ -1,8 +1,10 @@
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
+ ## Overview
4
4
 
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.
5
+ 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.
6
+
7
+ You just need to describe dependencies among those API calls in a single data flow graph (typically in YAML), create a GraphAI object with it, and run it.
6
8
 
7
9
  Here is an example:
8
10
 
@@ -10,24 +12,24 @@ Here is an example:
10
12
  nodes:
11
13
  taskA:
12
14
  params:
13
- // app-specific parameters for taskA
15
+ // agent-specific parameters for taskA
14
16
  taskB:
15
17
  params:
16
- // app-specific parameters for taskB
18
+ // agent-specific parameters for taskB
17
19
  taskC:
18
20
  params:
19
- // app-specific parameters for taskC
21
+ // agent-specific parameters for taskC
20
22
  inputs: [taskA, taskB]
21
23
  ```
22
24
 
23
25
  ``` TypeScript
24
- const nodeExecute = async (context: NodeExecuteContext) => {
26
+ const sampleAgentFunction = async (context: AgentFunctionContext) => {
25
27
  const {
26
28
  nodeId, // taskA, taskB or taskC
27
- params, // app-specific/task-specific parameters specified in the graph definition file
29
+ params, // agent-specific parameters specified in the graph definition file
28
30
  payload // for taskC, { taskA: resultA, taskB: resultB }
29
31
  } = context;
30
- // App-specific code (such as calling OpenAI's chat.completions API)
32
+ // Agent-specific code (such as calling OpenAI's chat.completions API)
31
33
  ...
32
34
  return result;
33
35
  }
@@ -35,10 +37,40 @@ const nodeExecute = async (context: NodeExecuteContext) => {
35
37
  ...
36
38
  const file = fs.readFileSync(pathToYamlFile, "utf8");
37
39
  const graphdata = YAML.parse(file);
38
- const graph = new GraphAI(graph_data, nodeExecute);
40
+ const graph = new GraphAI(graph_data, sampleAgentFunction);
39
41
  const results = await graph.run();
40
42
  return results["taskC"];
41
-
42
43
  ```
43
44
 
45
+ ## Data Flow Graph
46
+
47
+ A Data Flow Graph (DFG) is a JSON object, which defines the flow of data. It is typically described in YAML file and loaded at runtime.
48
+
49
+ 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, retry, timeout, source, dispatch, agentId) that dictate the node's behavior and its relationship with other nodes.
50
+
51
+ ### DFG Structure
52
+
53
+ - 'nodes': A list of node.
54
+ - 'concurrency': An optional number, which specifies the maximum number of concurrent operations (agent functions to be executed at the same time). The default is 8.
55
+
56
+ ## Agent
57
+
58
+ An agent is an abstract object, which takes some inputs and generates an output asynchronously. It could be an LLM (such as GPT-4), an image/video/music generation, a database, or a REST API over HTTP. Each node (except 'source node') is associated with an agent function, which takes data flow into the node as inputs, and generates an output.
59
+
60
+ ## Agent function
61
+
62
+ An agent function is a TypeScript function, which implements an agent. A DFG is associated one or more agent functions. If the DFG is associated with multiple agent functions, each node needs to be associated only one of them (either explicitly with 'agentId' or implicitly to the default Agent function).
63
+
64
+ An agent function receives two set of parameters via AgentFunctionContext, agent specific parameters specified in the DFG and input data came from other nodes (payload).
65
+
66
+ A regular agent function returns the data (type: ```Record<string, any>```), but a dispatcher agent function returns the date with outputID(s) (type: ```Record<string: Record<string, any>>```).
67
+
68
+ ## Node Structure
44
69
 
70
+ - 'inputs': An optional list of node identifiers that the current node depends on. This establishes a flow where the current node can only be executed after the completion of the nodes listed under 'inputs'. If this list is empty and the 'source' property is not set, the associated Agent Function will be immediatley executed.
71
+ - 'source': An optional flag, which specifies if the node is a 'source node' or not. A souce node is a special node, which receives data from either an external code (via GraphAI's injectResult method) or from a 'dispatcher node'.
72
+ - 'dispatch': An optinal property (type: ```Record<string, string>```) which indicates that the current node is a 'dispatcher node'. A dispatcher node is associated is a dispaher-style agent function, which has multiple possible 'outputs'.
73
+ - 'retry': An optional number, which specifies the maximum number of retries to be made. If the last attempt fails, that return value will be recorded.
74
+ - '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.
75
+ - 'params': An optional parameters to the associated agent function, which are agent specific.
76
+ - 'payloadMapping': An optional property (type: ```Record<string, string>```), which maps input nodeIds to agent specific inputIDs.
package/lib/graphai.d.ts CHANGED
@@ -3,66 +3,96 @@ export declare enum NodeState {
3
3
  Executing = 1,
4
4
  Failed = 2,
5
5
  TimedOut = 3,
6
- Completed = 4
6
+ Completed = 4,
7
+ Injected = 5,
8
+ Dispatched = 6
7
9
  }
8
10
  type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
9
11
  type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
10
- export type NodeDataParams = Record<string, any>;
12
+ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
11
13
  type NodeData = {
12
- inputs: undefined | Array<string>;
14
+ inputs?: Array<string>;
13
15
  params: NodeDataParams;
14
- retry: undefined | number;
15
- timeout: undefined | number;
16
- functionName: undefined | string;
16
+ payloadMapping?: Record<string, string>;
17
+ retry?: number;
18
+ timeout?: number;
19
+ agentId?: string;
20
+ source?: boolean;
21
+ dispatch?: Record<string, string>;
17
22
  };
18
- type GraphData = {
23
+ export type GraphData = {
19
24
  nodes: Record<string, NodeData>;
20
- concurrency: number;
25
+ concurrency?: number;
21
26
  };
22
- export type NodeExecuteContext<ResultType> = {
27
+ export type TransactionLog = {
28
+ nodeId: string;
29
+ state: NodeState;
30
+ startTime: number;
31
+ endTime?: number;
32
+ retryCount: number;
33
+ agentId?: string;
34
+ params?: NodeDataParams;
35
+ payload?: ResultDataDictonary<ResultData>;
36
+ errorMessage?: string;
37
+ result?: ResultData;
38
+ };
39
+ export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
23
40
  nodeId: string;
24
41
  retry: number;
25
- params: NodeDataParams;
26
- payload: ResultDataDictonary<ResultType>;
42
+ params: NodeDataParams<ParamsType>;
43
+ payload: ResultDataDictonary<PreviousResultType>;
27
44
  };
28
- type NodeExecute<ResultType> = (context: NodeExecuteContext<ResultType>) => Promise<ResultData<ResultType>>;
29
- declare class Node<ResultType = Record<string, any>> {
45
+ export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, ResultType, PreviousResultType>) => Promise<ResultData<ResultType>>;
46
+ export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
47
+ declare class Node {
30
48
  nodeId: string;
31
49
  params: NodeDataParams;
32
50
  inputs: Array<string>;
51
+ payloadMapping: Record<string, string>;
33
52
  pendings: Set<string>;
34
53
  waitlist: Set<string>;
35
54
  state: NodeState;
36
- functionName: string;
37
- result: ResultData<ResultType>;
55
+ agentId: string;
56
+ result: ResultData;
38
57
  retryLimit: number;
39
58
  retryCount: number;
40
59
  transactionId: undefined | number;
41
- timeout: number;
60
+ timeout?: number;
61
+ error?: Error;
62
+ source: boolean;
63
+ dispatch?: Record<string, string>;
42
64
  private graph;
43
- constructor(nodeId: string, data: NodeData, graph: GraphAI<ResultType>);
65
+ constructor(nodeId: string, data: NodeData, graph: GraphAI);
44
66
  asString(): string;
45
67
  private retry;
46
68
  removePending(nodeId: string): void;
47
- payload(): ResultDataDictonary<ResultType>;
69
+ payload(): ResultDataDictonary<Record<string, any>>;
48
70
  pushQueueIfReady(): void;
71
+ injectResult(result: ResultData): void;
72
+ private setResult;
49
73
  execute(): Promise<void>;
50
74
  }
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>;
75
+ type GraphNodes = Record<string, Node>;
76
+ export declare class GraphAI {
77
+ nodes: GraphNodes;
78
+ callbackDictonary: AgentFunctionDictonary;
79
+ isRunning: boolean;
56
80
  private runningNodes;
57
81
  private nodeQueue;
58
82
  private onComplete;
59
83
  private concurrency;
60
- constructor(data: GraphData, callbackDictonary: NodeExecuteDictonary<ResultType> | NodeExecute<ResultType>);
61
- getCallback(functionName: string): NodeExecute<ResultType>;
84
+ private logs;
85
+ constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>);
86
+ getCallback(agentId: string): AgentFunction<any, any, any>;
62
87
  asString(): string;
88
+ results(): ResultDataDictonary<Record<string, any>>;
89
+ errors(): Record<string, Error>;
63
90
  run(): Promise<unknown>;
64
91
  private runNode;
65
- pushQueue(node: Node<ResultType>): void;
66
- removeRunning(node: Node<ResultType>): void;
92
+ pushQueue(node: Node): void;
93
+ removeRunning(node: Node): void;
94
+ appendLog(log: TransactionLog): void;
95
+ transactionLogs(): TransactionLog[];
96
+ injectResult(nodeId: string, result: ResultData): void;
67
97
  }
68
98
  export {};
package/lib/graphai.js CHANGED
@@ -8,102 +8,179 @@ var NodeState;
8
8
  NodeState[NodeState["Failed"] = 2] = "Failed";
9
9
  NodeState[NodeState["TimedOut"] = 3] = "TimedOut";
10
10
  NodeState[NodeState["Completed"] = 4] = "Completed";
11
+ NodeState[NodeState["Injected"] = 5] = "Injected";
12
+ NodeState[NodeState["Dispatched"] = 6] = "Dispatched";
11
13
  })(NodeState || (exports.NodeState = NodeState = {}));
12
14
  class Node {
13
15
  constructor(nodeId, data, graph) {
16
+ this.waitlist = new Set(); // List of nodes which need data from this node.
17
+ this.state = NodeState.Waiting;
18
+ this.result = undefined;
19
+ this.retryCount = 0;
14
20
  this.nodeId = nodeId;
15
21
  this.inputs = data.inputs ?? [];
22
+ this.payloadMapping = data.payloadMapping ?? {};
16
23
  this.pendings = new Set(this.inputs);
17
24
  this.params = data.params;
18
- this.waitlist = new Set();
19
- this.state = NodeState.Waiting;
20
- this.functionName = data.functionName ?? "default";
21
- this.result = undefined;
25
+ this.agentId = data.agentId ?? "default";
22
26
  this.retryLimit = data.retry ?? 0;
23
- this.retryCount = 0;
24
- this.timeout = data.timeout ?? 0;
27
+ this.timeout = data.timeout;
28
+ this.source = data.source === true;
29
+ this.dispatch = data.dispatch;
25
30
  this.graph = graph;
26
31
  }
27
32
  asString() {
28
33
  return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
29
34
  }
30
- retry(state, result) {
35
+ retry(state, error) {
31
36
  if (this.retryCount < this.retryLimit) {
32
37
  this.retryCount++;
33
38
  this.execute();
34
39
  }
35
40
  else {
36
41
  this.state = state;
37
- this.result = result;
42
+ this.result = undefined;
43
+ this.error = error;
44
+ this.transactionId = undefined; // This is necessary for timeout case
38
45
  this.graph.removeRunning(this);
39
46
  }
40
47
  }
41
48
  removePending(nodeId) {
42
49
  this.pendings.delete(nodeId);
43
- this.pushQueueIfReady();
50
+ if (this.graph.isRunning) {
51
+ this.pushQueueIfReady();
52
+ }
44
53
  }
45
54
  payload() {
46
55
  return this.inputs.reduce((results, nodeId) => {
47
- results[nodeId] = this.graph.nodes[nodeId].result;
56
+ if (this.payloadMapping && this.payloadMapping[nodeId]) {
57
+ results[this.payloadMapping[nodeId]] = this.graph.nodes[nodeId].result;
58
+ }
59
+ else {
60
+ results[nodeId] = this.graph.nodes[nodeId].result;
61
+ }
48
62
  return results;
49
63
  }, {});
50
64
  }
51
65
  pushQueueIfReady() {
52
- if (this.pendings.size === 0) {
66
+ if (this.pendings.size === 0 && !this.source) {
53
67
  this.graph.pushQueue(this);
54
68
  }
55
69
  }
70
+ injectResult(result) {
71
+ if (this.source) {
72
+ const log = {
73
+ nodeId: this.nodeId,
74
+ retryCount: this.retryCount,
75
+ state: NodeState.Injected,
76
+ startTime: Date.now(),
77
+ };
78
+ log.endTime = log.startTime;
79
+ this.graph.appendLog(log);
80
+ this.setResult(result, NodeState.Injected);
81
+ }
82
+ else {
83
+ console.error("- injectResult called on non-source node.", this.nodeId);
84
+ }
85
+ }
86
+ setResult(result, state) {
87
+ this.state = state;
88
+ this.result = result;
89
+ this.waitlist.forEach((nodeId) => {
90
+ const node = this.graph.nodes[nodeId];
91
+ // Todo: Avoid running before Run()
92
+ node.removePending(this.nodeId);
93
+ });
94
+ }
56
95
  async execute() {
96
+ const payload = this.payload();
97
+ const log = {
98
+ nodeId: this.nodeId,
99
+ retryCount: this.retryCount,
100
+ state: NodeState.Executing,
101
+ startTime: Date.now(),
102
+ agentId: this.agentId,
103
+ params: this.params,
104
+ payload,
105
+ };
106
+ this.graph.appendLog(log);
57
107
  this.state = NodeState.Executing;
58
- const transactionId = Date.now();
108
+ const transactionId = log.startTime;
59
109
  this.transactionId = transactionId;
60
- if (this.timeout > 0) {
110
+ if (this.timeout && this.timeout > 0) {
61
111
  setTimeout(() => {
62
112
  if (this.state === NodeState.Executing && this.transactionId === transactionId) {
63
- console.log("*** timeout", this.timeout);
64
- this.retry(NodeState.TimedOut, undefined);
113
+ console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
114
+ log.errorMessage = "Timeout";
115
+ log.state = NodeState.TimedOut;
116
+ log.endTime = Date.now();
117
+ this.retry(NodeState.TimedOut, Error("Timeout"));
65
118
  }
66
119
  }, this.timeout);
67
120
  }
68
121
  try {
69
- const callback = this.graph.getCallback(this.functionName);
122
+ const callback = this.graph.getCallback(this.agentId);
70
123
  const result = await callback({
71
124
  nodeId: this.nodeId,
72
125
  retry: this.retryCount,
73
126
  params: this.params,
74
- payload: this.payload(),
127
+ payload,
75
128
  });
76
129
  if (this.transactionId !== transactionId) {
77
- console.log("****** transactionId mismatch (success)");
130
+ console.log(`-- ${this.nodeId}: transactionId mismatch`);
78
131
  return;
79
132
  }
80
- this.state = NodeState.Completed;
81
- this.result = result;
82
- this.waitlist.forEach((nodeId) => {
83
- const node = this.graph.nodes[nodeId];
84
- node.removePending(this.nodeId);
85
- });
133
+ log.endTime = Date.now();
134
+ log.result = result;
135
+ const dispatch = this.dispatch;
136
+ if (dispatch !== undefined) {
137
+ Object.keys(result).forEach((outputId) => {
138
+ const nodeId = dispatch[outputId];
139
+ this.graph.injectResult(nodeId, result[outputId]);
140
+ });
141
+ log.state = NodeState.Dispatched;
142
+ this.state = NodeState.Dispatched;
143
+ this.graph.removeRunning(this);
144
+ return;
145
+ }
146
+ log.state = NodeState.Completed;
147
+ this.setResult(result, NodeState.Completed);
86
148
  this.graph.removeRunning(this);
87
149
  }
88
- catch (e) {
150
+ catch (error) {
89
151
  if (this.transactionId !== transactionId) {
90
- console.log("****** transactionId mismatch (failed)");
152
+ console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
91
153
  return;
92
154
  }
93
- this.retry(NodeState.Failed, undefined);
155
+ log.state = NodeState.Failed;
156
+ log.endTime = Date.now();
157
+ if (error instanceof Error) {
158
+ log.errorMessage = error.message;
159
+ this.retry(NodeState.Failed, error);
160
+ }
161
+ else {
162
+ console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
163
+ log.errorMessage = "Unknown";
164
+ this.retry(NodeState.Failed, Error("Unknown"));
165
+ }
94
166
  }
95
167
  }
96
168
  }
169
+ const defaultConcurrency = 8;
97
170
  class GraphAI {
98
171
  constructor(data, callbackDictonary) {
172
+ this.isRunning = false;
173
+ this.runningNodes = new Set();
174
+ this.nodeQueue = [];
175
+ this.logs = [];
99
176
  this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
100
177
  if (this.callbackDictonary["default"] === undefined) {
101
178
  throw new Error("No default function");
102
179
  }
103
- this.concurrency = data.concurrency ?? 2;
104
- this.runningNodes = new Set();
105
- this.nodeQueue = [];
106
- this.onComplete = () => { };
180
+ this.concurrency = data.concurrency ?? defaultConcurrency;
181
+ this.onComplete = () => {
182
+ console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
183
+ };
107
184
  this.nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
108
185
  nodes[nodeId] = new Node(nodeId, data.nodes[nodeId], this);
109
186
  return nodes;
@@ -117,9 +194,9 @@ class GraphAI {
117
194
  });
118
195
  });
119
196
  }
120
- getCallback(functionName) {
121
- if (functionName && this.callbackDictonary[functionName]) {
122
- return this.callbackDictonary[functionName];
197
+ getCallback(agentId) {
198
+ if (agentId && this.callbackDictonary[agentId]) {
199
+ return this.callbackDictonary[agentId];
123
200
  }
124
201
  return this.callbackDictonary["default"];
125
202
  }
@@ -130,7 +207,29 @@ class GraphAI {
130
207
  })
131
208
  .join("\n");
132
209
  }
210
+ results() {
211
+ return Object.keys(this.nodes).reduce((results, nodeId) => {
212
+ const node = this.nodes[nodeId];
213
+ if (node.result !== undefined) {
214
+ results[nodeId] = node.result;
215
+ }
216
+ return results;
217
+ }, {});
218
+ }
219
+ errors() {
220
+ return Object.keys(this.nodes).reduce((errors, nodeId) => {
221
+ const node = this.nodes[nodeId];
222
+ if (node.error !== undefined) {
223
+ errors[nodeId] = node.error;
224
+ }
225
+ return errors;
226
+ }, {});
227
+ }
133
228
  async run() {
229
+ if (this.isRunning) {
230
+ console.error("-- Already Running");
231
+ }
232
+ this.isRunning = true;
134
233
  // Nodes without pending data should run immediately.
135
234
  Object.keys(this.nodes).forEach((nodeId) => {
136
235
  const node = this.nodes[nodeId];
@@ -138,11 +237,15 @@ class GraphAI {
138
237
  });
139
238
  return new Promise((resolve, reject) => {
140
239
  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);
240
+ this.isRunning = false;
241
+ const errors = this.errors();
242
+ const nodeIds = Object.keys(errors);
243
+ if (nodeIds.length > 0) {
244
+ reject(errors[nodeIds[0]]);
245
+ }
246
+ else {
247
+ resolve(this.results());
248
+ }
146
249
  };
147
250
  });
148
251
  }
@@ -170,5 +273,20 @@ class GraphAI {
170
273
  this.onComplete();
171
274
  }
172
275
  }
276
+ appendLog(log) {
277
+ this.logs.push(log);
278
+ }
279
+ transactionLogs() {
280
+ return this.logs;
281
+ }
282
+ injectResult(nodeId, result) {
283
+ const node = this.nodes[nodeId];
284
+ if (node) {
285
+ node.injectResult(result);
286
+ }
287
+ else {
288
+ console.error("-- Invalid nodeId", nodeId);
289
+ }
290
+ }
173
291
  }
174
292
  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.6",
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
- "build": "tsc",
7
+ "build": "tsc && tsc-alias",
8
8
  "eslint": "eslint --fix --ext src/**/*.{ts} tests/**/*.ts",
9
- "format": "prettier --write '{src,tests}/**/*.ts'",
10
- "test": "node --test --require ts-node/register ./tests/test_*.ts"
9
+ "format": "prettier --write '{src,tests,samples}/**/*.ts' .eslintrc.js",
10
+ "test": "node --test -r tsconfig-paths/register --require ts-node/register ./tests/**/test_*.ts",
11
+ "gpt": "npx ts-node -r tsconfig-paths/register ./samples/sample_gpt.ts"
11
12
  },
12
13
  "repository": {
13
14
  "type": "git",
@@ -20,15 +21,20 @@
20
21
  },
21
22
  "homepage": "https://github.com/snakajima/graphai#readme",
22
23
  "devDependencies": {
24
+ "@types/express": "^4.17.21",
23
25
  "@types/node": "^20.8.7",
24
26
  "@typescript-eslint/eslint-plugin": "^6.8.0",
25
27
  "@typescript-eslint/parser": "^6.8.0",
28
+ "arxiv-api-ts": "^1.0.3",
26
29
  "eslint": "^7.32.0 || ^8.2.0",
27
30
  "eslint-plugin-import": "^2.25.2",
31
+ "express": "^4.19.2",
28
32
  "openai": "^4.12.4",
29
33
  "prettier": "^3.0.3",
30
- "slashgpt": "^0.0.6",
34
+ "slashgpt": "^0.0.8",
31
35
  "ts-node": "^10.9.1",
36
+ "tsc-alias": "^1.8.8",
37
+ "tsconfig-paths": "^4.2.0",
32
38
  "typescript": "^5.2.2"
33
39
  },
34
40
  "dependencies": {