graphai 0.0.7 → 0.0.9

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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  GraphAI is an asynchronous data flow execution engine, which makes it easy to create AI applications that need to make asynchronous AI API calls multiple times with some dependencies among them, such as giving the answer from one LLM call to another LLM call as a prompt.
6
6
 
7
- You just need to describe dependencies among those API calls in a single data flow graph (typically in YAML), create a GraphAI object with it, and run it.
7
+ You just need to describe dependencies among those API calls in a single data flow graph, create a GraphAI object with that graph, and run it.
8
8
 
9
9
  Here is an example:
10
10
 
@@ -27,7 +27,7 @@ const sampleAgentFunction = async (context: AgentFunctionContext) => {
27
27
  const {
28
28
  nodeId, // taskA, taskB or taskC
29
29
  params, // agent-specific parameters specified in the graph definition file
30
- payload // for taskC, { taskA: resultA, taskB: resultB }
30
+ inputs // inputs from previous nodes
31
31
  } = context;
32
32
  // Agent-specific code (such as calling OpenAI's chat.completions API)
33
33
  ...
@@ -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, retry, timeout, source, dispatch, 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
 
@@ -61,19 +71,19 @@ An agent is an abstract object, which takes some inputs and generates an output
61
71
 
62
72
  An agent function is a TypeScript function, which implements an agent. A DFG is associated one or more agent functions. If the DFG is associated with multiple agent functions, each node needs to be associated only one of them (either explicitly with 'agentId' or implicitly to the default Agent function).
63
73
 
64
- An agent function receives two set of parameters via AgentFunctionContext, agent specific parameters specified in the DFG and input data came from other nodes (payload).
65
-
66
- A regular agent function returns the data (type: ```Record<string, any>```), but a dispatcher agent function returns the date with outputID(s) (type: ```Record<string: Record<string, any>>```).
74
+ An agent function receives two set of parameters via AgentFunctionContext, agent specific parameters specified in the DFG and input data came from other nodes.
67
75
 
68
76
  ## Node Structure
69
77
 
70
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.
71
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'.
72
- - 'dispatch': An optinal property (type: ```Record<string, string>```) which indicates that the current node is a 'dispatcher node'. A dispatcher node is associated is a dispaher-style agent function, which has multiple possible 'outputs'.
80
+ - 'result': An optional object, which injects the result into the source node (equivalent to calling the injectResult method).
73
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.
74
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.
75
83
  - 'params': An optional parameters to the associated agent function, which are agent specific.
76
- - 'payloadMapping': An optional property (type: ```Record<string, string>```), which maps input nodeIds to agent specific inputIDs.
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.
77
87
 
78
88
  ## GraphAI class
79
89
 
package/lib/graphai.d.ts CHANGED
@@ -12,13 +12,14 @@ 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: NodeDataParams;
16
- payloadMapping?: Record<string, string>;
15
+ params?: NodeDataParams;
17
16
  retry?: number;
18
17
  timeout?: number;
19
18
  agentId?: string;
19
+ fork?: number;
20
20
  source?: boolean;
21
- dispatch?: Record<string, string>;
21
+ result?: ResultData;
22
+ outputs?: Record<string, string>;
22
23
  };
23
24
  export type GraphData = {
24
25
  nodes: Record<string, NodeData>;
@@ -32,15 +33,16 @@ export type TransactionLog = {
32
33
  retryCount: number;
33
34
  agentId?: string;
34
35
  params?: NodeDataParams;
35
- payload?: ResultDataDictonary<ResultData>;
36
+ inputs?: Array<ResultData>;
36
37
  errorMessage?: string;
37
38
  result?: ResultData;
38
39
  };
39
40
  export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
40
41
  nodeId: string;
42
+ forkIndex?: number;
41
43
  retry: number;
42
44
  params: NodeDataParams<ParamsType>;
43
- payload: ResultDataDictonary<PreviousResultType>;
45
+ inputs: Array<PreviousResultType>;
44
46
  };
45
47
  export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, ResultType, PreviousResultType>) => Promise<ResultData<ResultType>>;
46
48
  export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
@@ -48,11 +50,12 @@ declare class Node {
48
50
  nodeId: string;
49
51
  params: NodeDataParams;
50
52
  inputs: Array<string>;
51
- payloadMapping: Record<string, string>;
52
53
  pendings: Set<string>;
53
54
  waitlist: Set<string>;
54
55
  state: NodeState;
55
- agentId: string;
56
+ agentId?: string;
57
+ fork?: number;
58
+ forkIndex?: number;
56
59
  result: ResultData;
57
60
  retryLimit: number;
58
61
  retryCount: number;
@@ -60,13 +63,12 @@ declare class Node {
60
63
  timeout?: number;
61
64
  error?: Error;
62
65
  source: boolean;
63
- dispatch?: Record<string, string>;
66
+ outputs?: Record<string, string>;
64
67
  private graph;
65
- constructor(nodeId: string, data: NodeData, graph: GraphAI);
68
+ constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI);
66
69
  asString(): string;
67
70
  private retry;
68
71
  removePending(nodeId: string): void;
69
- payload(): ResultDataDictonary<Record<string, any>>;
70
72
  pushQueueIfReady(): void;
71
73
  injectResult(result: ResultData): void;
72
74
  private setResult;
@@ -83,7 +85,7 @@ export declare class GraphAI {
83
85
  private concurrency;
84
86
  private logs;
85
87
  constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>);
86
- getCallback(agentId: string): AgentFunction<any, any, any>;
88
+ getCallback(_agentId?: string): AgentFunction<any, any, any>;
87
89
  asString(): string;
88
90
  results(): ResultDataDictonary<Record<string, any>>;
89
91
  errors(): Record<string, Error>;
@@ -94,5 +96,6 @@ export declare class GraphAI {
94
96
  appendLog(log: TransactionLog): void;
95
97
  transactionLogs(): TransactionLog[];
96
98
  injectResult(nodeId: string, result: ResultData): void;
99
+ resultsOf(nodeIds: Array<string>): ResultData<Record<string, any>>[];
97
100
  }
98
101
  export {};
package/lib/graphai.js CHANGED
@@ -12,21 +12,22 @@ 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
- this.payloadMapping = data.payloadMapping ?? {};
23
23
  this.pendings = new Set(this.inputs);
24
- this.params = data.params;
25
- this.agentId = data.agentId ?? "default";
24
+ this.params = data.params ?? {};
25
+ this.agentId = data.agentId;
26
+ this.fork = data.fork;
26
27
  this.retryLimit = data.retry ?? 0;
27
28
  this.timeout = data.timeout;
28
29
  this.source = data.source === true;
29
- this.dispatch = data.dispatch;
30
+ this.outputs = data.outputs;
30
31
  this.graph = graph;
31
32
  }
32
33
  asString() {
@@ -51,17 +52,6 @@ class Node {
51
52
  this.pushQueueIfReady();
52
53
  }
53
54
  }
54
- payload() {
55
- return this.inputs.reduce((results, nodeId) => {
56
- if (this.payloadMapping && this.payloadMapping[nodeId]) {
57
- results[this.payloadMapping[nodeId]] = this.graph.nodes[nodeId].result;
58
- }
59
- else {
60
- results[nodeId] = this.graph.nodes[nodeId].result;
61
- }
62
- return results;
63
- }, {});
64
- }
65
55
  pushQueueIfReady() {
66
56
  if (this.pendings.size === 0 && !this.source) {
67
57
  this.graph.pushQueue(this);
@@ -74,8 +64,9 @@ class Node {
74
64
  retryCount: this.retryCount,
75
65
  state: NodeState.Injected,
76
66
  startTime: Date.now(),
67
+ endTime: Date.now(),
68
+ result,
77
69
  };
78
- log.endTime = log.startTime;
79
70
  this.graph.appendLog(log);
80
71
  this.setResult(result, NodeState.Injected);
81
72
  }
@@ -93,19 +84,19 @@ class Node {
93
84
  });
94
85
  }
95
86
  async execute() {
96
- const payload = this.payload();
87
+ const results = this.graph.resultsOf(this.inputs);
88
+ const transactionId = Date.now();
97
89
  const log = {
98
90
  nodeId: this.nodeId,
99
91
  retryCount: this.retryCount,
100
92
  state: NodeState.Executing,
101
- startTime: Date.now(),
93
+ startTime: transactionId,
102
94
  agentId: this.agentId,
103
95
  params: this.params,
104
- payload,
96
+ inputs: results,
105
97
  };
106
98
  this.graph.appendLog(log);
107
99
  this.state = NodeState.Executing;
108
- const transactionId = log.startTime;
109
100
  this.transactionId = transactionId;
110
101
  if (this.timeout && this.timeout > 0) {
111
102
  setTimeout(() => {
@@ -124,7 +115,8 @@ class Node {
124
115
  nodeId: this.nodeId,
125
116
  retry: this.retryCount,
126
117
  params: this.params,
127
- payload,
118
+ inputs: results,
119
+ forkIndex: this.forkIndex,
128
120
  });
129
121
  if (this.transactionId !== transactionId) {
130
122
  console.log(`-- ${this.nodeId}: transactionId mismatch`);
@@ -132,10 +124,10 @@ class Node {
132
124
  }
133
125
  log.endTime = Date.now();
134
126
  log.result = result;
135
- const dispatch = this.dispatch;
136
- if (dispatch !== undefined) {
127
+ const outputs = this.outputs;
128
+ if (outputs !== undefined) {
137
129
  Object.keys(result).forEach((outputId) => {
138
- const nodeId = dispatch[outputId];
130
+ const nodeId = outputs[outputId];
139
131
  this.graph.injectResult(nodeId, result[outputId]);
140
132
  });
141
133
  log.state = NodeState.Dispatched;
@@ -173,32 +165,74 @@ class GraphAI {
173
165
  this.runningNodes = new Set();
174
166
  this.nodeQueue = [];
175
167
  this.logs = [];
176
- this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
177
- if (this.callbackDictonary["default"] === undefined) {
178
- throw new Error("No default function");
179
- }
168
+ this.callbackDictonary = typeof callbackDictonary === "function" ? { _default: callbackDictonary } : callbackDictonary;
180
169
  this.concurrency = data.concurrency ?? defaultConcurrency;
181
170
  this.onComplete = () => {
182
171
  console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
183
172
  };
173
+ const nodeId2forkedNodeIds = {};
174
+ const forkedNodeId2Index = {};
175
+ // Create node instances from data.nodes
184
176
  this.nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
185
- nodes[nodeId] = new Node(nodeId, data.nodes[nodeId], this);
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
+ }
186
191
  return nodes;
187
192
  }, {});
188
- // Generate the waitlist for each node
193
+ // Generate the waitlist for each node, and update the pendings in case of forked node.
189
194
  Object.keys(this.nodes).forEach((nodeId) => {
190
195
  const node = this.nodes[nodeId];
191
196
  node.pendings.forEach((pending) => {
192
- const node2 = this.nodes[pending];
193
- node2.waitlist.add(nodeId);
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
+ }
194
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
+ }
195
228
  });
196
229
  }
197
- getCallback(agentId) {
198
- if (agentId && this.callbackDictonary[agentId]) {
230
+ getCallback(_agentId) {
231
+ const agentId = _agentId ?? "_default";
232
+ if (this.callbackDictonary[agentId]) {
199
233
  return this.callbackDictonary[agentId];
200
234
  }
201
- return this.callbackDictonary["default"];
235
+ throw new Error("No agent: " + agentId);
202
236
  }
203
237
  asString() {
204
238
  return Object.keys(this.nodes)
@@ -288,5 +322,10 @@ class GraphAI {
288
322
  console.error("-- Invalid nodeId", nodeId);
289
323
  }
290
324
  }
325
+ resultsOf(nodeIds) {
326
+ return nodeIds.map((nodeId) => {
327
+ return this.nodes[nodeId].result;
328
+ });
329
+ }
291
330
  }
292
331
  exports.GraphAI = GraphAI;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,36 @@
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 fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const yaml_1 = __importDefault(require("yaml"));
11
+ const testAgent = async (context) => {
12
+ return {};
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_1.default.resolve(process.cwd() + "/" + file);
21
+ if (!fs_1.default.existsSync(file_path)) {
22
+ console.log("no file");
23
+ return;
24
+ }
25
+ try {
26
+ const graph_data_file = fs_1.default.readFileSync(file_path, "utf8");
27
+ const graph_data = yaml_1.default.parse(graph_data_file);
28
+ const graph = new graphai_1.GraphAI(graph_data, { test: testAgent });
29
+ const results = await graph.run();
30
+ console.log(results);
31
+ }
32
+ catch (e) {
33
+ console.log("error", e);
34
+ }
35
+ };
36
+ main();
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "graphai",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
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
- "gpt": "npx ts-node -r tsconfig-paths/register ./samples/sample_gpt.ts"
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",
@@ -35,12 +35,11 @@ export const arxivAgent: AgentFunction<{ keywords: string[]; limit: number }, ar
35
35
  };
36
36
 
37
37
  export const arxiv2TextAgent: AgentFunction<{}, string, string[]> = async (context) => {
38
- const result = (context?.payload?.inputData || [])
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
-
45
44
  return result;
46
45
  };
@@ -0,0 +1,5 @@
1
+ import { AgentFunction } from "@/graphai";
2
+
3
+ export const parrotingAgent: AgentFunction = async (context) => {
4
+ return context.params;
5
+ };
@@ -4,18 +4,47 @@ import { ChatSession, ChatConfig, ManifestData } from "slashgpt";
4
4
 
5
5
  const config = new ChatConfig(path.resolve(__dirname));
6
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 ?? {});
7
+ export const slashGPTFuncitons2TextAgent: AgentFunction<
8
+ { function_data_key: string; result_key: number },
9
+ string,
10
+ { function_data: { [key: string]: string[] } }
11
+ > = async (context) => {
12
+ const { params } = context;
13
+ const result = (context?.inputs[0].function_data[params.function_data_key] || []).map((r: any) => {
14
+ const { title, description } = r;
15
+ return ["title:", title, "description:", description].join("\n");
16
+ });
10
17
 
11
- const prompt = [context.params?.prompt, context.payload.inputData].join("\n\n");
18
+ return result[context.forkIndex ?? 0];
19
+ };
20
+
21
+ export const slashGPTAgent: AgentFunction<
22
+ { manifest: ManifestData; prompt: string; function_result?: boolean; debug?: boolean },
23
+ { answer: string },
24
+ string
25
+ > = async (context) => {
26
+ const { params } = context;
27
+ if (params.debug) {
28
+ console.log("executing", context.nodeId, context);
29
+ }
30
+ const session = new ChatSession(config, params?.manifest ?? {});
31
+
32
+ const prompt = params?.prompt ?? [context.inputs].filter((a) => a !== undefined).join("\n\n");
33
+ // console.log(prompt);
12
34
  session.append_user_question(prompt);
13
35
 
14
36
  await session.call_loop(() => {});
15
- const message = session.history.last_message();
37
+
38
+ // console.log(session.history)
39
+ const message = (() => {
40
+ if (params.function_result) {
41
+ return session.history.messages().find((m) => m.role === "function_result");
42
+ }
43
+ return session.history.last_message();
44
+ })();
16
45
  if (message === undefined) {
17
46
  throw new Error("No message in the history");
18
47
  }
19
- const result = { answer: message.content };
48
+ const result = { answer: message.content, function_data: message.function_data };
20
49
  return result;
21
50
  };
@@ -5,17 +5,13 @@ nodes:
5
5
  - llm
6
6
  - gpt
7
7
  limit: 10
8
- functionName: arxivAgent
8
+ agentId: arxivAgent
9
9
  arxiv2TextAgent:
10
10
  inputs: [searchArxiv]
11
- functionName: arxiv2TextAgent
12
- payloadMapping:
13
- searchArxiv: inputData
11
+ agentId: arxiv2TextAgent
14
12
  slashGPTAgent:
15
13
  inputs: [arxiv2TextAgent]
16
- payloadMapping:
17
- arxiv2TextAgent: inputData
18
- functionName: slashGPTAgent
14
+ agentId: slashGPTAgent
19
15
  params:
20
16
  prompt: |
21
17
  与えられたそれぞれの論文の要点をまとめ、以下の項目で日本語で出力せよ。それぞれの項目は最大でも180文字以内に要約せよ。
@@ -5,8 +5,15 @@ nodes:
5
5
  node2:
6
6
  inputs: [node1]
7
7
  params:
8
- prompt: Please evaluate following business ideas. ${node1}
8
+ prompt: |
9
+ Please evaluate following business ideas.
10
+ ${0}
9
11
  node3:
10
12
  inputs: [node1, node2]
11
13
  params:
12
- prompt: Please pick the winner of this business idea contest. ${node1} ${node2}
14
+ prompt: |
15
+ Please pick the winner of this business idea contest.
16
+ [ideas]
17
+ ${0}
18
+ [evalutations]
19
+ ${1}
@@ -0,0 +1,49 @@
1
+ import { GraphAI, GraphData } from "@/graphai";
2
+ import * as readline from "readline";
3
+ import path from "path";
4
+ import * as fs from "fs";
5
+ import { testAgent } from "~/agents/agents";
6
+
7
+ const getUserInput = async (question: string): Promise<string> => {
8
+ return new Promise((resolve, reject) => {
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+
14
+ rl.question(question, (answer) => {
15
+ rl.close();
16
+ resolve(answer);
17
+ });
18
+ });
19
+ };
20
+
21
+ const graph_data: GraphData = {
22
+ nodes: {
23
+ node1: {
24
+ source: true,
25
+ },
26
+ node2: {
27
+ inputs: ["node1"],
28
+ },
29
+ },
30
+ };
31
+
32
+ const runAgent = async (query: string) => {
33
+ console.log("query=", query);
34
+ graph_data.nodes.node1.result = { query };
35
+ const graph = new GraphAI(graph_data, testAgent);
36
+ // graph.injectResult("node1", { query });
37
+ const result = await graph.run();
38
+ const log_path = path.resolve(__dirname) + "/../tests/logs/interaction.log";
39
+ fs.writeFileSync(log_path, JSON.stringify(graph.transactionLogs(), null, 2));
40
+ console.log(result);
41
+ };
42
+
43
+ const main = async () => {
44
+ const query = await getUserInput("Please enter your question: ");
45
+ await runAgent(query);
46
+ console.log("COMPLETE 1");
47
+ };
48
+
49
+ main();
@@ -0,0 +1,85 @@
1
+ import { GraphAI, AgentFunction } from "@/graphai";
2
+ import { readGraphaiData } from "~/utils/file_utils";
3
+
4
+ import { slashGPTAgent, slashGPTFuncitons2TextAgent } from "./agents/slashgpt_agent";
5
+
6
+ const graph_data = {
7
+ nodes: {
8
+ slashGPTAgent: {
9
+ agentId: "slashGPTAgent",
10
+ params: {
11
+ function_result: true,
12
+ prompt: "世界で協力してco2を減らす方法を教えて下さい",
13
+ manifest: {
14
+ prompt: "あなたは世界経済の専門家です。ユーザの問い合わせについて考え、10この結果をfunctionsの結果に返してください。",
15
+ skip_function_result: true,
16
+ actions: {
17
+ your_ideas: {
18
+ type: "message_template",
19
+ message: "dummy",
20
+ },
21
+ },
22
+ functions: [
23
+ {
24
+ name: "your_ideas",
25
+ description: "Your answer to a user's inquiry",
26
+ parameters: {
27
+ type: "object",
28
+ properties: {
29
+ methods: {
30
+ type: "array",
31
+ items: {
32
+ type: "object",
33
+ properties: {
34
+ title: {
35
+ type: "string",
36
+ description: "title",
37
+ },
38
+ description: {
39
+ type: "string",
40
+ description: "description",
41
+ },
42
+ },
43
+ required: ["title", "description"],
44
+ },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ ],
50
+ },
51
+ },
52
+ },
53
+ function2prompt0: {
54
+ fork: 10,
55
+ params: {
56
+ function_data_key: "methods",
57
+ result_key: 0,
58
+ },
59
+ inputs: ["slashGPTAgent"],
60
+ agentId: "slashGPTFuncitons2TextAgent",
61
+ },
62
+ slashGPTAgent0: {
63
+ agentId: "slashGPTAgent",
64
+ fork: 10,
65
+ params: {
66
+ debug: true,
67
+ manifest: {
68
+ prompt: "ユーザの問い合わせにある文章の専門家です。専門家として、ユーザのアイデアに対して実現可能なシナリオを800文字で書いてください。",
69
+ },
70
+ },
71
+ inputs: ["function2prompt0"],
72
+ },
73
+ },
74
+ };
75
+ const runAgent = async () => {
76
+ const graph = new GraphAI(graph_data, { slashGPTAgent, slashGPTFuncitons2TextAgent });
77
+ const result = await graph.run();
78
+ console.log(result);
79
+ };
80
+
81
+ const main = async () => {
82
+ await runAgent();
83
+ console.log("COMPLETE 1");
84
+ };
85
+ main();
@@ -1,15 +1,16 @@
1
1
  import path from "path";
2
+ import * as fs from "fs";
2
3
  import { GraphAI, AgentFunction } from "@/graphai";
3
4
  import { ChatSession, ChatConfig, ManifestData } from "slashgpt";
4
5
  import { readGraphaiData } from "~/utils/file_utils";
5
6
 
6
7
  const config = new ChatConfig(path.resolve(__dirname));
7
8
 
8
- const slashGPTAgent: AgentFunction<{ manifest: ManifestData; prompt: string }, { answer: string }> = async (context) => {
9
+ const slashGPTAgent: AgentFunction<{ manifest: ManifestData; prompt: string }, { content: string }> = async (context) => {
9
10
  console.log("executing", context.nodeId, context.params);
10
11
  const session = new ChatSession(config, context.params.manifest ?? {});
11
- const prompt = Object.keys(context.payload).reduce((prompt, key) => {
12
- return prompt.replace("${" + key + "}", context.payload[key]!["answer"]);
12
+ const prompt = context.inputs.reduce((prompt, input, index) => {
13
+ return prompt.replace("${" + index + "}", input["content"]);
13
14
  }, context.params.prompt);
14
15
  session.append_user_question(prompt);
15
16
 
@@ -18,20 +19,22 @@ const slashGPTAgent: AgentFunction<{ manifest: ManifestData; prompt: string }, {
18
19
  if (message === undefined) {
19
20
  throw new Error("No message in the history");
20
21
  }
21
- const result = { answer: message.content };
22
- return result;
22
+ return message;
23
23
  };
24
24
 
25
25
  const runAgent = async (file: string) => {
26
26
  const file_path = path.resolve(__dirname) + file;
27
27
  const graph_data = readGraphaiData(file_path);
28
28
  const graph = new GraphAI(graph_data, slashGPTAgent);
29
- const result = await graph.run();
30
- console.log(result);
29
+ const results = (await graph.run()) as Record<string, any>;
30
+
31
+ const log_path = path.resolve(__dirname) + "/../tests/logs/" + path.basename(file_path).replace(/\.yml$/, ".log");
32
+ console.log(log_path);
33
+ fs.writeFileSync(log_path, JSON.stringify(graph.transactionLogs(), null, 2));
34
+ console.log(results["node3"]["content"]);
31
35
  };
32
36
 
33
37
  const main = async () => {
34
38
  await runAgent("/graphs/slash_gpt.yml");
35
- console.log("COMPLETE 1");
36
39
  };
37
40
  main();
@@ -7,14 +7,10 @@ import { readGraphaiData } from "~/utils/file_utils";
7
7
  import { slashGPTAgent } from "./agents/slashgpt_agent";
8
8
  import { arxivAgent, arxiv2TextAgent } from "./agents/arxiv_agent";
9
9
 
10
- export const parrotingAgent: AgentFunction = async (context) => {
11
- return {};
12
- };
13
-
14
10
  const runAgent = async (file: string) => {
15
11
  const file_path = path.resolve(__dirname) + file;
16
12
  const graph_data = readGraphaiData(file_path);
17
- const graph = new GraphAI(graph_data, { default: parrotingAgent, arxivAgent: arxivAgent, arxiv2TextAgent, slashGPTAgent });
13
+ const graph = new GraphAI(graph_data, { arxivAgent: arxivAgent, arxiv2TextAgent, slashGPTAgent });
18
14
  const result = await graph.run();
19
15
  console.log(result);
20
16
  };
package/src/graphai.ts CHANGED
@@ -14,13 +14,14 @@ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType; // Ag
14
14
 
15
15
  type NodeData = {
16
16
  inputs?: Array<string>;
17
- params: NodeDataParams;
18
- payloadMapping?: Record<string, string>;
17
+ params?: NodeDataParams;
19
18
  retry?: number;
20
19
  timeout?: number; // msec
21
20
  agentId?: string;
21
+ fork?: number;
22
22
  source?: boolean;
23
- dispatch?: Record<string, string>; // route to node
23
+ result?: ResultData; // preset result for source node.
24
+ outputs?: Record<string, string>; // mapping from routeId to nodeId
24
25
  };
25
26
 
26
27
  export type GraphData = {
@@ -36,16 +37,17 @@ export type TransactionLog = {
36
37
  retryCount: number;
37
38
  agentId?: string;
38
39
  params?: NodeDataParams;
39
- payload?: ResultDataDictonary<ResultData>;
40
+ inputs?: Array<ResultData>;
40
41
  errorMessage?: string;
41
42
  result?: ResultData;
42
43
  };
43
44
 
44
45
  export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
45
46
  nodeId: string;
47
+ forkIndex?: number;
46
48
  retry: number;
47
49
  params: NodeDataParams<ParamsType>;
48
- payload: ResultDataDictonary<PreviousResultType>;
50
+ inputs: Array<PreviousResultType>;
49
51
  };
50
52
 
51
53
  export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (
@@ -58,11 +60,12 @@ class Node {
58
60
  public nodeId: string;
59
61
  public params: NodeDataParams; // Agent-specific parameters
60
62
  public inputs: Array<string>; // List of nodes this node needs data from.
61
- public payloadMapping: Record<string, string>;
62
63
  public pendings: Set<string>; // List of nodes this node is waiting data from.
63
64
  public waitlist = new Set<string>(); // List of nodes which need data from this node.
64
65
  public state = NodeState.Waiting;
65
- public agentId: string;
66
+ public agentId?: string;
67
+ public fork?: number;
68
+ public forkIndex?: number;
66
69
  public result: ResultData = undefined;
67
70
  public retryLimit: number;
68
71
  public retryCount: number = 0;
@@ -70,21 +73,22 @@ class Node {
70
73
  public timeout?: number; // msec
71
74
  public error?: Error;
72
75
  public source: boolean;
73
- public dispatch?: Record<string, string>; // outputId to nodeId mapping
76
+ public outputs?: Record<string, string>; // Mapping from routeId to nodeId
74
77
 
75
78
  private graph: GraphAI;
76
79
 
77
- constructor(nodeId: string, data: NodeData, graph: GraphAI) {
80
+ constructor(nodeId: string, forkIndex: number | undefined, data: NodeData, graph: GraphAI) {
78
81
  this.nodeId = nodeId;
82
+ this.forkIndex = forkIndex;
79
83
  this.inputs = data.inputs ?? [];
80
- this.payloadMapping = data.payloadMapping ?? {};
81
84
  this.pendings = new Set(this.inputs);
82
- this.params = data.params;
83
- this.agentId = data.agentId ?? "default";
85
+ this.params = data.params ?? {};
86
+ this.agentId = data.agentId;
87
+ this.fork = data.fork;
84
88
  this.retryLimit = data.retry ?? 0;
85
89
  this.timeout = data.timeout;
86
90
  this.source = data.source === true;
87
- this.dispatch = data.dispatch;
91
+ this.outputs = data.outputs;
88
92
  this.graph = graph;
89
93
  }
90
94
 
@@ -112,17 +116,6 @@ class Node {
112
116
  }
113
117
  }
114
118
 
115
- public payload() {
116
- return this.inputs.reduce((results: ResultDataDictonary, nodeId) => {
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
- }
122
- return results;
123
- }, {});
124
- }
125
-
126
119
  public pushQueueIfReady() {
127
120
  if (this.pendings.size === 0 && !this.source) {
128
121
  this.graph.pushQueue(this);
@@ -136,8 +129,9 @@ class Node {
136
129
  retryCount: this.retryCount,
137
130
  state: NodeState.Injected,
138
131
  startTime: Date.now(),
132
+ endTime: Date.now(),
133
+ result,
139
134
  };
140
- log.endTime = log.startTime;
141
135
  this.graph.appendLog(log);
142
136
  this.setResult(result, NodeState.Injected);
143
137
  } else {
@@ -156,19 +150,19 @@ class Node {
156
150
  }
157
151
 
158
152
  public async execute() {
159
- const payload = this.payload();
153
+ const results = this.graph.resultsOf(this.inputs);
154
+ const transactionId = Date.now();
160
155
  const log: TransactionLog = {
161
156
  nodeId: this.nodeId,
162
157
  retryCount: this.retryCount,
163
158
  state: NodeState.Executing,
164
- startTime: Date.now(),
159
+ startTime: transactionId,
165
160
  agentId: this.agentId,
166
161
  params: this.params,
167
- payload,
162
+ inputs: results,
168
163
  };
169
164
  this.graph.appendLog(log);
170
165
  this.state = NodeState.Executing;
171
- const transactionId = log.startTime;
172
166
  this.transactionId = transactionId;
173
167
 
174
168
  if (this.timeout && this.timeout > 0) {
@@ -189,7 +183,8 @@ class Node {
189
183
  nodeId: this.nodeId,
190
184
  retry: this.retryCount,
191
185
  params: this.params,
192
- payload,
186
+ inputs: results,
187
+ forkIndex: this.forkIndex,
193
188
  });
194
189
  if (this.transactionId !== transactionId) {
195
190
  console.log(`-- ${this.nodeId}: transactionId mismatch`);
@@ -199,10 +194,10 @@ class Node {
199
194
  log.endTime = Date.now();
200
195
  log.result = result;
201
196
 
202
- const dispatch = this.dispatch;
203
- if (dispatch !== undefined) {
197
+ const outputs = this.outputs;
198
+ if (outputs !== undefined) {
204
199
  Object.keys(result).forEach((outputId) => {
205
- const nodeId = dispatch[outputId];
200
+ const nodeId = outputs[outputId];
206
201
  this.graph.injectResult(nodeId, result[outputId]);
207
202
  });
208
203
  log.state = NodeState.Dispatched;
@@ -247,34 +242,75 @@ export class GraphAI {
247
242
  private logs: Array<TransactionLog> = [];
248
243
 
249
244
  constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>) {
250
- this.callbackDictonary = typeof callbackDictonary === "function" ? { default: callbackDictonary } : callbackDictonary;
251
- if (this.callbackDictonary["default"] === undefined) {
252
- throw new Error("No default function");
253
- }
245
+ this.callbackDictonary = typeof callbackDictonary === "function" ? { _default: callbackDictonary } : callbackDictonary;
254
246
  this.concurrency = data.concurrency ?? defaultConcurrency;
255
247
  this.onComplete = () => {
256
248
  console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
257
249
  };
250
+ const nodeId2forkedNodeIds: Record<string, string[]> = {};
251
+ const forkedNodeId2Index: Record<string, number> = {};
252
+
253
+ // Create node instances from data.nodes
258
254
  this.nodes = Object.keys(data.nodes).reduce((nodes: GraphNodes, nodeId: string) => {
259
- nodes[nodeId] = new Node(nodeId, data.nodes[nodeId], this);
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
+ }
260
268
  return nodes;
261
269
  }, {});
262
270
 
263
- // Generate the waitlist for each node
271
+ // Generate the waitlist for each node, and update the pendings in case of forked node.
264
272
  Object.keys(this.nodes).forEach((nodeId) => {
265
273
  const node = this.nodes[nodeId];
266
274
  node.pendings.forEach((pending) => {
267
- const node2 = this.nodes[pending];
268
- node2.waitlist.add(nodeId);
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
+ }
269
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
+ }
270
305
  });
271
306
  }
272
307
 
273
- public getCallback(agentId: string) {
274
- if (agentId && this.callbackDictonary[agentId]) {
308
+ public getCallback(_agentId?: string) {
309
+ const agentId = _agentId ?? "_default";
310
+ if (this.callbackDictonary[agentId]) {
275
311
  return this.callbackDictonary[agentId];
276
312
  }
277
- return this.callbackDictonary["default"];
313
+ throw new Error("No agent: " + agentId);
278
314
  }
279
315
 
280
316
  public asString() {
@@ -372,4 +408,10 @@ export class GraphAI {
372
408
  console.error("-- Invalid nodeId", nodeId);
373
409
  }
374
410
  }
411
+
412
+ public resultsOf(nodeIds: Array<string>) {
413
+ return nodeIds.map((nodeId) => {
414
+ return this.nodes[nodeId].result;
415
+ });
416
+ }
375
417
  }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { GraphAI, AgentFunction } from "./graphai";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import YAML from "yaml";
7
+
8
+ const testAgent: AgentFunction<{ delay: number; fail: boolean }> = async (context) => {
9
+ return {};
10
+ };
11
+
12
+ const main = async () => {
13
+ const file = process.argv[2];
14
+ if (file === undefined) {
15
+ console.log("no file");
16
+ return;
17
+ }
18
+ const file_path = path.resolve(process.cwd() + "/" + file);
19
+ if (!fs.existsSync(file_path)) {
20
+ console.log("no file");
21
+ return;
22
+ }
23
+ try {
24
+ const graph_data_file = fs.readFileSync(file_path, "utf8");
25
+ const graph_data = YAML.parse(graph_data_file);
26
+
27
+ const graph = new GraphAI(graph_data, { test: testAgent });
28
+ const results = await graph.run();
29
+ console.log(results);
30
+ } catch (e) {
31
+ console.log("error", e);
32
+ }
33
+ };
34
+
35
+ main();
@@ -2,7 +2,7 @@ import { AgentFunction } from "@/graphai";
2
2
  import { sleep } from "~/utils/utils";
3
3
 
4
4
  export const testAgent: AgentFunction<{ delay: number; fail: boolean }> = async (context) => {
5
- const { nodeId, retry, params, payload } = context;
5
+ const { nodeId, retry, params, inputs } = context;
6
6
  console.log("executing", nodeId);
7
7
  await sleep(params.delay / (retry + 1));
8
8
 
@@ -11,10 +11,9 @@ export const testAgent: AgentFunction<{ delay: number; fail: boolean }> = async
11
11
  console.log("failed (intentional)", nodeId, retry);
12
12
  throw new Error("Intentional Failure");
13
13
  } else {
14
- const result = Object.keys(payload).reduce(
15
- (result, key) => {
16
- result = { ...result, ...payload[key] };
17
- return result;
14
+ const result = inputs.reduce(
15
+ (result: Record<string, any>, input: Record<string, any>) => {
16
+ return { ...result, ...input };
18
17
  },
19
18
  { [nodeId]: "output" },
20
19
  );
@@ -8,8 +8,8 @@ import { testAgent } from "~/agents/agents";
8
8
  import test from "node:test";
9
9
  import assert from "node:assert";
10
10
 
11
- const dispatchAgent: AgentFunction<{ delay: number; fail: boolean }> = async (context) => {
12
- const { nodeId, retry, params, payload } = context;
11
+ const dispatchAgent: AgentFunction<{ delay: number; fail: boolean }, Record<string, any>, Record<string, any>> = async (context) => {
12
+ const { nodeId, retry, params, inputs } = context;
13
13
  console.log("executing", nodeId);
14
14
  await sleep(params.delay / (retry + 1));
15
15
 
@@ -18,10 +18,9 @@ const dispatchAgent: AgentFunction<{ delay: number; fail: boolean }> = async (co
18
18
  console.log("failed (intentional)", nodeId, retry);
19
19
  throw new Error("Intentional Failure");
20
20
  } else {
21
- const result = Object.keys(payload).reduce(
22
- (result, key) => {
23
- result = { ...result, ...payload[key] };
24
- return result;
21
+ const result = inputs.reduce(
22
+ (result: Record<string, any>, input: Record<string, any>) => {
23
+ return { ...result, ...input };
25
24
  },
26
25
  { [nodeId]: "dispatch" },
27
26
  );
@@ -31,7 +30,7 @@ const dispatchAgent: AgentFunction<{ delay: number; fail: boolean }> = async (co
31
30
  };
32
31
 
33
32
  test("test dispatch", async () => {
34
- const result = await fileTestRunner("/graphs/test_dispatch.yml", { default: testAgent, alt: dispatchAgent });
33
+ const result = await fileTestRunner("/graphs/test_dispatch.yml", { test: testAgent, alt: dispatchAgent });
35
34
  assert.deepStrictEqual(result, {
36
35
  node1: { node1: "output" },
37
36
  node20: { node2: "dispatch" },
@@ -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
+ });
@@ -5,8 +5,8 @@ import test from "node:test";
5
5
  import assert from "node:assert";
6
6
 
7
7
  const httpClientAgent: AgentFunction<Record<string, string>> = async (context) => {
8
- const { nodeId, retry, params, payload } = context;
9
- console.log("executing", nodeId, params, payload);
8
+ const { nodeId, retry, params, inputs } = context;
9
+ console.log("executing", nodeId, params, inputs);
10
10
 
11
11
  const response = await fetch(params.url);
12
12
  const result = await response.json();
@@ -31,7 +31,7 @@ const graph_data = {
31
31
  },
32
32
  };
33
33
 
34
- test("test sample1", async () => {
34
+ test("test http client", async () => {
35
35
  const result = await graphDataTestRunner("http.log", graph_data, httpClientAgent);
36
36
  assert.deepStrictEqual(result, {
37
37
  node1: { result: true, messages: ["hello"] },
@@ -33,8 +33,8 @@ const numberTestAgent: AgentFunction<{ number: number }, { [key: string]: number
33
33
  return result;
34
34
  };
35
35
 
36
- test("test sample1", async () => {
37
- const result = await fileTestRunner("/graphs/test_multiple_functions_1.yml", { default: testAgent1, test2: testAgent2, numberTestAgent });
36
+ test("test multiple function", async () => {
37
+ const result = await fileTestRunner("/graphs/test_multiple_functions_1.yml", { test1: testAgent1, test2: testAgent2, numberTestAgent });
38
38
  assert.deepStrictEqual(result, {
39
39
  node1: { node1: "output 1" },
40
40
  node2: { node2: "output 1" },
@@ -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: "injected" },
68
- node3: { node3: "output", node1: "injected", node2: "injected" },
69
- node4: { node4: "output", node3: "output", node1: "injected", node2: "injected" },
70
- node5: { node5: "output", node4: "output", node3: "output", node1: "injected", node2: "injected" },
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
  });
@@ -0,0 +1,4 @@
1
+ nodes:
2
+ node1:
3
+ agentId: test
4
+
@@ -2,23 +2,28 @@ nodes:
2
2
  node1:
3
3
  params:
4
4
  delay: 500
5
+ agentId: test
5
6
  node2:
6
7
  params:
7
8
  delay: 100
8
9
  agentId: alt
9
- dispatch:
10
+ outputs:
10
11
  output1: node20
11
12
  node20:
12
13
  source: true
14
+ agentId: test
13
15
  node3:
14
16
  params:
15
17
  delay: 500
16
18
  inputs: [node1, node20]
19
+ agentId: test
17
20
  node4:
18
21
  params:
19
22
  delay: 100
20
23
  inputs: [node3]
24
+ agentId: test
21
25
  node5:
22
26
  params:
23
27
  delay: 500
24
28
  inputs: [node20, node4]
29
+ agentId: test
@@ -2,9 +2,11 @@ nodes:
2
2
  node1:
3
3
  params:
4
4
  delay: 500
5
+ agentId: test1
5
6
  node2:
6
7
  params:
7
8
  delay: 100
9
+ agentId: test1
8
10
  node3:
9
11
  params:
10
12
  delay: 500
@@ -14,6 +16,7 @@ nodes:
14
16
  params:
15
17
  delay: 100
16
18
  inputs: [node3]
19
+ agentId: test1
17
20
  node5:
18
21
  params:
19
22
  delay: 500
@@ -3,6 +3,8 @@ nodes:
3
3
  source: true
4
4
  node2:
5
5
  source: true
6
+ result:
7
+ node2: preset
6
8
  node3:
7
9
  params:
8
10
  delay: 500