graphai 0.0.11 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -13
- package/lib/experimental_agents/array_agents.d.ts +4 -0
- package/lib/experimental_agents/array_agents.js +30 -0
- package/lib/experimental_agents/index.d.ts +1 -0
- package/lib/experimental_agents/index.js +1 -0
- package/lib/experimental_agents/nested_agent.js +4 -2
- package/lib/experimental_agents/slashgpt_agent.js +3 -0
- package/lib/experimental_agents/sleeper_agent.d.ts +2 -2
- package/lib/experimental_agents/sleeper_agent.js +2 -2
- package/lib/graphai.d.ts +18 -88
- package/lib/graphai.js +117 -202
- package/lib/graphai_cli.js +1 -1
- package/lib/log.d.ts +6 -0
- package/lib/log.js +48 -0
- package/lib/node.d.ts +44 -0
- package/lib/node.js +179 -0
- package/lib/type.d.ts +64 -0
- package/lib/type.js +13 -0
- package/lib/utils/utils.d.ts +2 -0
- package/lib/utils/utils.js +11 -1
- package/lib/utils.d.ts +7 -0
- package/lib/utils.js +13 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ You just need to describe dependencies among those API calls in a single data fl
|
|
|
9
9
|
Here is an example:
|
|
10
10
|
|
|
11
11
|
```YAML
|
|
12
|
+
agentId: sample
|
|
12
13
|
nodes:
|
|
13
14
|
taskA:
|
|
14
15
|
params:
|
|
@@ -37,7 +38,7 @@ const sampleAgentFunction = async (context: AgentFunctionContext) => {
|
|
|
37
38
|
...
|
|
38
39
|
const file = fs.readFileSync(pathToYamlFile, "utf8");
|
|
39
40
|
const graphData = YAML.parse(file);
|
|
40
|
-
const graph = new GraphAI(graphData, sampleAgentFunction);
|
|
41
|
+
const graph = new GraphAI(graphData, { sample: sampleAgentFunction });
|
|
41
42
|
const results = await graph.run();
|
|
42
43
|
return results["taskC"];
|
|
43
44
|
```
|
|
@@ -48,14 +49,14 @@ As Andrew Ng has described in his article, "[The batch: Issue 242](https://www.d
|
|
|
48
49
|
|
|
49
50
|
Building applications that employ these workflows, however, is challenging due to the complexities of managing multiple asynchronous API calls, including error handling, timeouts, retries, and logging.
|
|
50
51
|
|
|
51
|
-
GraphAI is designed to simplify this process by decoupling the complexity of multiple asynchronous calls from the application's core logic. It enables developers to model these calls and their dependencies within a single Data Flow Graph, enhancing development and debugging processes.
|
|
52
|
+
GraphAI is designed to simplify this process by decoupling the complexity of multiple asynchronous calls from the application's core logic. It enables developers to model these calls and their dependencies within a single acyclic Data Flow Graph, enhancing development and debugging processes.
|
|
52
53
|
|
|
53
54
|
Furthermore, GraphAI's robust mechanisms for error handling, retry strategies, timeouts, and logging empower developers to concentrate on refining the application logic.
|
|
54
55
|
|
|
55
56
|
## Quick Install
|
|
56
57
|
|
|
57
58
|
```
|
|
58
|
-
|
|
59
|
+
npm install graphai
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
or
|
|
@@ -114,12 +115,43 @@ Key principles:
|
|
|
114
115
|
|
|
115
116
|
A Data Flow Graph (DFG) is a JavaScript object, which defines the flow of data. It is typically described in YAML file and loaded at runtime.
|
|
116
117
|
|
|
117
|
-
A DFG consists of a collection of 'nodes', which contains a series of nested
|
|
118
|
+
A DFG consists of a collection of 'nodes', which contains a series of nested properties representing individual nodes in the data flow. Each node is identified by a unique key, *nodeId* (e.g., node1, node2) and can contain several predefined properties (params, inputs, retry, timeout, agentId, fork, value, update) that dictate the node's behavior and its relationship with other nodes.
|
|
119
|
+
|
|
120
|
+
Connections between nodes will be established by references from one not to another, using either its "inputs" or "update" property. The values of those properties are *data sources*. A *data souce* is specified by either the nodeId (e.g., "node1"), or nodeId + propertyId ("node1.item").
|
|
118
121
|
|
|
119
122
|
### DFG Structure
|
|
120
123
|
|
|
121
124
|
- 'nodes': A list of node. Required.
|
|
122
125
|
- '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.
|
|
126
|
+
- 'agentId': An optional property, which specifies the default agent for all the nodes.
|
|
127
|
+
- 'loop': An optional property, which specifies if the graph needs to be executed multiple times. The loop is an JavaScript object, which has two optinoal properties. The *count* property specifies the number of times the graph needs to be executed and the *while* property specifies the condition required to contineu the loop in the form of node name (nodeId) or its property (nodeId.propId). Unlike JavaScript, an empty array will be treated as false.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
loop:
|
|
131
|
+
while: people
|
|
132
|
+
nodes:
|
|
133
|
+
people:
|
|
134
|
+
value:
|
|
135
|
+
- Steve Jobs
|
|
136
|
+
- Elon Musk
|
|
137
|
+
- Nikola Tesla
|
|
138
|
+
update: retriever.array
|
|
139
|
+
result:
|
|
140
|
+
value: []
|
|
141
|
+
update: reducer
|
|
142
|
+
retriever:
|
|
143
|
+
agentId: shift
|
|
144
|
+
inputs: [people]
|
|
145
|
+
query:
|
|
146
|
+
agentId: slashgpt
|
|
147
|
+
params:
|
|
148
|
+
manifest:
|
|
149
|
+
prompt: 指定した人について日本語で400字以内で答えて
|
|
150
|
+
inputs: [retriever.item]
|
|
151
|
+
reducer:
|
|
152
|
+
agentId: push
|
|
153
|
+
inputs: [result, query.content]
|
|
154
|
+
```
|
|
123
155
|
|
|
124
156
|
## Agent
|
|
125
157
|
|
|
@@ -133,19 +165,25 @@ An agent function receives two set of parameters via AgentFunctionContext, agent
|
|
|
133
165
|
|
|
134
166
|
## Node Structure
|
|
135
167
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
168
|
+
There are two types of Node, *computed nodes* and *static nodes*. A *computed node* is associated with an *agent function*, which receives some inputs, performs some computations asynchronously then returns the result (output). A *static node* is a placeholder of a value (just like a variable in programming language), which is injected by an external program, or is retrived from a *data source* (in case of interations).
|
|
169
|
+
|
|
170
|
+
A *computed node* have following properties.
|
|
171
|
+
|
|
172
|
+
- 'agentId': An **required** property, which specifies the id of the *agent function*.
|
|
173
|
+
- 'inputs': An optional list of *data sources* 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, the associated *agent function* will be immediatley executed.
|
|
139
174
|
- '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.
|
|
140
175
|
- 'timeout': An optional number, which specifies the maximum waittime in msec. If the associated agent function does not return the value in time, the "Timeout" error will be recorded and the returned value will be discarded.
|
|
141
|
-
- 'params': An optional
|
|
142
|
-
- 'agentId': An optional parameter, which specifies the id of the agent, when the graph is associated with multiple agents.
|
|
176
|
+
- 'params': An optional property to the associated agent function, which are agent specific.
|
|
143
177
|
- 'fork': An optional paramter, which specifies the number of concurrent transactions to be created for the current node.
|
|
144
|
-
|
|
178
|
+
|
|
179
|
+
A *static* node have following properties.
|
|
180
|
+
|
|
181
|
+
- 'value': An optional property, which specifies the value of this static node (equivalent to calling the injectValue method from outside).
|
|
182
|
+
- 'update': An optional property, which specifies the *data source* after each iteration.
|
|
145
183
|
|
|
146
184
|
## GraphAI class
|
|
147
185
|
|
|
148
|
-
### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary
|
|
186
|
+
### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary)```
|
|
149
187
|
Initializes a new instance of the GraphAI class with the specified graph data and a dictionary of callback functions.
|
|
150
188
|
|
|
151
189
|
- ```data: GraphData```: The graph data including nodes and optional concurrency limit.
|
|
@@ -172,7 +210,7 @@ Retrieves all transaction logs recorded during the execution of the graph.
|
|
|
172
210
|
Returns: An array of transaction logs detailing the execution states and outcomes of the nodes within the graph.
|
|
173
211
|
|
|
174
212
|
### ```injectResult(nodeId: string, result: ResultData): void```
|
|
175
|
-
Injects a result into a specified node. This is used to manually set the result of a
|
|
213
|
+
Injects a result into a specified node. This is used to manually set the result of a static node, allowing dependent nodes to proceed with execution.
|
|
176
214
|
|
|
177
|
-
- ```nodeId: string```: The ID of the
|
|
215
|
+
- ```nodeId: string```: The ID of the static node into which the result is to be injected.
|
|
178
216
|
- ```result: ResultData```: The result to be injected into the specified node.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AgentFunction } from "../graphai";
|
|
2
|
+
export declare const pushAgent: AgentFunction<Record<string, any>, Record<string, any>, Record<string, any>>;
|
|
3
|
+
export declare const popAgent: AgentFunction<Record<string, any>, Record<string, any>, Record<string, any>>;
|
|
4
|
+
export declare const shiftAgent: AgentFunction<Record<string, any>, Record<string, any>, Record<string, any>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.shiftAgent = exports.popAgent = exports.pushAgent = void 0;
|
|
7
|
+
const deepmerge_1 = __importDefault(require("deepmerge"));
|
|
8
|
+
const pushAgent = async ({ inputs }) => {
|
|
9
|
+
const [array, item] = (0, deepmerge_1.default)({ inputs }, {}).inputs;
|
|
10
|
+
// TODO: Validation
|
|
11
|
+
array.push(item);
|
|
12
|
+
return array;
|
|
13
|
+
};
|
|
14
|
+
exports.pushAgent = pushAgent;
|
|
15
|
+
const popAgent = async (context) => {
|
|
16
|
+
const { inputs } = context;
|
|
17
|
+
const [array] = (0, deepmerge_1.default)({ inputs }, {}).inputs;
|
|
18
|
+
// TODO: Varidation
|
|
19
|
+
const item = array.pop();
|
|
20
|
+
return { array, item };
|
|
21
|
+
};
|
|
22
|
+
exports.popAgent = popAgent;
|
|
23
|
+
const shiftAgent = async (context) => {
|
|
24
|
+
const { inputs } = context;
|
|
25
|
+
const [array] = (0, deepmerge_1.default)({ inputs }, {}).inputs;
|
|
26
|
+
// TODO: Varidation
|
|
27
|
+
const item = array.shift();
|
|
28
|
+
return { array, item };
|
|
29
|
+
};
|
|
30
|
+
exports.shiftAgent = shiftAgent;
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.nestedAgent = void 0;
|
|
4
4
|
const graphai_1 = require("../graphai");
|
|
5
|
-
const nestedAgent = async ({ params, inputs, agents }) => {
|
|
5
|
+
const nestedAgent = async ({ params, inputs, agents, log }) => {
|
|
6
6
|
const graph = new graphai_1.GraphAI(params.graph, agents);
|
|
7
7
|
try {
|
|
8
8
|
// Inject inputs to specified source nodes
|
|
9
9
|
(params.inputNodes ?? []).forEach((nodeId, index) => {
|
|
10
|
-
graph.
|
|
10
|
+
graph.injectValue(nodeId, inputs[index]);
|
|
11
11
|
});
|
|
12
12
|
const results = await graph.run();
|
|
13
|
+
log.push(...graph.transactionLogs());
|
|
13
14
|
return results[params.nodeId];
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
17
|
+
log.push(...graph.transactionLogs());
|
|
16
18
|
if (error instanceof Error) {
|
|
17
19
|
console.log("Error:", error.message);
|
|
18
20
|
}
|
|
@@ -14,6 +14,9 @@ const slashGPTAgent = async ({ nodeId, params, inputs, verbose }) => {
|
|
|
14
14
|
const session = new slashgpt_1.ChatSession(config, params.manifest ?? {});
|
|
15
15
|
const query = params?.query ? [params.query] : [];
|
|
16
16
|
const contents = query.concat(inputs.map((input) => {
|
|
17
|
+
if (typeof input === "string") {
|
|
18
|
+
return input;
|
|
19
|
+
}
|
|
17
20
|
return input.content;
|
|
18
21
|
}));
|
|
19
22
|
session.append_user_question(contents.join("\n"));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AgentFunction } from "../graphai";
|
|
2
2
|
export declare const sleeperAgent: AgentFunction<{
|
|
3
3
|
duration: number;
|
|
4
|
-
|
|
4
|
+
value?: Record<string, any>;
|
|
5
5
|
}>;
|
|
6
6
|
export declare const sleeperAgentDebug: AgentFunction<{
|
|
7
7
|
duration: number;
|
|
8
|
-
|
|
8
|
+
value?: Record<string, any>;
|
|
9
9
|
fail?: boolean;
|
|
10
10
|
}>;
|
|
@@ -11,7 +11,7 @@ const sleeperAgent = async (context) => {
|
|
|
11
11
|
await (0, utils_1.sleep)(params.duration);
|
|
12
12
|
return inputs.reduce((result, input) => {
|
|
13
13
|
return (0, deepmerge_1.default)(result, input);
|
|
14
|
-
}, params.
|
|
14
|
+
}, params.value ?? {});
|
|
15
15
|
};
|
|
16
16
|
exports.sleeperAgent = sleeperAgent;
|
|
17
17
|
const sleeperAgentDebug = async (context) => {
|
|
@@ -23,6 +23,6 @@ const sleeperAgentDebug = async (context) => {
|
|
|
23
23
|
}
|
|
24
24
|
return inputs.reduce((result, input) => {
|
|
25
25
|
return (0, deepmerge_1.default)(result, input);
|
|
26
|
-
}, params.
|
|
26
|
+
}, params.value ?? {});
|
|
27
27
|
};
|
|
28
28
|
exports.sleeperAgentDebug = sleeperAgentDebug;
|
package/lib/graphai.d.ts
CHANGED
|
@@ -1,106 +1,36 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
TimedOut = "timed-out",
|
|
6
|
-
Completed = "completed",
|
|
7
|
-
Injected = "injected",
|
|
8
|
-
Dispatched = "dispatched"
|
|
9
|
-
}
|
|
10
|
-
type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
|
|
11
|
-
type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
|
|
12
|
-
export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
|
|
13
|
-
type NodeData = {
|
|
14
|
-
inputs?: Array<string>;
|
|
15
|
-
params?: NodeDataParams;
|
|
16
|
-
retry?: number;
|
|
17
|
-
timeout?: number;
|
|
18
|
-
agentId?: string;
|
|
19
|
-
fork?: number;
|
|
20
|
-
source?: boolean;
|
|
21
|
-
result?: ResultData;
|
|
22
|
-
outputs?: Record<string, string>;
|
|
23
|
-
};
|
|
24
|
-
export type GraphData = {
|
|
25
|
-
nodes: Record<string, NodeData>;
|
|
26
|
-
concurrency?: number;
|
|
27
|
-
verbose?: boolean;
|
|
28
|
-
};
|
|
29
|
-
export type TransactionLog = {
|
|
30
|
-
nodeId: string;
|
|
31
|
-
state: NodeState;
|
|
32
|
-
startTime: number;
|
|
33
|
-
endTime?: number;
|
|
34
|
-
retryCount: number;
|
|
35
|
-
agentId?: string;
|
|
36
|
-
params?: NodeDataParams;
|
|
37
|
-
inputs?: Array<ResultData>;
|
|
38
|
-
errorMessage?: string;
|
|
39
|
-
result?: ResultData;
|
|
40
|
-
};
|
|
41
|
-
export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
|
|
42
|
-
nodeId: string;
|
|
43
|
-
forkIndex?: number;
|
|
44
|
-
retry: number;
|
|
45
|
-
params: NodeDataParams<ParamsType>;
|
|
46
|
-
inputs: Array<PreviousResultType>;
|
|
47
|
-
verbose: boolean;
|
|
48
|
-
agents: CallbackDictonaryArgs;
|
|
49
|
-
};
|
|
50
|
-
export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, ResultType, PreviousResultType>) => Promise<ResultData<ResultType>>;
|
|
51
|
-
export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
|
|
52
|
-
declare class Node {
|
|
53
|
-
nodeId: string;
|
|
54
|
-
params: NodeDataParams;
|
|
55
|
-
inputs: Array<string>;
|
|
56
|
-
pendings: Set<string>;
|
|
57
|
-
waitlist: Set<string>;
|
|
58
|
-
state: NodeState;
|
|
59
|
-
agentId?: string;
|
|
60
|
-
fork?: number;
|
|
61
|
-
forkIndex?: number;
|
|
62
|
-
result: ResultData;
|
|
63
|
-
retryLimit: number;
|
|
64
|
-
retryCount: number;
|
|
65
|
-
transactionId: undefined | number;
|
|
66
|
-
timeout?: number;
|
|
67
|
-
error?: Error;
|
|
68
|
-
source: boolean;
|
|
69
|
-
outputs?: Record<string, string>;
|
|
70
|
-
private graph;
|
|
71
|
-
constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
|
|
72
|
-
asString(): string;
|
|
73
|
-
private retry;
|
|
74
|
-
removePending(nodeId: string): void;
|
|
75
|
-
pushQueueIfReady(): void;
|
|
76
|
-
injectResult(result: ResultData): void;
|
|
77
|
-
private setResult;
|
|
78
|
-
execute(): Promise<void>;
|
|
79
|
-
}
|
|
80
|
-
type GraphNodes = Record<string, Node>;
|
|
81
|
-
export type CallbackDictonaryArgs = AgentFunctionDictonary | AgentFunction<any, any, any>;
|
|
1
|
+
export { AgentFunction, AgentFunctionDictonary, GraphData } from "./type";
|
|
2
|
+
import { AgentFunctionDictonary, GraphData, DataSource, TransactionLog, ResultDataDictonary, ResultData, CallbackDictonaryArgs } from "./type";
|
|
3
|
+
import { ComputedNode, StaticNode } from "./node";
|
|
4
|
+
type GraphNodes = Record<string, ComputedNode | StaticNode>;
|
|
82
5
|
export declare class GraphAI {
|
|
6
|
+
private data;
|
|
83
7
|
nodes: GraphNodes;
|
|
8
|
+
agentId?: string;
|
|
84
9
|
callbackDictonary: AgentFunctionDictonary;
|
|
85
10
|
isRunning: boolean;
|
|
86
11
|
private runningNodes;
|
|
87
12
|
private nodeQueue;
|
|
88
13
|
private onComplete;
|
|
89
14
|
private concurrency;
|
|
15
|
+
private loop?;
|
|
16
|
+
private repeatCount;
|
|
90
17
|
verbose: boolean;
|
|
91
18
|
private logs;
|
|
19
|
+
private createNodes;
|
|
20
|
+
private getValueFromResults;
|
|
21
|
+
private initializeNodes;
|
|
92
22
|
constructor(data: GraphData, callbackDictonary: CallbackDictonaryArgs);
|
|
93
|
-
getCallback(
|
|
23
|
+
getCallback(agentId?: string): import("./type").AgentFunction<any, any, any>;
|
|
94
24
|
asString(): string;
|
|
95
|
-
results(): ResultDataDictonary
|
|
25
|
+
results(): ResultDataDictonary;
|
|
96
26
|
errors(): Record<string, Error>;
|
|
27
|
+
private pushReadyNodesIntoQueue;
|
|
97
28
|
run(): Promise<ResultDataDictonary>;
|
|
98
29
|
private runNode;
|
|
99
|
-
pushQueue(node:
|
|
100
|
-
removeRunning(node:
|
|
30
|
+
pushQueue(node: ComputedNode): void;
|
|
31
|
+
removeRunning(node: ComputedNode): void;
|
|
101
32
|
appendLog(log: TransactionLog): void;
|
|
102
33
|
transactionLogs(): TransactionLog[];
|
|
103
|
-
|
|
104
|
-
resultsOf(
|
|
34
|
+
injectValue(nodeId: string, value: ResultData): void;
|
|
35
|
+
resultsOf(sources: Array<DataSource>): any[];
|
|
105
36
|
}
|
|
106
|
-
export {};
|
package/lib/graphai.js
CHANGED
|
@@ -1,201 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GraphAI =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
NodeState["Waiting"] = "waiting";
|
|
7
|
-
NodeState["Executing"] = "executing";
|
|
8
|
-
NodeState["Failed"] = "failed";
|
|
9
|
-
NodeState["TimedOut"] = "timed-out";
|
|
10
|
-
NodeState["Completed"] = "completed";
|
|
11
|
-
NodeState["Injected"] = "injected";
|
|
12
|
-
NodeState["Dispatched"] = "dispatched";
|
|
13
|
-
})(NodeState || (exports.NodeState = NodeState = {}));
|
|
14
|
-
class Node {
|
|
15
|
-
constructor(nodeId, forkIndex, 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;
|
|
20
|
-
this.nodeId = nodeId;
|
|
21
|
-
this.forkIndex = forkIndex;
|
|
22
|
-
this.inputs = data.inputs ?? [];
|
|
23
|
-
this.pendings = new Set(this.inputs);
|
|
24
|
-
this.params = data.params ?? {};
|
|
25
|
-
this.agentId = data.agentId;
|
|
26
|
-
this.fork = data.fork;
|
|
27
|
-
this.retryLimit = data.retry ?? 0;
|
|
28
|
-
this.timeout = data.timeout;
|
|
29
|
-
this.source = data.source === true;
|
|
30
|
-
this.outputs = data.outputs;
|
|
31
|
-
this.graph = graph;
|
|
32
|
-
}
|
|
33
|
-
asString() {
|
|
34
|
-
return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
|
|
35
|
-
}
|
|
36
|
-
retry(state, error) {
|
|
37
|
-
if (this.retryCount < this.retryLimit) {
|
|
38
|
-
this.retryCount++;
|
|
39
|
-
this.execute();
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
this.state = state;
|
|
43
|
-
this.result = undefined;
|
|
44
|
-
this.error = error;
|
|
45
|
-
this.transactionId = undefined; // This is necessary for timeout case
|
|
46
|
-
this.graph.removeRunning(this);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
removePending(nodeId) {
|
|
50
|
-
this.pendings.delete(nodeId);
|
|
51
|
-
if (this.graph.isRunning) {
|
|
52
|
-
this.pushQueueIfReady();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
pushQueueIfReady() {
|
|
56
|
-
if (this.pendings.size === 0 && !this.source) {
|
|
57
|
-
this.graph.pushQueue(this);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
injectResult(result) {
|
|
61
|
-
if (this.source) {
|
|
62
|
-
const log = {
|
|
63
|
-
nodeId: this.nodeId,
|
|
64
|
-
retryCount: this.retryCount,
|
|
65
|
-
state: NodeState.Injected,
|
|
66
|
-
startTime: Date.now(),
|
|
67
|
-
endTime: Date.now(),
|
|
68
|
-
result,
|
|
69
|
-
};
|
|
70
|
-
this.graph.appendLog(log);
|
|
71
|
-
this.setResult(result, NodeState.Injected);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
console.error("- injectResult called on non-source node.", this.nodeId);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
setResult(result, state) {
|
|
78
|
-
this.state = state;
|
|
79
|
-
this.result = result;
|
|
80
|
-
this.waitlist.forEach((nodeId) => {
|
|
81
|
-
const node = this.graph.nodes[nodeId];
|
|
82
|
-
// Todo: Avoid running before Run()
|
|
83
|
-
node.removePending(this.nodeId);
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
async execute() {
|
|
87
|
-
const results = this.graph.resultsOf(this.inputs);
|
|
88
|
-
const transactionId = Date.now();
|
|
89
|
-
const log = {
|
|
90
|
-
nodeId: this.nodeId,
|
|
91
|
-
retryCount: this.retryCount,
|
|
92
|
-
state: NodeState.Executing,
|
|
93
|
-
startTime: transactionId,
|
|
94
|
-
agentId: this.agentId,
|
|
95
|
-
params: this.params,
|
|
96
|
-
inputs: results,
|
|
97
|
-
};
|
|
98
|
-
this.graph.appendLog(log);
|
|
99
|
-
this.state = NodeState.Executing;
|
|
100
|
-
this.transactionId = transactionId;
|
|
101
|
-
if (this.timeout && this.timeout > 0) {
|
|
102
|
-
setTimeout(() => {
|
|
103
|
-
if (this.state === NodeState.Executing && this.transactionId === transactionId) {
|
|
104
|
-
console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
|
|
105
|
-
log.errorMessage = "Timeout";
|
|
106
|
-
log.state = NodeState.TimedOut;
|
|
107
|
-
log.endTime = Date.now();
|
|
108
|
-
this.retry(NodeState.TimedOut, Error("Timeout"));
|
|
109
|
-
}
|
|
110
|
-
}, this.timeout);
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const callback = this.graph.getCallback(this.agentId);
|
|
114
|
-
const result = await callback({
|
|
115
|
-
nodeId: this.nodeId,
|
|
116
|
-
retry: this.retryCount,
|
|
117
|
-
params: this.params,
|
|
118
|
-
inputs: results,
|
|
119
|
-
forkIndex: this.forkIndex,
|
|
120
|
-
verbose: this.graph.verbose,
|
|
121
|
-
agents: this.graph.callbackDictonary,
|
|
122
|
-
});
|
|
123
|
-
if (this.transactionId !== transactionId) {
|
|
124
|
-
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
log.endTime = Date.now();
|
|
128
|
-
log.result = result;
|
|
129
|
-
const outputs = this.outputs;
|
|
130
|
-
if (outputs !== undefined) {
|
|
131
|
-
Object.keys(result).forEach((outputId) => {
|
|
132
|
-
const nodeId = outputs[outputId];
|
|
133
|
-
this.graph.injectResult(nodeId, result[outputId]);
|
|
134
|
-
});
|
|
135
|
-
log.state = NodeState.Dispatched;
|
|
136
|
-
this.state = NodeState.Dispatched;
|
|
137
|
-
this.graph.removeRunning(this);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
log.state = NodeState.Completed;
|
|
141
|
-
this.setResult(result, NodeState.Completed);
|
|
142
|
-
this.graph.removeRunning(this);
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
if (this.transactionId !== transactionId) {
|
|
146
|
-
console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
log.state = NodeState.Failed;
|
|
150
|
-
log.endTime = Date.now();
|
|
151
|
-
if (error instanceof Error) {
|
|
152
|
-
log.errorMessage = error.message;
|
|
153
|
-
this.retry(NodeState.Failed, error);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
|
|
157
|
-
log.errorMessage = "Unknown";
|
|
158
|
-
this.retry(NodeState.Failed, Error("Unknown"));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
3
|
+
exports.GraphAI = void 0;
|
|
4
|
+
const node_1 = require("./node");
|
|
5
|
+
const utils_1 = require("./utils/utils");
|
|
163
6
|
const defaultConcurrency = 8;
|
|
164
7
|
class GraphAI {
|
|
165
|
-
|
|
166
|
-
this.isRunning = false;
|
|
167
|
-
this.runningNodes = new Set();
|
|
168
|
-
this.nodeQueue = [];
|
|
169
|
-
this.logs = [];
|
|
170
|
-
this.callbackDictonary = typeof callbackDictonary === "function" ? { _default: callbackDictonary } : callbackDictonary;
|
|
171
|
-
this.concurrency = data.concurrency ?? defaultConcurrency;
|
|
172
|
-
this.verbose = data.verbose === true;
|
|
173
|
-
this.onComplete = () => {
|
|
174
|
-
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
175
|
-
};
|
|
8
|
+
createNodes(data) {
|
|
176
9
|
const nodeId2forkedNodeIds = {};
|
|
177
10
|
const forkedNodeId2Index = {};
|
|
178
|
-
|
|
179
|
-
|
|
11
|
+
const forkedNodeId2NodeId = {}; // for sources
|
|
12
|
+
const nodes = Object.keys(data.nodes).reduce((_nodes, nodeId) => {
|
|
180
13
|
const fork = data.nodes[nodeId].fork;
|
|
14
|
+
const isStaticNode = (data.nodes[nodeId].agentId ?? data.agentId) === undefined;
|
|
15
|
+
const node = isStaticNode ? node_1.StaticNode : node_1.ComputedNode;
|
|
181
16
|
if (fork) {
|
|
182
17
|
// For fork, change the nodeId and increase the node
|
|
183
18
|
nodeId2forkedNodeIds[nodeId] = new Array(fork).fill(undefined).map((_, i) => {
|
|
184
19
|
const forkedNodeId = `${nodeId}_${i}`;
|
|
185
|
-
|
|
20
|
+
_nodes[forkedNodeId] = new node(forkedNodeId, i, data.nodes[nodeId], this);
|
|
186
21
|
// Data for pending and waiting
|
|
187
22
|
forkedNodeId2Index[forkedNodeId] = i;
|
|
23
|
+
forkedNodeId2NodeId[forkedNodeId] = nodeId;
|
|
188
24
|
return forkedNodeId;
|
|
189
25
|
});
|
|
190
26
|
}
|
|
191
27
|
else {
|
|
192
|
-
|
|
28
|
+
_nodes[nodeId] = new node(nodeId, undefined, data.nodes[nodeId], this);
|
|
193
29
|
}
|
|
194
|
-
return
|
|
30
|
+
return _nodes;
|
|
195
31
|
}, {});
|
|
196
32
|
// Generate the waitlist for each node, and update the pendings in case of forked node.
|
|
197
|
-
Object.keys(
|
|
198
|
-
const node =
|
|
33
|
+
Object.keys(nodes).forEach((nodeId) => {
|
|
34
|
+
const node = nodes[nodeId];
|
|
199
35
|
node.pendings.forEach((pending) => {
|
|
200
36
|
// If the pending(previous) node is forking
|
|
201
37
|
if (nodeId2forkedNodeIds[pending]) {
|
|
@@ -203,36 +39,83 @@ class GraphAI {
|
|
|
203
39
|
if (node.fork) {
|
|
204
40
|
// 1:1 if current nodes are also forking.
|
|
205
41
|
const newPendingId = nodeId2forkedNodeIds[pending][forkedNodeId2Index[nodeId]];
|
|
206
|
-
|
|
42
|
+
nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
207
43
|
node.pendings.add(newPendingId);
|
|
208
44
|
}
|
|
209
45
|
else {
|
|
210
46
|
// 1:n if current node is not forking.
|
|
211
47
|
nodeId2forkedNodeIds[pending].forEach((newPendingId) => {
|
|
212
|
-
|
|
48
|
+
nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
213
49
|
node.pendings.add(newPendingId);
|
|
214
50
|
});
|
|
215
51
|
}
|
|
216
52
|
node.pendings.delete(pending);
|
|
217
53
|
}
|
|
218
54
|
else {
|
|
219
|
-
|
|
55
|
+
if (nodes[pending]) {
|
|
56
|
+
nodes[pending].waitlist.add(nodeId); // previousNode
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.error(`--- invalid input ${pending} for node, ${nodeId}`);
|
|
60
|
+
}
|
|
220
61
|
}
|
|
221
62
|
});
|
|
222
63
|
node.inputs = Array.from(node.pendings); // for fork.
|
|
64
|
+
node.sources = node.inputs.reduce((sources, input) => {
|
|
65
|
+
const refNodeId = forkedNodeId2NodeId[input] ?? input;
|
|
66
|
+
sources[input] = { nodeId: input, propId: node.sources[refNodeId].propId };
|
|
67
|
+
return sources;
|
|
68
|
+
}, {});
|
|
223
69
|
});
|
|
70
|
+
return nodes;
|
|
71
|
+
}
|
|
72
|
+
getValueFromResults(key, results) {
|
|
73
|
+
const source = (0, utils_1.parseNodeName)(key);
|
|
74
|
+
const result = results[source.nodeId];
|
|
75
|
+
return result ? (source.propId ? result[source.propId] : result) : undefined;
|
|
76
|
+
}
|
|
77
|
+
// for static
|
|
78
|
+
initializeNodes(previousResults) {
|
|
224
79
|
// If the result property is specified, inject it.
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
80
|
+
// If the previousResults exists (indicating we are in a loop),
|
|
81
|
+
// process the update property (nodeId or nodeId.propId).
|
|
82
|
+
Object.keys(this.data.nodes).forEach((nodeId) => {
|
|
83
|
+
const node = this.nodes[nodeId];
|
|
84
|
+
if (node?.isStaticNode) {
|
|
85
|
+
const value = node?.value;
|
|
86
|
+
const update = node?.update;
|
|
87
|
+
if (value) {
|
|
88
|
+
this.injectValue(nodeId, value);
|
|
89
|
+
}
|
|
90
|
+
if (update && previousResults) {
|
|
91
|
+
const result = this.getValueFromResults(update, previousResults);
|
|
92
|
+
if (result) {
|
|
93
|
+
this.injectValue(nodeId, result);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
230
96
|
}
|
|
231
97
|
});
|
|
232
98
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
99
|
+
constructor(data, callbackDictonary) {
|
|
100
|
+
this.isRunning = false;
|
|
101
|
+
this.runningNodes = new Set();
|
|
102
|
+
this.nodeQueue = []; // for Computed Node
|
|
103
|
+
this.repeatCount = 0;
|
|
104
|
+
this.logs = [];
|
|
105
|
+
this.data = data;
|
|
106
|
+
this.callbackDictonary = callbackDictonary;
|
|
107
|
+
this.concurrency = data.concurrency ?? defaultConcurrency;
|
|
108
|
+
this.loop = data.loop;
|
|
109
|
+
this.agentId = data.agentId;
|
|
110
|
+
this.verbose = data.verbose === true;
|
|
111
|
+
this.onComplete = () => {
|
|
112
|
+
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
113
|
+
};
|
|
114
|
+
this.nodes = this.createNodes(data);
|
|
115
|
+
this.initializeNodes();
|
|
116
|
+
}
|
|
117
|
+
getCallback(agentId) {
|
|
118
|
+
if (agentId && this.callbackDictonary[agentId]) {
|
|
236
119
|
return this.callbackDictonary[agentId];
|
|
237
120
|
}
|
|
238
121
|
throw new Error("No agent: " + agentId);
|
|
@@ -256,22 +139,29 @@ class GraphAI {
|
|
|
256
139
|
errors() {
|
|
257
140
|
return Object.keys(this.nodes).reduce((errors, nodeId) => {
|
|
258
141
|
const node = this.nodes[nodeId];
|
|
259
|
-
if (node.
|
|
260
|
-
|
|
142
|
+
if (node.isComputedNode) {
|
|
143
|
+
if (node.error !== undefined) {
|
|
144
|
+
errors[nodeId] = node.error;
|
|
145
|
+
}
|
|
261
146
|
}
|
|
262
147
|
return errors;
|
|
263
148
|
}, {});
|
|
264
149
|
}
|
|
150
|
+
pushReadyNodesIntoQueue() {
|
|
151
|
+
// Nodes without pending data should run immediately.
|
|
152
|
+
Object.keys(this.nodes).forEach((nodeId) => {
|
|
153
|
+
const node = this.nodes[nodeId];
|
|
154
|
+
if (node.isComputedNode) {
|
|
155
|
+
node.pushQueueIfReady();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
265
159
|
async run() {
|
|
266
160
|
if (this.isRunning) {
|
|
267
161
|
console.error("-- Already Running");
|
|
268
162
|
}
|
|
269
163
|
this.isRunning = true;
|
|
270
|
-
|
|
271
|
-
Object.keys(this.nodes).forEach((nodeId) => {
|
|
272
|
-
const node = this.nodes[nodeId];
|
|
273
|
-
node.pushQueueIfReady();
|
|
274
|
-
});
|
|
164
|
+
this.pushReadyNodesIntoQueue();
|
|
275
165
|
return new Promise((resolve, reject) => {
|
|
276
166
|
this.onComplete = () => {
|
|
277
167
|
this.isRunning = false;
|
|
@@ -286,10 +176,12 @@ class GraphAI {
|
|
|
286
176
|
};
|
|
287
177
|
});
|
|
288
178
|
}
|
|
179
|
+
// for computed
|
|
289
180
|
runNode(node) {
|
|
290
181
|
this.runningNodes.add(node.nodeId);
|
|
291
182
|
node.execute();
|
|
292
183
|
}
|
|
184
|
+
// for computed
|
|
293
185
|
pushQueue(node) {
|
|
294
186
|
if (this.runningNodes.size < this.concurrency) {
|
|
295
187
|
this.runNode(node);
|
|
@@ -298,6 +190,7 @@ class GraphAI {
|
|
|
298
190
|
this.nodeQueue.push(node);
|
|
299
191
|
}
|
|
300
192
|
}
|
|
193
|
+
// for completed
|
|
301
194
|
removeRunning(node) {
|
|
302
195
|
this.runningNodes.delete(node.nodeId);
|
|
303
196
|
if (this.nodeQueue.length > 0) {
|
|
@@ -307,6 +200,27 @@ class GraphAI {
|
|
|
307
200
|
}
|
|
308
201
|
}
|
|
309
202
|
if (this.runningNodes.size === 0) {
|
|
203
|
+
this.repeatCount++;
|
|
204
|
+
const loop = this.loop;
|
|
205
|
+
if (loop && (loop.count === undefined || this.repeatCount < loop.count)) {
|
|
206
|
+
const results = this.results(); // results from previous loop
|
|
207
|
+
this.isRunning = false; // temporarily stop it
|
|
208
|
+
this.nodes = this.createNodes(this.data);
|
|
209
|
+
this.initializeNodes(results);
|
|
210
|
+
const checkWhileCondition = () => {
|
|
211
|
+
if (loop.while) {
|
|
212
|
+
const value = this.getValueFromResults(loop.while, this.results());
|
|
213
|
+
// NOTE: We treat an empty array as false.
|
|
214
|
+
return Array.isArray(value) ? value.length > 0 : !!value;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
};
|
|
218
|
+
if (checkWhileCondition()) {
|
|
219
|
+
this.isRunning = true; // restore it
|
|
220
|
+
this.pushReadyNodesIntoQueue();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
310
224
|
this.onComplete();
|
|
311
225
|
}
|
|
312
226
|
}
|
|
@@ -316,18 +230,19 @@ class GraphAI {
|
|
|
316
230
|
transactionLogs() {
|
|
317
231
|
return this.logs;
|
|
318
232
|
}
|
|
319
|
-
|
|
233
|
+
injectValue(nodeId, value) {
|
|
320
234
|
const node = this.nodes[nodeId];
|
|
321
|
-
if (node) {
|
|
322
|
-
node.
|
|
235
|
+
if (node && node.isStaticNode) {
|
|
236
|
+
node.injectValue(value);
|
|
323
237
|
}
|
|
324
238
|
else {
|
|
325
239
|
console.error("-- Invalid nodeId", nodeId);
|
|
326
240
|
}
|
|
327
241
|
}
|
|
328
|
-
resultsOf(
|
|
329
|
-
return
|
|
330
|
-
|
|
242
|
+
resultsOf(sources) {
|
|
243
|
+
return sources.map((source) => {
|
|
244
|
+
const result = this.nodes[source.nodeId].result;
|
|
245
|
+
return result && source.propId ? result[source.propId] : result;
|
|
331
246
|
});
|
|
332
247
|
}
|
|
333
248
|
}
|
package/lib/graphai_cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const experimental_agents_1 = require("./experimental_agents");
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const yaml_1 = __importDefault(require("yaml"));
|
|
12
|
-
const testAgent = async (
|
|
12
|
+
const testAgent = async () => {
|
|
13
13
|
return {};
|
|
14
14
|
};
|
|
15
15
|
const main = async () => {
|
package/lib/log.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ResultData, TransactionLog, NodeDataParams } from "./type";
|
|
2
|
+
export declare const injectValueLog: (nodeId: string, value: ResultData) => TransactionLog;
|
|
3
|
+
export declare const executeLog: (nodeId: string, retryCount: number, transactionId: number, agentId: string | undefined, params: NodeDataParams, results: ResultData[]) => TransactionLog;
|
|
4
|
+
export declare const timeoutLog: (log: TransactionLog) => void;
|
|
5
|
+
export declare const callbackLog: (log: TransactionLog, result: ResultData, localLog: TransactionLog[]) => void;
|
|
6
|
+
export declare const errorLog: (log: TransactionLog, errorMessage: string) => void;
|
package/lib/log.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.errorLog = exports.callbackLog = exports.timeoutLog = exports.executeLog = exports.injectValueLog = void 0;
|
|
4
|
+
const type_1 = require("./type");
|
|
5
|
+
const injectValueLog = (nodeId, value) => {
|
|
6
|
+
const log = {
|
|
7
|
+
nodeId,
|
|
8
|
+
state: type_1.NodeState.Injected,
|
|
9
|
+
startTime: Date.now(),
|
|
10
|
+
endTime: Date.now(),
|
|
11
|
+
result: value,
|
|
12
|
+
};
|
|
13
|
+
return log;
|
|
14
|
+
};
|
|
15
|
+
exports.injectValueLog = injectValueLog;
|
|
16
|
+
const executeLog = (nodeId, retryCount, transactionId, agentId, params, results) => {
|
|
17
|
+
const log = {
|
|
18
|
+
nodeId,
|
|
19
|
+
retryCount: retryCount > 0 ? retryCount : undefined,
|
|
20
|
+
state: type_1.NodeState.Executing,
|
|
21
|
+
startTime: transactionId,
|
|
22
|
+
agentId,
|
|
23
|
+
params,
|
|
24
|
+
inputs: results.length > 0 ? results : undefined,
|
|
25
|
+
};
|
|
26
|
+
return log;
|
|
27
|
+
};
|
|
28
|
+
exports.executeLog = executeLog;
|
|
29
|
+
const timeoutLog = (log) => {
|
|
30
|
+
log.errorMessage = "Timeout";
|
|
31
|
+
log.state = type_1.NodeState.TimedOut;
|
|
32
|
+
log.endTime = Date.now();
|
|
33
|
+
};
|
|
34
|
+
exports.timeoutLog = timeoutLog;
|
|
35
|
+
const callbackLog = (log, result, localLog) => {
|
|
36
|
+
log.endTime = Date.now();
|
|
37
|
+
log.result = result;
|
|
38
|
+
if (localLog.length > 0) {
|
|
39
|
+
log.log = localLog;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
exports.callbackLog = callbackLog;
|
|
43
|
+
const errorLog = (log, errorMessage) => {
|
|
44
|
+
log.state = type_1.NodeState.Failed;
|
|
45
|
+
log.endTime = Date.now();
|
|
46
|
+
log.errorMessage = errorMessage;
|
|
47
|
+
};
|
|
48
|
+
exports.errorLog = errorLog;
|
package/lib/node.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { NodeDataParams, ResultData, DataSource, NodeData } from "./type";
|
|
2
|
+
import type { GraphAI } from "./graphai";
|
|
3
|
+
import { NodeState } from "./type";
|
|
4
|
+
export declare class Node {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
sources: Record<string, DataSource>;
|
|
7
|
+
anyInput: boolean;
|
|
8
|
+
inputs: Array<string>;
|
|
9
|
+
pendings: Set<string>;
|
|
10
|
+
waitlist: Set<string>;
|
|
11
|
+
state: NodeState;
|
|
12
|
+
fork?: number;
|
|
13
|
+
forkIndex?: number;
|
|
14
|
+
result: ResultData;
|
|
15
|
+
transactionId: undefined | number;
|
|
16
|
+
protected graph: GraphAI;
|
|
17
|
+
constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
|
|
18
|
+
asString(): string;
|
|
19
|
+
removePending(nodeId: string): void;
|
|
20
|
+
protected setResult(result: ResultData, state: NodeState): void;
|
|
21
|
+
}
|
|
22
|
+
export declare class ComputedNode extends Node {
|
|
23
|
+
params: NodeDataParams;
|
|
24
|
+
retryLimit: number;
|
|
25
|
+
retryCount: number;
|
|
26
|
+
agentId?: string;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
error?: Error;
|
|
29
|
+
readonly isStaticNode = false;
|
|
30
|
+
readonly isComputedNode = true;
|
|
31
|
+
constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
|
|
32
|
+
pushQueueIfReady(): void;
|
|
33
|
+
private retry;
|
|
34
|
+
removePending(nodeId: string): void;
|
|
35
|
+
execute(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare class StaticNode extends Node {
|
|
38
|
+
value?: ResultData;
|
|
39
|
+
update?: string;
|
|
40
|
+
readonly isStaticNode = true;
|
|
41
|
+
readonly isComputedNode = false;
|
|
42
|
+
constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
|
|
43
|
+
injectValue(value: ResultData): void;
|
|
44
|
+
}
|
package/lib/node.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StaticNode = exports.ComputedNode = exports.Node = void 0;
|
|
4
|
+
const type_1 = require("./type");
|
|
5
|
+
const utils_1 = require("./utils/utils");
|
|
6
|
+
const log_1 = require("./log");
|
|
7
|
+
class Node {
|
|
8
|
+
constructor(nodeId, forkIndex, data, graph) {
|
|
9
|
+
this.sources = {}; // data sources.
|
|
10
|
+
this.waitlist = new Set(); // List of nodes which need data from this node.
|
|
11
|
+
this.state = type_1.NodeState.Waiting;
|
|
12
|
+
this.result = undefined;
|
|
13
|
+
this.nodeId = nodeId;
|
|
14
|
+
this.forkIndex = forkIndex;
|
|
15
|
+
this.anyInput = data.anyInput ?? false;
|
|
16
|
+
this.inputs = (data.inputs ?? []).map((input) => {
|
|
17
|
+
const source = (0, utils_1.parseNodeName)(input);
|
|
18
|
+
this.sources[source.nodeId] = source;
|
|
19
|
+
return source.nodeId;
|
|
20
|
+
});
|
|
21
|
+
this.pendings = new Set(this.inputs);
|
|
22
|
+
this.fork = data.fork;
|
|
23
|
+
this.graph = graph;
|
|
24
|
+
}
|
|
25
|
+
asString() {
|
|
26
|
+
return `${this.nodeId}: ${this.state} ${[...this.waitlist]}`;
|
|
27
|
+
}
|
|
28
|
+
removePending(nodeId) {
|
|
29
|
+
if (this.anyInput) {
|
|
30
|
+
const [result] = this.graph.resultsOf([this.sources[nodeId]]);
|
|
31
|
+
if (result) {
|
|
32
|
+
this.pendings.clear();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.pendings.delete(nodeId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
setResult(result, state) {
|
|
40
|
+
this.state = state;
|
|
41
|
+
this.result = result;
|
|
42
|
+
this.waitlist.forEach((nodeId) => {
|
|
43
|
+
const node = this.graph.nodes[nodeId];
|
|
44
|
+
// Todo: Avoid running before Run()
|
|
45
|
+
node.removePending(this.nodeId);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.Node = Node;
|
|
50
|
+
class ComputedNode extends Node {
|
|
51
|
+
constructor(nodeId, forkIndex, data, graph) {
|
|
52
|
+
super(nodeId, forkIndex, data, graph);
|
|
53
|
+
this.retryCount = 0;
|
|
54
|
+
this.isStaticNode = false;
|
|
55
|
+
this.isComputedNode = true;
|
|
56
|
+
this.params = data.params ?? {};
|
|
57
|
+
this.agentId = data.agentId ?? graph.agentId;
|
|
58
|
+
this.retryLimit = data.retry ?? 0;
|
|
59
|
+
this.timeout = data.timeout;
|
|
60
|
+
}
|
|
61
|
+
// for completed
|
|
62
|
+
pushQueueIfReady() {
|
|
63
|
+
if (this.pendings.size === 0) {
|
|
64
|
+
// If input property is specified, we need to ensure that the property value exists.
|
|
65
|
+
const count = this.inputs.reduce((count, nodeId) => {
|
|
66
|
+
const source = this.sources[nodeId];
|
|
67
|
+
if (source.propId) {
|
|
68
|
+
const [result] = this.graph.resultsOf([source]);
|
|
69
|
+
if (!result) {
|
|
70
|
+
return count;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return count + 1;
|
|
74
|
+
}, 0);
|
|
75
|
+
if ((this.anyInput && count > 0) || count == this.inputs.length) {
|
|
76
|
+
this.graph.pushQueue(this);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// for computed
|
|
81
|
+
retry(state, error) {
|
|
82
|
+
if (this.retryCount < this.retryLimit) {
|
|
83
|
+
this.retryCount++;
|
|
84
|
+
this.execute();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
this.state = state;
|
|
88
|
+
this.result = undefined;
|
|
89
|
+
this.error = error;
|
|
90
|
+
this.transactionId = undefined; // This is necessary for timeout case
|
|
91
|
+
this.graph.removeRunning(this);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
removePending(nodeId) {
|
|
95
|
+
super.removePending(nodeId);
|
|
96
|
+
if (this.graph.isRunning) {
|
|
97
|
+
this.pushQueueIfReady();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async execute() {
|
|
101
|
+
const results = this.graph
|
|
102
|
+
.resultsOf(this.inputs.map((input) => {
|
|
103
|
+
return this.sources[input];
|
|
104
|
+
}))
|
|
105
|
+
.filter((result) => {
|
|
106
|
+
// Remove undefined if anyInput flag is set.
|
|
107
|
+
return !this.anyInput || result !== undefined;
|
|
108
|
+
});
|
|
109
|
+
const transactionId = Date.now();
|
|
110
|
+
const log = (0, log_1.executeLog)(this.nodeId, this.retryCount, transactionId, this.agentId, this.params, results);
|
|
111
|
+
this.graph.appendLog(log);
|
|
112
|
+
this.state = type_1.NodeState.Executing;
|
|
113
|
+
this.transactionId = transactionId;
|
|
114
|
+
if (this.timeout && this.timeout > 0) {
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
if (this.state === type_1.NodeState.Executing && this.transactionId === transactionId) {
|
|
117
|
+
console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
|
|
118
|
+
(0, log_1.timeoutLog)(log);
|
|
119
|
+
this.retry(type_1.NodeState.TimedOut, Error("Timeout"));
|
|
120
|
+
}
|
|
121
|
+
}, this.timeout);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const callback = this.graph.getCallback(this.agentId);
|
|
125
|
+
const localLog = [];
|
|
126
|
+
const result = await callback({
|
|
127
|
+
nodeId: this.nodeId,
|
|
128
|
+
retry: this.retryCount,
|
|
129
|
+
params: this.params,
|
|
130
|
+
inputs: results,
|
|
131
|
+
forkIndex: this.forkIndex,
|
|
132
|
+
verbose: this.graph.verbose,
|
|
133
|
+
agents: this.graph.callbackDictonary,
|
|
134
|
+
log: localLog,
|
|
135
|
+
});
|
|
136
|
+
if (this.transactionId !== transactionId) {
|
|
137
|
+
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
(0, log_1.callbackLog)(log, result, localLog);
|
|
141
|
+
log.state = type_1.NodeState.Completed;
|
|
142
|
+
this.setResult(result, type_1.NodeState.Completed);
|
|
143
|
+
this.graph.removeRunning(this);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (this.transactionId !== transactionId) {
|
|
147
|
+
console.log(`-- ${this.nodeId}: transactionId mismatch(error)`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const isError = error instanceof Error;
|
|
151
|
+
(0, log_1.errorLog)(log, isError ? error.message : "Unknown");
|
|
152
|
+
if (isError) {
|
|
153
|
+
this.retry(type_1.NodeState.Failed, error);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
|
|
157
|
+
this.retry(type_1.NodeState.Failed, Error("Unknown"));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.ComputedNode = ComputedNode;
|
|
163
|
+
class StaticNode extends Node {
|
|
164
|
+
constructor(nodeId, forkIndex, data, graph) {
|
|
165
|
+
super(nodeId, forkIndex, data, graph);
|
|
166
|
+
this.isStaticNode = true;
|
|
167
|
+
this.isComputedNode = false;
|
|
168
|
+
this.value = data.value;
|
|
169
|
+
this.update = data.update;
|
|
170
|
+
}
|
|
171
|
+
// for static
|
|
172
|
+
injectValue(value) {
|
|
173
|
+
const log = (0, log_1.injectValueLog)(this.nodeId, value);
|
|
174
|
+
this.graph.appendLog(log);
|
|
175
|
+
this.setResult(value, type_1.NodeState.Injected);
|
|
176
|
+
//console.error("- injectValue called on non-source node.", this.nodeId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.StaticNode = StaticNode;
|
package/lib/type.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare enum NodeState {
|
|
2
|
+
Waiting = "waiting",
|
|
3
|
+
Executing = "executing",
|
|
4
|
+
Failed = "failed",
|
|
5
|
+
TimedOut = "timed-out",
|
|
6
|
+
Completed = "completed",
|
|
7
|
+
Injected = "injected",
|
|
8
|
+
Dispatched = "dispatched"
|
|
9
|
+
}
|
|
10
|
+
export type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
|
|
11
|
+
export type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
|
|
12
|
+
export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
|
|
13
|
+
export type DataSource = {
|
|
14
|
+
nodeId: string;
|
|
15
|
+
propId?: string;
|
|
16
|
+
};
|
|
17
|
+
export type NodeData = {
|
|
18
|
+
inputs?: Array<string>;
|
|
19
|
+
anyInput?: boolean;
|
|
20
|
+
params?: NodeDataParams;
|
|
21
|
+
retry?: number;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
agentId?: string;
|
|
24
|
+
fork?: number;
|
|
25
|
+
value?: ResultData;
|
|
26
|
+
update?: string;
|
|
27
|
+
};
|
|
28
|
+
export type LoopData = {
|
|
29
|
+
count?: number;
|
|
30
|
+
while?: string;
|
|
31
|
+
};
|
|
32
|
+
export type GraphData = {
|
|
33
|
+
agentId?: string;
|
|
34
|
+
nodes: Record<string, NodeData>;
|
|
35
|
+
concurrency?: number;
|
|
36
|
+
loop?: LoopData;
|
|
37
|
+
verbose?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type TransactionLog = {
|
|
40
|
+
nodeId: string;
|
|
41
|
+
state: NodeState;
|
|
42
|
+
startTime: number;
|
|
43
|
+
endTime?: number;
|
|
44
|
+
retryCount?: number;
|
|
45
|
+
agentId?: string;
|
|
46
|
+
params?: NodeDataParams;
|
|
47
|
+
inputs?: Array<ResultData>;
|
|
48
|
+
errorMessage?: string;
|
|
49
|
+
result?: ResultData;
|
|
50
|
+
log?: TransactionLog[];
|
|
51
|
+
};
|
|
52
|
+
export type AgentFunctionContext<ParamsType, PreviousResultType> = {
|
|
53
|
+
nodeId: string;
|
|
54
|
+
forkIndex?: number;
|
|
55
|
+
retry: number;
|
|
56
|
+
params: NodeDataParams<ParamsType>;
|
|
57
|
+
inputs: Array<PreviousResultType>;
|
|
58
|
+
verbose: boolean;
|
|
59
|
+
agents: CallbackDictonaryArgs;
|
|
60
|
+
log: TransactionLog[];
|
|
61
|
+
};
|
|
62
|
+
export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, PreviousResultType>) => Promise<ResultData<ResultType>>;
|
|
63
|
+
export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
|
|
64
|
+
export type CallbackDictonaryArgs = AgentFunctionDictonary;
|
package/lib/type.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeState = void 0;
|
|
4
|
+
var NodeState;
|
|
5
|
+
(function (NodeState) {
|
|
6
|
+
NodeState["Waiting"] = "waiting";
|
|
7
|
+
NodeState["Executing"] = "executing";
|
|
8
|
+
NodeState["Failed"] = "failed";
|
|
9
|
+
NodeState["TimedOut"] = "timed-out";
|
|
10
|
+
NodeState["Completed"] = "completed";
|
|
11
|
+
NodeState["Injected"] = "injected";
|
|
12
|
+
NodeState["Dispatched"] = "dispatched";
|
|
13
|
+
})(NodeState || (exports.NodeState = NodeState = {}));
|
package/lib/utils/utils.d.ts
CHANGED
package/lib/utils/utils.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sleep = void 0;
|
|
3
|
+
exports.parseNodeName = exports.sleep = void 0;
|
|
4
4
|
const sleep = async (milliseconds) => {
|
|
5
5
|
return await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
6
6
|
};
|
|
7
7
|
exports.sleep = sleep;
|
|
8
|
+
const parseNodeName = (name) => {
|
|
9
|
+
const parts = name.split(".");
|
|
10
|
+
if (parts.length == 1) {
|
|
11
|
+
return { nodeId: parts[0] };
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return { nodeId: parts[0], propId: parts[1] };
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
exports.parseNodeName = parseNodeName;
|
package/lib/utils.d.ts
ADDED
package/lib/utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseNodeName = void 0;
|
|
4
|
+
const parseNodeName = (name) => {
|
|
5
|
+
const parts = name.split(".");
|
|
6
|
+
if (parts.length == 1) {
|
|
7
|
+
return { sourceNodeId: parts[0] };
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
return { sourceNodeId: parts[0], propId: parts[1] };
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
exports.parseNodeName = parseNodeName;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphai",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Asynchronous data flow execution engine to make it simple to build LLM apps.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc && tsc-alias",
|
|
14
|
-
"eslint": "eslint --fix --ext
|
|
14
|
+
"eslint": "eslint --fix --ext .ts ./src ./tests ./samples",
|
|
15
15
|
"format": "prettier --write '{src,tests,samples}/**/*.ts' .eslintrc.js",
|
|
16
16
|
"test": "node --test -r tsconfig-paths/register --require ts-node/register ./tests/**/test_*.ts",
|
|
17
17
|
"cli": "npx ts-node -r tsconfig-paths/register ./src/graphai_cli.ts",
|