graphai 0.5.14 → 0.5.15
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 +134 -85
- package/README.template.md +324 -0
- package/lib/graphai.d.ts +2 -1
- package/lib/graphai.js +2 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -1
- package/lib/node.js +3 -0
- package/lib/type.d.ts +7 -0
- package/lib/utils/data_source.d.ts +1 -1
- package/lib/utils/data_source.js +1 -1
- package/lib/utils/prop_function.d.ts +0 -1
- package/lib/utils/prop_function.js +1 -21
- package/lib/validators/common.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,91 +2,125 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
GraphAI is an asynchronous
|
|
5
|
+
GraphAI is an asynchronous dataflow execution engine, which allows developers to build *agentic applications* by describing *agent workflows* as declarative dataflow graphs in YAML or JSON.
|
|
6
6
|
|
|
7
7
|
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.'
|
|
8
8
|
|
|
9
9
|
Such *agentic applications* require making multiple asynchronous API calls (e.g., OpenAI's chat-completion API, database queries, web searches) and managing data dependencies among them. As the complexity of the application increases, managing these dependencies in a traditional programming style becomes challenging due to the asynchronous nature of the APIs.
|
|
10
10
|
|
|
11
|
-
GraphAI allows developers to describe dependencies among those agents (asynchronous API calls) in a
|
|
11
|
+
GraphAI allows developers to describe dependencies among those agents (asynchronous API calls) in a dataflow graph in YAML or JSON, which is called *declarative dataflow programming* . The GraphAI engine will take care of all the complexity of concurrent asynchronous calls, data dependency management, task priority management, map-reduce processing, error handling, retries and logging.
|
|
12
12
|
|
|
13
|
-
## Declarative
|
|
13
|
+
## Declarative Dataflow Programming
|
|
14
14
|
|
|
15
15
|
Here is a simple example, which uses the Wikipedia as the data source and perform an in-memory RAG (Retrieval-Augmented Generation).
|
|
16
16
|
|
|
17
17
|
```YAML
|
|
18
|
+
version: 0.5
|
|
18
19
|
nodes:
|
|
19
|
-
source:
|
|
20
|
+
source:
|
|
20
21
|
value:
|
|
21
22
|
name: Sam Bankman-Fried
|
|
23
|
+
topic: sentence by the court
|
|
22
24
|
query: describe the final sentence by the court for Sam Bank-Fried
|
|
23
|
-
wikipedia:
|
|
25
|
+
wikipedia:
|
|
26
|
+
console:
|
|
27
|
+
before: ...fetching data from wikkpedia
|
|
24
28
|
agent: wikipediaAgent
|
|
25
29
|
inputs:
|
|
26
30
|
query: :source.name
|
|
27
|
-
|
|
31
|
+
params:
|
|
32
|
+
lang: en
|
|
33
|
+
chunks:
|
|
34
|
+
console:
|
|
35
|
+
before: ...splitting the article into chunks
|
|
28
36
|
agent: stringSplitterAgent
|
|
29
37
|
inputs:
|
|
30
|
-
text: :wikipedia
|
|
31
|
-
chunkEmbeddings:
|
|
38
|
+
text: :wikipedia.content
|
|
39
|
+
chunkEmbeddings:
|
|
40
|
+
console:
|
|
41
|
+
before: ...fetching embeddings for chunks
|
|
32
42
|
agent: stringEmbeddingsAgent
|
|
33
43
|
inputs:
|
|
34
|
-
array: :chunks
|
|
35
|
-
topicEmbedding:
|
|
44
|
+
array: :chunks.contents
|
|
45
|
+
topicEmbedding:
|
|
46
|
+
console:
|
|
47
|
+
before: ...fetching embedding for the topic
|
|
36
48
|
agent: stringEmbeddingsAgent
|
|
37
49
|
inputs:
|
|
38
|
-
item: :source.
|
|
39
|
-
similarities:
|
|
50
|
+
item: :source.topic
|
|
51
|
+
similarities:
|
|
40
52
|
agent: dotProductAgent
|
|
41
53
|
inputs:
|
|
42
54
|
matrix: :chunkEmbeddings
|
|
43
|
-
vector: :topicEmbedding
|
|
44
|
-
sortedChunks:
|
|
55
|
+
vector: :topicEmbedding.$0
|
|
56
|
+
sortedChunks:
|
|
45
57
|
agent: sortByValuesAgent
|
|
46
58
|
inputs:
|
|
47
|
-
array: :chunks
|
|
59
|
+
array: :chunks.contents
|
|
48
60
|
values: :similarities
|
|
49
|
-
referenceText:
|
|
61
|
+
referenceText:
|
|
50
62
|
agent: tokenBoundStringsAgent
|
|
51
63
|
inputs:
|
|
52
|
-
|
|
64
|
+
chunks: :sortedChunks
|
|
53
65
|
params:
|
|
54
66
|
limit: 5000
|
|
55
|
-
prompt:
|
|
67
|
+
prompt:
|
|
56
68
|
agent: stringTemplateAgent
|
|
57
69
|
inputs:
|
|
58
|
-
prompt: :source
|
|
59
|
-
text: :referenceText
|
|
70
|
+
prompt: :source.query
|
|
71
|
+
text: :referenceText.content
|
|
60
72
|
params:
|
|
61
73
|
template: |-
|
|
62
|
-
Using the following document, ${
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
isResult: true // indicating this is the final result
|
|
74
|
+
Using the following document, ${text}
|
|
75
|
+
|
|
76
|
+
${prompt}
|
|
77
|
+
RagQuery:
|
|
78
|
+
console:
|
|
79
|
+
before: ...performing the RAG query
|
|
80
|
+
agent: openAIAgent
|
|
70
81
|
inputs:
|
|
71
82
|
prompt: :prompt
|
|
83
|
+
params:
|
|
84
|
+
model: gpt-4o
|
|
85
|
+
OneShotQuery:
|
|
86
|
+
agent: openAIAgent
|
|
87
|
+
inputs:
|
|
88
|
+
prompt: :source.query
|
|
89
|
+
params:
|
|
90
|
+
model: gpt-4o
|
|
91
|
+
RagResult:
|
|
92
|
+
agent: copyAgent
|
|
93
|
+
inputs:
|
|
94
|
+
result: :RagQuery.text
|
|
95
|
+
isResult: true
|
|
96
|
+
OneShotResult:
|
|
97
|
+
agent: copyAgent
|
|
98
|
+
inputs:
|
|
99
|
+
result: :OneShotQuery.text
|
|
100
|
+
isResult: true
|
|
101
|
+
|
|
72
102
|
```
|
|
73
103
|
|
|
74
104
|
```mermaid
|
|
75
105
|
flowchart TD
|
|
76
106
|
source -- name --> wikipedia(wikipedia)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
wikipedia -- content --> chunks(chunks)
|
|
108
|
+
chunks -- contents --> chunkEmbeddings(chunkEmbeddings)
|
|
109
|
+
source -- topic --> topicEmbedding(topicEmbedding)
|
|
80
110
|
chunkEmbeddings --> similarities(similarities)
|
|
81
|
-
topicEmbedding --> similarities
|
|
111
|
+
topicEmbedding -- $0 --> similarities
|
|
82
112
|
similarities --> sortedChunks(sortedChunks)
|
|
113
|
+
chunks -- contents --> sortedChunks
|
|
83
114
|
sortedChunks --> referenceText(resourceText)
|
|
115
|
+
referenceText -- content --> prompt
|
|
84
116
|
source -- query --> prompt(prompt)
|
|
85
|
-
|
|
86
|
-
|
|
117
|
+
prompt --> RagQuery(RagQuery)
|
|
118
|
+
source -- query --> OneShotQuery(OneShotQuery)
|
|
119
|
+
RagQuery -- text --> RagResult(RagResult)
|
|
120
|
+
OneShotQuery -- text --> OneShotResult(OneShotResult)
|
|
87
121
|
```
|
|
88
122
|
|
|
89
|
-
Notice that the conversion of the query text into an embedding vector and text chunks into an array of embedding vectors can be done concurrently because there is no dependency between them. GraphAI will automatically recognize it and execute them concurrently. This kind of *concurrent
|
|
123
|
+
Notice that the conversion of the query text into an embedding vector and text chunks into an array of embedding vectors can be done concurrently because there is no dependency between them. GraphAI will automatically recognize it and execute them concurrently. This kind of *concurrent programming* is very difficult in traditional programming style, and GraphAI's *dataflow programming* style is much better alternative.
|
|
90
124
|
|
|
91
125
|
## Quick Install
|
|
92
126
|
|
|
@@ -100,28 +134,28 @@ or
|
|
|
100
134
|
yarn add graphai
|
|
101
135
|
```
|
|
102
136
|
|
|
103
|
-
##
|
|
137
|
+
## Dataflow Graph
|
|
104
138
|
|
|
105
|
-
A
|
|
139
|
+
A Dataflow Graph (DFG) is a JavaScript object, which defines the flow of data. It is described in YAML or JSON and loaded at runtime.
|
|
106
140
|
|
|
107
|
-
A DFG consists of a collection of [nodes](#node), which contains a series of nested properties representing individual nodes in the
|
|
141
|
+
A DFG consists of a collection of [nodes](#node), which contains a series of nested properties representing individual nodes in the dataflow. Each node is identified by a unique key, *nodeId* (e.g., node1, node2) and can contain several predefined properties (such as params, inputs, and value) that dictate the node's behavior and its relationship with other nodes. There are two types of nodes, [computed nodes](#computed-node) and [static nodes](#static-node), which are described below.
|
|
108
142
|
|
|
109
143
|
### Data Source
|
|
110
144
|
|
|
111
|
-
Connections between nodes will be established by references from one node to another, using either its "inputs", "update", "if" or "while" property. The values of those properties are *data sources*. A *data souce* is specified by either the ":" + nodeId (e.g., ":node1"), or ":" + nodeId + propertyId (e.g., ":node1.item"), index (e.g., ":node1.$0", ":node2.$last") or combinations (e.g.,
|
|
145
|
+
Connections between nodes will be established by references from one node to another, using either its "inputs", "update", "if", "unless" or "while" property. The values of those properties are *data sources*. A *data souce* is specified by either the ":" + nodeId (e.g., ":node1"), or ":" + nodeId + propertyId (e.g., ":node1.item"), index (e.g., ":node1.$0", ":node2.$last") or combinations (e.g., ```:node1.messages.$0.content```).
|
|
112
146
|
|
|
113
147
|
### DFG Structure
|
|
114
148
|
|
|
115
|
-
- *version*: GraphAI version,
|
|
149
|
+
- *version*: GraphAI version, required. The latest version is 0.5.
|
|
116
150
|
- *nodes*: A list of node. Required.
|
|
117
151
|
- *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.
|
|
118
152
|
- *loop*: An optional property, which specifies if the graph needs to be executed multiple times (iterations). See the [Loop section below](#loop) for details.
|
|
119
153
|
|
|
120
154
|
## Agent
|
|
121
155
|
|
|
122
|
-
An *agent* is an abstract object which takes some inputs and generates an output asynchronously. It could be an LLM call (such as GPT-4), a media generator, a database access, or a REST API over HTTP. A node associated with an agent (specified by the *agent*'* property) is called [computed node](#computed-node), which takes a set of *inputs* from *data sources*, asks the *agent function* to process it, and makes the returned value available to other nodes.
|
|
156
|
+
An *agent* is an abstract object which takes some inputs and generates an output asynchronously. It could be an LLM call (such as GPT-4), a media generator, a database access, or a REST API over HTTP. A node associated with an agent (specified by the *agent*'* property) is called [computed node](#computed-node), which takes a set of *inputs* from *data sources*, asks the *agent function* to process it, and makes the returned value available to other nodes as its output.
|
|
123
157
|
|
|
124
|
-
### Agent
|
|
158
|
+
### Agent Function
|
|
125
159
|
|
|
126
160
|
An *agent function* is a TypeScript function, which implements a particular *agent*, performing some computations for the associated *computed node*. An *agent function* receives a *context* (type AgentFunctionContext), which has following properties:
|
|
127
161
|
|
|
@@ -139,18 +173,20 @@ There are additional optional parameters for developers of nested agents and age
|
|
|
139
173
|
|
|
140
174
|
### Inline Agent Function
|
|
141
175
|
|
|
142
|
-
An *inline agent function* is a simplified version of *agent function*, which is embedded in the graph (available only when the graph was described in TypeScript). An *inline agent function* receives
|
|
176
|
+
An *inline agent function* is a simplified version of *agent function*, which is embedded in the graph (available only when the graph was described in TypeScript). An *inline agent function* receives the *inputs* paramter as its only argument.
|
|
143
177
|
|
|
144
|
-
Here is an
|
|
178
|
+
Here is an example:
|
|
145
179
|
|
|
146
180
|
```typescript
|
|
147
181
|
messagesWithUserInput: {
|
|
148
182
|
// Appends the user's input to the messages.
|
|
149
|
-
agent: ({ messages: Array<any>, content: string }) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
agent: ({ messages: Array<any>, content: string }) => {
|
|
184
|
+
return { array:[...messages, { role: "user", content }] };
|
|
185
|
+
},
|
|
186
|
+
inputs: {
|
|
187
|
+
messages: ":messages.array"
|
|
188
|
+
content: ":userInput.text"
|
|
189
|
+
}
|
|
154
190
|
},
|
|
155
191
|
```
|
|
156
192
|
|
|
@@ -168,14 +204,14 @@ A *computed node* has following properties.
|
|
|
168
204
|
|
|
169
205
|
- *agent*: An **required** property, which specifies the id of the *agent function*, or an *inline agent function* (NOTE: this is not possible in JSON or YAML).
|
|
170
206
|
- *params*: An optional agent-specific property to control the behavior of the associated agent function. The top level property may reference a *data source*.
|
|
171
|
-
- *inputs*: An optional list of *data sources* that the current node receives the data from. This establishes a
|
|
172
|
-
- *anyInput*: An
|
|
207
|
+
- *inputs*: An optional list of *data sources* that the current node receives the data from. This establishes a dataflow 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 immediately executed.
|
|
208
|
+
- *anyInput*: An optional boolean flag (default is false), which indicates that the associated *agent function* will be called when at least one of input data became available. Otherwise (default), it will wait until all the data became available.
|
|
173
209
|
- *retry*: An optional number, which specifies the maximum number of retries to be made. If the last attempt fails, the error will be recorded.
|
|
174
210
|
- *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. The returned value received after the time out will be discarded.
|
|
175
211
|
- *isResult*: An optional boolean value, which indicates that the return value of this node, should be included as a property of the return value from the run() method of the GraphUI instance.
|
|
176
212
|
- *priority*: An optional number, which specifies the priority of the execution of the associated agent (the task). Default is 0, which means "neutral". Negative numbers are allowed as well.
|
|
177
213
|
- *if*: An optional data source property. The node will be activated only if the value from the data source is truthy.
|
|
178
|
-
- *unless*: An optional data source property. The node will be activated only if the value from the data source is
|
|
214
|
+
- *unless*: An optional data source property. The node will be activated only if the value from the data source is falsy (including empty array).
|
|
179
215
|
- *graph*: An optional property for nested agents, which specifies the inner graph. This value can be a graph itself or the data souce, whose value is a graph.
|
|
180
216
|
|
|
181
217
|
### Static Node
|
|
@@ -187,7 +223,7 @@ A *static* node has following properties.
|
|
|
187
223
|
|
|
188
224
|
## Flow Control
|
|
189
225
|
|
|
190
|
-
Since the
|
|
226
|
+
Since the dataflow graph must be acyclic by design, we added a few mechanisms to control dataflows, [nesting](#nesting), [loop](#loop), [mapping](#mapping) and [conditional flow](#conditional-flow).
|
|
191
227
|
|
|
192
228
|
### Nested Graph
|
|
193
229
|
|
|
@@ -202,7 +238,7 @@ nodes:
|
|
|
202
238
|
projectId: // identifies the projectId from the question
|
|
203
239
|
agent: identifierAgent
|
|
204
240
|
inputs:
|
|
205
|
-
id: :
|
|
241
|
+
id: :question
|
|
206
242
|
database:
|
|
207
243
|
agent: "nestedAgent"
|
|
208
244
|
inputs:
|
|
@@ -210,9 +246,10 @@ nodes:
|
|
|
210
246
|
projectId: ":projectId"
|
|
211
247
|
graph:
|
|
212
248
|
nodes:
|
|
213
|
-
schema: // retrieves the database schema for the
|
|
249
|
+
schema: // retrieves the database schema for the specified projectId
|
|
214
250
|
agent: "schemaAgent"
|
|
215
|
-
inputs:
|
|
251
|
+
inputs:
|
|
252
|
+
projectId: :projectId
|
|
216
253
|
... // issue query to the database and build an appropriate prompt with it.
|
|
217
254
|
query: // send the generated prompt to the LLM
|
|
218
255
|
agent: "llama3Agent"
|
|
@@ -222,10 +259,10 @@ nodes:
|
|
|
222
259
|
response: // Deliver the answer
|
|
223
260
|
agent: "deliveryAgent"
|
|
224
261
|
inputs:
|
|
225
|
-
text: :database.query
|
|
262
|
+
text: :database.query.text
|
|
226
263
|
```
|
|
227
264
|
|
|
228
|
-
The databaseQuery node (which is associated "nestedAgent") takes the data from "question" node
|
|
265
|
+
The databaseQuery node (which is associated "nestedAgent") takes the data from "question" node and "projectId" node, and make them available to inner nodes (nodes of the child graph) via phantom node, ":question" and ":projectId". After the completion of the child graph, the data from "query" node (which has "isResult" property) becomes available as a property of the output of "database" node.
|
|
229
266
|
|
|
230
267
|
Here is the diagram of the parent graph.
|
|
231
268
|
|
|
@@ -234,20 +271,20 @@ flowchart LR
|
|
|
234
271
|
question --> projectId(projectId)
|
|
235
272
|
question --> database
|
|
236
273
|
projectId --> database
|
|
237
|
-
database[[database]] -- query --> response(response)
|
|
274
|
+
database[[database]] -- query.text --> response(response)
|
|
238
275
|
```
|
|
239
276
|
|
|
240
277
|
Here is the diagram of the child graph. Notice that two phantom nodes are automatically created to allow inner nodes to access input data from the parent graph.
|
|
241
278
|
|
|
242
279
|
```mermaid
|
|
243
280
|
flowchart LR
|
|
244
|
-
|
|
245
|
-
|
|
281
|
+
:question --> ...
|
|
282
|
+
:projectId --> schema(schema)
|
|
246
283
|
schema --> ...(...)
|
|
247
284
|
... --> query(query)
|
|
248
285
|
```
|
|
249
286
|
|
|
250
|
-
This mechanism does not only
|
|
287
|
+
This mechanism does not only allow developer to reuse code, but also make it possible to execute the child graph on another machine using a "remote" agent, enabling the *distributed execution* of nested graphs.
|
|
251
288
|
|
|
252
289
|
### Loop
|
|
253
290
|
|
|
@@ -258,34 +295,40 @@ The loop is an optional property of a graph, which has two optional properties.
|
|
|
258
295
|
|
|
259
296
|
Here is an example, which performs an LLM query for each person in the list and create the list of answers. The "people" node (static), is initialized with an array of names, and the "retriever" node (computed) retrieves one name at a time, and sends it to the "query" node (computed) to perform an LLM query. The "reducer" append it the array retrieved form the "result" node (static node, which is initialized as an empty array).
|
|
260
297
|
|
|
261
|
-
The "update" property of two static nodes ("people" and "result"), updates those properties based on the results from the previous
|
|
298
|
+
The "update" property of two static nodes ("people" and "result"), updates those properties based on the results from the previous iteration. This loop continues until the value of "people" node become an empty array.
|
|
262
299
|
|
|
263
|
-
```
|
|
300
|
+
```YAML
|
|
301
|
+
version: 0.5
|
|
264
302
|
loop:
|
|
265
303
|
while: :people
|
|
266
304
|
nodes:
|
|
267
305
|
people:
|
|
268
|
-
value:
|
|
306
|
+
value:
|
|
307
|
+
- Steve Jobs
|
|
308
|
+
- Elon Musk
|
|
309
|
+
- Nikola Tesla
|
|
269
310
|
update: :retriever.array
|
|
270
311
|
result:
|
|
271
312
|
value: []
|
|
272
|
-
update: :reducer
|
|
313
|
+
update: :reducer.array
|
|
273
314
|
isResult: true
|
|
274
315
|
retriever:
|
|
275
|
-
agent:
|
|
316
|
+
agent: shiftAgent
|
|
276
317
|
inputs:
|
|
277
318
|
array: :people
|
|
278
319
|
query:
|
|
279
|
-
agent:
|
|
320
|
+
agent: openAIAgent
|
|
280
321
|
params:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
inputs:
|
|
322
|
+
system: Describe about the person in less than 100 words
|
|
323
|
+
model: gpt-4o
|
|
324
|
+
inputs:
|
|
325
|
+
prompt: :retriever.item
|
|
284
326
|
reducer:
|
|
285
|
-
agent:
|
|
327
|
+
agent: pushAgent
|
|
286
328
|
inputs:
|
|
287
329
|
array: :result
|
|
288
|
-
item: :query.
|
|
330
|
+
item: :query.text
|
|
331
|
+
|
|
289
332
|
```
|
|
290
333
|
|
|
291
334
|
```mermaid
|
|
@@ -293,14 +336,14 @@ flowchart LR
|
|
|
293
336
|
result --> reducer(reducer)
|
|
294
337
|
people --> retriever(retriever)
|
|
295
338
|
retriever -- item --> query(query)
|
|
296
|
-
query --
|
|
339
|
+
query -- text --> reducer
|
|
297
340
|
retriever -. array .-> people
|
|
298
341
|
reducer -.-> result
|
|
299
342
|
```
|
|
300
343
|
|
|
301
344
|
The *loop* mechanism is often used with a nested graph, which receives an array of data from a node of the parent graph and performs the "reduction" process of a *map-reduce* operation, just like the *reduce* method of JavaScript.
|
|
302
345
|
|
|
303
|
-
Please notice that each iteration will be done
|
|
346
|
+
Please notice that each iteration will be done sequentially unlike the *mapping* described below.
|
|
304
347
|
|
|
305
348
|
### Mapping
|
|
306
349
|
|
|
@@ -310,23 +353,27 @@ If the size of array is N, the mapAgent creates N instances of GraphAI object, a
|
|
|
310
353
|
|
|
311
354
|
After the completion of all of instances, the mapAgent returns an array of results, just like the map function of JavaScript.
|
|
312
355
|
|
|
313
|
-
The following graph will generate the same result (an array of answers) as the sample graph for the *loop*, but three queries will be issued
|
|
356
|
+
The following graph will generate the same result (an array of answers) as the sample graph for the *loop*, but three queries will be issued concurrently.
|
|
314
357
|
|
|
315
|
-
```
|
|
358
|
+
```YAML
|
|
359
|
+
version: 0.5
|
|
316
360
|
nodes:
|
|
317
361
|
people:
|
|
318
362
|
value: [Steve Jobs, Elon Musk, Nikola Tesla]
|
|
319
363
|
retriever:
|
|
320
364
|
agent: "mapAgent"
|
|
321
365
|
inputs: { rows: ":people" }
|
|
366
|
+
isResult: true
|
|
322
367
|
graph:
|
|
323
368
|
nodes:
|
|
324
369
|
query:
|
|
325
|
-
agent:
|
|
370
|
+
agent: openAIAgent
|
|
326
371
|
params:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
372
|
+
system: Describe about the person in less than 100 words
|
|
373
|
+
inputs:
|
|
374
|
+
prompt: ":row"
|
|
375
|
+
isResult: true
|
|
376
|
+
|
|
330
377
|
```
|
|
331
378
|
|
|
332
379
|
Here is the conceptual representation of this operation.
|
|
@@ -342,7 +389,7 @@ flowchart LR
|
|
|
342
389
|
```
|
|
343
390
|
### Conditional Flow
|
|
344
391
|
|
|
345
|
-
GraphAI provides mechanisms to control the flow of data based on certain conditions. This is achieved through the *if* and *anyInput* properties.
|
|
392
|
+
GraphAI provides mechanisms to control the flow of data based on certain conditions. This is achieved through the *if*, *unless* and *anyInput* properties.
|
|
346
393
|
|
|
347
394
|
#### If/Unless Property
|
|
348
395
|
|
|
@@ -354,8 +401,10 @@ For example, the following node will be executed only if the *tool_calls* proper
|
|
|
354
401
|
tool_calls: {
|
|
355
402
|
// This node is activated if the LLM requests a tool call.
|
|
356
403
|
agent: "nestedAgent",
|
|
357
|
-
inputs:
|
|
358
|
-
|
|
404
|
+
inputs: {
|
|
405
|
+
array: [":groq.tool", ":messagesWithFirstRes"]
|
|
406
|
+
},
|
|
407
|
+
if: ":groq.tool",
|
|
359
408
|
graph: {
|
|
360
409
|
// This graph is nested only for the readability.
|
|
361
410
|
```
|
|
@@ -364,7 +413,7 @@ It is recommended to use the *if* property in conjunction with nested graphs for
|
|
|
364
413
|
|
|
365
414
|
#### AnyInput Property
|
|
366
415
|
|
|
367
|
-
The *anyInput* property (boolean) allows you to merge multiple
|
|
416
|
+
The *anyInput* property (boolean) allows you to merge multiple dataflow paths into a single node. When set to *true*, the agent function associated with the node will be executed as soon as data becomes available from any of the specified input data sources.
|
|
368
417
|
|
|
369
418
|
This property is particularly useful when you want to continue the flow regardless of which path the data comes from. In the weather chat sample application, it is used to continue the chat iteration whether a tool was requested by the LLM or not:
|
|
370
419
|
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# GraphAI
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
GraphAI is an asynchronous dataflow execution engine, which allows developers to build *agentic applications* by describing *agent workflows* as declarative dataflow graphs in YAML or JSON.
|
|
6
|
+
|
|
7
|
+
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.'
|
|
8
|
+
|
|
9
|
+
Such *agentic applications* require making multiple asynchronous API calls (e.g., OpenAI's chat-completion API, database queries, web searches) and managing data dependencies among them. As the complexity of the application increases, managing these dependencies in a traditional programming style becomes challenging due to the asynchronous nature of the APIs.
|
|
10
|
+
|
|
11
|
+
GraphAI allows developers to describe dependencies among those agents (asynchronous API calls) in a dataflow graph in YAML or JSON, which is called *declarative dataflow programming* . The GraphAI engine will take care of all the complexity of concurrent asynchronous calls, data dependency management, task priority management, map-reduce processing, error handling, retries and logging.
|
|
12
|
+
|
|
13
|
+
## Declarative Dataflow Programming
|
|
14
|
+
|
|
15
|
+
Here is a simple example, which uses the Wikipedia as the data source and perform an in-memory RAG (Retrieval-Augmented Generation).
|
|
16
|
+
|
|
17
|
+
```YAML
|
|
18
|
+
${packages/samples/graph_data/openai/wikipedia_rag.yaml}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart TD
|
|
23
|
+
source -- name --> wikipedia(wikipedia)
|
|
24
|
+
wikipedia -- content --> chunks(chunks)
|
|
25
|
+
chunks -- contents --> chunkEmbeddings(chunkEmbeddings)
|
|
26
|
+
source -- topic --> topicEmbedding(topicEmbedding)
|
|
27
|
+
chunkEmbeddings --> similarities(similarities)
|
|
28
|
+
topicEmbedding -- $0 --> similarities
|
|
29
|
+
similarities --> sortedChunks(sortedChunks)
|
|
30
|
+
chunks -- contents --> sortedChunks
|
|
31
|
+
sortedChunks --> referenceText(resourceText)
|
|
32
|
+
referenceText -- content --> prompt
|
|
33
|
+
source -- query --> prompt(prompt)
|
|
34
|
+
prompt --> RagQuery(RagQuery)
|
|
35
|
+
source -- query --> OneShotQuery(OneShotQuery)
|
|
36
|
+
RagQuery -- text --> RagResult(RagResult)
|
|
37
|
+
OneShotQuery -- text --> OneShotResult(OneShotResult)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Notice that the conversion of the query text into an embedding vector and text chunks into an array of embedding vectors can be done concurrently because there is no dependency between them. GraphAI will automatically recognize it and execute them concurrently. This kind of *concurrent programming* is very difficult in traditional programming style, and GraphAI's *dataflow programming* style is much better alternative.
|
|
41
|
+
|
|
42
|
+
## Quick Install
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
npm install graphai
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
or
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
yarn add graphai
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Dataflow Graph
|
|
55
|
+
|
|
56
|
+
A Dataflow Graph (DFG) is a JavaScript object, which defines the flow of data. It is described in YAML or JSON and loaded at runtime.
|
|
57
|
+
|
|
58
|
+
A DFG consists of a collection of [nodes](#node), which contains a series of nested properties representing individual nodes in the dataflow. Each node is identified by a unique key, *nodeId* (e.g., node1, node2) and can contain several predefined properties (such as params, inputs, and value) that dictate the node's behavior and its relationship with other nodes. There are two types of nodes, [computed nodes](#computed-node) and [static nodes](#static-node), which are described below.
|
|
59
|
+
|
|
60
|
+
### Data Source
|
|
61
|
+
|
|
62
|
+
Connections between nodes will be established by references from one node to another, using either its "inputs", "update", "if", "unless" or "while" property. The values of those properties are *data sources*. A *data souce* is specified by either the ":" + nodeId (e.g., ":node1"), or ":" + nodeId + propertyId (e.g., ":node1.item"), index (e.g., ":node1.$0", ":node2.$last") or combinations (e.g., ```:node1.messages.$0.content```).
|
|
63
|
+
|
|
64
|
+
### DFG Structure
|
|
65
|
+
|
|
66
|
+
- *version*: GraphAI version, required. The latest version is 0.5.
|
|
67
|
+
- *nodes*: A list of node. Required.
|
|
68
|
+
- *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.
|
|
69
|
+
- *loop*: An optional property, which specifies if the graph needs to be executed multiple times (iterations). See the [Loop section below](#loop) for details.
|
|
70
|
+
|
|
71
|
+
## Agent
|
|
72
|
+
|
|
73
|
+
An *agent* is an abstract object which takes some inputs and generates an output asynchronously. It could be an LLM call (such as GPT-4), a media generator, a database access, or a REST API over HTTP. A node associated with an agent (specified by the *agent*'* property) is called [computed node](#computed-node), which takes a set of *inputs* from *data sources*, asks the *agent function* to process it, and makes the returned value available to other nodes as its output.
|
|
74
|
+
|
|
75
|
+
### Agent Function
|
|
76
|
+
|
|
77
|
+
An *agent function* is a TypeScript function, which implements a particular *agent*, performing some computations for the associated *computed node*. An *agent function* receives a *context* (type AgentFunctionContext), which has following properties:
|
|
78
|
+
|
|
79
|
+
- *params*: agent specific parameters specified in the DFG (specified by the "params" property of the node)
|
|
80
|
+
- *inputs*: a set of inputs came from other nodes (specified by "inputs" property of the node).
|
|
81
|
+
- *debugInfo*: a set of information for debugging purposes.
|
|
82
|
+
|
|
83
|
+
There are additional optional parameters for developers of nested agents and agent filters.
|
|
84
|
+
|
|
85
|
+
- *graphData*: an optional GraphData (for nested agents)
|
|
86
|
+
- *agents*: AgentFunctionInfoDictionary (for nested agents)
|
|
87
|
+
- *taskManager*: TaskManager (for nested agents)
|
|
88
|
+
- *log*: TransactionLog[] (for nested agents)
|
|
89
|
+
- *filterParams*: agent filter parameters (for agent filters)
|
|
90
|
+
|
|
91
|
+
### Inline Agent Function
|
|
92
|
+
|
|
93
|
+
An *inline agent function* is a simplified version of *agent function*, which is embedded in the graph (available only when the graph was described in TypeScript). An *inline agent function* receives the *inputs* paramter as its only argument.
|
|
94
|
+
|
|
95
|
+
Here is an example:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
messagesWithUserInput: {
|
|
99
|
+
// Appends the user's input to the messages.
|
|
100
|
+
agent: ({ messages: Array<any>, content: string }) => {
|
|
101
|
+
return { array:[...messages, { role: "user", content }] };
|
|
102
|
+
},
|
|
103
|
+
inputs: {
|
|
104
|
+
messages: ":messages.array"
|
|
105
|
+
content: ":userInput.text"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Node
|
|
111
|
+
|
|
112
|
+
There are two types of Node, *computed nodes* and *static nodes*.
|
|
113
|
+
|
|
114
|
+
A *computed node* is associated with an *agent function*, which receives some inputs, performs some computations asynchronously, and returns the result (output).
|
|
115
|
+
|
|
116
|
+
A *static node* is a placeholder of a value (just like a variable in programming languages), which is initially specified by its *value* property, and can be updated by an external program (before the execution of the graph), or updated using the *update* property at the end of each iteration of a [loop](#loop) operation.
|
|
117
|
+
|
|
118
|
+
### Computed Node
|
|
119
|
+
|
|
120
|
+
A *computed node* has following properties.
|
|
121
|
+
|
|
122
|
+
- *agent*: An **required** property, which specifies the id of the *agent function*, or an *inline agent function* (NOTE: this is not possible in JSON or YAML).
|
|
123
|
+
- *params*: An optional agent-specific property to control the behavior of the associated agent function. The top level property may reference a *data source*.
|
|
124
|
+
- *inputs*: An optional list of *data sources* that the current node receives the data from. This establishes a dataflow 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 immediately executed.
|
|
125
|
+
- *anyInput*: An optional boolean flag (default is false), which indicates that the associated *agent function* will be called when at least one of input data became available. Otherwise (default), it will wait until all the data became available.
|
|
126
|
+
- *retry*: An optional number, which specifies the maximum number of retries to be made. If the last attempt fails, the error will be recorded.
|
|
127
|
+
- *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. The returned value received after the time out will be discarded.
|
|
128
|
+
- *isResult*: An optional boolean value, which indicates that the return value of this node, should be included as a property of the return value from the run() method of the GraphUI instance.
|
|
129
|
+
- *priority*: An optional number, which specifies the priority of the execution of the associated agent (the task). Default is 0, which means "neutral". Negative numbers are allowed as well.
|
|
130
|
+
- *if*: An optional data source property. The node will be activated only if the value from the data source is truthy.
|
|
131
|
+
- *unless*: An optional data source property. The node will be activated only if the value from the data source is falsy (including empty array).
|
|
132
|
+
- *graph*: An optional property for nested agents, which specifies the inner graph. This value can be a graph itself or the data souce, whose value is a graph.
|
|
133
|
+
|
|
134
|
+
### Static Node
|
|
135
|
+
|
|
136
|
+
A *static* node has following properties.
|
|
137
|
+
|
|
138
|
+
- *value*: An **required** property, which specifies the initial value of this static node (equivalent to calling the injectValue method from outside).
|
|
139
|
+
- *update*: An optional property, which specifies the *data source* for a [loop](#loop) operation. After each iteration, the value of this node will be updated with the data from the specified *data source*.
|
|
140
|
+
|
|
141
|
+
## Flow Control
|
|
142
|
+
|
|
143
|
+
Since the dataflow graph must be acyclic by design, we added a few mechanisms to control dataflows, [nesting](#nesting), [loop](#loop), [mapping](#mapping) and [conditional flow](#conditional-flow).
|
|
144
|
+
|
|
145
|
+
### Nested Graph
|
|
146
|
+
|
|
147
|
+
In order to make it easy to reuse some code, GraphAI supports nesting. It requires a special agent function, which creates an instance (or instances) of GraphAI object within the agent function and execute it. The system supports two types of nesting agent functions (nestAgent and mapAgent), but developers can create their own using the standard agent extension mechanism.
|
|
148
|
+
|
|
149
|
+
A typical nesting graph looks like this:
|
|
150
|
+
|
|
151
|
+
```YAML
|
|
152
|
+
nodes:
|
|
153
|
+
question:
|
|
154
|
+
value: "Find out which materials we need to purchase this week for Joe Smith's residential house project."
|
|
155
|
+
projectId: // identifies the projectId from the question
|
|
156
|
+
agent: identifierAgent
|
|
157
|
+
inputs:
|
|
158
|
+
id: :question
|
|
159
|
+
database:
|
|
160
|
+
agent: "nestedAgent"
|
|
161
|
+
inputs:
|
|
162
|
+
prompt: ":question"
|
|
163
|
+
projectId: ":projectId"
|
|
164
|
+
graph:
|
|
165
|
+
nodes:
|
|
166
|
+
schema: // retrieves the database schema for the specified projectId
|
|
167
|
+
agent: "schemaAgent"
|
|
168
|
+
inputs:
|
|
169
|
+
projectId: :projectId
|
|
170
|
+
... // issue query to the database and build an appropriate prompt with it.
|
|
171
|
+
query: // send the generated prompt to the LLM
|
|
172
|
+
agent: "llama3Agent"
|
|
173
|
+
inputs:
|
|
174
|
+
promot: ":prompt"
|
|
175
|
+
isResult: true
|
|
176
|
+
response: // Deliver the answer
|
|
177
|
+
agent: "deliveryAgent"
|
|
178
|
+
inputs:
|
|
179
|
+
text: :database.query.text
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The databaseQuery node (which is associated "nestedAgent") takes the data from "question" node and "projectId" node, and make them available to inner nodes (nodes of the child graph) via phantom node, ":question" and ":projectId". After the completion of the child graph, the data from "query" node (which has "isResult" property) becomes available as a property of the output of "database" node.
|
|
183
|
+
|
|
184
|
+
Here is the diagram of the parent graph.
|
|
185
|
+
|
|
186
|
+
```mermaid
|
|
187
|
+
flowchart LR
|
|
188
|
+
question --> projectId(projectId)
|
|
189
|
+
question --> database
|
|
190
|
+
projectId --> database
|
|
191
|
+
database[[database]] -- query.text --> response(response)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Here is the diagram of the child graph. Notice that two phantom nodes are automatically created to allow inner nodes to access input data from the parent graph.
|
|
195
|
+
|
|
196
|
+
```mermaid
|
|
197
|
+
flowchart LR
|
|
198
|
+
:question --> ...
|
|
199
|
+
:projectId --> schema(schema)
|
|
200
|
+
schema --> ...(...)
|
|
201
|
+
... --> query(query)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This mechanism does not only allow developer to reuse code, but also make it possible to execute the child graph on another machine using a "remote" agent, enabling the *distributed execution* of nested graphs.
|
|
205
|
+
|
|
206
|
+
### Loop
|
|
207
|
+
|
|
208
|
+
The loop is an optional property of a graph, which has two optional properties.
|
|
209
|
+
|
|
210
|
+
- *count*: Specifies the number of times the graph needs to be executed.
|
|
211
|
+
- *while*: Specifies the *data source* to check after the each iteration. It continues if the data from that *data source* is *true*. Unlike JavaScript, an empty array will be treated as *false*.
|
|
212
|
+
|
|
213
|
+
Here is an example, which performs an LLM query for each person in the list and create the list of answers. The "people" node (static), is initialized with an array of names, and the "retriever" node (computed) retrieves one name at a time, and sends it to the "query" node (computed) to perform an LLM query. The "reducer" append it the array retrieved form the "result" node (static node, which is initialized as an empty array).
|
|
214
|
+
|
|
215
|
+
The "update" property of two static nodes ("people" and "result"), updates those properties based on the results from the previous iteration. This loop continues until the value of "people" node become an empty array.
|
|
216
|
+
|
|
217
|
+
```YAML
|
|
218
|
+
${packages/samples/graph_data/test/loop.yaml}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```mermaid
|
|
222
|
+
flowchart LR
|
|
223
|
+
result --> reducer(reducer)
|
|
224
|
+
people --> retriever(retriever)
|
|
225
|
+
retriever -- item --> query(query)
|
|
226
|
+
query -- text --> reducer
|
|
227
|
+
retriever -. array .-> people
|
|
228
|
+
reducer -.-> result
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The *loop* mechanism is often used with a nested graph, which receives an array of data from a node of the parent graph and performs the "reduction" process of a *map-reduce* operation, just like the *reduce* method of JavaScript.
|
|
232
|
+
|
|
233
|
+
Please notice that each iteration will be done sequentially unlike the *mapping* described below.
|
|
234
|
+
|
|
235
|
+
### Mapping
|
|
236
|
+
|
|
237
|
+
The mapAgent is one of nested agents, which receives an array of data as an input (inputs[0]) and performs the same operation (specified by its graph property) on each item concurrently.
|
|
238
|
+
|
|
239
|
+
If the size of array is N, the mapAgent creates N instances of GraphAI object, and run them concurrently.
|
|
240
|
+
|
|
241
|
+
After the completion of all of instances, the mapAgent returns an array of results, just like the map function of JavaScript.
|
|
242
|
+
|
|
243
|
+
The following graph will generate the same result (an array of answers) as the sample graph for the *loop*, but three queries will be issued concurrently.
|
|
244
|
+
|
|
245
|
+
```YAML
|
|
246
|
+
${packages/samples/src/simple/map_people.yaml}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Here is the conceptual representation of this operation.
|
|
250
|
+
|
|
251
|
+
```mermaid
|
|
252
|
+
flowchart LR
|
|
253
|
+
people -- "[0]" --> query_0(query_0)
|
|
254
|
+
people -- "[1]" --> query_1(query_1)
|
|
255
|
+
people -- "[2]" --> query_2(query_2)
|
|
256
|
+
query_0 --> retriever[[retriever]]
|
|
257
|
+
query_1 --> retriever
|
|
258
|
+
query_2 --> retriever
|
|
259
|
+
```
|
|
260
|
+
### Conditional Flow
|
|
261
|
+
|
|
262
|
+
GraphAI provides mechanisms to control the flow of data based on certain conditions. This is achieved through the *if*, *unless* and *anyInput* properties.
|
|
263
|
+
|
|
264
|
+
#### If/Unless Property
|
|
265
|
+
|
|
266
|
+
The *if* property allows you to specify a condition that must be met for the data to flow into a particular node. The condition is defined by a data source. If the value obtained from the specified *data source* is truthy (i.e., not null, undefined, 0, false, NaN, or an empty array/string), the node will be executed; otherwise, it will be skipped.The *unless* property is just the opporsite of the *if* property.
|
|
267
|
+
|
|
268
|
+
For example, the following node will be executed only if the *tool_calls* property of the message from the LLM contains a non-zero/non-empty value:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
tool_calls: {
|
|
272
|
+
// This node is activated if the LLM requests a tool call.
|
|
273
|
+
agent: "nestedAgent",
|
|
274
|
+
inputs: {
|
|
275
|
+
array: [":groq.tool", ":messagesWithFirstRes"]
|
|
276
|
+
},
|
|
277
|
+
if: ":groq.tool",
|
|
278
|
+
graph: {
|
|
279
|
+
// This graph is nested only for the readability.
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
It is recommended to use the *if* property in conjunction with nested graphs for better code readability and organization.
|
|
283
|
+
|
|
284
|
+
#### AnyInput Property
|
|
285
|
+
|
|
286
|
+
The *anyInput* property (boolean) allows you to merge multiple dataflow paths into a single node. When set to *true*, the agent function associated with the node will be executed as soon as data becomes available from any of the specified input data sources.
|
|
287
|
+
|
|
288
|
+
This property is particularly useful when you want to continue the flow regardless of which path the data comes from. In the weather chat sample application, it is used to continue the chat iteration whether a tool was requested by the LLM or not:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
reducer: {
|
|
292
|
+
// Receives messages from either case.
|
|
293
|
+
agent: "copyAgent",
|
|
294
|
+
anyInput: true,
|
|
295
|
+
inputs:
|
|
296
|
+
array: [":no_tool_calls", ":tool_calls.messagesWithSecondRes"],
|
|
297
|
+
},
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
In this example, the "reducer" node will execute as soon as data is available from either the "no_tool_calls" or "tool_calls.messagesWithSecondRes" data source.
|
|
301
|
+
|
|
302
|
+
By combining the *if* and *anyInput* properties, you can create complex conditional flows that control the execution of nodes based on the availability and values of data from various sources. This flexibility allows you to build sophisticated data-driven applications with GraphAI.
|
|
303
|
+
|
|
304
|
+
## Concurrency
|
|
305
|
+
|
|
306
|
+
GraphAI supports concurrent execution of tasks, allowing you to leverage parallelism and improve performance. The level of concurrency can be controlled through the *concurrency* property at the top level of the graph definition.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
concurrency: 16 # Maximum number of concurrent tasks
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
If the *concurrency* property is not specified, the default value of 8 is used.
|
|
313
|
+
|
|
314
|
+
### Concurrency and Nested Graphs
|
|
315
|
+
|
|
316
|
+
Since the task queue is shared between the parent graph and the children graph (uness the graph is running remotely), tasks created by the child graph will be bound by the same concurrency specified by the parent graph.
|
|
317
|
+
|
|
318
|
+
Since the task executing the nested graph will be in "running" state while tasks within the child graph are runnig, the concurrency limit will be incremented by one when we start running the child graph and restored when it is completed.
|
|
319
|
+
|
|
320
|
+
### Task Prioritization
|
|
321
|
+
|
|
322
|
+
By default, tasks are executed in a first-in, first-out (FIFO) order with a neutral priority (0). However, you can assign custom priorities to nodes using the *priority* property. Tasks associated with nodes that have a higher priority value will be executed before those with lower priorities.
|
|
323
|
+
|
|
324
|
+
Negative priority values are allowed, enabling you to fine-tune the execution order based on your application's requirements.
|
package/lib/graphai.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentFunctionInfoDictionary, AgentFilterInfo, GraphData, DataSource, ResultDataDictionary, ResultData, DefaultResultData, GraphOptions } from "./type";
|
|
1
|
+
import { AgentFunctionInfoDictionary, AgentFilterInfo, GraphData, DataSource, ResultDataDictionary, ResultData, DefaultResultData, GraphOptions, GraphDataLoader } from "./type";
|
|
2
2
|
import { TransactionLog } from "./transaction_log";
|
|
3
3
|
import { ComputedNode, GraphNodes } from "./node";
|
|
4
4
|
import { TaskManager } from "./task_manager";
|
|
@@ -17,6 +17,7 @@ export declare class GraphAI {
|
|
|
17
17
|
readonly agentFilters: AgentFilterInfo[];
|
|
18
18
|
readonly retryLimit?: number;
|
|
19
19
|
private readonly propFunctions;
|
|
20
|
+
readonly graphLoader?: GraphDataLoader;
|
|
20
21
|
nodes: GraphNodes;
|
|
21
22
|
onLogCallback: (__log: TransactionLog, __isUpdate: boolean) => void;
|
|
22
23
|
verbose: boolean;
|
package/lib/graphai.js
CHANGED
|
@@ -87,6 +87,7 @@ class GraphAI {
|
|
|
87
87
|
agentFilters: [],
|
|
88
88
|
bypassAgentIds: [],
|
|
89
89
|
config: {},
|
|
90
|
+
graphLoader: undefined,
|
|
90
91
|
}) {
|
|
91
92
|
this.logs = [];
|
|
92
93
|
this.config = {};
|
|
@@ -108,6 +109,7 @@ class GraphAI {
|
|
|
108
109
|
this.agentFilters = options.agentFilters ?? [];
|
|
109
110
|
this.bypassAgentIds = options.bypassAgentIds ?? [];
|
|
110
111
|
this.config = options.config;
|
|
112
|
+
this.graphLoader = options.graphLoader;
|
|
111
113
|
this.loop = data.loop;
|
|
112
114
|
this.verbose = data.verbose === true;
|
|
113
115
|
this.onComplete = () => {
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { GraphAI, defaultConcurrency, graphDataLatestVersion } from "./graphai";
|
|
2
|
-
export { AgentFunction, AgentFunctionInfo, AgentFunctionInfoDictionary, AgentFunctionInfoSample, AgentFunctionContext, GraphData, ResultDataDictionary, ResultData, NodeState, AgentFilterFunction, AgentFilterInfo, NodeData, StaticNodeData, ComputedNodeData, DefaultResultData, DefaultInputData, DefaultParamsType, } from "./type";
|
|
2
|
+
export { AgentFunction, AgentFunctionInfo, AgentFunctionInfoDictionary, AgentFunctionInfoSample, AgentFunctionContext, GraphData, ResultDataDictionary, ResultData, NodeState, AgentFilterFunction, AgentFilterInfo, NodeData, StaticNodeData, ComputedNodeData, DefaultResultData, DefaultInputData, DefaultParamsType, GraphDataLoaderOption, GraphDataLoader, } from "./type";
|
|
3
3
|
export type { TransactionLog } from "./transaction_log";
|
|
4
|
-
export { defaultAgentInfo, agentInfoWrapper, defaultTestContext, strIntentionalError, assert, sleep, isObject } from "./utils/utils";
|
|
4
|
+
export { defaultAgentInfo, agentInfoWrapper, defaultTestContext, strIntentionalError, assert, sleep, isObject, parseNodeName } from "./utils/utils";
|
|
5
5
|
export { ValidationError } from "./validators/common";
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ValidationError = exports.isObject = exports.sleep = exports.assert = exports.strIntentionalError = exports.defaultTestContext = exports.agentInfoWrapper = exports.defaultAgentInfo = exports.NodeState = exports.graphDataLatestVersion = exports.defaultConcurrency = exports.GraphAI = void 0;
|
|
3
|
+
exports.ValidationError = exports.parseNodeName = exports.isObject = exports.sleep = exports.assert = exports.strIntentionalError = exports.defaultTestContext = exports.agentInfoWrapper = exports.defaultAgentInfo = exports.NodeState = exports.graphDataLatestVersion = exports.defaultConcurrency = exports.GraphAI = void 0;
|
|
4
4
|
var graphai_1 = require("./graphai");
|
|
5
5
|
Object.defineProperty(exports, "GraphAI", { enumerable: true, get: function () { return graphai_1.GraphAI; } });
|
|
6
6
|
Object.defineProperty(exports, "defaultConcurrency", { enumerable: true, get: function () { return graphai_1.defaultConcurrency; } });
|
|
@@ -15,5 +15,6 @@ Object.defineProperty(exports, "strIntentionalError", { enumerable: true, get: f
|
|
|
15
15
|
Object.defineProperty(exports, "assert", { enumerable: true, get: function () { return utils_1.assert; } });
|
|
16
16
|
Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return utils_1.sleep; } });
|
|
17
17
|
Object.defineProperty(exports, "isObject", { enumerable: true, get: function () { return utils_1.isObject; } });
|
|
18
|
+
Object.defineProperty(exports, "parseNodeName", { enumerable: true, get: function () { return utils_1.parseNodeName; } });
|
|
18
19
|
var common_1 = require("./validators/common");
|
|
19
20
|
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return common_1.ValidationError; } });
|
package/lib/node.js
CHANGED
|
@@ -76,6 +76,9 @@ class ComputedNode extends Node {
|
|
|
76
76
|
if (data.graph) {
|
|
77
77
|
this.nestedGraph = typeof data.graph === "string" ? this.addPendingNode(data.graph) : data.graph;
|
|
78
78
|
}
|
|
79
|
+
if (data.graphLoader && graph.graphLoader) {
|
|
80
|
+
this.nestedGraph = graph.graphLoader(data.graphLoader);
|
|
81
|
+
}
|
|
79
82
|
if (data.if) {
|
|
80
83
|
this.ifSource = this.addPendingNode(data.if);
|
|
81
84
|
}
|
package/lib/type.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export type StaticNodeData = {
|
|
|
31
31
|
};
|
|
32
32
|
export type AgentAnonymousFunction = (...params: any[]) => unknown;
|
|
33
33
|
export type AgentFilterParams = Record<string, any>;
|
|
34
|
+
export type GraphDataLoaderOption = {
|
|
35
|
+
fileName: string;
|
|
36
|
+
option?: any;
|
|
37
|
+
};
|
|
34
38
|
export type ComputedNodeData = {
|
|
35
39
|
agent: string | AgentAnonymousFunction;
|
|
36
40
|
inputs?: Array<any> | Record<string, any>;
|
|
@@ -42,6 +46,7 @@ export type ComputedNodeData = {
|
|
|
42
46
|
if?: string;
|
|
43
47
|
unless?: string;
|
|
44
48
|
graph?: GraphData | string;
|
|
49
|
+
graphLoader?: GraphDataLoaderOption;
|
|
45
50
|
isResult?: boolean;
|
|
46
51
|
priority?: number;
|
|
47
52
|
passThrough?: PassThrough;
|
|
@@ -61,11 +66,13 @@ export type GraphData = {
|
|
|
61
66
|
retry?: number;
|
|
62
67
|
metadata?: any;
|
|
63
68
|
};
|
|
69
|
+
export type GraphDataLoader = (loaderOption: GraphDataLoaderOption) => GraphData;
|
|
64
70
|
export type GraphOptions = {
|
|
65
71
|
agentFilters?: AgentFilterInfo[] | undefined;
|
|
66
72
|
taskManager?: TaskManager | undefined;
|
|
67
73
|
bypassAgentIds?: string[] | undefined;
|
|
68
74
|
config?: Record<string, unknown>;
|
|
75
|
+
graphLoader?: GraphDataLoader;
|
|
69
76
|
};
|
|
70
77
|
export type AgentFunctionContext<ParamsType = DefaultParamsType, InputDataType = DefaultInputData, NamedInputDataType = DefaultInputData> = {
|
|
71
78
|
params: NodeDataParams<ParamsType>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ResultData, DataSource, PropFunction } from "../type";
|
|
2
|
-
export declare const getDataFromSource: (result: ResultData | undefined, source: DataSource, propFunctions
|
|
2
|
+
export declare const getDataFromSource: (result: ResultData | undefined, source: DataSource, propFunctions?: PropFunction[]) => ResultData | undefined;
|
package/lib/utils/data_source.js
CHANGED
|
@@ -47,7 +47,7 @@ const innerGetDataFromSource = (result, propIds, propFunctions) => {
|
|
|
47
47
|
}
|
|
48
48
|
return result;
|
|
49
49
|
};
|
|
50
|
-
const getDataFromSource = (result, source, propFunctions) => {
|
|
50
|
+
const getDataFromSource = (result, source, propFunctions = []) => {
|
|
51
51
|
if (!source.nodeId) {
|
|
52
52
|
return source.value;
|
|
53
53
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.propFunctions = exports.propFunctionRegex = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
5
|
exports.propFunctionRegex = /^[a-zA-Z]+\([^)]*\)$/;
|
|
6
6
|
const propArrayFunction = (result, propId) => {
|
|
@@ -81,23 +81,3 @@ const propBooleanFunction = (result, propId) => {
|
|
|
81
81
|
return undefined;
|
|
82
82
|
};
|
|
83
83
|
exports.propFunctions = [propArrayFunction, propObjectFunction, propStringFunction, propNumberFunction, propBooleanFunction];
|
|
84
|
-
const propFunction = (result, propId) => {
|
|
85
|
-
if (Array.isArray(result)) {
|
|
86
|
-
// flat, join
|
|
87
|
-
return propArrayFunction(result, propId);
|
|
88
|
-
}
|
|
89
|
-
else if ((0, utils_1.isObject)(result)) {
|
|
90
|
-
return propObjectFunction(result, propId);
|
|
91
|
-
}
|
|
92
|
-
else if (typeof result === "string") {
|
|
93
|
-
return propStringFunction(result, propId);
|
|
94
|
-
}
|
|
95
|
-
else if (result !== undefined && Number.isFinite(result)) {
|
|
96
|
-
return propNumberFunction(result, propId);
|
|
97
|
-
}
|
|
98
|
-
else if (typeof result === "boolean") {
|
|
99
|
-
return propBooleanFunction(result, propId);
|
|
100
|
-
}
|
|
101
|
-
return undefined;
|
|
102
|
-
};
|
|
103
|
-
exports.propFunction = propFunction;
|
package/lib/validators/common.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphai",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
4
4
|
"description": "Asynchronous data flow execution engine for agentic AI apps.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/receptron/graphai#readme",
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"typedoc": "^0.26.
|
|
29
|
+
"typedoc": "^0.26.11"
|
|
30
30
|
},
|
|
31
31
|
"types": "./lib/index.d.ts",
|
|
32
32
|
"directories": {
|