graphai 0.0.8 → 0.0.10
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 +15 -2
- package/lib/experimental_agents/index.d.ts +2 -0
- package/lib/experimental_agents/index.js +18 -0
- package/lib/experimental_agents/slashgpt_agent.d.ts +9 -0
- package/lib/experimental_agents/slashgpt_agent.js +30 -0
- package/lib/experimental_agents/string_agent.d.ts +7 -0
- package/lib/experimental_agents/string_agent.js +11 -0
- package/lib/graphai.d.ts +8 -3
- package/lib/graphai.js +58 -14
- package/lib/graphai_cli.d.ts +2 -0
- package/lib/graphai_cli.js +37 -0
- package/package.json +4 -2
- package/samples/agents/arxiv_agent.ts +2 -2
- package/samples/agents/slashgpt_agent.ts +2 -32
- package/samples/graphs/arxiv.yml +1 -1
- package/samples/graphs/slash_gpt.yml +13 -4
- package/samples/home.json +112 -0
- package/samples/home.ts +51 -0
- package/samples/interaction.ts +42 -0
- package/samples/runner.ts +15 -0
- package/samples/sample_co2.ts +7 -30
- package/samples/sample_gpt.ts +3 -32
- package/samples/sample_paper_ai.ts +3 -15
- package/src/experimental_agents/index.ts +2 -0
- package/src/experimental_agents/slashgpt_agent.ts +30 -0
- package/src/experimental_agents/string_agent.ts +10 -0
- package/src/graphai.ts +64 -16
- package/src/graphai_cli.ts +37 -0
- package/tests/agents/test_string_agent.ts +28 -0
- package/tests/graphai/test_fork.ts +106 -0
- package/tests/graphai/test_sample_flow.ts +4 -5
- package/tests/graphs/test_cli.yaml +4 -0
- package/tests/graphs/test_source2.yml +2 -0
package/README.md
CHANGED
|
@@ -42,11 +42,21 @@ const sampleAgentFunction = async (context: AgentFunctionContext) => {
|
|
|
42
42
|
return results["taskC"];
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## Background
|
|
46
|
+
|
|
47
|
+
As Andrew Ng has described in his article, "[The batch: Issue 242](https://www.deeplearning.ai/the-batch/issue-242/)", better results can often be achieved by making multiple calls to a Large Language Model (LLM) and allowing it to incrementally build towards a higher-quality output. Dr. Ng refers to this approach as 'agentic workflows.'
|
|
48
|
+
|
|
49
|
+
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
|
+
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
|
+
|
|
53
|
+
Furthermore, GraphAI's robust mechanisms for error handling, retry strategies, timeouts, and logging empower developers to concentrate on refining the application logic.
|
|
54
|
+
|
|
45
55
|
## Data Flow Graph
|
|
46
56
|
|
|
47
57
|
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
58
|
|
|
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, outputs, retry, timeout, source, agentId) that dictate the node's behavior and its relationship with other nodes.
|
|
59
|
+
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, result, fork) that dictate the node's behavior and its relationship with other nodes.
|
|
50
60
|
|
|
51
61
|
### DFG Structure
|
|
52
62
|
|
|
@@ -67,10 +77,13 @@ An agent function receives two set of parameters via AgentFunctionContext, agent
|
|
|
67
77
|
|
|
68
78
|
- '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.
|
|
69
79
|
- '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'.
|
|
70
|
-
- '
|
|
80
|
+
- 'result': An optional object, which injects the result into the source node (equivalent to calling the injectResult method).
|
|
71
81
|
- '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.
|
|
72
82
|
- '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.
|
|
73
83
|
- 'params': An optional parameters to the associated agent function, which are agent specific.
|
|
84
|
+
- 'agentId': An optional parameter, which specifies the id of the agent, when the graph is associated with multiple agents.
|
|
85
|
+
- 'fork': An optional paramter, which specifies the number of concurrent transactions to be created for the current node.
|
|
86
|
+
- 'outputs': 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 nodes, enabling the dynamic flow of data.
|
|
74
87
|
|
|
75
88
|
## GraphAI class
|
|
76
89
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./string_agent"), exports);
|
|
18
|
+
__exportStar(require("./slashgpt_agent"), exports);
|
|
@@ -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.slashGPTAgent = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const slashgpt_1 = require("slashgpt");
|
|
9
|
+
const config = new slashgpt_1.ChatConfig(path_1.default.resolve(__dirname));
|
|
10
|
+
const slashGPTAgent = async (context) => {
|
|
11
|
+
console.log("executing", context.nodeId, context.params);
|
|
12
|
+
const session = new slashgpt_1.ChatSession(config, context.params.manifest ?? {});
|
|
13
|
+
const query = context.params?.query ? [context.params.query] : [];
|
|
14
|
+
const contents = query.concat(context.inputs.map((input) => {
|
|
15
|
+
return input.content;
|
|
16
|
+
}));
|
|
17
|
+
session.append_user_question(contents.join("\n"));
|
|
18
|
+
await session.call_loop(() => { });
|
|
19
|
+
const message = (() => {
|
|
20
|
+
if (context.params?.function_result) {
|
|
21
|
+
return session.history.messages().find((m) => m.role === "function_result");
|
|
22
|
+
}
|
|
23
|
+
return session.history.last_message();
|
|
24
|
+
})();
|
|
25
|
+
if (message === undefined) {
|
|
26
|
+
throw new Error("No message in the history");
|
|
27
|
+
}
|
|
28
|
+
return message;
|
|
29
|
+
};
|
|
30
|
+
exports.slashGPTAgent = slashGPTAgent;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stringTemplateAgent = void 0;
|
|
4
|
+
const stringTemplateAgent = async (context) => {
|
|
5
|
+
console.log("executing", context.nodeId, context.params);
|
|
6
|
+
const content = context.inputs.reduce((template, input, index) => {
|
|
7
|
+
return template.replace("${" + index + "}", input[context.params.inputKey ?? "content"]);
|
|
8
|
+
}, context.params.template);
|
|
9
|
+
return { content };
|
|
10
|
+
};
|
|
11
|
+
exports.stringTemplateAgent = stringTemplateAgent;
|
package/lib/graphai.d.ts
CHANGED
|
@@ -12,11 +12,13 @@ type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, Resu
|
|
|
12
12
|
export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
|
|
13
13
|
type NodeData = {
|
|
14
14
|
inputs?: Array<string>;
|
|
15
|
-
params
|
|
15
|
+
params?: NodeDataParams;
|
|
16
16
|
retry?: number;
|
|
17
17
|
timeout?: number;
|
|
18
18
|
agentId?: string;
|
|
19
|
+
fork?: number;
|
|
19
20
|
source?: boolean;
|
|
21
|
+
result?: ResultData;
|
|
20
22
|
outputs?: Record<string, string>;
|
|
21
23
|
};
|
|
22
24
|
export type GraphData = {
|
|
@@ -37,6 +39,7 @@ export type TransactionLog = {
|
|
|
37
39
|
};
|
|
38
40
|
export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
|
|
39
41
|
nodeId: string;
|
|
42
|
+
forkIndex?: number;
|
|
40
43
|
retry: number;
|
|
41
44
|
params: NodeDataParams<ParamsType>;
|
|
42
45
|
inputs: Array<PreviousResultType>;
|
|
@@ -51,6 +54,8 @@ declare class Node {
|
|
|
51
54
|
waitlist: Set<string>;
|
|
52
55
|
state: NodeState;
|
|
53
56
|
agentId?: string;
|
|
57
|
+
fork?: number;
|
|
58
|
+
forkIndex?: number;
|
|
54
59
|
result: ResultData;
|
|
55
60
|
retryLimit: number;
|
|
56
61
|
retryCount: number;
|
|
@@ -60,7 +65,7 @@ declare class Node {
|
|
|
60
65
|
source: boolean;
|
|
61
66
|
outputs?: Record<string, string>;
|
|
62
67
|
private graph;
|
|
63
|
-
constructor(nodeId: string, data: NodeData, graph: GraphAI);
|
|
68
|
+
constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
|
|
64
69
|
asString(): string;
|
|
65
70
|
private retry;
|
|
66
71
|
removePending(nodeId: string): void;
|
|
@@ -84,7 +89,7 @@ export declare class GraphAI {
|
|
|
84
89
|
asString(): string;
|
|
85
90
|
results(): ResultDataDictonary<Record<string, any>>;
|
|
86
91
|
errors(): Record<string, Error>;
|
|
87
|
-
run(): Promise<
|
|
92
|
+
run(): Promise<ResultDataDictonary>;
|
|
88
93
|
private runNode;
|
|
89
94
|
pushQueue(node: Node): void;
|
|
90
95
|
removeRunning(node: Node): void;
|
package/lib/graphai.js
CHANGED
|
@@ -12,16 +12,18 @@ var NodeState;
|
|
|
12
12
|
NodeState["Dispatched"] = "dispatched";
|
|
13
13
|
})(NodeState || (exports.NodeState = NodeState = {}));
|
|
14
14
|
class Node {
|
|
15
|
-
constructor(nodeId, data, graph) {
|
|
15
|
+
constructor(nodeId, forkIndex, data, graph) {
|
|
16
16
|
this.waitlist = new Set(); // List of nodes which need data from this node.
|
|
17
17
|
this.state = NodeState.Waiting;
|
|
18
18
|
this.result = undefined;
|
|
19
19
|
this.retryCount = 0;
|
|
20
20
|
this.nodeId = nodeId;
|
|
21
|
+
this.forkIndex = forkIndex;
|
|
21
22
|
this.inputs = data.inputs ?? [];
|
|
22
23
|
this.pendings = new Set(this.inputs);
|
|
23
|
-
this.params = data.params;
|
|
24
|
+
this.params = data.params ?? {};
|
|
24
25
|
this.agentId = data.agentId;
|
|
26
|
+
this.fork = data.fork;
|
|
25
27
|
this.retryLimit = data.retry ?? 0;
|
|
26
28
|
this.timeout = data.timeout;
|
|
27
29
|
this.source = data.source === true;
|
|
@@ -62,9 +64,9 @@ class Node {
|
|
|
62
64
|
retryCount: this.retryCount,
|
|
63
65
|
state: NodeState.Injected,
|
|
64
66
|
startTime: Date.now(),
|
|
67
|
+
endTime: Date.now(),
|
|
65
68
|
result,
|
|
66
69
|
};
|
|
67
|
-
log.endTime = log.startTime;
|
|
68
70
|
this.graph.appendLog(log);
|
|
69
71
|
this.setResult(result, NodeState.Injected);
|
|
70
72
|
}
|
|
@@ -82,21 +84,19 @@ class Node {
|
|
|
82
84
|
});
|
|
83
85
|
}
|
|
84
86
|
async execute() {
|
|
87
|
+
const results = this.graph.resultsOf(this.inputs);
|
|
88
|
+
const transactionId = Date.now();
|
|
85
89
|
const log = {
|
|
86
90
|
nodeId: this.nodeId,
|
|
87
91
|
retryCount: this.retryCount,
|
|
88
92
|
state: NodeState.Executing,
|
|
89
|
-
startTime:
|
|
93
|
+
startTime: transactionId,
|
|
90
94
|
agentId: this.agentId,
|
|
91
95
|
params: this.params,
|
|
96
|
+
inputs: results,
|
|
92
97
|
};
|
|
93
|
-
const results = this.graph.resultsOf(this.inputs);
|
|
94
|
-
if (results.length > 0) {
|
|
95
|
-
log.inputs = results;
|
|
96
|
-
}
|
|
97
98
|
this.graph.appendLog(log);
|
|
98
99
|
this.state = NodeState.Executing;
|
|
99
|
-
const transactionId = log.startTime;
|
|
100
100
|
this.transactionId = transactionId;
|
|
101
101
|
if (this.timeout && this.timeout > 0) {
|
|
102
102
|
setTimeout(() => {
|
|
@@ -116,6 +116,7 @@ class Node {
|
|
|
116
116
|
retry: this.retryCount,
|
|
117
117
|
params: this.params,
|
|
118
118
|
inputs: results,
|
|
119
|
+
forkIndex: this.forkIndex,
|
|
119
120
|
});
|
|
120
121
|
if (this.transactionId !== transactionId) {
|
|
121
122
|
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
@@ -169,22 +170,65 @@ class GraphAI {
|
|
|
169
170
|
this.onComplete = () => {
|
|
170
171
|
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
171
172
|
};
|
|
173
|
+
const nodeId2forkedNodeIds = {};
|
|
174
|
+
const forkedNodeId2Index = {};
|
|
175
|
+
// Create node instances from data.nodes
|
|
172
176
|
this.nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
|
|
173
|
-
|
|
177
|
+
const fork = data.nodes[nodeId].fork;
|
|
178
|
+
if (fork) {
|
|
179
|
+
// For fork, change the nodeId and increase the node
|
|
180
|
+
nodeId2forkedNodeIds[nodeId] = new Array(fork).fill(undefined).map((_, i) => {
|
|
181
|
+
const forkedNodeId = `${nodeId}_${i}`;
|
|
182
|
+
nodes[forkedNodeId] = new Node(forkedNodeId, i, data.nodes[nodeId], this);
|
|
183
|
+
// Data for pending and waiting
|
|
184
|
+
forkedNodeId2Index[forkedNodeId] = i;
|
|
185
|
+
return forkedNodeId;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
nodes[nodeId] = new Node(nodeId, undefined, data.nodes[nodeId], this);
|
|
190
|
+
}
|
|
174
191
|
return nodes;
|
|
175
192
|
}, {});
|
|
176
|
-
// Generate the waitlist for each node
|
|
193
|
+
// Generate the waitlist for each node, and update the pendings in case of forked node.
|
|
177
194
|
Object.keys(this.nodes).forEach((nodeId) => {
|
|
178
195
|
const node = this.nodes[nodeId];
|
|
179
196
|
node.pendings.forEach((pending) => {
|
|
180
|
-
|
|
181
|
-
|
|
197
|
+
// If the pending(previous) node is forking
|
|
198
|
+
if (nodeId2forkedNodeIds[pending]) {
|
|
199
|
+
// update node.pending and pending(previous) node.wailtlist
|
|
200
|
+
if (node.fork) {
|
|
201
|
+
// 1:1 if current nodes are also forking.
|
|
202
|
+
const newPendingId = nodeId2forkedNodeIds[pending][forkedNodeId2Index[nodeId]];
|
|
203
|
+
this.nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
204
|
+
node.pendings.add(newPendingId);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// 1:n if current node is not forking.
|
|
208
|
+
nodeId2forkedNodeIds[pending].forEach((newPendingId) => {
|
|
209
|
+
this.nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
210
|
+
node.pendings.add(newPendingId);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
node.pendings.delete(pending);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
this.nodes[pending].waitlist.add(nodeId); // previousNode
|
|
217
|
+
}
|
|
182
218
|
});
|
|
219
|
+
node.inputs = Array.from(node.pendings); // for fork.
|
|
220
|
+
});
|
|
221
|
+
// If the result property is specified, inject it.
|
|
222
|
+
// NOTE: This must be done at the end of this constructor
|
|
223
|
+
Object.keys(data.nodes).forEach((nodeId) => {
|
|
224
|
+
const result = data.nodes[nodeId].result;
|
|
225
|
+
if (result) {
|
|
226
|
+
this.injectResult(nodeId, result);
|
|
227
|
+
}
|
|
183
228
|
});
|
|
184
229
|
}
|
|
185
230
|
getCallback(_agentId) {
|
|
186
231
|
const agentId = _agentId ?? "_default";
|
|
187
|
-
console.log(agentId);
|
|
188
232
|
if (this.callbackDictonary[agentId]) {
|
|
189
233
|
return this.callbackDictonary[agentId];
|
|
190
234
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const graphai_1 = require("./graphai");
|
|
8
|
+
const experimental_agents_1 = require("./experimental_agents");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
12
|
+
const testAgent = async (context) => {
|
|
13
|
+
return {};
|
|
14
|
+
};
|
|
15
|
+
const main = async () => {
|
|
16
|
+
const file = process.argv[2];
|
|
17
|
+
if (file === undefined) {
|
|
18
|
+
console.log("no file");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const file_path = path_1.default.resolve(process.cwd() + "/" + file);
|
|
22
|
+
if (!fs_1.default.existsSync(file_path)) {
|
|
23
|
+
console.log("no file");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const graph_data_file = fs_1.default.readFileSync(file_path, "utf8");
|
|
28
|
+
const graph_data = yaml_1.default.parse(graph_data_file);
|
|
29
|
+
const graph = new graphai_1.GraphAI(graph_data, { testAgent, slashGPTAgent: experimental_agents_1.slashGPTAgent, stringTemplateAgent: experimental_agents_1.stringTemplateAgent });
|
|
30
|
+
const results = await graph.run();
|
|
31
|
+
console.log(results);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.log("error", e);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Asynchronous data flow execution engine to make it simple to build LLM apps.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"bin": "lib/graphai_cli.js",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsc && tsc-alias",
|
|
8
9
|
"eslint": "eslint --fix --ext src/**/*.{ts} tests/**/*.ts",
|
|
9
10
|
"format": "prettier --write '{src,tests,samples}/**/*.ts' .eslintrc.js",
|
|
10
11
|
"test": "node --test -r tsconfig-paths/register --require ts-node/register ./tests/**/test_*.ts",
|
|
11
|
-
"
|
|
12
|
+
"cli": "npx ts-node -r tsconfig-paths/register ./src/graphai_cli.ts",
|
|
13
|
+
"sample": "npx ts-node -r tsconfig-paths/register"
|
|
12
14
|
},
|
|
13
15
|
"repository": {
|
|
14
16
|
"type": "git",
|
|
@@ -34,12 +34,12 @@ export const arxivAgent: AgentFunction<{ keywords: string[]; limit: number }, ar
|
|
|
34
34
|
return result;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
export const arxiv2TextAgent: AgentFunction<{}, string, string[]> = async (context) => {
|
|
37
|
+
export const arxiv2TextAgent: AgentFunction<{}, Record<string, any>, string[]> = async (context) => {
|
|
38
38
|
const result = (context.inputs[0] || [])
|
|
39
39
|
.map((r: any) => {
|
|
40
40
|
const { id, title, summary } = r;
|
|
41
41
|
return ["id:", id, "title:", title, "summary:", summary].join("\n");
|
|
42
42
|
})
|
|
43
43
|
.join("\n\n\n");
|
|
44
|
-
return result;
|
|
44
|
+
return { content: result };
|
|
45
45
|
};
|
|
@@ -6,7 +6,7 @@ const config = new ChatConfig(path.resolve(__dirname));
|
|
|
6
6
|
|
|
7
7
|
export const slashGPTFuncitons2TextAgent: AgentFunction<
|
|
8
8
|
{ function_data_key: string; result_key: number },
|
|
9
|
-
string,
|
|
9
|
+
Record<string, string>,
|
|
10
10
|
{ function_data: { [key: string]: string[] } }
|
|
11
11
|
> = async (context) => {
|
|
12
12
|
const { params } = context;
|
|
@@ -14,36 +14,6 @@ export const slashGPTFuncitons2TextAgent: AgentFunction<
|
|
|
14
14
|
const { title, description } = r;
|
|
15
15
|
return ["title:", title, "description:", description].join("\n");
|
|
16
16
|
});
|
|
17
|
-
return result[params.result_key];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const slashGPTAgent: AgentFunction<
|
|
21
|
-
{ manifest: ManifestData; prompt: string; function_result?: boolean; debug?: boolean },
|
|
22
|
-
{ answer: string },
|
|
23
|
-
string
|
|
24
|
-
> = async (context) => {
|
|
25
|
-
const { params } = context;
|
|
26
|
-
if (params.debug) {
|
|
27
|
-
console.log("executing", context.nodeId, context);
|
|
28
|
-
}
|
|
29
|
-
const session = new ChatSession(config, params?.manifest ?? {});
|
|
30
|
-
|
|
31
|
-
const prompt = params?.prompt ?? [context.inputs].filter((a) => a !== undefined).join("\n\n");
|
|
32
|
-
// console.log(prompt);
|
|
33
|
-
session.append_user_question(prompt);
|
|
34
|
-
|
|
35
|
-
await session.call_loop(() => {});
|
|
36
17
|
|
|
37
|
-
|
|
38
|
-
const message = (() => {
|
|
39
|
-
if (params.function_result) {
|
|
40
|
-
return session.history.messages().find((m) => m.role === "function_result");
|
|
41
|
-
}
|
|
42
|
-
return session.history.last_message();
|
|
43
|
-
})();
|
|
44
|
-
if (message === undefined) {
|
|
45
|
-
throw new Error("No message in the history");
|
|
46
|
-
}
|
|
47
|
-
const result = { answer: message.content, function_data: message.function_data };
|
|
48
|
-
return result;
|
|
18
|
+
return { content: result[context.forkIndex ?? 0] };
|
|
49
19
|
};
|
package/samples/graphs/arxiv.yml
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
nodes:
|
|
2
2
|
node1:
|
|
3
|
+
agentId: slashgpt
|
|
3
4
|
params:
|
|
4
|
-
|
|
5
|
+
query: Come up with ten business ideas for AI startup
|
|
5
6
|
node2:
|
|
7
|
+
agentId: stringTemplate
|
|
6
8
|
inputs: [node1]
|
|
7
9
|
params:
|
|
8
|
-
|
|
10
|
+
template: |
|
|
9
11
|
Please evaluate following business ideas.
|
|
10
12
|
${0}
|
|
11
13
|
node3:
|
|
12
|
-
|
|
14
|
+
agentId: slashgpt
|
|
15
|
+
inputs: [node2]
|
|
16
|
+
node4:
|
|
17
|
+
agentId: stringTemplate
|
|
18
|
+
inputs: [node1, node3]
|
|
13
19
|
params:
|
|
14
|
-
|
|
20
|
+
template: |
|
|
15
21
|
Please pick the winner of this business idea contest.
|
|
16
22
|
[ideas]
|
|
17
23
|
${0}
|
|
18
24
|
[evalutations]
|
|
19
25
|
${1}
|
|
26
|
+
node5:
|
|
27
|
+
agentId: slashgpt
|
|
28
|
+
inputs: [node4]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "fill_bath",
|
|
4
|
+
"description": "Fill the bath tub",
|
|
5
|
+
"parameters": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"temperature": {
|
|
9
|
+
"type": "number",
|
|
10
|
+
"description": "Water temperature in celsius. If omitted, 41 degree"
|
|
11
|
+
},
|
|
12
|
+
"at": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Time to fill. If omitted immediately."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": []
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "set_temperature",
|
|
22
|
+
"description": "Set the temperature",
|
|
23
|
+
"parameters": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"temperature": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "Room temperature in celsius."
|
|
29
|
+
},
|
|
30
|
+
"location": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": ["kitchen", "living room", "master bedroom", "bedroom 2", "bedroom 3", "all bedrooms"],
|
|
33
|
+
"description": "The room to set the temperature."
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": ["temperature", "location"]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "start_sprinkler",
|
|
41
|
+
"description": "Start the sprinkler system",
|
|
42
|
+
"parameters": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"location": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": ["all", "lawn", "vegetable garden", "front yard"],
|
|
48
|
+
"description": "Specify the location to water."
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": ["location"]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "take_picture",
|
|
56
|
+
"description": "Take a picture and send it",
|
|
57
|
+
"parameters": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"location": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"enum": ["entrance", "backyard", "kitchen", "living room"],
|
|
63
|
+
"description": "Specify the camera location to use."
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"required": ["location"]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "play_music",
|
|
71
|
+
"description": "Play music",
|
|
72
|
+
"parameters": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"location": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": ["living room", "master bedroom"],
|
|
78
|
+
"description": "Location to play a music."
|
|
79
|
+
},
|
|
80
|
+
"music": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Music to play, such as artist, title and playlist."
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"required": ["location"]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "control_light",
|
|
90
|
+
"description": "Turn on/off various lights",
|
|
91
|
+
"parameters": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": {
|
|
94
|
+
"location": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"enum": ["kitchen", "living room", "master bedroom", "bedroom 2", "bedroom 3", "front gate"],
|
|
97
|
+
"description": "Specify the location to control the light."
|
|
98
|
+
},
|
|
99
|
+
"switch": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"enum": ["on", "off"],
|
|
102
|
+
"description": "Specify the light switch."
|
|
103
|
+
},
|
|
104
|
+
"dim": {
|
|
105
|
+
"type": "number",
|
|
106
|
+
"description": "Specify the dim level between 0 and 1.0. The 'switch' parameter must be 'on'."
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"required": ["location", "switch"]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
package/samples/home.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GraphAI, GraphData } from "@/graphai";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import { slashGPTAgent } from "@/experimental_agents";
|
|
5
|
+
|
|
6
|
+
const home_actions = {
|
|
7
|
+
fill_bath: { type: "message_template", message: "Success. I started filling the bath tab." },
|
|
8
|
+
set_temperature: { type: "message_template", message: "Success. I set the temperature to {temperature} for {location}" },
|
|
9
|
+
start_sprinkler: { type: "message_template", message: "Success. I started the sprinkler for {location}" },
|
|
10
|
+
take_picture: { type: "message_template", message: "Success. I took a picture of {location}" },
|
|
11
|
+
play_music: { type: "message_template", message: "Success. I started playing {music} in {location}" },
|
|
12
|
+
control_light: { type: "message_template", message: "Success. The light switch of {location} is now {switch}." },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const fileName = path.resolve(__dirname) + "/home.json";
|
|
16
|
+
const json_file = fs.readFileSync(fileName, "utf8");
|
|
17
|
+
const home_functions = JSON.parse(json_file);
|
|
18
|
+
|
|
19
|
+
const graph_data: GraphData = {
|
|
20
|
+
nodes: {
|
|
21
|
+
node1: {
|
|
22
|
+
source: true,
|
|
23
|
+
result: { content: "Turn on the light in the kitchen" },
|
|
24
|
+
},
|
|
25
|
+
node2: {
|
|
26
|
+
inputs: ["node1"],
|
|
27
|
+
params: {
|
|
28
|
+
manifest: {
|
|
29
|
+
skip_function_result: true,
|
|
30
|
+
actions: home_actions,
|
|
31
|
+
functions: home_functions,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const runAgent = async () => {
|
|
39
|
+
const graph = new GraphAI(graph_data, slashGPTAgent);
|
|
40
|
+
const result = await graph.run();
|
|
41
|
+
const log_path = path.resolve(__dirname) + "/../tests/logs/home.log";
|
|
42
|
+
fs.writeFileSync(log_path, JSON.stringify(graph.transactionLogs(), null, 2));
|
|
43
|
+
console.log(result["node2"]!.content);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const main = async () => {
|
|
47
|
+
await runAgent();
|
|
48
|
+
console.log("COMPLETE 1");
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
main();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { GraphAI, GraphData } from "@/graphai";
|
|
2
|
+
import * as readline from "readline";
|
|
3
|
+
import { testAgent } from "~/agents/agents";
|
|
4
|
+
import { graphDataTestRunner } from "~/utils/runner";
|
|
5
|
+
|
|
6
|
+
const getUserInput = async (question: string): Promise<string> => {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const rl = readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
rl.question(question, (answer) => {
|
|
14
|
+
rl.close();
|
|
15
|
+
resolve(answer);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const graph_data: GraphData = {
|
|
21
|
+
nodes: {
|
|
22
|
+
node1: {
|
|
23
|
+
source: true,
|
|
24
|
+
},
|
|
25
|
+
node2: {
|
|
26
|
+
inputs: ["node1"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const main = async () => {
|
|
32
|
+
const query = await getUserInput("Please enter your question: ");
|
|
33
|
+
console.log("query=", query);
|
|
34
|
+
graph_data.nodes.node1.result = { query };
|
|
35
|
+
|
|
36
|
+
const result = await graphDataTestRunner("interaction.yaml", graph_data, testAgent);
|
|
37
|
+
console.log(result);
|
|
38
|
+
|
|
39
|
+
console.log("COMPLETE 1");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
main();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
import { GraphAI, AgentFunctionDictonary, AgentFunction } from "@/graphai";
|
|
4
|
+
import { readGraphaiData } from "~/utils/file_utils";
|
|
5
|
+
import { graphDataTestRunner } from "~/utils/runner";
|
|
6
|
+
|
|
7
|
+
export const fileTestRunner = async (
|
|
8
|
+
file: string,
|
|
9
|
+
callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>,
|
|
10
|
+
callback: (graph: GraphAI) => void = () => {},
|
|
11
|
+
) => {
|
|
12
|
+
const file_path = path.resolve(__dirname) + file;
|
|
13
|
+
const graph_data = readGraphaiData(file_path);
|
|
14
|
+
return await graphDataTestRunner(file, graph_data, callbackDictonary, callback);
|
|
15
|
+
};
|
package/samples/sample_co2.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { slashGPTFuncitons2TextAgent } from "./agents/slashgpt_agent";
|
|
2
|
+
import { slashGPTAgent } from "@/experimental_agents";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { graphDataTestRunner } from "~/utils/runner";
|
|
5
5
|
|
|
6
6
|
const graph_data = {
|
|
7
7
|
nodes: {
|
|
@@ -9,7 +9,7 @@ const graph_data = {
|
|
|
9
9
|
agentId: "slashGPTAgent",
|
|
10
10
|
params: {
|
|
11
11
|
function_result: true,
|
|
12
|
-
|
|
12
|
+
query: "世界で協力してco2を減らす方法を教えて下さい",
|
|
13
13
|
manifest: {
|
|
14
14
|
prompt: "あなたは世界経済の専門家です。ユーザの問い合わせについて考え、10この結果をfunctionsの結果に返してください。",
|
|
15
15
|
skip_function_result: true,
|
|
@@ -51,6 +51,7 @@ const graph_data = {
|
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
53
|
function2prompt0: {
|
|
54
|
+
fork: 10,
|
|
54
55
|
params: {
|
|
55
56
|
function_data_key: "methods",
|
|
56
57
|
result_key: 0,
|
|
@@ -60,6 +61,7 @@ const graph_data = {
|
|
|
60
61
|
},
|
|
61
62
|
slashGPTAgent0: {
|
|
62
63
|
agentId: "slashGPTAgent",
|
|
64
|
+
fork: 10,
|
|
63
65
|
params: {
|
|
64
66
|
debug: true,
|
|
65
67
|
manifest: {
|
|
@@ -68,36 +70,11 @@ const graph_data = {
|
|
|
68
70
|
},
|
|
69
71
|
inputs: ["function2prompt0"],
|
|
70
72
|
},
|
|
71
|
-
// 1
|
|
72
|
-
function2prompt1: {
|
|
73
|
-
params: {
|
|
74
|
-
debug: true,
|
|
75
|
-
function_data_key: "methods",
|
|
76
|
-
result_key: 1,
|
|
77
|
-
},
|
|
78
|
-
inputs: ["slashGPTAgent"],
|
|
79
|
-
agentId: "slashGPTFuncitons2TextAgent",
|
|
80
|
-
},
|
|
81
|
-
slashGPTAgent1: {
|
|
82
|
-
agentId: "slashGPTAgent",
|
|
83
|
-
params: {
|
|
84
|
-
debug: true,
|
|
85
|
-
manifest: {
|
|
86
|
-
prompt: "ユーザの問い合わせにある文章の専門家です。専門家として、ユーザのアイデアに対して実現可能なシナリオを800文字で書いてください。",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
inputs: ["function2prompt1"],
|
|
90
|
-
},
|
|
91
73
|
},
|
|
92
74
|
};
|
|
93
|
-
const runAgent = async () => {
|
|
94
|
-
const graph = new GraphAI(graph_data, { slashGPTAgent, slashGPTFuncitons2TextAgent });
|
|
95
|
-
const result = await graph.run();
|
|
96
|
-
console.log(result);
|
|
97
|
-
};
|
|
98
75
|
|
|
99
76
|
const main = async () => {
|
|
100
|
-
await
|
|
77
|
+
const result = await graphDataTestRunner("sample_co2.yaml", graph_data, { slashGPTAgent, slashGPTFuncitons2TextAgent });
|
|
101
78
|
console.log("COMPLETE 1");
|
|
102
79
|
};
|
|
103
80
|
main();
|
package/samples/sample_gpt.ts
CHANGED
|
@@ -1,37 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { GraphAI, AgentFunction } from "@/graphai";
|
|
3
|
-
import { ChatSession, ChatConfig, ManifestData } from "slashgpt";
|
|
4
|
-
import { readGraphaiData } from "~/utils/file_utils";
|
|
1
|
+
import { fileTestRunner } from "./runner";
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const slashGPTAgent: AgentFunction<{ manifest: ManifestData; prompt: string }, { answer: string }> = async (context) => {
|
|
9
|
-
console.log("executing", context.nodeId, context.params);
|
|
10
|
-
const session = new ChatSession(config, context.params.manifest ?? {});
|
|
11
|
-
const prompt = context.inputs.reduce((prompt, input, index) => {
|
|
12
|
-
return prompt.replace("${" + index + "}", input["answer"]);
|
|
13
|
-
}, context.params.prompt);
|
|
14
|
-
session.append_user_question(prompt);
|
|
15
|
-
|
|
16
|
-
await session.call_loop(() => {});
|
|
17
|
-
const message = session.history.last_message();
|
|
18
|
-
if (message === undefined) {
|
|
19
|
-
throw new Error("No message in the history");
|
|
20
|
-
}
|
|
21
|
-
const result = { answer: message.content };
|
|
22
|
-
return result;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const runAgent = async (file: string) => {
|
|
26
|
-
const file_path = path.resolve(__dirname) + file;
|
|
27
|
-
const graph_data = readGraphaiData(file_path);
|
|
28
|
-
const graph = new GraphAI(graph_data, slashGPTAgent);
|
|
29
|
-
const result = await graph.run();
|
|
30
|
-
console.log(result);
|
|
31
|
-
};
|
|
3
|
+
import { stringTemplateAgent, slashGPTAgent } from "@/experimental_agents";
|
|
32
4
|
|
|
33
5
|
const main = async () => {
|
|
34
|
-
await
|
|
35
|
-
console.log("COMPLETE 1");
|
|
6
|
+
await fileTestRunner("/graphs/slash_gpt.yml", { slashgpt: slashGPTAgent, stringTemplate: stringTemplateAgent });
|
|
36
7
|
};
|
|
37
8
|
main();
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import search from "arXiv-api-ts";
|
|
1
|
+
import { fileTestRunner } from "./runner";
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import { readGraphaiData } from "~/utils/file_utils";
|
|
6
|
-
|
|
7
|
-
import { slashGPTAgent } from "./agents/slashgpt_agent";
|
|
3
|
+
import { slashGPTAgent } from "@/experimental_agents";
|
|
8
4
|
import { arxivAgent, arxiv2TextAgent } from "./agents/arxiv_agent";
|
|
9
5
|
|
|
10
|
-
const runAgent = async (file: string) => {
|
|
11
|
-
const file_path = path.resolve(__dirname) + file;
|
|
12
|
-
const graph_data = readGraphaiData(file_path);
|
|
13
|
-
const graph = new GraphAI(graph_data, { arxivAgent: arxivAgent, arxiv2TextAgent, slashGPTAgent });
|
|
14
|
-
const result = await graph.run();
|
|
15
|
-
console.log(result);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
6
|
const main = async () => {
|
|
19
|
-
await
|
|
7
|
+
await fileTestRunner("/graphs/arxiv.yml", { arxivAgent: arxivAgent, arxiv2TextAgent, slashGPTAgent });
|
|
20
8
|
console.log("COMPLETE 1");
|
|
21
9
|
};
|
|
22
10
|
main();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { AgentFunction } from "@/graphai";
|
|
3
|
+
import { ChatSession, ChatConfig, ManifestData } from "slashgpt";
|
|
4
|
+
|
|
5
|
+
const config = new ChatConfig(path.resolve(__dirname));
|
|
6
|
+
|
|
7
|
+
export const slashGPTAgent: AgentFunction<{ manifest: ManifestData; query?: string; function_result?: boolean }, { content: string }> = async (context) => {
|
|
8
|
+
console.log("executing", context.nodeId, context.params);
|
|
9
|
+
const session = new ChatSession(config, context.params.manifest ?? {});
|
|
10
|
+
|
|
11
|
+
const query = context.params?.query ? [context.params.query] : [];
|
|
12
|
+
const contents = query.concat(
|
|
13
|
+
context.inputs.map((input) => {
|
|
14
|
+
return input.content;
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
session.append_user_question(contents.join("\n"));
|
|
19
|
+
await session.call_loop(() => {});
|
|
20
|
+
const message = (() => {
|
|
21
|
+
if (context.params?.function_result) {
|
|
22
|
+
return session.history.messages().find((m) => m.role === "function_result");
|
|
23
|
+
}
|
|
24
|
+
return session.history.last_message();
|
|
25
|
+
})();
|
|
26
|
+
if (message === undefined) {
|
|
27
|
+
throw new Error("No message in the history");
|
|
28
|
+
}
|
|
29
|
+
return message;
|
|
30
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AgentFunction } from "@/graphai";
|
|
2
|
+
|
|
3
|
+
export const stringTemplateAgent: AgentFunction<{ template: string; inputKey?: string }, { content: string }> = async (context) => {
|
|
4
|
+
console.log("executing", context.nodeId, context.params);
|
|
5
|
+
const content = context.inputs.reduce((template, input, index) => {
|
|
6
|
+
return template.replace("${" + index + "}", input[context.params.inputKey ?? "content"]);
|
|
7
|
+
}, context.params.template);
|
|
8
|
+
|
|
9
|
+
return { content };
|
|
10
|
+
};
|
package/src/graphai.ts
CHANGED
|
@@ -14,11 +14,13 @@ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType; // Ag
|
|
|
14
14
|
|
|
15
15
|
type NodeData = {
|
|
16
16
|
inputs?: Array<string>;
|
|
17
|
-
params
|
|
17
|
+
params?: NodeDataParams;
|
|
18
18
|
retry?: number;
|
|
19
19
|
timeout?: number; // msec
|
|
20
20
|
agentId?: string;
|
|
21
|
+
fork?: number;
|
|
21
22
|
source?: boolean;
|
|
23
|
+
result?: ResultData; // preset result for source node.
|
|
22
24
|
outputs?: Record<string, string>; // mapping from routeId to nodeId
|
|
23
25
|
};
|
|
24
26
|
|
|
@@ -42,6 +44,7 @@ export type TransactionLog = {
|
|
|
42
44
|
|
|
43
45
|
export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
|
|
44
46
|
nodeId: string;
|
|
47
|
+
forkIndex?: number;
|
|
45
48
|
retry: number;
|
|
46
49
|
params: NodeDataParams<ParamsType>;
|
|
47
50
|
inputs: Array<PreviousResultType>;
|
|
@@ -61,6 +64,8 @@ class Node {
|
|
|
61
64
|
public waitlist = new Set<string>(); // List of nodes which need data from this node.
|
|
62
65
|
public state = NodeState.Waiting;
|
|
63
66
|
public agentId?: string;
|
|
67
|
+
public fork?: number;
|
|
68
|
+
public forkIndex?: number;
|
|
64
69
|
public result: ResultData = undefined;
|
|
65
70
|
public retryLimit: number;
|
|
66
71
|
public retryCount: number = 0;
|
|
@@ -72,12 +77,14 @@ class Node {
|
|
|
72
77
|
|
|
73
78
|
private graph: GraphAI;
|
|
74
79
|
|
|
75
|
-
constructor(nodeId: string, data: NodeData, graph: GraphAI) {
|
|
80
|
+
constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI) {
|
|
76
81
|
this.nodeId = nodeId;
|
|
82
|
+
this.forkIndex = forkIndex;
|
|
77
83
|
this.inputs = data.inputs ?? [];
|
|
78
84
|
this.pendings = new Set(this.inputs);
|
|
79
|
-
this.params = data.params;
|
|
85
|
+
this.params = data.params ?? {};
|
|
80
86
|
this.agentId = data.agentId;
|
|
87
|
+
this.fork = data.fork;
|
|
81
88
|
this.retryLimit = data.retry ?? 0;
|
|
82
89
|
this.timeout = data.timeout;
|
|
83
90
|
this.source = data.source === true;
|
|
@@ -122,9 +129,9 @@ class Node {
|
|
|
122
129
|
retryCount: this.retryCount,
|
|
123
130
|
state: NodeState.Injected,
|
|
124
131
|
startTime: Date.now(),
|
|
132
|
+
endTime: Date.now(),
|
|
125
133
|
result,
|
|
126
134
|
};
|
|
127
|
-
log.endTime = log.startTime;
|
|
128
135
|
this.graph.appendLog(log);
|
|
129
136
|
this.setResult(result, NodeState.Injected);
|
|
130
137
|
} else {
|
|
@@ -143,21 +150,19 @@ class Node {
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
public async execute() {
|
|
153
|
+
const results = this.graph.resultsOf(this.inputs);
|
|
154
|
+
const transactionId = Date.now();
|
|
146
155
|
const log: TransactionLog = {
|
|
147
156
|
nodeId: this.nodeId,
|
|
148
157
|
retryCount: this.retryCount,
|
|
149
158
|
state: NodeState.Executing,
|
|
150
|
-
startTime:
|
|
159
|
+
startTime: transactionId,
|
|
151
160
|
agentId: this.agentId,
|
|
152
161
|
params: this.params,
|
|
162
|
+
inputs: results,
|
|
153
163
|
};
|
|
154
|
-
const results = this.graph.resultsOf(this.inputs);
|
|
155
|
-
if (results.length > 0) {
|
|
156
|
-
log.inputs = results;
|
|
157
|
-
}
|
|
158
164
|
this.graph.appendLog(log);
|
|
159
165
|
this.state = NodeState.Executing;
|
|
160
|
-
const transactionId = log.startTime;
|
|
161
166
|
this.transactionId = transactionId;
|
|
162
167
|
|
|
163
168
|
if (this.timeout && this.timeout > 0) {
|
|
@@ -179,6 +184,7 @@ class Node {
|
|
|
179
184
|
retry: this.retryCount,
|
|
180
185
|
params: this.params,
|
|
181
186
|
inputs: results,
|
|
187
|
+
forkIndex: this.forkIndex,
|
|
182
188
|
});
|
|
183
189
|
if (this.transactionId !== transactionId) {
|
|
184
190
|
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
@@ -241,24 +247,66 @@ export class GraphAI {
|
|
|
241
247
|
this.onComplete = () => {
|
|
242
248
|
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
243
249
|
};
|
|
250
|
+
const nodeId2forkedNodeIds: Record<string, string[]> = {};
|
|
251
|
+
const forkedNodeId2Index: Record<string, number> = {};
|
|
252
|
+
|
|
253
|
+
// Create node instances from data.nodes
|
|
244
254
|
this.nodes = Object.keys(data.nodes).reduce((nodes: GraphNodes, nodeId: string) => {
|
|
245
|
-
|
|
255
|
+
const fork = data.nodes[nodeId].fork;
|
|
256
|
+
if (fork) {
|
|
257
|
+
// For fork, change the nodeId and increase the node
|
|
258
|
+
nodeId2forkedNodeIds[nodeId] = new Array(fork).fill(undefined).map((_, i) => {
|
|
259
|
+
const forkedNodeId = `${nodeId}_${i}`;
|
|
260
|
+
nodes[forkedNodeId] = new Node(forkedNodeId, i, data.nodes[nodeId], this);
|
|
261
|
+
// Data for pending and waiting
|
|
262
|
+
forkedNodeId2Index[forkedNodeId] = i;
|
|
263
|
+
return forkedNodeId;
|
|
264
|
+
});
|
|
265
|
+
} else {
|
|
266
|
+
nodes[nodeId] = new Node(nodeId, undefined, data.nodes[nodeId], this);
|
|
267
|
+
}
|
|
246
268
|
return nodes;
|
|
247
269
|
}, {});
|
|
248
270
|
|
|
249
|
-
// Generate the waitlist for each node
|
|
271
|
+
// Generate the waitlist for each node, and update the pendings in case of forked node.
|
|
250
272
|
Object.keys(this.nodes).forEach((nodeId) => {
|
|
251
273
|
const node = this.nodes[nodeId];
|
|
252
274
|
node.pendings.forEach((pending) => {
|
|
253
|
-
|
|
254
|
-
|
|
275
|
+
// If the pending(previous) node is forking
|
|
276
|
+
if (nodeId2forkedNodeIds[pending]) {
|
|
277
|
+
// update node.pending and pending(previous) node.wailtlist
|
|
278
|
+
if (node.fork) {
|
|
279
|
+
// 1:1 if current nodes are also forking.
|
|
280
|
+
const newPendingId = nodeId2forkedNodeIds[pending][forkedNodeId2Index[nodeId]];
|
|
281
|
+
this.nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
282
|
+
node.pendings.add(newPendingId);
|
|
283
|
+
} else {
|
|
284
|
+
// 1:n if current node is not forking.
|
|
285
|
+
nodeId2forkedNodeIds[pending].forEach((newPendingId) => {
|
|
286
|
+
this.nodes[newPendingId].waitlist.add(nodeId); // previousNode
|
|
287
|
+
node.pendings.add(newPendingId);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
node.pendings.delete(pending);
|
|
291
|
+
} else {
|
|
292
|
+
this.nodes[pending].waitlist.add(nodeId); // previousNode
|
|
293
|
+
}
|
|
255
294
|
});
|
|
295
|
+
node.inputs = Array.from(node.pendings); // for fork.
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// If the result property is specified, inject it.
|
|
299
|
+
// NOTE: This must be done at the end of this constructor
|
|
300
|
+
Object.keys(data.nodes).forEach((nodeId) => {
|
|
301
|
+
const result = data.nodes[nodeId].result;
|
|
302
|
+
if (result) {
|
|
303
|
+
this.injectResult(nodeId, result);
|
|
304
|
+
}
|
|
256
305
|
});
|
|
257
306
|
}
|
|
258
307
|
|
|
259
308
|
public getCallback(_agentId?: string) {
|
|
260
309
|
const agentId = _agentId ?? "_default";
|
|
261
|
-
console.log(agentId);
|
|
262
310
|
if (this.callbackDictonary[agentId]) {
|
|
263
311
|
return this.callbackDictonary[agentId];
|
|
264
312
|
}
|
|
@@ -293,7 +341,7 @@ export class GraphAI {
|
|
|
293
341
|
}, {});
|
|
294
342
|
}
|
|
295
343
|
|
|
296
|
-
public async run() {
|
|
344
|
+
public async run(): Promise<ResultDataDictonary> {
|
|
297
345
|
if (this.isRunning) {
|
|
298
346
|
console.error("-- Already Running");
|
|
299
347
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { GraphAI, AgentFunction } from "./graphai";
|
|
4
|
+
import { slashGPTAgent, stringTemplateAgent } from "./experimental_agents";
|
|
5
|
+
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
|
|
10
|
+
const testAgent: AgentFunction<{ delay: number; fail: boolean }> = async (context) => {
|
|
11
|
+
return {};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const main = async () => {
|
|
15
|
+
const file = process.argv[2];
|
|
16
|
+
if (file === undefined) {
|
|
17
|
+
console.log("no file");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const file_path = path.resolve(process.cwd() + "/" + file);
|
|
21
|
+
if (!fs.existsSync(file_path)) {
|
|
22
|
+
console.log("no file");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const graph_data_file = fs.readFileSync(file_path, "utf8");
|
|
27
|
+
const graph_data = YAML.parse(graph_data_file);
|
|
28
|
+
|
|
29
|
+
const graph = new GraphAI(graph_data, { testAgent, slashGPTAgent, stringTemplateAgent });
|
|
30
|
+
const results = await graph.run();
|
|
31
|
+
console.log(results);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.log("error", e);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
main();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { stringTemplateAgent } from "@/experimental_agents";
|
|
2
|
+
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
|
|
6
|
+
test("test stringTemplateAgent simple", async () => {
|
|
7
|
+
const result = await stringTemplateAgent({
|
|
8
|
+
nodeId: "test",
|
|
9
|
+
retry: 0,
|
|
10
|
+
params: { template: "${0}: ${1}" },
|
|
11
|
+
inputs: [{ content: "hello" }, { content: "test" }] as any,
|
|
12
|
+
});
|
|
13
|
+
assert.deepStrictEqual(result, {
|
|
14
|
+
content: "hello: test",
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("test stringTemplateAgent simple", async () => {
|
|
19
|
+
const result = await stringTemplateAgent({
|
|
20
|
+
nodeId: "test",
|
|
21
|
+
retry: 0,
|
|
22
|
+
params: { template: "${0}: ${1}", inputKey: "key" },
|
|
23
|
+
inputs: [{ key: "hello" }, { key: "test" }],
|
|
24
|
+
});
|
|
25
|
+
assert.deepStrictEqual(result, {
|
|
26
|
+
content: "hello: test",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { GraphAI, AgentFunction } from "@/graphai";
|
|
2
|
+
import { testAgent } from "~/agents/agents";
|
|
3
|
+
import { graphDataTestRunner } from "~/utils/runner";
|
|
4
|
+
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert";
|
|
7
|
+
|
|
8
|
+
const testAgent1: AgentFunction = async (context) => {
|
|
9
|
+
const { nodeId, retry, params, inputs } = context;
|
|
10
|
+
console.log("executing", nodeId, params, inputs);
|
|
11
|
+
|
|
12
|
+
const result = {
|
|
13
|
+
[nodeId]: [nodeId, inputs.map((a) => Object.values(a).flat())]
|
|
14
|
+
.flat()
|
|
15
|
+
.filter((a) => !!a)
|
|
16
|
+
.join(":"),
|
|
17
|
+
};
|
|
18
|
+
console.log("completing", nodeId, result);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
test("test base", async () => {
|
|
23
|
+
const forkGraph = {
|
|
24
|
+
nodes: {
|
|
25
|
+
node1: {
|
|
26
|
+
params: {},
|
|
27
|
+
},
|
|
28
|
+
node2: {
|
|
29
|
+
params: {},
|
|
30
|
+
fork: 10,
|
|
31
|
+
inputs: ["node1"],
|
|
32
|
+
},
|
|
33
|
+
node3: {
|
|
34
|
+
params: {},
|
|
35
|
+
// fork: 10,
|
|
36
|
+
inputs: ["node2"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const result = await graphDataTestRunner("fork.yml", forkGraph, testAgent1);
|
|
42
|
+
// console.log(result);
|
|
43
|
+
assert.deepStrictEqual(result, {
|
|
44
|
+
node1: { node1: "node1" },
|
|
45
|
+
node2_0: { node2_0: "node2_0:node1" },
|
|
46
|
+
node2_1: { node2_1: "node2_1:node1" },
|
|
47
|
+
node2_2: { node2_2: "node2_2:node1" },
|
|
48
|
+
node2_3: { node2_3: "node2_3:node1" },
|
|
49
|
+
node2_4: { node2_4: "node2_4:node1" },
|
|
50
|
+
node2_5: { node2_5: "node2_5:node1" },
|
|
51
|
+
node2_6: { node2_6: "node2_6:node1" },
|
|
52
|
+
node2_7: { node2_7: "node2_7:node1" },
|
|
53
|
+
node2_8: { node2_8: "node2_8:node1" },
|
|
54
|
+
node2_9: { node2_9: "node2_9:node1" },
|
|
55
|
+
node3: {
|
|
56
|
+
node3:
|
|
57
|
+
"node3:node2_0:node1:node2_1:node1:node2_2:node1:node2_3:node1:node2_4:node1:node2_5:node1:node2_6:node1:node2_7:node1:node2_8:node1:node2_9:node1",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("test base", async () => {
|
|
63
|
+
const forkGraph = {
|
|
64
|
+
nodes: {
|
|
65
|
+
node1: {
|
|
66
|
+
params: {},
|
|
67
|
+
},
|
|
68
|
+
node2: {
|
|
69
|
+
params: {},
|
|
70
|
+
fork: 10,
|
|
71
|
+
inputs: ["node1"],
|
|
72
|
+
},
|
|
73
|
+
node3: {
|
|
74
|
+
params: {},
|
|
75
|
+
fork: 10,
|
|
76
|
+
inputs: ["node2"],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = await graphDataTestRunner("fork.yml", forkGraph, testAgent1);
|
|
82
|
+
// console.log(result);
|
|
83
|
+
assert.deepStrictEqual(result, {
|
|
84
|
+
node1: { node1: "node1" },
|
|
85
|
+
node2_0: { node2_0: "node2_0:node1" },
|
|
86
|
+
node2_1: { node2_1: "node2_1:node1" },
|
|
87
|
+
node2_2: { node2_2: "node2_2:node1" },
|
|
88
|
+
node2_3: { node2_3: "node2_3:node1" },
|
|
89
|
+
node2_4: { node2_4: "node2_4:node1" },
|
|
90
|
+
node2_5: { node2_5: "node2_5:node1" },
|
|
91
|
+
node2_6: { node2_6: "node2_6:node1" },
|
|
92
|
+
node2_7: { node2_7: "node2_7:node1" },
|
|
93
|
+
node2_8: { node2_8: "node2_8:node1" },
|
|
94
|
+
node2_9: { node2_9: "node2_9:node1" },
|
|
95
|
+
node3_0: { node3_0: "node3_0:node2_0:node1" },
|
|
96
|
+
node3_1: { node3_1: "node3_1:node2_1:node1" },
|
|
97
|
+
node3_2: { node3_2: "node3_2:node2_2:node1" },
|
|
98
|
+
node3_3: { node3_3: "node3_3:node2_3:node1" },
|
|
99
|
+
node3_4: { node3_4: "node3_4:node2_4:node1" },
|
|
100
|
+
node3_5: { node3_5: "node3_5:node2_5:node1" },
|
|
101
|
+
node3_6: { node3_6: "node3_6:node2_6:node1" },
|
|
102
|
+
node3_7: { node3_7: "node3_7:node2_7:node1" },
|
|
103
|
+
node3_8: { node3_8: "node3_8:node2_8:node1" },
|
|
104
|
+
node3_9: { node3_9: "node3_9:node2_9:node1" },
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -60,13 +60,12 @@ test("test source", async () => {
|
|
|
60
60
|
test("test source2", async () => {
|
|
61
61
|
const result = await fileTestRunner("/graphs/test_source2.yml", testAgent, (graph: GraphAI) => {
|
|
62
62
|
graph.injectResult("node1", { node1: "injected" });
|
|
63
|
-
graph.injectResult("node2", { node2: "injected" });
|
|
64
63
|
});
|
|
65
64
|
assert.deepStrictEqual(result, {
|
|
66
65
|
node1: { node1: "injected" },
|
|
67
|
-
node2: { node2: "
|
|
68
|
-
node3: { node3: "output", node1: "injected", node2: "
|
|
69
|
-
node4: { node4: "output", node3: "output", node1: "injected", node2: "
|
|
70
|
-
node5: { node5: "output", node4: "output", node3: "output", node1: "injected", node2: "
|
|
66
|
+
node2: { node2: "preset" },
|
|
67
|
+
node3: { node3: "output", node1: "injected", node2: "preset" },
|
|
68
|
+
node4: { node4: "output", node3: "output", node1: "injected", node2: "preset" },
|
|
69
|
+
node5: { node5: "output", node4: "output", node3: "output", node1: "injected", node2: "preset" },
|
|
71
70
|
});
|
|
72
71
|
});
|