graphai 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +27 -46
- package/.github/workflows/node.js.yml +1 -0
- package/README.md +76 -9
- package/lib/graphai.d.ts +43 -28
- package/lib/graphai.js +98 -36
- package/package.json +10 -5
- package/samples/agents/arxiv_agent.ts +46 -0
- package/samples/agents/slashgpt_agent.ts +21 -0
- package/samples/express.ts +47 -0
- package/samples/graphs/arxiv.yml +29 -0
- package/samples/sample_gpt.ts +10 -10
- package/samples/sample_paper_ai.ts +26 -0
- package/src/graphai.ts +136 -72
- package/tests/agents/agents.ts +24 -0
- package/tests/graphai/test_dispatch.ts +42 -0
- package/tests/graphai/test_http_client.ts +40 -0
- package/tests/{test_multiple_functions.ts → graphai/test_multiple_functions.ts} +7 -18
- package/tests/graphai/test_sample_flow.ts +72 -0
- package/tests/graphs/test_dispatch.yml +24 -0
- package/tests/graphs/test_multiple_functions_1.yml +3 -3
- package/tests/graphs/test_source.yml +18 -0
- package/tests/graphs/test_source2.yml +17 -0
- package/tests/http-server/README.md +10 -0
- package/tests/http-server/docs/llm.json +4 -0
- package/tests/http-server/docs/llm2.json +4 -0
- package/tests/{file_utils.ts → utils/file_utils.ts} +10 -1
- package/tests/utils/runner.ts +40 -0
- package/tsconfig.json +5 -2
- package/tests/test_sample_flow.ts +0 -87
- /package/tests/{utils.ts → utils/utils.ts} +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
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; prompt: string }, { answer: string }> = async (context) => {
|
|
8
|
+
console.log("executing", context.nodeId, context);
|
|
9
|
+
const session = new ChatSession(config, context.params?.manifest ?? {});
|
|
10
|
+
|
|
11
|
+
const prompt = [context.params?.prompt, context.payload.inputData].join("\n\n");
|
|
12
|
+
session.append_user_question(prompt);
|
|
13
|
+
|
|
14
|
+
await session.call_loop(() => {});
|
|
15
|
+
const message = session.history.last_message();
|
|
16
|
+
if (message === undefined) {
|
|
17
|
+
throw new Error("No message in the history");
|
|
18
|
+
}
|
|
19
|
+
const result = { answer: message.content };
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// npx ts-node samples/express.ts
|
|
2
|
+
import { GraphAI, AgentFunction } from "@/graphai";
|
|
3
|
+
|
|
4
|
+
import express from "express";
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
|
|
8
|
+
const graphAISample = async (req: express.Request, res: express.Response) => {
|
|
9
|
+
const graph_data = {
|
|
10
|
+
nodes: {
|
|
11
|
+
node1: {
|
|
12
|
+
params: {},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
concurrency: 8,
|
|
16
|
+
};
|
|
17
|
+
const testFunction: AgentFunction<Record<string, string>> = async (context) => {
|
|
18
|
+
console.log("hello");
|
|
19
|
+
return {};
|
|
20
|
+
};
|
|
21
|
+
const graph = new GraphAI(graph_data, testFunction);
|
|
22
|
+
const response = await graph.run();
|
|
23
|
+
res.json({ result: response });
|
|
24
|
+
res.end();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const hello = async (req: express.Request, res: express.Response) => {
|
|
28
|
+
const { params, query } = req;
|
|
29
|
+
res.json({
|
|
30
|
+
result: [
|
|
31
|
+
{
|
|
32
|
+
message: "hello",
|
|
33
|
+
params,
|
|
34
|
+
query,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
res.end();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
app.use(express.json());
|
|
42
|
+
app.get("/", hello);
|
|
43
|
+
app.get("/mock", graphAISample);
|
|
44
|
+
|
|
45
|
+
const server = app.listen(8080, () => {
|
|
46
|
+
console.log("Running Server");
|
|
47
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
nodes:
|
|
2
|
+
searchArxiv:
|
|
3
|
+
params:
|
|
4
|
+
keywords:
|
|
5
|
+
- llm
|
|
6
|
+
- gpt
|
|
7
|
+
limit: 10
|
|
8
|
+
functionName: arxivAgent
|
|
9
|
+
arxiv2TextAgent:
|
|
10
|
+
inputs: [searchArxiv]
|
|
11
|
+
functionName: arxiv2TextAgent
|
|
12
|
+
payloadMapping:
|
|
13
|
+
searchArxiv: inputData
|
|
14
|
+
slashGPTAgent:
|
|
15
|
+
inputs: [arxiv2TextAgent]
|
|
16
|
+
payloadMapping:
|
|
17
|
+
arxiv2TextAgent: inputData
|
|
18
|
+
functionName: slashGPTAgent
|
|
19
|
+
params:
|
|
20
|
+
prompt: |
|
|
21
|
+
与えられたそれぞれの論文の要点をまとめ、以下の項目で日本語で出力せよ。それぞれの項目は最大でも180文字以内に要約せよ。
|
|
22
|
+
```
|
|
23
|
+
論文名:タイトルの日本語訳
|
|
24
|
+
キーワード:この論文のキーワード
|
|
25
|
+
課題:この論文が解決する課題
|
|
26
|
+
手法:この論文が提案する手法,
|
|
27
|
+
結果:提案手法によって得られた結果
|
|
28
|
+
```
|
|
29
|
+
|
package/samples/sample_gpt.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import { GraphAI,
|
|
3
|
-
import { ChatSession, ChatConfig } from "slashgpt";
|
|
4
|
-
import {
|
|
2
|
+
import { GraphAI, AgentFunction } from "@/graphai";
|
|
3
|
+
import { ChatSession, ChatConfig, ManifestData } from "slashgpt";
|
|
4
|
+
import { readGraphaiData } from "~/utils/file_utils";
|
|
5
5
|
|
|
6
6
|
const config = new ChatConfig(path.resolve(__dirname));
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const slashGPTAgent: AgentFunction<{ manifest: ManifestData; prompt: string }, { answer: string }> = async (context) => {
|
|
9
9
|
console.log("executing", context.nodeId, context.params);
|
|
10
10
|
const session = new ChatSession(config, context.params.manifest ?? {});
|
|
11
11
|
const prompt = Object.keys(context.payload).reduce((prompt, key) => {
|
|
@@ -19,19 +19,19 @@ const testFunction: NodeExecute<Record<string, string>> = async (context) => {
|
|
|
19
19
|
throw new Error("No message in the history");
|
|
20
20
|
}
|
|
21
21
|
const result = { answer: message.content };
|
|
22
|
-
console.log(result);
|
|
23
22
|
return result;
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
const
|
|
25
|
+
const runAgent = async (file: string) => {
|
|
27
26
|
const file_path = path.resolve(__dirname) + file;
|
|
28
|
-
const graph_data =
|
|
29
|
-
const graph = new GraphAI(graph_data,
|
|
30
|
-
await graph.run();
|
|
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
31
|
};
|
|
32
32
|
|
|
33
33
|
const main = async () => {
|
|
34
|
-
await
|
|
34
|
+
await runAgent("/graphs/slash_gpt.yml");
|
|
35
35
|
console.log("COMPLETE 1");
|
|
36
36
|
};
|
|
37
37
|
main();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import search from "arXiv-api-ts";
|
|
3
|
+
|
|
4
|
+
import { GraphAI, AgentFunction } from "@/graphai";
|
|
5
|
+
import { readGraphaiData } from "~/utils/file_utils";
|
|
6
|
+
|
|
7
|
+
import { slashGPTAgent } from "./agents/slashgpt_agent";
|
|
8
|
+
import { arxivAgent, arxiv2TextAgent } from "./agents/arxiv_agent";
|
|
9
|
+
|
|
10
|
+
export const parrotingAgent: AgentFunction = async (context) => {
|
|
11
|
+
return {};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const runAgent = async (file: string) => {
|
|
15
|
+
const file_path = path.resolve(__dirname) + file;
|
|
16
|
+
const graph_data = readGraphaiData(file_path);
|
|
17
|
+
const graph = new GraphAI(graph_data, { default: parrotingAgent, arxivAgent: arxivAgent, arxiv2TextAgent, slashGPTAgent });
|
|
18
|
+
const result = await graph.run();
|
|
19
|
+
console.log(result);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const main = async () => {
|
|
23
|
+
await runAgent("/graphs/arxiv.yml");
|
|
24
|
+
console.log("COMPLETE 1");
|
|
25
|
+
};
|
|
26
|
+
main();
|
package/src/graphai.ts
CHANGED
|
@@ -1,81 +1,90 @@
|
|
|
1
|
-
import { AssertionError } from "assert";
|
|
2
|
-
|
|
3
1
|
export enum NodeState {
|
|
4
|
-
Waiting,
|
|
5
|
-
Executing,
|
|
6
|
-
Failed,
|
|
7
|
-
TimedOut,
|
|
8
|
-
Completed,
|
|
2
|
+
Waiting = "waiting",
|
|
3
|
+
Executing = "executing",
|
|
4
|
+
Failed = "failed",
|
|
5
|
+
TimedOut = "timed-out",
|
|
6
|
+
Completed = "completed",
|
|
7
|
+
Injected = "injected",
|
|
8
|
+
Dispatched = "dispatched",
|
|
9
9
|
}
|
|
10
10
|
type ResultData<ResultType = Record<string, any>> = ResultType | undefined;
|
|
11
11
|
type ResultDataDictonary<ResultType = Record<string, any>> = Record<string, ResultData<ResultType>>;
|
|
12
12
|
|
|
13
|
-
export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType; //
|
|
13
|
+
export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType; // Agent-specific parameters
|
|
14
14
|
|
|
15
15
|
type NodeData = {
|
|
16
|
-
inputs
|
|
16
|
+
inputs?: Array<string>;
|
|
17
17
|
params: NodeDataParams;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
payloadMapping?: Record<string, string>;
|
|
19
|
+
retry?: number;
|
|
20
|
+
timeout?: number; // msec
|
|
21
|
+
agentId?: string;
|
|
22
|
+
source?: boolean;
|
|
23
|
+
dispatch?: Record<string, string>; // route to node
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
type GraphData = {
|
|
26
|
+
export type GraphData = {
|
|
24
27
|
nodes: Record<string, NodeData>;
|
|
25
|
-
concurrency
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type NodeExecuteContext<ResultType, ParamsType> = {
|
|
29
|
-
nodeId: string;
|
|
30
|
-
retry: number;
|
|
31
|
-
params: NodeDataParams<ParamsType>;
|
|
32
|
-
payload: ResultDataDictonary<ResultType>;
|
|
28
|
+
concurrency?: number;
|
|
33
29
|
};
|
|
34
30
|
|
|
35
31
|
export type TransactionLog = {
|
|
36
32
|
nodeId: string;
|
|
37
33
|
state: NodeState;
|
|
38
|
-
startTime:
|
|
39
|
-
endTime
|
|
34
|
+
startTime: number;
|
|
35
|
+
endTime?: number;
|
|
40
36
|
retryCount: number;
|
|
41
|
-
|
|
37
|
+
agentId?: string;
|
|
38
|
+
params?: NodeDataParams;
|
|
39
|
+
payload?: ResultDataDictonary<ResultData>;
|
|
40
|
+
errorMessage?: string;
|
|
42
41
|
result?: ResultData;
|
|
43
42
|
};
|
|
44
43
|
|
|
45
|
-
export type
|
|
46
|
-
|
|
44
|
+
export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
|
|
45
|
+
nodeId: string;
|
|
46
|
+
retry: number;
|
|
47
|
+
params: NodeDataParams<ParamsType>;
|
|
48
|
+
payload: ResultDataDictonary<PreviousResultType>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (
|
|
52
|
+
context: AgentFunctionContext<ParamsType, ResultType, PreviousResultType>,
|
|
47
53
|
) => Promise<ResultData<ResultType>>;
|
|
48
54
|
|
|
55
|
+
export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
|
|
56
|
+
|
|
49
57
|
class Node {
|
|
50
58
|
public nodeId: string;
|
|
51
|
-
public params: NodeDataParams; //
|
|
59
|
+
public params: NodeDataParams; // Agent-specific parameters
|
|
52
60
|
public inputs: Array<string>; // List of nodes this node needs data from.
|
|
61
|
+
public payloadMapping: Record<string, string>;
|
|
53
62
|
public pendings: Set<string>; // List of nodes this node is waiting data from.
|
|
54
|
-
public waitlist
|
|
55
|
-
public state
|
|
56
|
-
public
|
|
57
|
-
public result: ResultData;
|
|
63
|
+
public waitlist = new Set<string>(); // List of nodes which need data from this node.
|
|
64
|
+
public state = NodeState.Waiting;
|
|
65
|
+
public agentId: string;
|
|
66
|
+
public result: ResultData = undefined;
|
|
58
67
|
public retryLimit: number;
|
|
59
|
-
public retryCount: number;
|
|
68
|
+
public retryCount: number = 0;
|
|
60
69
|
public transactionId: undefined | number; // To reject callbacks from timed-out transactions
|
|
61
|
-
public timeout
|
|
62
|
-
public error
|
|
70
|
+
public timeout?: number; // msec
|
|
71
|
+
public error?: Error;
|
|
72
|
+
public source: boolean;
|
|
73
|
+
public dispatch?: Record<string, string>; // outputId to nodeId mapping
|
|
63
74
|
|
|
64
75
|
private graph: GraphAI;
|
|
65
76
|
|
|
66
77
|
constructor(nodeId: string, data: NodeData, graph: GraphAI) {
|
|
67
78
|
this.nodeId = nodeId;
|
|
68
79
|
this.inputs = data.inputs ?? [];
|
|
80
|
+
this.payloadMapping = data.payloadMapping ?? {};
|
|
69
81
|
this.pendings = new Set(this.inputs);
|
|
70
82
|
this.params = data.params;
|
|
71
|
-
this.
|
|
72
|
-
this.state = NodeState.Waiting;
|
|
73
|
-
this.functionName = data.functionName ?? "default";
|
|
74
|
-
this.result = undefined;
|
|
83
|
+
this.agentId = data.agentId ?? "default";
|
|
75
84
|
this.retryLimit = data.retry ?? 0;
|
|
76
|
-
this.
|
|
77
|
-
this.
|
|
78
|
-
|
|
85
|
+
this.timeout = data.timeout;
|
|
86
|
+
this.source = data.source === true;
|
|
87
|
+
this.dispatch = data.dispatch;
|
|
79
88
|
this.graph = graph;
|
|
80
89
|
}
|
|
81
90
|
|
|
@@ -91,48 +100,82 @@ class Node {
|
|
|
91
100
|
this.state = state;
|
|
92
101
|
this.result = undefined;
|
|
93
102
|
this.error = error;
|
|
94
|
-
this.transactionId =
|
|
103
|
+
this.transactionId = undefined; // This is necessary for timeout case
|
|
95
104
|
this.graph.removeRunning(this);
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
public removePending(nodeId: string) {
|
|
100
109
|
this.pendings.delete(nodeId);
|
|
101
|
-
this.
|
|
110
|
+
if (this.graph.isRunning) {
|
|
111
|
+
this.pushQueueIfReady();
|
|
112
|
+
}
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
public payload() {
|
|
105
116
|
return this.inputs.reduce((results: ResultDataDictonary, nodeId) => {
|
|
106
|
-
|
|
117
|
+
if (this.payloadMapping && this.payloadMapping[nodeId]) {
|
|
118
|
+
results[this.payloadMapping[nodeId]] = this.graph.nodes[nodeId].result;
|
|
119
|
+
} else {
|
|
120
|
+
results[nodeId] = this.graph.nodes[nodeId].result;
|
|
121
|
+
}
|
|
107
122
|
return results;
|
|
108
123
|
}, {});
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
public pushQueueIfReady() {
|
|
112
|
-
if (this.pendings.size === 0) {
|
|
127
|
+
if (this.pendings.size === 0 && !this.source) {
|
|
113
128
|
this.graph.pushQueue(this);
|
|
114
129
|
}
|
|
115
130
|
}
|
|
116
131
|
|
|
132
|
+
public injectResult(result: ResultData) {
|
|
133
|
+
if (this.source) {
|
|
134
|
+
const log: TransactionLog = {
|
|
135
|
+
nodeId: this.nodeId,
|
|
136
|
+
retryCount: this.retryCount,
|
|
137
|
+
state: NodeState.Injected,
|
|
138
|
+
startTime: Date.now(),
|
|
139
|
+
};
|
|
140
|
+
log.endTime = log.startTime;
|
|
141
|
+
this.graph.appendLog(log);
|
|
142
|
+
this.setResult(result, NodeState.Injected);
|
|
143
|
+
} else {
|
|
144
|
+
console.error("- injectResult called on non-source node.", this.nodeId);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private setResult(result: ResultData, state: NodeState) {
|
|
149
|
+
this.state = state;
|
|
150
|
+
this.result = result;
|
|
151
|
+
this.waitlist.forEach((nodeId) => {
|
|
152
|
+
const node = this.graph.nodes[nodeId];
|
|
153
|
+
// Todo: Avoid running before Run()
|
|
154
|
+
node.removePending(this.nodeId);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
117
158
|
public async execute() {
|
|
159
|
+
const payload = this.payload();
|
|
118
160
|
const log: TransactionLog = {
|
|
119
161
|
nodeId: this.nodeId,
|
|
120
162
|
retryCount: this.retryCount,
|
|
121
163
|
state: NodeState.Executing,
|
|
122
164
|
startTime: Date.now(),
|
|
123
|
-
|
|
124
|
-
|
|
165
|
+
agentId: this.agentId,
|
|
166
|
+
params: this.params,
|
|
167
|
+
payload,
|
|
125
168
|
};
|
|
126
169
|
this.graph.appendLog(log);
|
|
127
170
|
this.state = NodeState.Executing;
|
|
128
171
|
const transactionId = log.startTime;
|
|
129
172
|
this.transactionId = transactionId;
|
|
130
173
|
|
|
131
|
-
if (this.timeout > 0) {
|
|
174
|
+
if (this.timeout && this.timeout > 0) {
|
|
132
175
|
setTimeout(() => {
|
|
133
176
|
if (this.state === NodeState.Executing && this.transactionId === transactionId) {
|
|
134
177
|
console.log(`-- ${this.nodeId}: timeout ${this.timeout}`);
|
|
135
|
-
log.
|
|
178
|
+
log.errorMessage = "Timeout";
|
|
136
179
|
log.state = NodeState.TimedOut;
|
|
137
180
|
log.endTime = Date.now();
|
|
138
181
|
this.retry(NodeState.TimedOut, Error("Timeout"));
|
|
@@ -141,26 +184,34 @@ class Node {
|
|
|
141
184
|
}
|
|
142
185
|
|
|
143
186
|
try {
|
|
144
|
-
const callback = this.graph.getCallback(this.
|
|
187
|
+
const callback = this.graph.getCallback(this.agentId);
|
|
145
188
|
const result = await callback({
|
|
146
189
|
nodeId: this.nodeId,
|
|
147
190
|
retry: this.retryCount,
|
|
148
191
|
params: this.params,
|
|
149
|
-
payload
|
|
192
|
+
payload,
|
|
150
193
|
});
|
|
151
194
|
if (this.transactionId !== transactionId) {
|
|
152
195
|
console.log(`-- ${this.nodeId}: transactionId mismatch`);
|
|
153
196
|
return;
|
|
154
197
|
}
|
|
155
|
-
|
|
198
|
+
|
|
156
199
|
log.endTime = Date.now();
|
|
157
200
|
log.result = result;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
201
|
+
|
|
202
|
+
const dispatch = this.dispatch;
|
|
203
|
+
if (dispatch !== undefined) {
|
|
204
|
+
Object.keys(result).forEach((outputId) => {
|
|
205
|
+
const nodeId = dispatch[outputId];
|
|
206
|
+
this.graph.injectResult(nodeId, result[outputId]);
|
|
207
|
+
});
|
|
208
|
+
log.state = NodeState.Dispatched;
|
|
209
|
+
this.state = NodeState.Dispatched;
|
|
210
|
+
this.graph.removeRunning(this);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
log.state = NodeState.Completed;
|
|
214
|
+
this.setResult(result, NodeState.Completed);
|
|
164
215
|
this.graph.removeRunning(this);
|
|
165
216
|
} catch (error) {
|
|
166
217
|
if (this.transactionId !== transactionId) {
|
|
@@ -170,11 +221,11 @@ class Node {
|
|
|
170
221
|
log.state = NodeState.Failed;
|
|
171
222
|
log.endTime = Date.now();
|
|
172
223
|
if (error instanceof Error) {
|
|
173
|
-
log.
|
|
224
|
+
log.errorMessage = error.message;
|
|
174
225
|
this.retry(NodeState.Failed, error);
|
|
175
226
|
} else {
|
|
176
227
|
console.error(`-- ${this.nodeId}: Unexpecrted error was caught`);
|
|
177
|
-
log.
|
|
228
|
+
log.errorMessage = "Unknown";
|
|
178
229
|
this.retry(NodeState.Failed, Error("Unknown"));
|
|
179
230
|
}
|
|
180
231
|
}
|
|
@@ -183,28 +234,27 @@ class Node {
|
|
|
183
234
|
|
|
184
235
|
type GraphNodes = Record<string, Node>;
|
|
185
236
|
|
|
186
|
-
type NodeExecuteDictonary = Record<string, NodeExecute>;
|
|
187
|
-
|
|
188
237
|
const defaultConcurrency = 8;
|
|
189
238
|
|
|
190
239
|
export class GraphAI {
|
|
191
240
|
public nodes: GraphNodes;
|
|
192
|
-
public callbackDictonary:
|
|
193
|
-
|
|
194
|
-
private
|
|
241
|
+
public callbackDictonary: AgentFunctionDictonary;
|
|
242
|
+
public isRunning = false;
|
|
243
|
+
private runningNodes = new Set<string>();
|
|
244
|
+
private nodeQueue: Array<Node> = [];
|
|
195
245
|
private onComplete: () => void;
|
|
196
246
|
private concurrency: number;
|
|
197
247
|
private logs: Array<TransactionLog> = [];
|
|
198
248
|
|
|
199
|
-
constructor(data: GraphData, callbackDictonary:
|
|
249
|
+
constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>) {
|
|
200
250
|
this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
|
|
201
251
|
if (this.callbackDictonary["default"] === undefined) {
|
|
202
252
|
throw new Error("No default function");
|
|
203
253
|
}
|
|
204
254
|
this.concurrency = data.concurrency ?? defaultConcurrency;
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
255
|
+
this.onComplete = () => {
|
|
256
|
+
console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
|
|
257
|
+
};
|
|
208
258
|
this.nodes = Object.keys(data.nodes).reduce((nodes: GraphNodes, nodeId: string) => {
|
|
209
259
|
nodes[nodeId] = new Node(nodeId, data.nodes[nodeId], this);
|
|
210
260
|
return nodes;
|
|
@@ -220,9 +270,9 @@ export class GraphAI {
|
|
|
220
270
|
});
|
|
221
271
|
}
|
|
222
272
|
|
|
223
|
-
public getCallback(
|
|
224
|
-
if (
|
|
225
|
-
return this.callbackDictonary[
|
|
273
|
+
public getCallback(agentId: string) {
|
|
274
|
+
if (agentId && this.callbackDictonary[agentId]) {
|
|
275
|
+
return this.callbackDictonary[agentId];
|
|
226
276
|
}
|
|
227
277
|
return this.callbackDictonary["default"];
|
|
228
278
|
}
|
|
@@ -256,6 +306,10 @@ export class GraphAI {
|
|
|
256
306
|
}
|
|
257
307
|
|
|
258
308
|
public async run() {
|
|
309
|
+
if (this.isRunning) {
|
|
310
|
+
console.error("-- Already Running");
|
|
311
|
+
}
|
|
312
|
+
this.isRunning = true;
|
|
259
313
|
// Nodes without pending data should run immediately.
|
|
260
314
|
Object.keys(this.nodes).forEach((nodeId) => {
|
|
261
315
|
const node = this.nodes[nodeId];
|
|
@@ -264,6 +318,7 @@ export class GraphAI {
|
|
|
264
318
|
|
|
265
319
|
return new Promise((resolve, reject) => {
|
|
266
320
|
this.onComplete = () => {
|
|
321
|
+
this.isRunning = false;
|
|
267
322
|
const errors = this.errors();
|
|
268
323
|
const nodeIds = Object.keys(errors);
|
|
269
324
|
if (nodeIds.length > 0) {
|
|
@@ -308,4 +363,13 @@ export class GraphAI {
|
|
|
308
363
|
public transactionLogs() {
|
|
309
364
|
return this.logs;
|
|
310
365
|
}
|
|
366
|
+
|
|
367
|
+
public injectResult(nodeId: string, result: ResultData) {
|
|
368
|
+
const node = this.nodes[nodeId];
|
|
369
|
+
if (node) {
|
|
370
|
+
node.injectResult(result);
|
|
371
|
+
} else {
|
|
372
|
+
console.error("-- Invalid nodeId", nodeId);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
311
375
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AgentFunction } from "@/graphai";
|
|
2
|
+
import { sleep } from "~/utils/utils";
|
|
3
|
+
|
|
4
|
+
export const testAgent: AgentFunction<{ delay: number; fail: boolean }> = async (context) => {
|
|
5
|
+
const { nodeId, retry, params, payload } = context;
|
|
6
|
+
console.log("executing", nodeId);
|
|
7
|
+
await sleep(params.delay / (retry + 1));
|
|
8
|
+
|
|
9
|
+
if (params.fail && retry < 2) {
|
|
10
|
+
const result = { [nodeId]: "failed" };
|
|
11
|
+
console.log("failed (intentional)", nodeId, retry);
|
|
12
|
+
throw new Error("Intentional Failure");
|
|
13
|
+
} else {
|
|
14
|
+
const result = Object.keys(payload).reduce(
|
|
15
|
+
(result, key) => {
|
|
16
|
+
result = { ...result, ...payload[key] };
|
|
17
|
+
return result;
|
|
18
|
+
},
|
|
19
|
+
{ [nodeId]: "output" },
|
|
20
|
+
);
|
|
21
|
+
console.log("completing", nodeId);
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AgentFunction } from "@/graphai";
|
|
2
|
+
import { fileTestRunner } from "~/utils/runner";
|
|
3
|
+
|
|
4
|
+
import { sleep } from "~/utils/utils";
|
|
5
|
+
|
|
6
|
+
import { testAgent } from "~/agents/agents";
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert";
|
|
10
|
+
|
|
11
|
+
const dispatchAgent: AgentFunction<{ delay: number; fail: boolean }> = async (context) => {
|
|
12
|
+
const { nodeId, retry, params, payload } = context;
|
|
13
|
+
console.log("executing", nodeId);
|
|
14
|
+
await sleep(params.delay / (retry + 1));
|
|
15
|
+
|
|
16
|
+
if (params.fail && retry < 2) {
|
|
17
|
+
const result = { [nodeId]: "failed" };
|
|
18
|
+
console.log("failed (intentional)", nodeId, retry);
|
|
19
|
+
throw new Error("Intentional Failure");
|
|
20
|
+
} else {
|
|
21
|
+
const result = Object.keys(payload).reduce(
|
|
22
|
+
(result, key) => {
|
|
23
|
+
result = { ...result, ...payload[key] };
|
|
24
|
+
return result;
|
|
25
|
+
},
|
|
26
|
+
{ [nodeId]: "dispatch" },
|
|
27
|
+
);
|
|
28
|
+
console.log("completing", nodeId);
|
|
29
|
+
return { output1: result };
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
test("test dispatch", async () => {
|
|
34
|
+
const result = await fileTestRunner("/graphs/test_dispatch.yml", { default: testAgent, alt: dispatchAgent });
|
|
35
|
+
assert.deepStrictEqual(result, {
|
|
36
|
+
node1: { node1: "output" },
|
|
37
|
+
node20: { node2: "dispatch" },
|
|
38
|
+
node3: { node3: "output", node1: "output", node2: "dispatch" },
|
|
39
|
+
node4: { node4: "output", node3: "output", node1: "output", node2: "dispatch" },
|
|
40
|
+
node5: { node5: "output", node4: "output", node3: "output", node1: "output", node2: "dispatch" },
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GraphAI, AgentFunction } from "@/graphai";
|
|
2
|
+
import { graphDataTestRunner } from "~/utils/runner";
|
|
3
|
+
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert";
|
|
6
|
+
|
|
7
|
+
const httpClientAgent: AgentFunction<Record<string, string>> = async (context) => {
|
|
8
|
+
const { nodeId, retry, params, payload } = context;
|
|
9
|
+
console.log("executing", nodeId, params, payload);
|
|
10
|
+
|
|
11
|
+
const response = await fetch(params.url);
|
|
12
|
+
const result = await response.json();
|
|
13
|
+
|
|
14
|
+
console.log("completing", nodeId, result);
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const graph_data = {
|
|
19
|
+
nodes: {
|
|
20
|
+
node1: {
|
|
21
|
+
params: {
|
|
22
|
+
url: "http://127.0.0.1:8080/llm.json",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
node2: {
|
|
26
|
+
params: {
|
|
27
|
+
url: "http://127.0.0.1:8080/llm2.json",
|
|
28
|
+
},
|
|
29
|
+
inputs: ["node1"],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
test("test sample1", async () => {
|
|
35
|
+
const result = await graphDataTestRunner("http.log", graph_data, httpClientAgent);
|
|
36
|
+
assert.deepStrictEqual(result, {
|
|
37
|
+
node1: { result: true, messages: ["hello"] },
|
|
38
|
+
node2: { result: true, messages: ["hello2"] },
|
|
39
|
+
});
|
|
40
|
+
});
|