graphai 0.0.6 → 0.0.8

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
  ...
@@ -36,41 +36,72 @@ const sampleAgentFunction = async (context: AgentFunctionContext) => {
36
36
 
37
37
  ...
38
38
  const file = fs.readFileSync(pathToYamlFile, "utf8");
39
- const graphdata = YAML.parse(file);
40
- const graph = new GraphAI(graph_data, sampleAgentFunction);
39
+ const graphData = YAML.parse(file);
40
+ const graph = new GraphAI(graphData, sampleAgentFunction);
41
41
  const results = await graph.run();
42
42
  return results["taskC"];
43
43
  ```
44
44
 
45
45
  ## Data Flow Graph
46
46
 
47
- A Data Flow Graph (DFG) is a JSON object, which defines the flow of data. It is typically described in YAML file and loaded at runtime.
47
+ 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
48
 
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.
49
+ A DFG consists of a collection of 'nodes', which contains a series of nested keys representing individual nodes in the data flow. Each node is identified by a unique key (e.g., node1, node2) and can contain several predefined keys (params, inputs, outputs, retry, timeout, source, agentId) that dictate the node's behavior and its relationship with other nodes.
50
50
 
51
51
  ### DFG Structure
52
52
 
53
- - 'nodes': A list of node.
54
- - 'concurrency': An optional number, which specifies the maximum number of concurrent operations (agent functions to be executed at the same time). The default is 8.
53
+ - 'nodes': A list of node. Required.
54
+ - '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.
55
55
 
56
56
  ## Agent
57
57
 
58
- An agent is an abstract object, which takes some inputs and generates an output asynchronously. It could be an LLM (such as GPT-4), an image/video/music generation, a database, or a REST API over HTTP. Each node (except 'source node') is associated with an agent function, which takes data flow into the node as inputs, and generates an output.
58
+ An agent is an abstract object, which takes some inputs and generates an output asynchronously. It could be an LLM (such as GPT-4), an image/video/music generator, a database, or a REST API over HTTP. Each node (except 'source node') is associated with an agent function, which takes data flow into the node as inputs, and generates an output.
59
59
 
60
60
  ## Agent function
61
61
 
62
62
  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
63
 
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>>```).
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.
67
65
 
68
66
  ## Node Structure
69
67
 
70
68
  - '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
69
  - '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'.
70
+ - '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.
73
71
  - '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
72
  - '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
73
  - '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.
74
+
75
+ ## GraphAI class
76
+
77
+ ### ```constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>)```
78
+ Initializes a new instance of the GraphAI class with the specified graph data and a dictionary of callback functions.
79
+
80
+ - ```data: GraphData```: The graph data including nodes and optional concurrency limit.
81
+ - ```callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>```: A dictionary mapping agent IDs to their respective callback functions, or a single default callback function to be used for all nodes.
82
+
83
+ ### ```async run(): Promise<ResultDataDictonary<ResultData>>```
84
+ Executes the graph asynchronously, starting with nodes that have no dependencies or whose dependencies have been met. The method continues to execute nodes as their dependencies are satisfied until all nodes have been executed or an error occurs.
85
+
86
+ Returns: A promise that resolves with the results of all executed nodes or rejects with the first encountered error.
87
+
88
+ ### ```results(): ResultDataDictonary<ResultData>```
89
+ Compiles and returns the results of all executed nodes in the graph.
90
+
91
+ Returns: A dictionary mapping node IDs to their results. Only includes nodes that have completed execution and produced a result.
92
+
93
+ ### ```errors(): Record<string, Error>```
94
+ Compiles and returns the errors from all nodes that encountered an error during execution.
95
+
96
+ Returns: A dictionary mapping node IDs to the errors they encountered. Only includes nodes that have executed and encountered an error. It does not include any errors which have been retried.
97
+
98
+ ### ```transactionLogs(): Array<TransactionLog>```
99
+ Retrieves all transaction logs recorded during the execution of the graph.
100
+
101
+ Returns: An array of transaction logs detailing the execution states and outcomes of the nodes within the graph.
102
+
103
+ ### ```injectResult(nodeId: string, result: ResultData): void```
104
+ 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.
105
+
106
+ - ```nodeId: string```: The ID of the source node into which the result is to be injected.
107
+ - ```result: ResultData```: The result to be injected into the specified node.
package/lib/graphai.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export declare enum NodeState {
2
- Waiting = 0,
3
- Executing = 1,
4
- Failed = 2,
5
- TimedOut = 3,
6
- Completed = 4,
7
- Injected = 5,
8
- Dispatched = 6
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>>;
@@ -13,12 +13,11 @@ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType;
13
13
  type NodeData = {
14
14
  inputs?: Array<string>;
15
15
  params: NodeDataParams;
16
- payloadMapping?: Record<string, string>;
17
16
  retry?: number;
18
17
  timeout?: number;
19
18
  agentId?: string;
20
19
  source?: boolean;
21
- dispatch?: Record<string, string>;
20
+ outputs?: Record<string, string>;
22
21
  };
23
22
  export type GraphData = {
24
23
  nodes: Record<string, NodeData>;
@@ -32,7 +31,7 @@ export type TransactionLog = {
32
31
  retryCount: number;
33
32
  agentId?: string;
34
33
  params?: NodeDataParams;
35
- payload?: ResultDataDictonary<ResultData>;
34
+ inputs?: Array<ResultData>;
36
35
  errorMessage?: string;
37
36
  result?: ResultData;
38
37
  };
@@ -40,7 +39,7 @@ export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
40
39
  nodeId: string;
41
40
  retry: number;
42
41
  params: NodeDataParams<ParamsType>;
43
- payload: ResultDataDictonary<PreviousResultType>;
42
+ inputs: Array<PreviousResultType>;
44
43
  };
45
44
  export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (context: AgentFunctionContext<ParamsType, ResultType, PreviousResultType>) => Promise<ResultData<ResultType>>;
46
45
  export type AgentFunctionDictonary = Record<string, AgentFunction<any, any, any>>;
@@ -48,11 +47,10 @@ declare class Node {
48
47
  nodeId: string;
49
48
  params: NodeDataParams;
50
49
  inputs: Array<string>;
51
- payloadMapping: Record<string, string>;
52
50
  pendings: Set<string>;
53
51
  waitlist: Set<string>;
54
52
  state: NodeState;
55
- agentId: string;
53
+ agentId?: string;
56
54
  result: ResultData;
57
55
  retryLimit: number;
58
56
  retryCount: number;
@@ -60,13 +58,12 @@ declare class Node {
60
58
  timeout?: number;
61
59
  error?: Error;
62
60
  source: boolean;
63
- dispatch?: Record<string, string>;
61
+ outputs?: Record<string, string>;
64
62
  private graph;
65
63
  constructor(nodeId: string, data: NodeData, graph: GraphAI);
66
64
  asString(): string;
67
65
  private retry;
68
66
  removePending(nodeId: string): void;
69
- payload(): ResultDataDictonary<Record<string, any>>;
70
67
  pushQueueIfReady(): void;
71
68
  injectResult(result: ResultData): void;
72
69
  private setResult;
@@ -83,7 +80,7 @@ export declare class GraphAI {
83
80
  private concurrency;
84
81
  private logs;
85
82
  constructor(data: GraphData, callbackDictonary: AgentFunctionDictonary | AgentFunction<any, any, any>);
86
- getCallback(agentId: string): AgentFunction<any, any, any>;
83
+ getCallback(_agentId?: string): AgentFunction<any, any, any>;
87
84
  asString(): string;
88
85
  results(): ResultDataDictonary<Record<string, any>>;
89
86
  errors(): Record<string, Error>;
@@ -94,5 +91,6 @@ export declare class GraphAI {
94
91
  appendLog(log: TransactionLog): void;
95
92
  transactionLogs(): TransactionLog[];
96
93
  injectResult(nodeId: string, result: ResultData): void;
94
+ resultsOf(nodeIds: Array<string>): ResultData<Record<string, any>>[];
97
95
  }
98
96
  export {};
package/lib/graphai.js CHANGED
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GraphAI = exports.NodeState = void 0;
4
4
  var NodeState;
5
5
  (function (NodeState) {
6
- NodeState[NodeState["Waiting"] = 0] = "Waiting";
7
- NodeState[NodeState["Executing"] = 1] = "Executing";
8
- NodeState[NodeState["Failed"] = 2] = "Failed";
9
- NodeState[NodeState["TimedOut"] = 3] = "TimedOut";
10
- NodeState[NodeState["Completed"] = 4] = "Completed";
11
- NodeState[NodeState["Injected"] = 5] = "Injected";
12
- NodeState[NodeState["Dispatched"] = 6] = "Dispatched";
6
+ NodeState["Waiting"] = "waiting";
7
+ NodeState["Executing"] = "executing";
8
+ NodeState["Failed"] = "failed";
9
+ NodeState["TimedOut"] = "timed-out";
10
+ NodeState["Completed"] = "completed";
11
+ NodeState["Injected"] = "injected";
12
+ NodeState["Dispatched"] = "dispatched";
13
13
  })(NodeState || (exports.NodeState = NodeState = {}));
14
14
  class Node {
15
15
  constructor(nodeId, data, graph) {
@@ -19,14 +19,13 @@ class Node {
19
19
  this.retryCount = 0;
20
20
  this.nodeId = nodeId;
21
21
  this.inputs = data.inputs ?? [];
22
- this.payloadMapping = data.payloadMapping ?? {};
23
22
  this.pendings = new Set(this.inputs);
24
23
  this.params = data.params;
25
- this.agentId = data.agentId ?? "default";
24
+ this.agentId = data.agentId;
26
25
  this.retryLimit = data.retry ?? 0;
27
26
  this.timeout = data.timeout;
28
27
  this.source = data.source === true;
29
- this.dispatch = data.dispatch;
28
+ this.outputs = data.outputs;
30
29
  this.graph = graph;
31
30
  }
32
31
  asString() {
@@ -51,17 +50,6 @@ class Node {
51
50
  this.pushQueueIfReady();
52
51
  }
53
52
  }
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
53
  pushQueueIfReady() {
66
54
  if (this.pendings.size === 0 && !this.source) {
67
55
  this.graph.pushQueue(this);
@@ -74,6 +62,7 @@ class Node {
74
62
  retryCount: this.retryCount,
75
63
  state: NodeState.Injected,
76
64
  startTime: Date.now(),
65
+ result,
77
66
  };
78
67
  log.endTime = log.startTime;
79
68
  this.graph.appendLog(log);
@@ -93,7 +82,6 @@ class Node {
93
82
  });
94
83
  }
95
84
  async execute() {
96
- const payload = this.payload();
97
85
  const log = {
98
86
  nodeId: this.nodeId,
99
87
  retryCount: this.retryCount,
@@ -101,8 +89,11 @@ class Node {
101
89
  startTime: Date.now(),
102
90
  agentId: this.agentId,
103
91
  params: this.params,
104
- payload,
105
92
  };
93
+ const results = this.graph.resultsOf(this.inputs);
94
+ if (results.length > 0) {
95
+ log.inputs = results;
96
+ }
106
97
  this.graph.appendLog(log);
107
98
  this.state = NodeState.Executing;
108
99
  const transactionId = log.startTime;
@@ -124,7 +115,7 @@ class Node {
124
115
  nodeId: this.nodeId,
125
116
  retry: this.retryCount,
126
117
  params: this.params,
127
- payload,
118
+ inputs: results,
128
119
  });
129
120
  if (this.transactionId !== transactionId) {
130
121
  console.log(`-- ${this.nodeId}: transactionId mismatch`);
@@ -132,10 +123,10 @@ class Node {
132
123
  }
133
124
  log.endTime = Date.now();
134
125
  log.result = result;
135
- const dispatch = this.dispatch;
136
- if (dispatch !== undefined) {
126
+ const outputs = this.outputs;
127
+ if (outputs !== undefined) {
137
128
  Object.keys(result).forEach((outputId) => {
138
- const nodeId = dispatch[outputId];
129
+ const nodeId = outputs[outputId];
139
130
  this.graph.injectResult(nodeId, result[outputId]);
140
131
  });
141
132
  log.state = NodeState.Dispatched;
@@ -173,10 +164,7 @@ class GraphAI {
173
164
  this.runningNodes = new Set();
174
165
  this.nodeQueue = [];
175
166
  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
- }
167
+ this.callbackDictonary = typeof callbackDictonary === "function" ? { _default: callbackDictonary } : callbackDictonary;
180
168
  this.concurrency = data.concurrency ?? defaultConcurrency;
181
169
  this.onComplete = () => {
182
170
  console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
@@ -194,11 +182,13 @@ class GraphAI {
194
182
  });
195
183
  });
196
184
  }
197
- getCallback(agentId) {
198
- if (agentId && this.callbackDictonary[agentId]) {
185
+ getCallback(_agentId) {
186
+ const agentId = _agentId ?? "_default";
187
+ console.log(agentId);
188
+ if (this.callbackDictonary[agentId]) {
199
189
  return this.callbackDictonary[agentId];
200
190
  }
201
- return this.callbackDictonary["default"];
191
+ throw new Error("No agent: " + agentId);
202
192
  }
203
193
  asString() {
204
194
  return Object.keys(this.nodes)
@@ -288,5 +278,10 @@ class GraphAI {
288
278
  console.error("-- Invalid nodeId", nodeId);
289
279
  }
290
280
  }
281
+ resultsOf(nodeIds) {
282
+ return nodeIds.map((nodeId) => {
283
+ return this.nodes[nodeId].result;
284
+ });
285
+ }
291
286
  }
292
287
  exports.GraphAI = GraphAI;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphai",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Asynchronous data flow execution engine to make it simple to build LLM apps.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -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,46 @@ 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
+ });
17
+ return result[params.result_key];
18
+ };
19
+
20
+ export const slashGPTAgent: AgentFunction<
21
+ { manifest: ManifestData; prompt: string; function_result?: boolean; debug?: boolean },
22
+ { answer: string },
23
+ string
24
+ > = async (context) => {
25
+ const { params } = context;
26
+ if (params.debug) {
27
+ console.log("executing", context.nodeId, context);
28
+ }
29
+ const session = new ChatSession(config, params?.manifest ?? {});
10
30
 
11
- const prompt = [context.params?.prompt, context.payload.inputData].join("\n\n");
31
+ const prompt = params?.prompt ?? [context.inputs].filter((a) => a !== undefined).join("\n\n");
32
+ // console.log(prompt);
12
33
  session.append_user_question(prompt);
13
34
 
14
35
  await session.call_loop(() => {});
15
- const message = session.history.last_message();
36
+
37
+ // console.log(session.history)
38
+ const message = (() => {
39
+ if (params.function_result) {
40
+ return session.history.messages().find((m) => m.role === "function_result");
41
+ }
42
+ return session.history.last_message();
43
+ })();
16
44
  if (message === undefined) {
17
45
  throw new Error("No message in the history");
18
46
  }
19
- const result = { answer: message.content };
47
+ const result = { answer: message.content, function_data: message.function_data };
20
48
  return result;
21
49
  };
@@ -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,103 @@
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
+ params: {
55
+ function_data_key: "methods",
56
+ result_key: 0,
57
+ },
58
+ inputs: ["slashGPTAgent"],
59
+ agentId: "slashGPTFuncitons2TextAgent",
60
+ },
61
+ slashGPTAgent0: {
62
+ agentId: "slashGPTAgent",
63
+ params: {
64
+ debug: true,
65
+ manifest: {
66
+ prompt: "ユーザの問い合わせにある文章の専門家です。専門家として、ユーザのアイデアに対して実現可能なシナリオを800文字で書いてください。",
67
+ },
68
+ },
69
+ inputs: ["function2prompt0"],
70
+ },
71
+ // 1
72
+ function2prompt1: {
73
+ params: {
74
+ debug: true,
75
+ function_data_key: "methods",
76
+ result_key: 1,
77
+ },
78
+ inputs: ["slashGPTAgent"],
79
+ agentId: "slashGPTFuncitons2TextAgent",
80
+ },
81
+ slashGPTAgent1: {
82
+ agentId: "slashGPTAgent",
83
+ params: {
84
+ debug: true,
85
+ manifest: {
86
+ prompt: "ユーザの問い合わせにある文章の専門家です。専門家として、ユーザのアイデアに対して実現可能なシナリオを800文字で書いてください。",
87
+ },
88
+ },
89
+ inputs: ["function2prompt1"],
90
+ },
91
+ },
92
+ };
93
+ const runAgent = async () => {
94
+ const graph = new GraphAI(graph_data, { slashGPTAgent, slashGPTFuncitons2TextAgent });
95
+ const result = await graph.run();
96
+ console.log(result);
97
+ };
98
+
99
+ const main = async () => {
100
+ await runAgent();
101
+ console.log("COMPLETE 1");
102
+ };
103
+ main();
@@ -8,8 +8,8 @@ const config = new ChatConfig(path.resolve(__dirname));
8
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
- const prompt = Object.keys(context.payload).reduce((prompt, key) => {
12
- return prompt.replace("${" + key + "}", context.payload[key]!["answer"]);
11
+ const prompt = context.inputs.reduce((prompt, input, index) => {
12
+ return prompt.replace("${" + index + "}", input["answer"]);
13
13
  }, context.params.prompt);
14
14
  session.append_user_question(prompt);
15
15
 
@@ -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
@@ -1,11 +1,11 @@
1
1
  export enum NodeState {
2
- Waiting,
3
- Executing,
4
- Failed,
5
- TimedOut,
6
- Completed,
7
- Injected,
8
- Dispatched,
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>>;
@@ -15,12 +15,11 @@ export type NodeDataParams<ParamsType = Record<string, any>> = ParamsType; // Ag
15
15
  type NodeData = {
16
16
  inputs?: Array<string>;
17
17
  params: NodeDataParams;
18
- payloadMapping?: Record<string, string>;
19
18
  retry?: number;
20
19
  timeout?: number; // msec
21
20
  agentId?: string;
22
21
  source?: boolean;
23
- dispatch?: Record<string, string>; // route to node
22
+ outputs?: Record<string, string>; // mapping from routeId to nodeId
24
23
  };
25
24
 
26
25
  export type GraphData = {
@@ -36,7 +35,7 @@ export type TransactionLog = {
36
35
  retryCount: number;
37
36
  agentId?: string;
38
37
  params?: NodeDataParams;
39
- payload?: ResultDataDictonary<ResultData>;
38
+ inputs?: Array<ResultData>;
40
39
  errorMessage?: string;
41
40
  result?: ResultData;
42
41
  };
@@ -45,7 +44,7 @@ export type AgentFunctionContext<ParamsType, ResultType, PreviousResultType> = {
45
44
  nodeId: string;
46
45
  retry: number;
47
46
  params: NodeDataParams<ParamsType>;
48
- payload: ResultDataDictonary<PreviousResultType>;
47
+ inputs: Array<PreviousResultType>;
49
48
  };
50
49
 
51
50
  export type AgentFunction<ParamsType = Record<string, any>, ResultType = Record<string, any>, PreviousResultType = Record<string, any>> = (
@@ -58,11 +57,10 @@ class Node {
58
57
  public nodeId: string;
59
58
  public params: NodeDataParams; // Agent-specific parameters
60
59
  public inputs: Array<string>; // List of nodes this node needs data from.
61
- public payloadMapping: Record<string, string>;
62
60
  public pendings: Set<string>; // List of nodes this node is waiting data from.
63
61
  public waitlist = new Set<string>(); // List of nodes which need data from this node.
64
62
  public state = NodeState.Waiting;
65
- public agentId: string;
63
+ public agentId?: string;
66
64
  public result: ResultData = undefined;
67
65
  public retryLimit: number;
68
66
  public retryCount: number = 0;
@@ -70,21 +68,20 @@ class Node {
70
68
  public timeout?: number; // msec
71
69
  public error?: Error;
72
70
  public source: boolean;
73
- public dispatch?: Record<string, string>; // outputId to nodeId mapping
71
+ public outputs?: Record<string, string>; // Mapping from routeId to nodeId
74
72
 
75
73
  private graph: GraphAI;
76
74
 
77
75
  constructor(nodeId: string, data: NodeData, graph: GraphAI) {
78
76
  this.nodeId = nodeId;
79
77
  this.inputs = data.inputs ?? [];
80
- this.payloadMapping = data.payloadMapping ?? {};
81
78
  this.pendings = new Set(this.inputs);
82
79
  this.params = data.params;
83
- this.agentId = data.agentId ?? "default";
80
+ this.agentId = data.agentId;
84
81
  this.retryLimit = data.retry ?? 0;
85
82
  this.timeout = data.timeout;
86
83
  this.source = data.source === true;
87
- this.dispatch = data.dispatch;
84
+ this.outputs = data.outputs;
88
85
  this.graph = graph;
89
86
  }
90
87
 
@@ -112,17 +109,6 @@ class Node {
112
109
  }
113
110
  }
114
111
 
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
112
  public pushQueueIfReady() {
127
113
  if (this.pendings.size === 0 && !this.source) {
128
114
  this.graph.pushQueue(this);
@@ -136,6 +122,7 @@ class Node {
136
122
  retryCount: this.retryCount,
137
123
  state: NodeState.Injected,
138
124
  startTime: Date.now(),
125
+ result,
139
126
  };
140
127
  log.endTime = log.startTime;
141
128
  this.graph.appendLog(log);
@@ -156,7 +143,6 @@ class Node {
156
143
  }
157
144
 
158
145
  public async execute() {
159
- const payload = this.payload();
160
146
  const log: TransactionLog = {
161
147
  nodeId: this.nodeId,
162
148
  retryCount: this.retryCount,
@@ -164,8 +150,11 @@ class Node {
164
150
  startTime: Date.now(),
165
151
  agentId: this.agentId,
166
152
  params: this.params,
167
- payload,
168
153
  };
154
+ const results = this.graph.resultsOf(this.inputs);
155
+ if (results.length > 0) {
156
+ log.inputs = results;
157
+ }
169
158
  this.graph.appendLog(log);
170
159
  this.state = NodeState.Executing;
171
160
  const transactionId = log.startTime;
@@ -189,7 +178,7 @@ class Node {
189
178
  nodeId: this.nodeId,
190
179
  retry: this.retryCount,
191
180
  params: this.params,
192
- payload,
181
+ inputs: results,
193
182
  });
194
183
  if (this.transactionId !== transactionId) {
195
184
  console.log(`-- ${this.nodeId}: transactionId mismatch`);
@@ -199,10 +188,10 @@ class Node {
199
188
  log.endTime = Date.now();
200
189
  log.result = result;
201
190
 
202
- const dispatch = this.dispatch;
203
- if (dispatch !== undefined) {
191
+ const outputs = this.outputs;
192
+ if (outputs !== undefined) {
204
193
  Object.keys(result).forEach((outputId) => {
205
- const nodeId = dispatch[outputId];
194
+ const nodeId = outputs[outputId];
206
195
  this.graph.injectResult(nodeId, result[outputId]);
207
196
  });
208
197
  log.state = NodeState.Dispatched;
@@ -247,10 +236,7 @@ export class GraphAI {
247
236
  private logs: Array<TransactionLog> = [];
248
237
 
249
238
  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
- }
239
+ this.callbackDictonary = typeof callbackDictonary === "function" ? { _default: callbackDictonary } : callbackDictonary;
254
240
  this.concurrency = data.concurrency ?? defaultConcurrency;
255
241
  this.onComplete = () => {
256
242
  console.error("-- SOMETHING IS WRONG: onComplete is called without run()");
@@ -270,11 +256,13 @@ export class GraphAI {
270
256
  });
271
257
  }
272
258
 
273
- public getCallback(agentId: string) {
274
- if (agentId && this.callbackDictonary[agentId]) {
259
+ public getCallback(_agentId?: string) {
260
+ const agentId = _agentId ?? "_default";
261
+ console.log(agentId);
262
+ if (this.callbackDictonary[agentId]) {
275
263
  return this.callbackDictonary[agentId];
276
264
  }
277
- return this.callbackDictonary["default"];
265
+ throw new Error("No agent: " + agentId);
278
266
  }
279
267
 
280
268
  public asString() {
@@ -372,4 +360,10 @@ export class GraphAI {
372
360
  console.error("-- Invalid nodeId", nodeId);
373
361
  }
374
362
  }
363
+
364
+ public resultsOf(nodeIds: Array<string>) {
365
+ return nodeIds.map((nodeId) => {
366
+ return this.nodes[nodeId].result;
367
+ });
368
+ }
375
369
  }
@@ -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" },
@@ -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" },
@@ -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