graphai 0.0.11 → 0.0.12
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 +47 -11
- 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 +26 -8
- package/lib/graphai.js +136 -44
- package/lib/graphai_cli.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ Furthermore, GraphAI's robust mechanisms for error handling, retry strategies, t
|
|
|
55
55
|
## Quick Install
|
|
56
56
|
|
|
57
57
|
```
|
|
58
|
-
|
|
58
|
+
npm install graphai
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
or
|
|
@@ -114,12 +114,41 @@ Key principles:
|
|
|
114
114
|
|
|
115
115
|
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
116
|
|
|
117
|
-
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, outputs, retry, timeout, source, agentId,
|
|
117
|
+
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, outputs, retry, timeout, source, agentId, fork, value) that dictate the node's behavior and its relationship with other nodes.
|
|
118
118
|
|
|
119
119
|
### DFG Structure
|
|
120
120
|
|
|
121
121
|
- 'nodes': A list of node. Required.
|
|
122
122
|
- '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.
|
|
123
|
+
- 'agentId': An optional property, which specifies the default agent for all the nodes.
|
|
124
|
+
- '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.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
loop:
|
|
128
|
+
while: people
|
|
129
|
+
nodes:
|
|
130
|
+
people:
|
|
131
|
+
value:
|
|
132
|
+
- Steve Jobs
|
|
133
|
+
- Elon Musk
|
|
134
|
+
- Nikola Tesla
|
|
135
|
+
next: retriever.array
|
|
136
|
+
result:
|
|
137
|
+
value: []
|
|
138
|
+
next: reducer
|
|
139
|
+
retriever:
|
|
140
|
+
agentId: shift
|
|
141
|
+
inputs: [people]
|
|
142
|
+
query:
|
|
143
|
+
agentId: slashgpt
|
|
144
|
+
params:
|
|
145
|
+
manifest:
|
|
146
|
+
prompt: 指定した人について日本語で400字以内で答えて
|
|
147
|
+
inputs: [retriever.item]
|
|
148
|
+
reducer:
|
|
149
|
+
agentId: push
|
|
150
|
+
inputs: [result, query.content]
|
|
151
|
+
```
|
|
123
152
|
|
|
124
153
|
## Agent
|
|
125
154
|
|
|
@@ -133,19 +162,26 @@ An agent function receives two set of parameters via AgentFunctionContext, agent
|
|
|
133
162
|
|
|
134
163
|
## Node Structure
|
|
135
164
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
165
|
+
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).
|
|
166
|
+
|
|
167
|
+
A *computed node* have following properties.
|
|
168
|
+
|
|
169
|
+
- 'agentId': An **required** property, which specifies the id of the *agent function*.
|
|
170
|
+
- '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
171
|
- '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
172
|
- '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.
|
|
173
|
+
- 'params': An optional property to the associated agent function, which are agent specific.
|
|
143
174
|
- 'fork': An optional paramter, which specifies the number of concurrent transactions to be created for the current node.
|
|
144
|
-
- 'outputs': An optinal property, which specifies the mapping from outputId to nodeId. If this property is set, the node become a special node called
|
|
175
|
+
- 'outputs' (MAY BECOME OBSOLETE): An optinal property, which specifies the mapping from outputId to nodeId. If this property is set, the node become a special node called *dispatcher*. A *dispatcher* node injects result(s) into specified static nodes, enabling the dynamic flow of data.
|
|
176
|
+
|
|
177
|
+
A *static* node have following properties.
|
|
178
|
+
|
|
179
|
+
- 'value': An optional property, which specifies the value of this static node (equivalent to calling the injectValue method from outside).
|
|
180
|
+
- 'next': An optional property, which specifies the *data source* after each iteration.
|
|
145
181
|
|
|
146
182
|
## GraphAI class
|
|
147
183
|
|
|
148
|
-
### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary
|
|
184
|
+
### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary)```
|
|
149
185
|
Initializes a new instance of the GraphAI class with the specified graph data and a dictionary of callback functions.
|
|
150
186
|
|
|
151
187
|
- ```data: GraphData```: The graph data including nodes and optional concurrency limit.
|
|
@@ -172,7 +208,7 @@ Retrieves all transaction logs recorded during the execution of the graph.
|
|
|
172
208
|
Returns: An array of transaction logs detailing the execution states and outcomes of the nodes within the graph.
|
|
173
209
|
|
|
174
210
|
### ```injectResult(nodeId: string, result: ResultData): void```
|
|
175
|
-
Injects a result into a specified node. This is used to manually set the result of a
|
|
211
|
+
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
212
|
|
|
177
|
-
- ```nodeId: string```: The ID of the
|
|
213
|
+
- ```nodeId: string```: The ID of the static node into which the result is to be injected.
|
|
178
214
|
- ```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
|
@@ -18,12 +18,19 @@ type NodeData = {
|
|
|
18
18
|
agentId?: string;
|
|
19
19
|
fork?: number;
|
|
20
20
|
source?: boolean;
|
|
21
|
-
|
|
21
|
+
value?: ResultData;
|
|
22
|
+
next?: string;
|
|
22
23
|
outputs?: Record<string, string>;
|
|
23
24
|
};
|
|
25
|
+
type LoopData = {
|
|
26
|
+
count?: number;
|
|
27
|
+
while?: string;
|
|
28
|
+
};
|
|
24
29
|
export type GraphData = {
|
|
30
|
+
agentId?: string;
|
|
25
31
|
nodes: Record<string, NodeData>;
|
|
26
32
|
concurrency?: number;
|
|
33
|
+
loop?: LoopData;
|
|
27
34
|
verbose?: boolean;
|
|
28
35
|
};
|
|
29
36
|
export type TransactionLog = {
|
|
@@ -31,14 +38,15 @@ export type TransactionLog = {
|
|
|
31
38
|
state: NodeState;
|
|
32
39
|
startTime: number;
|
|
33
40
|
endTime?: number;
|
|
34
|
-
retryCount
|
|
41
|
+
retryCount?: number;
|
|
35
42
|
agentId?: string;
|
|
36
43
|
params?: NodeDataParams;
|
|
37
44
|
inputs?: Array<ResultData>;
|
|
38
45
|
errorMessage?: string;
|
|
39
46
|
result?: ResultData;
|
|
47
|
+
log?: TransactionLog[];
|
|
40
48
|
};
|
|
41
|
-
export type AgentFunctionContext<ParamsType,
|
|
49
|
+
export type AgentFunctionContext<ParamsType, PreviousResultType> = {
|
|
42
50
|
nodeId: string;
|
|
43
51
|
forkIndex?: number;
|
|
44
52
|
retry: number;
|
|
@@ -46,13 +54,15 @@ export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
|
|
|
46
54
|
inputs: Array<PreviousResultType>;
|
|
47
55
|
verbose: boolean;
|
|
48
56
|
agents: CallbackDictonaryArgs;
|
|
57
|
+
log: TransactionLog[];
|
|
49
58
|
};
|
|
50
|
-
export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType,
|
|
59
|
+
export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, PreviousResultType>) => Promise<ResultData<ResultType>>;
|
|
51
60
|
export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
|
|
52
61
|
declare class Node {
|
|
53
62
|
nodeId: string;
|
|
54
63
|
params: NodeDataParams;
|
|
55
64
|
inputs: Array<string>;
|
|
65
|
+
inputProps: Record<string, string>;
|
|
56
66
|
pendings: Set<string>;
|
|
57
67
|
waitlist: Set<string>;
|
|
58
68
|
state: NodeState;
|
|
@@ -73,34 +83,42 @@ declare class Node {
|
|
|
73
83
|
private retry;
|
|
74
84
|
removePending(nodeId: string): void;
|
|
75
85
|
pushQueueIfReady(): void;
|
|
76
|
-
|
|
86
|
+
injectValue(value: ResultData): void;
|
|
77
87
|
private setResult;
|
|
78
88
|
execute(): Promise<void>;
|
|
79
89
|
}
|
|
80
90
|
type GraphNodes = Record<string, Node>;
|
|
81
|
-
export type CallbackDictonaryArgs = AgentFunctionDictonary
|
|
91
|
+
export type CallbackDictonaryArgs = AgentFunctionDictonary;
|
|
82
92
|
export declare class GraphAI {
|
|
93
|
+
private data;
|
|
83
94
|
nodes: GraphNodes;
|
|
95
|
+
agentId?: string;
|
|
84
96
|
callbackDictonary: AgentFunctionDictonary;
|
|
85
97
|
isRunning: boolean;
|
|
86
98
|
private runningNodes;
|
|
87
99
|
private nodeQueue;
|
|
88
100
|
private onComplete;
|
|
89
101
|
private concurrency;
|
|
102
|
+
private loop?;
|
|
103
|
+
private repeatCount;
|
|
90
104
|
verbose: boolean;
|
|
91
105
|
private logs;
|
|
106
|
+
private createNodes;
|
|
107
|
+
private getValueFromResults;
|
|
108
|
+
private initializeNodes;
|
|
92
109
|
constructor(data: GraphData, callbackDictonary: CallbackDictonaryArgs);
|
|
93
|
-
getCallback(
|
|
110
|
+
getCallback(agentId?: string): AgentFunction<any, any, any>;
|
|
94
111
|
asString(): string;
|
|
95
112
|
results(): ResultDataDictonary<Record<string, any>>;
|
|
96
113
|
errors(): Record<string, Error>;
|
|
114
|
+
private pushReadyNodesIntoQueue;
|
|
97
115
|
run(): Promise<ResultDataDictonary>;
|
|
98
116
|
private runNode;
|
|
99
117
|
pushQueue(node: Node): void;
|
|
100
118
|
removeRunning(node: Node): void;
|
|
101
119
|
appendLog(log: TransactionLog): void;
|
|
102
120
|
transactionLogs(): TransactionLog[];
|
|
103
|
-
|
|
121
|
+
injectValue(nodeId: string, value: ResultData): void;
|
|
104
122
|
resultsOf(nodeIds: Array<string>): ResultData<Record<string, any>>[];
|
|
105
123
|
}
|
|
106
124
|
export {};
|
package/lib/graphai.js
CHANGED
|
@@ -11,22 +11,38 @@ var NodeState;
|
|
|
11
11
|
NodeState["Injected"] = "injected";
|
|
12
12
|
NodeState["Dispatched"] = "dispatched";
|
|
13
13
|
})(NodeState || (exports.NodeState = NodeState = {}));
|
|
14
|
+
const parseNodeName = (name) => {
|
|
15
|
+
const parts = name.split(".");
|
|
16
|
+
if (parts.length == 1) {
|
|
17
|
+
return { sourceNodeId: parts[0] };
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return { sourceNodeId: parts[0], propId: parts[1] };
|
|
21
|
+
}
|
|
22
|
+
};
|
|
14
23
|
class Node {
|
|
15
24
|
constructor(nodeId, forkIndex, data, graph) {
|
|
25
|
+
this.inputProps = {}; // optional properties for input
|
|
16
26
|
this.waitlist = new Set(); // List of nodes which need data from this node.
|
|
17
27
|
this.state = NodeState.Waiting;
|
|
18
28
|
this.result = undefined;
|
|
19
29
|
this.retryCount = 0;
|
|
20
30
|
this.nodeId = nodeId;
|
|
21
31
|
this.forkIndex = forkIndex;
|
|
22
|
-
this.inputs = data.inputs ?? []
|
|
32
|
+
this.inputs = (data.inputs ?? []).map((input) => {
|
|
33
|
+
const { sourceNodeId, propId } = parseNodeName(input);
|
|
34
|
+
if (propId) {
|
|
35
|
+
this.inputProps[sourceNodeId] = propId;
|
|
36
|
+
}
|
|
37
|
+
return sourceNodeId;
|
|
38
|
+
});
|
|
23
39
|
this.pendings = new Set(this.inputs);
|
|
24
40
|
this.params = data.params ?? {};
|
|
25
|
-
this.agentId = data.agentId;
|
|
41
|
+
this.agentId = data.agentId ?? graph.agentId;
|
|
26
42
|
this.fork = data.fork;
|
|
27
43
|
this.retryLimit = data.retry ?? 0;
|
|
28
44
|
this.timeout = data.timeout;
|
|
29
|
-
this.source =
|
|
45
|
+
this.source = this.agentId === undefined;
|
|
30
46
|
this.outputs = data.outputs;
|
|
31
47
|
this.graph = graph;
|
|
32
48
|
}
|
|
@@ -54,10 +70,18 @@ class Node {
|
|
|
54
70
|
}
|
|
55
71
|
pushQueueIfReady() {
|
|
56
72
|
if (this.pendings.size === 0 && !this.source) {
|
|
73
|
+
// If input property is specified, we need to ensure that the property value exists.
|
|
74
|
+
Object.keys(this.inputProps).forEach((nodeId) => {
|
|
75
|
+
const [result] = this.graph.resultsOf([nodeId]);
|
|
76
|
+
const propId = this.inputProps[nodeId];
|
|
77
|
+
if (!result || !(propId in result)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
57
81
|
this.graph.pushQueue(this);
|
|
58
82
|
}
|
|
59
83
|
}
|
|
60
|
-
|
|
84
|
+
injectValue(value) {
|
|
61
85
|
if (this.source) {
|
|
62
86
|
const log = {
|
|
63
87
|
nodeId: this.nodeId,
|
|
@@ -65,13 +89,13 @@ class Node {
|
|
|
65
89
|
state: NodeState.Injected,
|
|
66
90
|
startTime: Date.now(),
|
|
67
91
|
endTime: Date.now(),
|
|
68
|
-
result,
|
|
92
|
+
result: value,
|
|
69
93
|
};
|
|
70
94
|
this.graph.appendLog(log);
|
|
71
|
-
this.setResult(
|
|
95
|
+
this.setResult(value, NodeState.Injected);
|
|
72
96
|
}
|
|
73
97
|
else {
|
|
74
|
-
console.error("-
|
|
98
|
+
console.error("- injectValue called on non-source node.", this.nodeId);
|
|
75
99
|
}
|
|
76
100
|
}
|
|
77
101
|
setResult(result, state) {
|
|
@@ -85,15 +109,21 @@ class Node {
|
|
|
85
109
|
}
|
|
86
110
|
async execute() {
|
|
87
111
|
const results = this.graph.resultsOf(this.inputs);
|
|
112
|
+
this.inputs.forEach((nodeId, index) => {
|
|
113
|
+
const propId = this.inputProps[nodeId];
|
|
114
|
+
if (propId) {
|
|
115
|
+
results[index] = results[index][propId];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
88
118
|
const transactionId = Date.now();
|
|
89
119
|
const log = {
|
|
90
120
|
nodeId: this.nodeId,
|
|
91
|
-
retryCount: this.retryCount,
|
|
121
|
+
retryCount: this.retryCount > 0 ? this.retryCount : undefined,
|
|
92
122
|
state: NodeState.Executing,
|
|
93
123
|
startTime: transactionId,
|
|
94
124
|
agentId: this.agentId,
|
|
95
125
|
params: this.params,
|
|
96
|
-
inputs: results,
|
|
126
|
+
inputs: results.length > 0 ? results : undefined,
|
|
97
127
|
};
|
|
98
128
|
this.graph.appendLog(log);
|
|
99
129
|
this.state = NodeState.Executing;
|
|
@@ -111,6 +141,7 @@ class Node {
|
|
|
111
141
|
}
|
|
112
142
|
try {
|
|
113
143
|
const callback = this.graph.getCallback(this.agentId);
|
|
144
|
+
const localLog = [];
|
|
114
145
|
const result = await callback({
|
|
115
146
|
nodeId: this.nodeId,
|
|
116
147
|
retry: this.retryCount,
|
|
@@ -119,6 +150,7 @@ class Node {
|
|
|
119
150
|
forkIndex: this.forkIndex,
|
|
120
151
|
verbose: this.graph.verbose,
|
|
121
152
|
agents: this.graph.callbackDictonary,
|
|
153
|
+
log: localLog,
|
|
122
154
|
});
|
|
123
155
|
if (this.transactionId !== transactionId) {
|
|
124
156
|
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
@@ -126,11 +158,20 @@ class Node {
|
|
|
126
158
|
}
|
|
127
159
|
log.endTime = Date.now();
|
|
128
160
|
log.result = result;
|
|
161
|
+
if (localLog.length > 0) {
|
|
162
|
+
log.log = localLog;
|
|
163
|
+
}
|
|
129
164
|
const outputs = this.outputs;
|
|
130
165
|
if (outputs !== undefined) {
|
|
131
|
-
Object.keys(
|
|
166
|
+
Object.keys(outputs).forEach((outputId) => {
|
|
132
167
|
const nodeId = outputs[outputId];
|
|
133
|
-
|
|
168
|
+
const value = result[outputId];
|
|
169
|
+
if (value) {
|
|
170
|
+
this.graph.injectValue(nodeId, value);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.error("-- Invalid outputId", outputId, result);
|
|
174
|
+
}
|
|
134
175
|
});
|
|
135
176
|
log.state = NodeState.Dispatched;
|
|
136
177
|
this.state = NodeState.Dispatched;
|
|
@@ -162,21 +203,10 @@ class Node {
|
|
|
162
203
|
}
|
|
163
204
|
const defaultConcurrency = 8;
|
|
164
205
|
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
|
-
};
|
|
206
|
+
createNodes(data) {
|
|
176
207
|
const nodeId2forkedNodeIds = {};
|
|
177
208
|
const forkedNodeId2Index = {};
|
|
178
|
-
|
|
179
|
-
this.nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
|
|
209
|
+
const nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
|
|
180
210
|
const fork = data.nodes[nodeId].fork;
|
|
181
211
|
if (fork) {
|
|
182
212
|
// For fork, change the nodeId and increase the node
|
|
@@ -194,8 +224,8 @@ class GraphAI {
|
|
|
194
224
|
return nodes;
|
|
195
225
|
}, {});
|
|
196
226
|
// Generate the waitlist for each node, and update the pendings in case of forked node.
|
|
197
|
-
Object.keys(
|
|
198
|
-
const node =
|
|
227
|
+
Object.keys(nodes).forEach((nodeId) => {
|
|
228
|
+
const node = nodes[nodeId];
|
|
199
229
|
node.pendings.forEach((pending) => {
|
|
200
230
|
// If the pending(previous) node is forking
|
|
201
231
|
if (nodeId2forkedNodeIds[pending]) {
|
|
@@ -203,36 +233,74 @@ class GraphAI {
|
|
|
203
233
|
if (node.fork) {
|
|
204
234
|
// 1:1 if current nodes are also forking.
|
|
205
235
|
const newPendingId = nodeId2forkedNodeIds[pending][forkedNodeId2Index[nodeId]];
|
|
206
|
-
|
|
236
|
+
nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
207
237
|
node.pendings.add(newPendingId);
|
|
208
238
|
}
|
|
209
239
|
else {
|
|
210
240
|
// 1:n if current node is not forking.
|
|
211
241
|
nodeId2forkedNodeIds[pending].forEach((newPendingId) => {
|
|
212
|
-
|
|
242
|
+
nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
213
243
|
node.pendings.add(newPendingId);
|
|
214
244
|
});
|
|
215
245
|
}
|
|
216
246
|
node.pendings.delete(pending);
|
|
217
247
|
}
|
|
218
248
|
else {
|
|
219
|
-
|
|
249
|
+
if (nodes[pending]) {
|
|
250
|
+
nodes[pending].waitlist.add(nodeId); // previousNode
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
console.error(`--- invalid input ${pending} for node, ${nodeId}`);
|
|
254
|
+
}
|
|
220
255
|
}
|
|
221
256
|
});
|
|
222
257
|
node.inputs = Array.from(node.pendings); // for fork.
|
|
223
258
|
});
|
|
259
|
+
return nodes;
|
|
260
|
+
}
|
|
261
|
+
getValueFromResults(key, results) {
|
|
262
|
+
const { sourceNodeId, propId } = parseNodeName(key);
|
|
263
|
+
const result = results[sourceNodeId];
|
|
264
|
+
return result ? (propId ? result[propId] : result) : undefined;
|
|
265
|
+
}
|
|
266
|
+
initializeNodes(previousResults) {
|
|
224
267
|
// If the result property is specified, inject it.
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
268
|
+
// If the previousResults exists (indicating we are in a loop),
|
|
269
|
+
// process the next property (nodeId or nodeId.propId).
|
|
270
|
+
Object.keys(this.data.nodes).forEach((nodeId) => {
|
|
271
|
+
const node = this.data.nodes[nodeId];
|
|
272
|
+
const { value, next } = node;
|
|
273
|
+
if (value) {
|
|
274
|
+
this.injectValue(nodeId, value);
|
|
275
|
+
}
|
|
276
|
+
if (next && previousResults) {
|
|
277
|
+
const result = this.getValueFromResults(next, previousResults);
|
|
278
|
+
if (result) {
|
|
279
|
+
this.injectValue(nodeId, result);
|
|
280
|
+
}
|
|
230
281
|
}
|
|
231
282
|
});
|
|
232
283
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
284
|
+
constructor(data, callbackDictonary) {
|
|
285
|
+
this.isRunning = false;
|
|
286
|
+
this.runningNodes = new Set();
|
|
287
|
+
this.nodeQueue = [];
|
|
288
|
+
this.repeatCount = 0;
|
|
289
|
+
this.logs = [];
|
|
290
|
+
this.data = data;
|
|
291
|
+
this.callbackDictonary = callbackDictonary;
|
|
292
|
+
this.concurrency = data.concurrency ?? defaultConcurrency;
|
|
293
|
+
this.loop = data.loop;
|
|
294
|
+
this.agentId = data.agentId;
|
|
295
|
+
this.verbose = data.verbose === true;
|
|
296
|
+
this.onComplete = () => {
|
|
297
|
+
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
298
|
+
};
|
|
299
|
+
this.nodes = this.createNodes(data);
|
|
300
|
+
this.initializeNodes();
|
|
301
|
+
}
|
|
302
|
+
getCallback(agentId) {
|
|
303
|
+
if (agentId && this.callbackDictonary[agentId]) {
|
|
236
304
|
return this.callbackDictonary[agentId];
|
|
237
305
|
}
|
|
238
306
|
throw new Error("No agent: " + agentId);
|
|
@@ -262,16 +330,19 @@ class GraphAI {
|
|
|
262
330
|
return errors;
|
|
263
331
|
}, {});
|
|
264
332
|
}
|
|
265
|
-
|
|
266
|
-
if (this.isRunning) {
|
|
267
|
-
console.error("-- Already Running");
|
|
268
|
-
}
|
|
269
|
-
this.isRunning = true;
|
|
333
|
+
pushReadyNodesIntoQueue() {
|
|
270
334
|
// Nodes without pending data should run immediately.
|
|
271
335
|
Object.keys(this.nodes).forEach((nodeId) => {
|
|
272
336
|
const node = this.nodes[nodeId];
|
|
273
337
|
node.pushQueueIfReady();
|
|
274
338
|
});
|
|
339
|
+
}
|
|
340
|
+
async run() {
|
|
341
|
+
if (this.isRunning) {
|
|
342
|
+
console.error("-- Already Running");
|
|
343
|
+
}
|
|
344
|
+
this.isRunning = true;
|
|
345
|
+
this.pushReadyNodesIntoQueue();
|
|
275
346
|
return new Promise((resolve, reject) => {
|
|
276
347
|
this.onComplete = () => {
|
|
277
348
|
this.isRunning = false;
|
|
@@ -307,6 +378,27 @@ class GraphAI {
|
|
|
307
378
|
}
|
|
308
379
|
}
|
|
309
380
|
if (this.runningNodes.size === 0) {
|
|
381
|
+
this.repeatCount++;
|
|
382
|
+
const loop = this.loop;
|
|
383
|
+
if (loop && (loop.count === undefined || this.repeatCount < loop.count)) {
|
|
384
|
+
const results = this.results(); // results from previous loop
|
|
385
|
+
this.isRunning = false; // temporarily stop it
|
|
386
|
+
this.nodes = this.createNodes(this.data);
|
|
387
|
+
this.initializeNodes(results);
|
|
388
|
+
const checkWhileCondition = () => {
|
|
389
|
+
if (loop.while) {
|
|
390
|
+
const value = this.getValueFromResults(loop.while, this.results());
|
|
391
|
+
// NOTE: We treat an empty array as false.
|
|
392
|
+
return Array.isArray(value) ? value.length > 0 : !!value;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
};
|
|
396
|
+
if (checkWhileCondition()) {
|
|
397
|
+
this.isRunning = true; // restore it
|
|
398
|
+
this.pushReadyNodesIntoQueue();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
310
402
|
this.onComplete();
|
|
311
403
|
}
|
|
312
404
|
}
|
|
@@ -316,10 +408,10 @@ class GraphAI {
|
|
|
316
408
|
transactionLogs() {
|
|
317
409
|
return this.logs;
|
|
318
410
|
}
|
|
319
|
-
|
|
411
|
+
injectValue(nodeId, value) {
|
|
320
412
|
const node = this.nodes[nodeId];
|
|
321
413
|
if (node) {
|
|
322
|
-
node.
|
|
414
|
+
node.injectValue(value);
|
|
323
415
|
}
|
|
324
416
|
else {
|
|
325
417
|
console.error("-- Invalid nodeId", nodeId);
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
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",
|