graphai 0.5.14 → 0.5.16

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
@@ -2,91 +2,125 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- GraphAI is an asynchronous data flow execution engine, which allows developers to build *agentic applications* by describing *agent workflows* as declarative data flow graphs in YAML or JSON.
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 data flow graph in YAML or JSON, which is called *declarative data flow 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.
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 Data Flow Programming
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: // Input data to this RAG application
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: // Retrieve data from Wikipedia
25
+ wikipedia:
26
+ console:
27
+ before: ...fetching data from wikkpedia
24
28
  agent: wikipediaAgent
25
29
  inputs:
26
30
  query: :source.name
27
- chunks: // Break the text from Wikipedia into chunks (2048 character each with 512 overlap)
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: // Get embedding vector of each chunk
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: // Get embedding vector of the question
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.query
39
- similarities: // Calculate the cosine similarity of each chunk
50
+ item: :source.topic
51
+ similarities:
40
52
  agent: dotProductAgent
41
53
  inputs:
42
54
  matrix: :chunkEmbeddings
43
- vector: :topicEmbedding
44
- sortedChunks: // Sort chunks based on their similarities
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: // Concatenate chunks up to the token limit (5000)
61
+ referenceText:
50
62
  agent: tokenBoundStringsAgent
51
63
  inputs:
52
- array: :sortedChunks
64
+ chunks: :sortedChunks
53
65
  params:
54
66
  limit: 5000
55
- prompt: // Generate a prompt with that reference text
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, ${prompt}
63
- ${text}
64
- query: // retrieves the answer from GPT3.5
65
- agent: slashGPTAgent
66
- params:
67
- manifest:
68
- model: gpt-3.5-turbo
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
- source -- query --> topicEmbedding(topicEmbedding)
78
- wikipedia --> chunks(chunks)
79
- chunks --> chunkEmbeddings(chunkEmbeddings)
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
- referenceText --> prompt
86
- prompt --> query(query)
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 programing* is very difficult in traditional programming style, and GraphAI's *data flow programming* style is much better alternative.
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
- ## Data Flow Graph
137
+ ## Dataflow Graph
104
138
 
105
- 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.
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 data flow. 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.
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., ":node1.messages.$0.content").
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, *required*. The latest version is 0.3.
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 function
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 only the *inputs* paramter as a variable length arguments.
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 examnple (from [weather chat](https://github.com/receptron/graphai/blob/main/samples/sample_weather.ts)):
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 }) => [...messages, { role: "user", content }],
150
- inputs:
151
- messages: ":messages"
152
- content: ":userInput"
153
- if: "checkInput",
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 data flow where the current node can only be executed after the completion of the nodes listed under *inputs*. If this list is empty, the associated *agent function* will be immediatley executed.
172
- - *anyInput*: An optiona boolean flag, which indicates that the associated *agent function* will be called when at least one of input data became available. Otherwise, it will wait until all the data became available.
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 falty (including empty array).
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 data-flow graph must be asyclic by design, we added a few mechanisms to control data flows, [nesting](#nesting), [loop](#loop), [mapping](#mapping) and [conditional flow](#conditional-flow).
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: :source
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 apecified projectId
249
+ schema: // retrieves the database schema for the specified projectId
214
250
  agent: "schemaAgent"
215
- inputs: [":projectId"]
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.$last.content
262
+ text: :database.query.text
226
263
  ```
227
264
 
228
- The databaseQuery node (which is associated "nestedAgent") takes the data from "question" node abd "projectId" node, and make them available to inner nodes (nodes of the child graph) via phantom node, "$0" and "$1". 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.
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
- $0 --> ...
245
- $1 --> schema(schema)
281
+ :question --> ...
282
+ :projectId --> schema(schema)
246
283
  schema --> ...(...)
247
284
  ... --> query(query)
248
285
  ```
249
286
 
250
- This mechanism does not only allows devleoper to reuse code, but also makes it possible to execute the child graph on another machine using a "remote" agent (which will be released later), enabling the *distributed execution* of nested graphs.
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 itelation. This loop continues until the value of "people" node become an empty array.
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: [Steve Jobs, Elon Musk, Nikola Tesla]
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: shift
316
+ agent: shiftAgent
276
317
  inputs:
277
318
  array: :people
278
319
  query:
279
- agent: slashgpt
320
+ agent: openAIAgent
280
321
  params:
281
- manifest:
282
- prompt: Describe about the person in less than 100 words
283
- inputs: [:retriever.item]
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: push
327
+ agent: pushAgent
286
328
  inputs:
287
329
  array: :result
288
- item: :query.content
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 -- content --> reducer
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 sequencially unlike the *mapping* described below.
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 concurretly.
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: slashgpt
370
+ agent: openAIAgent
326
371
  params:
327
- manifest:
328
- prompt: Describe about the person in less than 100 words
329
- inputs: [":row"]
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: [":groq.choices.$0.message.tool_calls", ":messagesWithFirstRes"],
358
- if: ":groq.choices.$0.message.tool_calls",
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 data flow 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.
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,6 @@
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
+ export { inputs2dataSources } from "./utils/nodeUtils";
5
6
  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.inputs2dataSources = 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,8 @@ 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; } });
19
+ var nodeUtils_1 = require("./utils/nodeUtils");
20
+ Object.defineProperty(exports, "inputs2dataSources", { enumerable: true, get: function () { return nodeUtils_1.inputs2dataSources; } });
18
21
  var common_1 = require("./validators/common");
19
22
  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: PropFunction[]) => ResultData | undefined;
2
+ export declare const getDataFromSource: (result: ResultData | undefined, source: DataSource, propFunctions?: PropFunction[]) => ResultData | undefined;
@@ -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,4 +1,3 @@
1
1
  import { PropFunction } from "../type";
2
2
  export declare const propFunctionRegex: RegExp;
3
3
  export declare const propFunctions: PropFunction[];
4
- export declare const propFunction: PropFunction;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.propFunction = exports.propFunctions = exports.propFunctionRegex = void 0;
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;
@@ -10,6 +10,7 @@ exports.computedNodeAttributeKeys = [
10
10
  "timeout",
11
11
  "agent",
12
12
  "graph",
13
+ "graphLoader",
13
14
  "isResult",
14
15
  "priority",
15
16
  "if",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphai",
3
- "version": "0.5.14",
3
+ "version": "0.5.16",
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.10"
29
+ "typedoc": "^0.26.11"
30
30
  },
31
31
  "types": "./lib/index.d.ts",
32
32
  "directories": {