graphai 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,7 @@ Furthermore, GraphAI's robust mechanisms for error handling, retry strategies, t
55
55
  ## Quick Install
56
56
 
57
57
  ```
58
- pip install graphai
58
+ npm install graphai
59
59
  ```
60
60
 
61
61
  or
@@ -114,12 +114,41 @@ Key principles:
114
114
 
115
115
  A Data Flow Graph (DFG) is a JavaScript object, which defines the flow of data. It is typically described in YAML file and loaded at runtime.
116
116
 
117
- A DFG consists of a collection of 'nodes', which contains a series of nested keys representing individual nodes in the data flow. Each node is identified by a unique key (e.g., node1, node2) and can contain several predefined keys (params, inputs, outputs, retry, timeout, source, agentId, result, fork) that dictate the node's behavior and its relationship with other nodes.
117
+ A DFG consists of a collection of 'nodes', which contains a series of nested keys representing individual nodes in the data flow. Each node is identified by a unique key (e.g., node1, node2) and can contain several predefined keys (params, inputs, outputs, retry, timeout, source, agentId, fork, value) that dictate the node's behavior and its relationship with other nodes.
118
118
 
119
119
  ### DFG Structure
120
120
 
121
121
  - 'nodes': A list of node. Required.
122
122
  - 'concurrency': An optional property, which specifies the maximum number of concurrent operations (agent functions to be executed at the same time). The default is 8.
123
+ - 'agentId': An optional property, which specifies the default agent for all the nodes.
124
+ - 'loop': An optional property, which specifies if the graph needs to be executed multiple times. The loop is an JavaScript object, which has two optinoal properties. The *count* property specifies the number of times the graph needs to be executed and the *while* property specifies the condition required to contineu the loop in the form of node name (nodeId) or its property (nodeId.propId). Unlike JavaScript, an empty array will be treated as false.
125
+
126
+ ```
127
+ loop:
128
+ while: people
129
+ nodes:
130
+ people:
131
+ value:
132
+ - Steve Jobs
133
+ - Elon Musk
134
+ - Nikola Tesla
135
+ next: retriever.array
136
+ result:
137
+ value: []
138
+ next: reducer
139
+ retriever:
140
+ agentId: shift
141
+ inputs: [people]
142
+ query:
143
+ agentId: slashgpt
144
+ params:
145
+ manifest:
146
+ prompt: 指定した人について日本語で400字以内で答えて
147
+ inputs: [retriever.item]
148
+ reducer:
149
+ agentId: push
150
+ inputs: [result, query.content]
151
+ ```
123
152
 
124
153
  ## Agent
125
154
 
@@ -133,19 +162,26 @@ An agent function receives two set of parameters via AgentFunctionContext, agent
133
162
 
134
163
  ## Node Structure
135
164
 
136
- - '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.
137
- - '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'.
138
- - 'result': An optional object, which injects the result into the source node (equivalent to calling the injectResult method).
165
+ There are two types of Node, *computed nodes* and *static nodes*. A *computed node* is associated with an *agent function*, which receives some inputs, performs some computations asynchronously then returns the result (output). A *static node* is a placeholder of a value (just like a variable in programming language), which is injected by an external program, or is retrived from a *data source* (in case of interations).
166
+
167
+ A *computed node* have following properties.
168
+
169
+ - 'agentId': An **required** property, which specifies the id of the *agent function*.
170
+ - 'inputs': An optional list of *data sources* that the current node depends on. This establishes a flow where the current node can only be executed after the completion of the nodes listed under 'inputs'. If this list is empty, the associated *agent function* will be immediatley executed.
139
171
  - 'retry': An optional number, which specifies the maximum number of retries to be made. If the last attempt fails, that return value will be recorded.
140
172
  - 'timeout': An optional number, which specifies the maximum waittime in msec. If the associated agent function does not return the value in time, the "Timeout" error will be recorded and the returned value will be discarded.
141
- - 'params': An optional parameters to the associated agent function, which are agent specific.
142
- - 'agentId': An optional parameter, which specifies the id of the agent, when the graph is associated with multiple agents.
173
+ - 'params': An optional property to the associated agent function, which are agent specific.
143
174
  - 'fork': An optional paramter, which specifies the number of concurrent transactions to be created for the current node.
144
- - 'outputs': An optinal property, which specifies the mapping from outputId to nodeId. If this property is set, the node become a special node called "dispatcher". A dispatcher node injects result(s) into specified nodes, enabling the dynamic flow of data.
175
+ - 'outputs' (MAY BECOME OBSOLETE): An optinal property, which specifies the mapping from outputId to nodeId. If this property is set, the node become a special node called *dispatcher*. A *dispatcher* node injects result(s) into specified static nodes, enabling the dynamic flow of data.
176
+
177
+ A *static* node have following properties.
178
+
179
+ - 'value': An optional property, which specifies the value of this static node (equivalent to calling the injectValue method from outside).
180
+ - 'next': An optional property, which specifies the *data source* after each iteration.
145
181
 
146
182
  ## GraphAI class
147
183
 
148
- ### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>)```
184
+ ### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary)```
149
185
  Initializes a new instance of the GraphAI class with the specified graph data and a dictionary of callback functions.
150
186
 
151
187
  - ```data: GraphData```: The graph data including nodes and optional concurrency limit.
@@ -172,7 +208,7 @@ Retrieves all transaction logs recorded during the execution of the graph.
172
208
  Returns: An array of transaction logs detailing the execution states and outcomes of the nodes within the graph.
173
209
 
174
210
  ### ```injectResult(nodeId: string, result: ResultData): void```
175
- Injects a result into a specified node. This is used to manually set the result of a source node, allowing dependent nodes to proceed with execution.
211
+ Injects a result into a specified node. This is used to manually set the result of a static node, allowing dependent nodes to proceed with execution.
176
212
 
177
- - ```nodeId: string```: The ID of the source node into which the result is to be injected.
213
+ - ```nodeId: string```: The ID of the static node into which the result is to be injected.
178
214
  - ```result: ResultData```: The result to be injected into the specified node.
@@ -0,0 +1,4 @@
1
+ import { AgentFunction } from "../graphai";
2
+ export declare const pushAgent: AgentFunction<Record<string, any>, Record<string, any>, Record<string, any>>;
3
+ export declare const popAgent: AgentFunction<Record<string, any>, Record<string, any>, Record<string, any>>;
4
+ export declare const shiftAgent: AgentFunction<Record<string, any>, Record<string, any>, Record<string, any>>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shiftAgent = exports.popAgent = exports.pushAgent = void 0;
7
+ const deepmerge_1 = __importDefault(require("deepmerge"));
8
+ const pushAgent = async ({ inputs }) => {
9
+ const [array, item] = (0, deepmerge_1.default)({ inputs }, {}).inputs;
10
+ // TODO: Validation
11
+ array.push(item);
12
+ return array;
13
+ };
14
+ exports.pushAgent = pushAgent;
15
+ const popAgent = async (context) => {
16
+ const { inputs } = context;
17
+ const [array] = (0, deepmerge_1.default)({ inputs }, {}).inputs;
18
+ // TODO: Varidation
19
+ const item = array.pop();
20
+ return { array, item };
21
+ };
22
+ exports.popAgent = popAgent;
23
+ const shiftAgent = async (context) => {
24
+ const { inputs } = context;
25
+ const [array] = (0, deepmerge_1.default)({ inputs }, {}).inputs;
26
+ // TODO: Varidation
27
+ const item = array.shift();
28
+ return { array, item };
29
+ };
30
+ exports.shiftAgent = shiftAgent;
@@ -3,3 +3,4 @@ export * from "./slashgpt_agent";
3
3
  export * from "./sleeper_agent";
4
4
  export * from "./data_agent";
5
5
  export * from "./nested_agent";
6
+ export * from "./array_agents";
@@ -19,3 +19,4 @@ __exportStar(require("./slashgpt_agent"), exports);
19
19
  __exportStar(require("./sleeper_agent"), exports);
20
20
  __exportStar(require("./data_agent"), exports);
21
21
  __exportStar(require("./nested_agent"), exports);
22
+ __exportStar(require("./array_agents"), exports);
@@ -2,17 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nestedAgent = void 0;
4
4
  const graphai_1 = require("../graphai");
5
- const nestedAgent = async ({ params, inputs, agents }) => {
5
+ const nestedAgent = async ({ params, inputs, agents, log }) => {
6
6
  const graph = new graphai_1.GraphAI(params.graph, agents);
7
7
  try {
8
8
  // Inject inputs to specified source nodes
9
9
  (params.inputNodes ?? []).forEach((nodeId, index) => {
10
- graph.injectResult(nodeId, inputs[index]);
10
+ graph.injectValue(nodeId, inputs[index]);
11
11
  });
12
12
  const results = await graph.run();
13
+ log.push(...graph.transactionLogs());
13
14
  return results[params.nodeId];
14
15
  }
15
16
  catch (error) {
17
+ log.push(...graph.transactionLogs());
16
18
  if (error instanceof Error) {
17
19
  console.log("Error:", error.message);
18
20
  }
@@ -14,6 +14,9 @@ const slashGPTAgent = async ({ nodeId, params, inputs, verbose }) => {
14
14
  const session = new slashgpt_1.ChatSession(config, params.manifest ?? {});
15
15
  const query = params?.query ? [params.query] : [];
16
16
  const contents = query.concat(inputs.map((input) => {
17
+ if (typeof input === "string") {
18
+ return input;
19
+ }
17
20
  return input.content;
18
21
  }));
19
22
  session.append_user_question(contents.join("\n"));
@@ -1,10 +1,10 @@
1
1
  import { AgentFunction } from "../graphai";
2
2
  export declare const sleeperAgent: AgentFunction<{
3
3
  duration: number;
4
- result?: Record<string, any>;
4
+ value?: Record<string, any>;
5
5
  }>;
6
6
  export declare const sleeperAgentDebug: AgentFunction<{
7
7
  duration: number;
8
- result?: Record<string, any>;
8
+ value?: Record<string, any>;
9
9
  fail?: boolean;
10
10
  }>;
@@ -11,7 +11,7 @@ const sleeperAgent = async (context) => {
11
11
  await (0, utils_1.sleep)(params.duration);
12
12
  return inputs.reduce((result, input) => {
13
13
  return (0, deepmerge_1.default)(result, input);
14
- }, params.result ?? {});
14
+ }, params.value ?? {});
15
15
  };
16
16
  exports.sleeperAgent = sleeperAgent;
17
17
  const sleeperAgentDebug = async (context) => {
@@ -23,6 +23,6 @@ const sleeperAgentDebug = async (context) => {
23
23
  }
24
24
  return inputs.reduce((result, input) => {
25
25
  return (0, deepmerge_1.default)(result, input);
26
- }, params.result ?? {});
26
+ }, params.value ?? {});
27
27
  };
28
28
  exports.sleeperAgentDebug = sleeperAgentDebug;
package/lib/graphai.d.ts CHANGED
@@ -18,12 +18,19 @@ type NodeData = {
18
18
  agentId?: string;
19
19
  fork?: number;
20
20
  source?: boolean;
21
- result?: ResultData;
21
+ value?: ResultData;
22
+ next?: string;
22
23
  outputs?: Record<string, string>;
23
24
  };
25
+ type LoopData = {
26
+ count?: number;
27
+ while?: string;
28
+ };
24
29
  export type GraphData = {
30
+ agentId?: string;
25
31
  nodes: Record<string, NodeData>;
26
32
  concurrency?: number;
33
+ loop?: LoopData;
27
34
  verbose?: boolean;
28
35
  };
29
36
  export type TransactionLog = {
@@ -31,14 +38,15 @@ export type TransactionLog = {
31
38
  state: NodeState;
32
39
  startTime: number;
33
40
  endTime?: number;
34
- retryCount: number;
41
+ retryCount?: number;
35
42
  agentId?: string;
36
43
  params?: NodeDataParams;
37
44
  inputs?: Array<ResultData>;
38
45
  errorMessage?: string;
39
46
  result?: ResultData;
47
+ log?: TransactionLog[];
40
48
  };
41
- export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
49
+ export type AgentFunctionContext<ParamsType, PreviousResultType> = {
42
50
  nodeId: string;
43
51
  forkIndex?: number;
44
52
  retry: number;
@@ -46,13 +54,15 @@ export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
46
54
  inputs: Array<PreviousResultType>;
47
55
  verbose: boolean;
48
56
  agents: CallbackDictonaryArgs;
57
+ log: TransactionLog[];
49
58
  };
50
- export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, ResultType, PreviousResultType>) => Promise<ResultData<ResultType>>;
59
+ export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, PreviousResultType>) => Promise<ResultData<ResultType>>;
51
60
  export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
52
61
  declare class Node {
53
62
  nodeId: string;
54
63
  params: NodeDataParams;
55
64
  inputs: Array<string>;
65
+ inputProps: Record<string, string>;
56
66
  pendings: Set<string>;
57
67
  waitlist: Set<string>;
58
68
  state: NodeState;
@@ -73,34 +83,42 @@ declare class Node {
73
83
  private retry;
74
84
  removePending(nodeId: string): void;
75
85
  pushQueueIfReady(): void;
76
- injectResult(result: ResultData): void;
86
+ injectValue(value: ResultData): void;
77
87
  private setResult;
78
88
  execute(): Promise<void>;
79
89
  }
80
90
  type GraphNodes = Record<string, Node>;
81
- export type CallbackDictonaryArgs = AgentFunctionDictonary | AgentFunction<any, any, any>;
91
+ export type CallbackDictonaryArgs = AgentFunctionDictonary;
82
92
  export declare class GraphAI {
93
+ private data;
83
94
  nodes: GraphNodes;
95
+ agentId?: string;
84
96
  callbackDictonary: AgentFunctionDictonary;
85
97
  isRunning: boolean;
86
98
  private runningNodes;
87
99
  private nodeQueue;
88
100
  private onComplete;
89
101
  private concurrency;
102
+ private loop?;
103
+ private repeatCount;
90
104
  verbose: boolean;
91
105
  private logs;
106
+ private createNodes;
107
+ private getValueFromResults;
108
+ private initializeNodes;
92
109
  constructor(data: GraphData, callbackDictonary: CallbackDictonaryArgs);
93
- getCallback(_agentId?: string): AgentFunction<any, any, any>;
110
+ getCallback(agentId?: string): AgentFunction<any, any, any>;
94
111
  asString(): string;
95
112
  results(): ResultDataDictonary<Record<string, any>>;
96
113
  errors(): Record<string, Error>;
114
+ private pushReadyNodesIntoQueue;
97
115
  run(): Promise<ResultDataDictonary>;
98
116
  private runNode;
99
117
  pushQueue(node: Node): void;
100
118
  removeRunning(node: Node): void;
101
119
  appendLog(log: TransactionLog): void;
102
120
  transactionLogs(): TransactionLog[];
103
- injectResult(nodeId: string, result: ResultData): void;
121
+ injectValue(nodeId: string, value: ResultData): void;
104
122
  resultsOf(nodeIds: Array<string>): ResultData<Record<string, any>>[];
105
123
  }
106
124
  export {};
package/lib/graphai.js CHANGED
@@ -11,22 +11,38 @@ var NodeState;
11
11
  NodeState["Injected"] = "injected";
12
12
  NodeState["Dispatched"] = "dispatched";
13
13
  })(NodeState || (exports.NodeState = NodeState = {}));
14
+ const parseNodeName = (name) => {
15
+ const parts = name.split(".");
16
+ if (parts.length == 1) {
17
+ return { sourceNodeId: parts[0] };
18
+ }
19
+ else {
20
+ return { sourceNodeId: parts[0], propId: parts[1] };
21
+ }
22
+ };
14
23
  class Node {
15
24
  constructor(nodeId, forkIndex, data, graph) {
25
+ this.inputProps = {}; // optional properties for input
16
26
  this.waitlist = new Set(); // List of nodes which need data from this node.
17
27
  this.state = NodeState.Waiting;
18
28
  this.result = undefined;
19
29
  this.retryCount = 0;
20
30
  this.nodeId = nodeId;
21
31
  this.forkIndex = forkIndex;
22
- this.inputs = data.inputs ?? [];
32
+ this.inputs = (data.inputs ?? []).map((input) => {
33
+ const { sourceNodeId, propId } = parseNodeName(input);
34
+ if (propId) {
35
+ this.inputProps[sourceNodeId] = propId;
36
+ }
37
+ return sourceNodeId;
38
+ });
23
39
  this.pendings = new Set(this.inputs);
24
40
  this.params = data.params ?? {};
25
- this.agentId = data.agentId;
41
+ this.agentId = data.agentId ?? graph.agentId;
26
42
  this.fork = data.fork;
27
43
  this.retryLimit = data.retry ?? 0;
28
44
  this.timeout = data.timeout;
29
- this.source = data.source === true;
45
+ this.source = this.agentId === undefined;
30
46
  this.outputs = data.outputs;
31
47
  this.graph = graph;
32
48
  }
@@ -54,10 +70,18 @@ class Node {
54
70
  }
55
71
  pushQueueIfReady() {
56
72
  if (this.pendings.size === 0 && !this.source) {
73
+ // If input property is specified, we need to ensure that the property value exists.
74
+ Object.keys(this.inputProps).forEach((nodeId) => {
75
+ const [result] = this.graph.resultsOf([nodeId]);
76
+ const propId = this.inputProps[nodeId];
77
+ if (!result || !(propId in result)) {
78
+ return;
79
+ }
80
+ });
57
81
  this.graph.pushQueue(this);
58
82
  }
59
83
  }
60
- injectResult(result) {
84
+ injectValue(value) {
61
85
  if (this.source) {
62
86
  const log = {
63
87
  nodeId: this.nodeId,
@@ -65,13 +89,13 @@ class Node {
65
89
  state: NodeState.Injected,
66
90
  startTime: Date.now(),
67
91
  endTime: Date.now(),
68
- result,
92
+ result: value,
69
93
  };
70
94
  this.graph.appendLog(log);
71
- this.setResult(result, NodeState.Injected);
95
+ this.setResult(value, NodeState.Injected);
72
96
  }
73
97
  else {
74
- console.error("- injectResult called on non-source node.", this.nodeId);
98
+ console.error("- injectValue called on non-source node.", this.nodeId);
75
99
  }
76
100
  }
77
101
  setResult(result, state) {
@@ -85,15 +109,21 @@ class Node {
85
109
  }
86
110
  async execute() {
87
111
  const results = this.graph.resultsOf(this.inputs);
112
+ this.inputs.forEach((nodeId, index) => {
113
+ const propId = this.inputProps[nodeId];
114
+ if (propId) {
115
+ results[index] = results[index][propId];
116
+ }
117
+ });
88
118
  const transactionId = Date.now();
89
119
  const log = {
90
120
  nodeId: this.nodeId,
91
- retryCount: this.retryCount,
121
+ retryCount: this.retryCount > 0 ? this.retryCount : undefined,
92
122
  state: NodeState.Executing,
93
123
  startTime: transactionId,
94
124
  agentId: this.agentId,
95
125
  params: this.params,
96
- inputs: results,
126
+ inputs: results.length > 0 ? results : undefined,
97
127
  };
98
128
  this.graph.appendLog(log);
99
129
  this.state = NodeState.Executing;
@@ -111,6 +141,7 @@ class Node {
111
141
  }
112
142
  try {
113
143
  const callback = this.graph.getCallback(this.agentId);
144
+ const localLog = [];
114
145
  const result = await callback({
115
146
  nodeId: this.nodeId,
116
147
  retry: this.retryCount,
@@ -119,6 +150,7 @@ class Node {
119
150
  forkIndex: this.forkIndex,
120
151
  verbose: this.graph.verbose,
121
152
  agents: this.graph.callbackDictonary,
153
+ log: localLog,
122
154
  });
123
155
  if (this.transactionId !== transactionId) {
124
156
  console.log(`-- ${this.nodeId}: transactionId mismatch`);
@@ -126,11 +158,20 @@ class Node {
126
158
  }
127
159
  log.endTime = Date.now();
128
160
  log.result = result;
161
+ if (localLog.length > 0) {
162
+ log.log = localLog;
163
+ }
129
164
  const outputs = this.outputs;
130
165
  if (outputs !== undefined) {
131
- Object.keys(result).forEach((outputId) => {
166
+ Object.keys(outputs).forEach((outputId) => {
132
167
  const nodeId = outputs[outputId];
133
- this.graph.injectResult(nodeId, result[outputId]);
168
+ const value = result[outputId];
169
+ if (value) {
170
+ this.graph.injectValue(nodeId, value);
171
+ }
172
+ else {
173
+ console.error("-- Invalid outputId", outputId, result);
174
+ }
134
175
  });
135
176
  log.state = NodeState.Dispatched;
136
177
  this.state = NodeState.Dispatched;
@@ -162,21 +203,10 @@ class Node {
162
203
  }
163
204
  const defaultConcurrency = 8;
164
205
  class GraphAI {
165
- constructor(data, callbackDictonary) {
166
- this.isRunning = false;
167
- this.runningNodes = new Set();
168
- this.nodeQueue = [];
169
- this.logs = [];
170
- this.callbackDictonary = typeof callbackDictonary === "function" ? { _default: callbackDictonary } : callbackDictonary;
171
- this.concurrency = data.concurrency ?? defaultConcurrency;
172
- this.verbose = data.verbose === true;
173
- this.onComplete = () => {
174
- console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
175
- };
206
+ createNodes(data) {
176
207
  const nodeId2forkedNodeIds = {};
177
208
  const forkedNodeId2Index = {};
178
- // Create node instances from data.nodes
179
- this.nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
209
+ const nodes = Object.keys(data.nodes).reduce((nodes, nodeId) => {
180
210
  const fork = data.nodes[nodeId].fork;
181
211
  if (fork) {
182
212
  // For fork, change the nodeId and increase the node
@@ -194,8 +224,8 @@ class GraphAI {
194
224
  return nodes;
195
225
  }, {});
196
226
  // Generate the waitlist for each node, and update the pendings in case of forked node.
197
- Object.keys(this.nodes).forEach((nodeId) => {
198
- const node = this.nodes[nodeId];
227
+ Object.keys(nodes).forEach((nodeId) => {
228
+ const node = nodes[nodeId];
199
229
  node.pendings.forEach((pending) => {
200
230
  // If the pending(previous) node is forking
201
231
  if (nodeId2forkedNodeIds[pending]) {
@@ -203,36 +233,74 @@ class GraphAI {
203
233
  if (node.fork) {
204
234
  // 1:1 if current nodes are also forking.
205
235
  const newPendingId = nodeId2forkedNodeIds[pending][forkedNodeId2Index[nodeId]];
206
- this.nodes[newPendingId].waitlist.add(nodeId); // previousNode
236
+ nodes[newPendingId].waitlist.add(nodeId); // previousNode
207
237
  node.pendings.add(newPendingId);
208
238
  }
209
239
  else {
210
240
  // 1:n if current node is not forking.
211
241
  nodeId2forkedNodeIds[pending].forEach((newPendingId) => {
212
- this.nodes[newPendingId].waitlist.add(nodeId); // previousNode
242
+ nodes[newPendingId].waitlist.add(nodeId); // previousNode
213
243
  node.pendings.add(newPendingId);
214
244
  });
215
245
  }
216
246
  node.pendings.delete(pending);
217
247
  }
218
248
  else {
219
- this.nodes[pending].waitlist.add(nodeId); // previousNode
249
+ if (nodes[pending]) {
250
+ nodes[pending].waitlist.add(nodeId); // previousNode
251
+ }
252
+ else {
253
+ console.error(`--- invalid input ${pending} for node, ${nodeId}`);
254
+ }
220
255
  }
221
256
  });
222
257
  node.inputs = Array.from(node.pendings); // for fork.
223
258
  });
259
+ return nodes;
260
+ }
261
+ getValueFromResults(key, results) {
262
+ const { sourceNodeId, propId } = parseNodeName(key);
263
+ const result = results[sourceNodeId];
264
+ return result ? (propId ? result[propId] : result) : undefined;
265
+ }
266
+ initializeNodes(previousResults) {
224
267
  // If the result property is specified, inject it.
225
- // NOTE: This must be done at the end of this constructor
226
- Object.keys(data.nodes).forEach((nodeId) => {
227
- const result = data.nodes[nodeId].result;
228
- if (result) {
229
- this.injectResult(nodeId, result);
268
+ // If the previousResults exists (indicating we are in a loop),
269
+ // process the next property (nodeId or nodeId.propId).
270
+ Object.keys(this.data.nodes).forEach((nodeId) => {
271
+ const node = this.data.nodes[nodeId];
272
+ const { value, next } = node;
273
+ if (value) {
274
+ this.injectValue(nodeId, value);
275
+ }
276
+ if (next && previousResults) {
277
+ const result = this.getValueFromResults(next, previousResults);
278
+ if (result) {
279
+ this.injectValue(nodeId, result);
280
+ }
230
281
  }
231
282
  });
232
283
  }
233
- getCallback(_agentId) {
234
- const agentId = _agentId ?? "_default";
235
- if (this.callbackDictonary[agentId]) {
284
+ constructor(data, callbackDictonary) {
285
+ this.isRunning = false;
286
+ this.runningNodes = new Set();
287
+ this.nodeQueue = [];
288
+ this.repeatCount = 0;
289
+ this.logs = [];
290
+ this.data = data;
291
+ this.callbackDictonary = callbackDictonary;
292
+ this.concurrency = data.concurrency ?? defaultConcurrency;
293
+ this.loop = data.loop;
294
+ this.agentId = data.agentId;
295
+ this.verbose = data.verbose === true;
296
+ this.onComplete = () => {
297
+ console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
298
+ };
299
+ this.nodes = this.createNodes(data);
300
+ this.initializeNodes();
301
+ }
302
+ getCallback(agentId) {
303
+ if (agentId && this.callbackDictonary[agentId]) {
236
304
  return this.callbackDictonary[agentId];
237
305
  }
238
306
  throw new Error("No agent: " + agentId);
@@ -262,16 +330,19 @@ class GraphAI {
262
330
  return errors;
263
331
  }, {});
264
332
  }
265
- async run() {
266
- if (this.isRunning) {
267
- console.error("-- Already Running");
268
- }
269
- this.isRunning = true;
333
+ pushReadyNodesIntoQueue() {
270
334
  // Nodes without pending data should run immediately.
271
335
  Object.keys(this.nodes).forEach((nodeId) => {
272
336
  const node = this.nodes[nodeId];
273
337
  node.pushQueueIfReady();
274
338
  });
339
+ }
340
+ async run() {
341
+ if (this.isRunning) {
342
+ console.error("-- Already Running");
343
+ }
344
+ this.isRunning = true;
345
+ this.pushReadyNodesIntoQueue();
275
346
  return new Promise((resolve, reject) => {
276
347
  this.onComplete = () => {
277
348
  this.isRunning = false;
@@ -307,6 +378,27 @@ class GraphAI {
307
378
  }
308
379
  }
309
380
  if (this.runningNodes.size === 0) {
381
+ this.repeatCount++;
382
+ const loop = this.loop;
383
+ if (loop && (loop.count === undefined || this.repeatCount < loop.count)) {
384
+ const results = this.results(); // results from previous loop
385
+ this.isRunning = false; // temporarily stop it
386
+ this.nodes = this.createNodes(this.data);
387
+ this.initializeNodes(results);
388
+ const checkWhileCondition = () => {
389
+ if (loop.while) {
390
+ const value = this.getValueFromResults(loop.while, this.results());
391
+ // NOTE: We treat an empty array as false.
392
+ return Array.isArray(value) ? value.length > 0 : !!value;
393
+ }
394
+ return true;
395
+ };
396
+ if (checkWhileCondition()) {
397
+ this.isRunning = true; // restore it
398
+ this.pushReadyNodesIntoQueue();
399
+ return;
400
+ }
401
+ }
310
402
  this.onComplete();
311
403
  }
312
404
  }
@@ -316,10 +408,10 @@ class GraphAI {
316
408
  transactionLogs() {
317
409
  return this.logs;
318
410
  }
319
- injectResult(nodeId, result) {
411
+ injectValue(nodeId, value) {
320
412
  const node = this.nodes[nodeId];
321
413
  if (node) {
322
- node.injectResult(result);
414
+ node.injectValue(value);
323
415
  }
324
416
  else {
325
417
  console.error("-- Invalid nodeId", nodeId);
@@ -9,7 +9,7 @@ const experimental_agents_1 = require("./experimental_agents");
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const yaml_1 = __importDefault(require("yaml"));
12
- const testAgent = async (context) => {
12
+ const testAgent = async () => {
13
13
  return {};
14
14
  };
15
15
  const main = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphai",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Asynchronous data flow execution engine to make it simple to build LLM apps.",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "build": "tsc && tsc-alias",
14
- "eslint": "eslint --fix --ext src/**/*.{ts} tests/**/*.ts",
14
+ "eslint": "eslint --fix --ext .ts ./src ./tests ./samples",
15
15
  "format": "prettier --write '{src,tests,samples}/**/*.ts' .eslintrc.js",
16
16
  "test": "node --test -r tsconfig-paths/register --require ts-node/register ./tests/**/test_*.ts",
17
17
  "cli": "npx ts-node -r tsconfig-paths/register ./src/graphai_cli.ts",