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.
- package/.eslintrc.js +27 -46
- package/.github/workflows/node.js.yml +1 -0
- package/README.md +42 -10
- package/lib/graphai.d.ts +57 -27
- package/lib/graphai.js +157 -39
- package/package.json +11 -5
- package/samples/agents/arxiv_agent.ts +46 -0
- package/samples/agents/slashgpt_agent.ts +21 -0
- package/samples/express.ts +47 -0
- package/samples/graphs/arxiv.yml +29 -0
- package/{tests → samples}/sample_gpt.ts +11 -11
- package/samples/sample_paper_ai.ts +26 -0
- package/src/graphai.ts +213 -77
- package/tests/agents/agents.ts +24 -0
- package/tests/graphai/test_dispatch.ts +42 -0
- package/tests/graphai/test_http_client.ts +40 -0
- package/tests/{test_multiple_functions.ts → graphai/test_multiple_functions.ts} +13 -14
- package/tests/graphai/test_sample_flow.ts +72 -0
- package/tests/graphs/test_dispatch.yml +24 -0
- package/tests/graphs/test_error.yml +22 -0
- package/tests/graphs/test_multiple_functions_1.yml +8 -2
- package/tests/graphs/test_source.yml +18 -0
- package/tests/graphs/test_source2.yml +17 -0
- package/tests/graphs/test_timeout.yml +22 -0
- package/tests/http-server/README.md +10 -0
- package/tests/http-server/docs/llm.json +4 -0
- package/tests/http-server/docs/llm2.json +4 -0
- package/tests/{file_utils.ts → utils/file_utils.ts} +10 -1
- package/tests/utils/runner.ts +40 -0
- package/tsconfig.json +5 -2
- package/tests/test_sample_flow.ts +0 -63
- /package/{tests/graphs/sample3.yml → samples/graphs/slash_gpt.yml} +0 -0
- /package/tests/graphs/{sample1.yml → test_base.yml} +0 -0
- /package/tests/graphs/{sample2.yml → test_retry.yml} +0 -0
- /package/tests/{utils.ts → utils/utils.ts} +0 -0
package/.eslintrc.js
CHANGED
|
@@ -1,49 +1,30 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
};
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# GraphAI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
//
|
|
15
|
+
// agent-specific parameters for taskA
|
|
14
16
|
taskB:
|
|
15
17
|
params:
|
|
16
|
-
//
|
|
18
|
+
// agent-specific parameters for taskB
|
|
17
19
|
taskC:
|
|
18
20
|
params:
|
|
19
|
-
//
|
|
21
|
+
// agent-specific parameters for taskC
|
|
20
22
|
inputs: [taskA, taskB]
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
``` TypeScript
|
|
24
|
-
const
|
|
26
|
+
const sampleAgentFunction = async (context: AgentFunctionContext) => {
|
|
25
27
|
const {
|
|
26
28
|
nodeId, // taskA, taskB or taskC
|
|
27
|
-
params, //
|
|
29
|
+
params, // agent-specific parameters specified in the graph definition file
|
|
28
30
|
payload // for taskC, { taskA: resultA, taskB: resultB }
|
|
29
31
|
} = context;
|
|
30
|
-
//
|
|
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,
|
|
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
|
|
14
|
+
inputs?: Array<string>;
|
|
13
15
|
params: NodeDataParams;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
25
|
+
concurrency?: number;
|
|
21
26
|
};
|
|
22
|
-
export type
|
|
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<
|
|
42
|
+
params: NodeDataParams<ParamsType>;
|
|
43
|
+
payload: ResultDataDictonary<PreviousResultType>;
|
|
27
44
|
};
|
|
28
|
-
type
|
|
29
|
-
|
|
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
|
-
|
|
37
|
-
result: ResultData
|
|
55
|
+
agentId: string;
|
|
56
|
+
result: ResultData;
|
|
38
57
|
retryLimit: number;
|
|
39
58
|
retryCount: number;
|
|
40
59
|
transactionId: undefined | number;
|
|
41
|
-
timeout
|
|
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
|
|
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<
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
66
|
-
removeRunning(node: Node
|
|
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.
|
|
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.
|
|
24
|
-
this.
|
|
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,
|
|
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 =
|
|
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.
|
|
50
|
+
if (this.graph.isRunning) {
|
|
51
|
+
this.pushQueueIfReady();
|
|
52
|
+
}
|
|
44
53
|
}
|
|
45
54
|
payload() {
|
|
46
55
|
return this.inputs.reduce((results, nodeId) => {
|
|
47
|
-
|
|
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 =
|
|
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(
|
|
64
|
-
|
|
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.
|
|
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
|
|
127
|
+
payload,
|
|
75
128
|
});
|
|
76
129
|
if (this.transactionId !== transactionId) {
|
|
77
|
-
console.log(
|
|
130
|
+
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
78
131
|
return;
|
|
79
132
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
|
|
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 (
|
|
150
|
+
catch (error) {
|
|
89
151
|
if (this.transactionId !== transactionId) {
|
|
90
|
-
console.log(
|
|
152
|
+
console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
|
|
91
153
|
return;
|
|
92
154
|
}
|
|
93
|
-
|
|
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 ??
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
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(
|
|
121
|
-
if (
|
|
122
|
-
return this.callbackDictonary[
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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": {
|