graphai 0.0.5 → 0.0.7
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 +76 -9
- package/lib/graphai.d.ts +43 -28
- package/lib/graphai.js +98 -36
- package/package.json +10 -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/samples/sample_gpt.ts +10 -10
- package/samples/sample_paper_ai.ts +26 -0
- package/src/graphai.ts +136 -72
- 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} +7 -18
- package/tests/graphai/test_sample_flow.ts +72 -0
- package/tests/graphs/test_dispatch.yml +24 -0
- package/tests/graphs/test_multiple_functions_1.yml +3 -3
- package/tests/graphs/test_source.yml +18 -0
- package/tests/graphs/test_source2.yml +17 -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 -87
- /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
|
+
## Overview
|
|
4
|
+
|
|
3
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.
|
|
4
6
|
|
|
5
|
-
You just need to describe dependencies among those API calls in a single
|
|
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,34 +12,99 @@ 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
|
}
|
|
34
36
|
|
|
35
37
|
...
|
|
36
38
|
const file = fs.readFileSync(pathToYamlFile, "utf8");
|
|
37
|
-
const
|
|
38
|
-
const graph = new GraphAI(
|
|
39
|
+
const graphData = YAML.parse(file);
|
|
40
|
+
const graph = new GraphAI(graphData, sampleAgentFunction);
|
|
39
41
|
const results = await graph.run();
|
|
40
42
|
return results["taskC"];
|
|
41
43
|
```
|
|
42
44
|
|
|
45
|
+
## Data Flow Graph
|
|
46
|
+
|
|
47
|
+
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.
|
|
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. Required.
|
|
54
|
+
- 'concurrency': An optional property, 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 generator, 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
|
|
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.
|
|
77
|
+
|
|
78
|
+
## GraphAI class
|
|
79
|
+
|
|
80
|
+
### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>)```
|
|
81
|
+
Initializes a new instance of the GraphAI class with the specified graph data and a dictionary of callback functions.
|
|
82
|
+
|
|
83
|
+
- ```data: GraphData```: The graph data including nodes and optional concurrency limit.
|
|
84
|
+
- ```callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>```: A dictionary mapping agent IDs to their respective callback functions, or a single default callback function to be used for all nodes.
|
|
85
|
+
|
|
86
|
+
### ```async run(): Promise<ResultDataDictonary<ResultData>>```
|
|
87
|
+
Executes the graph asynchronously, starting with nodes that have no dependencies or whose dependencies have been met. The method continues to execute nodes as their dependencies are satisfied until all nodes have been executed or an error occurs.
|
|
88
|
+
|
|
89
|
+
Returns: A promise that resolves with the results of all executed nodes or rejects with the first encountered error.
|
|
90
|
+
|
|
91
|
+
### ```results(): ResultDataDictonary<ResultData>```
|
|
92
|
+
Compiles and returns the results of all executed nodes in the graph.
|
|
93
|
+
|
|
94
|
+
Returns: A dictionary mapping node IDs to their results. Only includes nodes that have completed execution and produced a result.
|
|
95
|
+
|
|
96
|
+
### ```errors(): Record<string, Error>```
|
|
97
|
+
Compiles and returns the errors from all nodes that encountered an error during execution.
|
|
98
|
+
|
|
99
|
+
Returns: A dictionary mapping node IDs to the errors they encountered. Only includes nodes that have executed and encountered an error. It does not include any errors which have been retried.
|
|
100
|
+
|
|
101
|
+
### ```transactionLogs(): Array<TransactionLog>```
|
|
102
|
+
Retrieves all transaction logs recorded during the execution of the graph.
|
|
103
|
+
|
|
104
|
+
Returns: An array of transaction logs detailing the execution states and outcomes of the nodes within the graph.
|
|
105
|
+
|
|
106
|
+
### ```injectResult(nodeId: string, result: ResultData): void```
|
|
107
|
+
Injects a result into a specified node. This is used to manually set the result of a source node, allowing dependent nodes to proceed with execution.
|
|
43
108
|
|
|
109
|
+
- ```nodeId: string```: The ID of the source node into which the result is to be injected.
|
|
110
|
+
- ```result: ResultData```: The result to be injected into the specified node.
|
package/lib/graphai.d.ts
CHANGED
|
@@ -1,54 +1,66 @@
|
|
|
1
1
|
export declare enum NodeState {
|
|
2
|
-
Waiting =
|
|
3
|
-
Executing =
|
|
4
|
-
Failed =
|
|
5
|
-
TimedOut =
|
|
6
|
-
Completed =
|
|
2
|
+
Waiting = "waiting",
|
|
3
|
+
Executing = "executing",
|
|
4
|
+
Failed = "failed",
|
|
5
|
+
TimedOut = "timed-out",
|
|
6
|
+
Completed = "completed",
|
|
7
|
+
Injected = "injected",
|
|
8
|
+
Dispatched = "dispatched"
|
|
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
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
|
|
21
|
-
};
|
|
22
|
-
type NodeExecuteContext<ResultType, ParamsType> = {
|
|
23
|
-
nodeId: string;
|
|
24
|
-
retry: number;
|
|
25
|
-
params: NodeDataParams<ParamsType>;
|
|
26
|
-
payload: ResultDataDictonary<ResultType>;
|
|
25
|
+
concurrency?: number;
|
|
27
26
|
};
|
|
28
27
|
export type TransactionLog = {
|
|
29
28
|
nodeId: string;
|
|
30
29
|
state: NodeState;
|
|
31
|
-
startTime:
|
|
32
|
-
endTime
|
|
30
|
+
startTime: number;
|
|
31
|
+
endTime?: number;
|
|
33
32
|
retryCount: number;
|
|
34
|
-
|
|
33
|
+
agentId?: string;
|
|
34
|
+
params?: NodeDataParams;
|
|
35
|
+
payload?: ResultDataDictonary<ResultData>;
|
|
36
|
+
errorMessage?: string;
|
|
35
37
|
result?: ResultData;
|
|
36
38
|
};
|
|
37
|
-
export type
|
|
39
|
+
export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
|
|
40
|
+
nodeId: string;
|
|
41
|
+
retry: number;
|
|
42
|
+
params: NodeDataParams<ParamsType>;
|
|
43
|
+
payload: ResultDataDictonary<PreviousResultType>;
|
|
44
|
+
};
|
|
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>>;
|
|
38
47
|
declare class Node {
|
|
39
48
|
nodeId: string;
|
|
40
49
|
params: NodeDataParams;
|
|
41
50
|
inputs: Array<string>;
|
|
51
|
+
payloadMapping: Record<string, string>;
|
|
42
52
|
pendings: Set<string>;
|
|
43
53
|
waitlist: Set<string>;
|
|
44
54
|
state: NodeState;
|
|
45
|
-
|
|
55
|
+
agentId: string;
|
|
46
56
|
result: ResultData;
|
|
47
57
|
retryLimit: number;
|
|
48
58
|
retryCount: number;
|
|
49
59
|
transactionId: undefined | number;
|
|
50
|
-
timeout
|
|
51
|
-
error
|
|
60
|
+
timeout?: number;
|
|
61
|
+
error?: Error;
|
|
62
|
+
source: boolean;
|
|
63
|
+
dispatch?: Record<string, string>;
|
|
52
64
|
private graph;
|
|
53
65
|
constructor(nodeId: string, data: NodeData, graph: GraphAI);
|
|
54
66
|
asString(): string;
|
|
@@ -56,20 +68,22 @@ declare class Node {
|
|
|
56
68
|
removePending(nodeId: string): void;
|
|
57
69
|
payload(): ResultDataDictonary<Record<string, any>>;
|
|
58
70
|
pushQueueIfReady(): void;
|
|
71
|
+
injectResult(result: ResultData): void;
|
|
72
|
+
private setResult;
|
|
59
73
|
execute(): Promise<void>;
|
|
60
74
|
}
|
|
61
75
|
type GraphNodes = Record<string, Node>;
|
|
62
|
-
type NodeExecuteDictonary = Record<string, NodeExecute>;
|
|
63
76
|
export declare class GraphAI {
|
|
64
77
|
nodes: GraphNodes;
|
|
65
|
-
callbackDictonary:
|
|
78
|
+
callbackDictonary: AgentFunctionDictonary;
|
|
79
|
+
isRunning: boolean;
|
|
66
80
|
private runningNodes;
|
|
67
81
|
private nodeQueue;
|
|
68
82
|
private onComplete;
|
|
69
83
|
private concurrency;
|
|
70
84
|
private logs;
|
|
71
|
-
constructor(data: GraphData, callbackDictonary:
|
|
72
|
-
getCallback(
|
|
85
|
+
constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>);
|
|
86
|
+
getCallback(agentId: string): AgentFunction<any, any, any>;
|
|
73
87
|
asString(): string;
|
|
74
88
|
results(): ResultDataDictonary<Record<string, any>>;
|
|
75
89
|
errors(): Record<string, Error>;
|
|
@@ -79,5 +93,6 @@ export declare class GraphAI {
|
|
|
79
93
|
removeRunning(node: Node): void;
|
|
80
94
|
appendLog(log: TransactionLog): void;
|
|
81
95
|
transactionLogs(): TransactionLog[];
|
|
96
|
+
injectResult(nodeId: string, result: ResultData): void;
|
|
82
97
|
}
|
|
83
98
|
export {};
|
package/lib/graphai.js
CHANGED
|
@@ -3,25 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GraphAI = exports.NodeState = void 0;
|
|
4
4
|
var NodeState;
|
|
5
5
|
(function (NodeState) {
|
|
6
|
-
NodeState[
|
|
7
|
-
NodeState[
|
|
8
|
-
NodeState[
|
|
9
|
-
NodeState[
|
|
10
|
-
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";
|
|
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() {
|
|
@@ -36,43 +41,77 @@ class Node {
|
|
|
36
41
|
this.state = state;
|
|
37
42
|
this.result = undefined;
|
|
38
43
|
this.error = error;
|
|
39
|
-
this.transactionId =
|
|
44
|
+
this.transactionId = undefined; // This is necessary for timeout case
|
|
40
45
|
this.graph.removeRunning(this);
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
removePending(nodeId) {
|
|
44
49
|
this.pendings.delete(nodeId);
|
|
45
|
-
this.
|
|
50
|
+
if (this.graph.isRunning) {
|
|
51
|
+
this.pushQueueIfReady();
|
|
52
|
+
}
|
|
46
53
|
}
|
|
47
54
|
payload() {
|
|
48
55
|
return this.inputs.reduce((results, nodeId) => {
|
|
49
|
-
|
|
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
|
+
}
|
|
50
62
|
return results;
|
|
51
63
|
}, {});
|
|
52
64
|
}
|
|
53
65
|
pushQueueIfReady() {
|
|
54
|
-
if (this.pendings.size === 0) {
|
|
66
|
+
if (this.pendings.size === 0 && !this.source) {
|
|
55
67
|
this.graph.pushQueue(this);
|
|
56
68
|
}
|
|
57
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
|
+
}
|
|
58
95
|
async execute() {
|
|
96
|
+
const payload = this.payload();
|
|
59
97
|
const log = {
|
|
60
98
|
nodeId: this.nodeId,
|
|
61
99
|
retryCount: this.retryCount,
|
|
62
100
|
state: NodeState.Executing,
|
|
63
101
|
startTime: Date.now(),
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
agentId: this.agentId,
|
|
103
|
+
params: this.params,
|
|
104
|
+
payload,
|
|
66
105
|
};
|
|
67
106
|
this.graph.appendLog(log);
|
|
68
107
|
this.state = NodeState.Executing;
|
|
69
108
|
const transactionId = log.startTime;
|
|
70
109
|
this.transactionId = transactionId;
|
|
71
|
-
if (this.timeout > 0) {
|
|
110
|
+
if (this.timeout && this.timeout > 0) {
|
|
72
111
|
setTimeout(() => {
|
|
73
112
|
if (this.state === NodeState.Executing && this.transactionId === transactionId) {
|
|
74
113
|
console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
|
|
75
|
-
log.
|
|
114
|
+
log.errorMessage = "Timeout";
|
|
76
115
|
log.state = NodeState.TimedOut;
|
|
77
116
|
log.endTime = Date.now();
|
|
78
117
|
this.retry(NodeState.TimedOut, Error("Timeout"));
|
|
@@ -80,26 +119,32 @@ class Node {
|
|
|
80
119
|
}, this.timeout);
|
|
81
120
|
}
|
|
82
121
|
try {
|
|
83
|
-
const callback = this.graph.getCallback(this.
|
|
122
|
+
const callback = this.graph.getCallback(this.agentId);
|
|
84
123
|
const result = await callback({
|
|
85
124
|
nodeId: this.nodeId,
|
|
86
125
|
retry: this.retryCount,
|
|
87
126
|
params: this.params,
|
|
88
|
-
payload
|
|
127
|
+
payload,
|
|
89
128
|
});
|
|
90
129
|
if (this.transactionId !== transactionId) {
|
|
91
130
|
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
92
131
|
return;
|
|
93
132
|
}
|
|
94
|
-
log.state = NodeState.Completed;
|
|
95
133
|
log.endTime = Date.now();
|
|
96
134
|
log.result = result;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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);
|
|
103
148
|
this.graph.removeRunning(this);
|
|
104
149
|
}
|
|
105
150
|
catch (error) {
|
|
@@ -110,12 +155,12 @@ class Node {
|
|
|
110
155
|
log.state = NodeState.Failed;
|
|
111
156
|
log.endTime = Date.now();
|
|
112
157
|
if (error instanceof Error) {
|
|
113
|
-
log.
|
|
158
|
+
log.errorMessage = error.message;
|
|
114
159
|
this.retry(NodeState.Failed, error);
|
|
115
160
|
}
|
|
116
161
|
else {
|
|
117
162
|
console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
|
|
118
|
-
log.
|
|
163
|
+
log.errorMessage = "Unknown";
|
|
119
164
|
this.retry(NodeState.Failed, Error("Unknown"));
|
|
120
165
|
}
|
|
121
166
|
}
|
|
@@ -124,15 +169,18 @@ class Node {
|
|
|
124
169
|
const defaultConcurrency = 8;
|
|
125
170
|
class GraphAI {
|
|
126
171
|
constructor(data, callbackDictonary) {
|
|
172
|
+
this.isRunning = false;
|
|
173
|
+
this.runningNodes = new Set();
|
|
174
|
+
this.nodeQueue = [];
|
|
127
175
|
this.logs = [];
|
|
128
176
|
this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
|
|
129
177
|
if (this.callbackDictonary["default"] === undefined) {
|
|
130
178
|
throw new Error("No default function");
|
|
131
179
|
}
|
|
132
180
|
this.concurrency = data.concurrency ?? defaultConcurrency;
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
|
|
181
|
+
this.onComplete = () => {
|
|
182
|
+
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
183
|
+
};
|
|
136
184
|
this.nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
|
|
137
185
|
nodes[nodeId] = new Node(nodeId, data.nodes[nodeId], this);
|
|
138
186
|
return nodes;
|
|
@@ -146,9 +194,9 @@ class GraphAI {
|
|
|
146
194
|
});
|
|
147
195
|
});
|
|
148
196
|
}
|
|
149
|
-
getCallback(
|
|
150
|
-
if (
|
|
151
|
-
return this.callbackDictonary[
|
|
197
|
+
getCallback(agentId) {
|
|
198
|
+
if (agentId && this.callbackDictonary[agentId]) {
|
|
199
|
+
return this.callbackDictonary[agentId];
|
|
152
200
|
}
|
|
153
201
|
return this.callbackDictonary["default"];
|
|
154
202
|
}
|
|
@@ -178,6 +226,10 @@ class GraphAI {
|
|
|
178
226
|
}, {});
|
|
179
227
|
}
|
|
180
228
|
async run() {
|
|
229
|
+
if (this.isRunning) {
|
|
230
|
+
console.error("-- Already Running");
|
|
231
|
+
}
|
|
232
|
+
this.isRunning = true;
|
|
181
233
|
// Nodes without pending data should run immediately.
|
|
182
234
|
Object.keys(this.nodes).forEach((nodeId) => {
|
|
183
235
|
const node = this.nodes[nodeId];
|
|
@@ -185,6 +237,7 @@ class GraphAI {
|
|
|
185
237
|
});
|
|
186
238
|
return new Promise((resolve, reject) => {
|
|
187
239
|
this.onComplete = () => {
|
|
240
|
+
this.isRunning = false;
|
|
188
241
|
const errors = this.errors();
|
|
189
242
|
const nodeIds = Object.keys(errors);
|
|
190
243
|
if (nodeIds.length > 0) {
|
|
@@ -226,5 +279,14 @@ class GraphAI {
|
|
|
226
279
|
transactionLogs() {
|
|
227
280
|
return this.logs;
|
|
228
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
|
+
}
|
|
229
291
|
}
|
|
230
292
|
exports.GraphAI = GraphAI;
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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
|
|
11
|
-
"gpt": "npx ts-node ./samples/sample_gpt.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"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
@@ -21,15 +21,20 @@
|
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/snakajima/graphai#readme",
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@types/express": "^4.17.21",
|
|
24
25
|
"@types/node": "^20.8.7",
|
|
25
26
|
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
|
26
27
|
"@typescript-eslint/parser": "^6.8.0",
|
|
28
|
+
"arxiv-api-ts": "^1.0.3",
|
|
27
29
|
"eslint": "^7.32.0 || ^8.2.0",
|
|
28
30
|
"eslint-plugin-import": "^2.25.2",
|
|
31
|
+
"express": "^4.19.2",
|
|
29
32
|
"openai": "^4.12.4",
|
|
30
33
|
"prettier": "^3.0.3",
|
|
31
34
|
"slashgpt": "^0.0.8",
|
|
32
35
|
"ts-node": "^10.9.1",
|
|
36
|
+
"tsc-alias": "^1.8.8",
|
|
37
|
+
"tsconfig-paths": "^4.2.0",
|
|
33
38
|
"typescript": "^5.2.2"
|
|
34
39
|
},
|
|
35
40
|
"dependencies": {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import search from "arXiv-api-ts";
|
|
2
|
+
|
|
3
|
+
import { AgentFunction } from "@/graphai";
|
|
4
|
+
|
|
5
|
+
type arxivData = { id: string; title: string; summary: string };
|
|
6
|
+
|
|
7
|
+
const search_arxiv_papers = async (keywords: string[], limit = 10) => {
|
|
8
|
+
const includes = keywords.map((k) => {
|
|
9
|
+
return { name: k };
|
|
10
|
+
});
|
|
11
|
+
const papers = await search({
|
|
12
|
+
searchQueryParams: [
|
|
13
|
+
{
|
|
14
|
+
include: includes,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
sortBy: "lastUpdatedDate",
|
|
18
|
+
sortOrder: "descending",
|
|
19
|
+
start: 0,
|
|
20
|
+
maxResults: limit,
|
|
21
|
+
});
|
|
22
|
+
return papers.entries || [];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const arxivAgent: AgentFunction<{ keywords: string[]; limit: number }, arxivData[]> = async (context) => {
|
|
26
|
+
const { keywords, limit } = context.params;
|
|
27
|
+
const arxivResult = await search_arxiv_papers(keywords, limit);
|
|
28
|
+
// console.log("executing", arxivResult, context.params.keywords);
|
|
29
|
+
|
|
30
|
+
const result = arxivResult.map((r: any) => {
|
|
31
|
+
const { id, title, summary } = r;
|
|
32
|
+
return { id, title, summary };
|
|
33
|
+
});
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const arxiv2TextAgent: AgentFunction<{}, string, string[]> = async (context) => {
|
|
38
|
+
const result = (context?.payload?.inputData || [])
|
|
39
|
+
.map((r: any) => {
|
|
40
|
+
const { id, title, summary } = r;
|
|
41
|
+
return ["id:", id, "title:", title, "summary:", summary].join("\n");
|
|
42
|
+
})
|
|
43
|
+
.join("\n\n\n");
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
};
|