@ai.ntellect/core 0.6.21 → 0.7.1
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/.mocharc.json +2 -1
- package/README.md +61 -85
- package/graph/controller.ts +1 -1
- package/graph/event-manager.ts +288 -0
- package/graph/index.ts +153 -367
- package/graph/logger.ts +70 -0
- package/graph/node.ts +398 -0
- package/graph/observer.ts +361 -0
- package/interfaces/index.ts +102 -1
- package/modules/agenda/index.ts +3 -16
- package/modules/memory/adapters/in-memory/index.ts +6 -2
- package/modules/memory/adapters/meilisearch/index.ts +6 -2
- package/modules/memory/adapters/redis/index.ts +2 -2
- package/modules/memory/index.ts +1 -1
- package/package.json +10 -5
- package/test/graph/index.test.ts +244 -113
- package/test/graph/observer.test.ts +398 -0
- package/test/modules/agenda/node-cron.test.ts +37 -16
- package/test/modules/memory/adapters/in-memory.test.ts +2 -2
- package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
- package/test/modules/memory/base.test.ts +3 -3
- package/tsconfig.json +4 -2
- package/types/index.ts +24 -3
- package/dist/graph/controller.js +0 -72
- package/dist/graph/index.js +0 -501
- package/dist/index.js +0 -41
- package/dist/interfaces/index.js +0 -17
- package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
- package/dist/modules/agenda/index.js +0 -140
- package/dist/modules/embedding/adapters/ai/index.js +0 -57
- package/dist/modules/embedding/index.js +0 -59
- package/dist/modules/memory/adapters/in-memory/index.js +0 -210
- package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
- package/dist/modules/memory/adapters/redis/index.js +0 -158
- package/dist/modules/memory/index.js +0 -103
- package/dist/types/index.js +0 -2
- package/dist/utils/generate-action-schema.js +0 -43
- package/dist/utils/header-builder.js +0 -34
- package/test/modules/embedding/ai.test.ts +0 -78
- package/test/modules/memory/adapters/redis.test.ts +0 -169
- package/test/services/agenda.test.ts +0 -279
package/.mocharc.json
CHANGED
package/README.md
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
@ai.ntellect/core is a modular and event-driven framework designed to orchestrate and execute intelligent workflows using execution graphs. It enables automation of complex tasks, seamless integration with external services, and the creation of AI-driven agents in a flexible and scalable way.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Features
|
6
6
|
|
7
|
-
- **GraphFlow** –
|
8
|
-
- **Event-Driven** – Nodes can react to real-time events and trigger actions dynamically
|
9
|
-
- **Modular** – Plug-and-play modules and adapters for memory, scheduling, and external APIs
|
10
|
-
- **Extensible** – Custom nodes, adapters, and interactions with third-party services
|
11
|
-
- **
|
7
|
+
- **GraphFlow** – Graph-based execution engine for automating business processes
|
8
|
+
- **Event-Driven** – Nodes can react to real-time events and trigger actions dynamically
|
9
|
+
- **Modular** – Plug-and-play modules and adapters for memory, scheduling, and external APIs
|
10
|
+
- **Extensible** – Custom nodes, adapters, and interactions with third-party services
|
11
|
+
- **Observable** – Complete state and event monitoring
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -25,124 +25,100 @@ node -v
|
|
25
25
|
npm -v
|
26
26
|
```
|
27
27
|
|
28
|
-
If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/).
|
29
|
-
|
30
28
|
### Installing the framework
|
31
29
|
|
32
|
-
Create a new Node.js project:
|
33
|
-
|
34
|
-
```sh
|
35
|
-
mkdir ai-ntellect-demo
|
36
|
-
cd ai-ntellect-demo
|
37
|
-
npm init -y
|
38
|
-
```
|
39
|
-
|
40
|
-
Install TypeScript and Node.js types:
|
41
|
-
|
42
|
-
```sh
|
43
|
-
npm install --save-dev typescript @types/node
|
44
|
-
npx tsc --init
|
45
|
-
```
|
46
|
-
|
47
|
-
Install @ai.ntellect/core and its dependencies:
|
48
|
-
|
49
30
|
```sh
|
50
31
|
npm install @ai.ntellect/core zod
|
51
32
|
```
|
52
33
|
|
53
|
-
##
|
54
|
-
|
55
|
-
Create a new file `index.ts`:
|
56
|
-
|
57
|
-
```sh
|
58
|
-
touch index.ts
|
59
|
-
```
|
34
|
+
## Example
|
60
35
|
|
61
|
-
|
62
|
-
|
63
|
-
```ts
|
64
|
-
import { GraphFlow } from "@ai.ntellect/core";
|
36
|
+
```typescript
|
65
37
|
import { z } from "zod";
|
38
|
+
import { GraphFlow } from "@ai.ntellect/core";
|
66
39
|
|
40
|
+
// Define context schema
|
67
41
|
const ContextSchema = z.object({
|
68
42
|
message: z.string(),
|
43
|
+
counter: z.number(),
|
69
44
|
});
|
70
45
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
name: "TestGraph",
|
75
|
-
context: { message: "Installation successful!" },
|
46
|
+
// Create graph instance
|
47
|
+
const graph = new GraphFlow<typeof ContextSchema>("MyGraph", {
|
48
|
+
name: "MyGraph",
|
76
49
|
schema: ContextSchema,
|
50
|
+
context: { message: "Hello", counter: 0 },
|
77
51
|
nodes: [
|
78
52
|
{
|
79
|
-
name: "
|
53
|
+
name: "incrementCounter",
|
80
54
|
execute: async (context) => {
|
81
|
-
|
55
|
+
context.counter++;
|
56
|
+
},
|
57
|
+
next: ["checkThreshold"],
|
58
|
+
},
|
59
|
+
{
|
60
|
+
name: "checkThreshold",
|
61
|
+
condition: (context) => context.counter < 5,
|
62
|
+
execute: async (context) => {
|
63
|
+
if (context.counter >= 5) {
|
64
|
+
context.message = "Threshold reached!";
|
65
|
+
}
|
66
|
+
},
|
67
|
+
next: ["incrementCounter"],
|
68
|
+
retry: {
|
69
|
+
maxAttempts: 3,
|
70
|
+
delay: 1000,
|
82
71
|
},
|
83
|
-
next: [],
|
84
72
|
},
|
85
73
|
],
|
86
74
|
});
|
87
75
|
|
76
|
+
// Observe state changes
|
88
77
|
(async () => {
|
89
|
-
|
78
|
+
// Execute the graph
|
79
|
+
graph.execute("incrementCounter");
|
80
|
+
graph.observe().state().subscribe(console.log);
|
90
81
|
})();
|
91
82
|
```
|
92
83
|
|
93
|
-
|
84
|
+
## Features
|
94
85
|
|
95
|
-
|
96
|
-
npx ts-node index.ts
|
97
|
-
```
|
86
|
+
### Event handling
|
98
87
|
|
99
|
-
|
88
|
+
```typescript
|
89
|
+
// Event-driven node
|
90
|
+
{
|
91
|
+
name: "waitForEvent",
|
92
|
+
events: ["dataReceived"],
|
93
|
+
execute: async (context, event) => {
|
94
|
+
context.data = event.payload;
|
95
|
+
}
|
96
|
+
}
|
100
97
|
|
98
|
+
// Emit events
|
99
|
+
graph.emit("dataReceived", { value: 42 });
|
101
100
|
```
|
102
|
-
Installation successful!
|
103
|
-
```
|
104
|
-
|
105
|
-
## Core concepts
|
106
101
|
|
107
|
-
###
|
102
|
+
### State observation
|
108
103
|
|
109
|
-
|
104
|
+
```typescript
|
105
|
+
// Observe specific node
|
106
|
+
graph.observe().node("myNode").subscribe(console.log);
|
110
107
|
|
111
|
-
|
112
|
-
|
113
|
-
- Depend on conditional logic.
|
114
|
-
- Modify a shared execution context.
|
108
|
+
// Observe specific properties
|
109
|
+
graph.observe().property("counter").subscribe(console.log);
|
115
110
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
- Sequential or parallel execution of multiple graphs.
|
121
|
-
- Inter-graph communication for complex workflows.
|
122
|
-
- Advanced event-based automation.
|
123
|
-
|
124
|
-
### Modules and Adapters
|
125
|
-
|
126
|
-
The framework provides modular extensions:
|
127
|
-
|
128
|
-
- **Memory Module** – Stores and retrieves contextual information.
|
129
|
-
- **Scheduler (Agenda)** – Manages task scheduling and timed executions.
|
130
|
-
- **Adapters** – Integrate with databases, APIs, and external services.
|
131
|
-
|
132
|
-
## Tutorials
|
133
|
-
|
134
|
-
Step-by-step guides are available for:
|
111
|
+
// Observe events
|
112
|
+
graph.observe().event("nodeCompleted").subscribe(console.log);
|
113
|
+
```
|
135
114
|
|
136
|
-
|
137
|
-
- Adding conditions and handling errors
|
138
|
-
- Waiting for events and executing multiple graphs
|
139
|
-
- Building an AI-powered agent with @ai.ntellect/core
|
115
|
+
## Documentation
|
140
116
|
|
141
|
-
|
117
|
+
For complete documentation, visit our [GitBook](https://ai-ntellect.gitbook.io/core).
|
142
118
|
|
143
119
|
## Contributing
|
144
120
|
|
145
|
-
Contributions are welcome
|
121
|
+
Contributions are welcome! To suggest an improvement or report an issue:
|
146
122
|
|
147
123
|
- Join our [Discord community](https://discord.gg/kEc5gWXJ)
|
148
124
|
- Explore the [GitBook documentation](https://ai-ntellect.gitbook.io/core)
|
package/graph/controller.ts
CHANGED
@@ -6,7 +6,7 @@ import { GraphFlow } from "./index";
|
|
6
6
|
* Controller class for managing the execution of graph flows
|
7
7
|
* Handles both sequential and parallel execution of multiple graphs
|
8
8
|
*/
|
9
|
-
export class
|
9
|
+
export class GraphFlowController {
|
10
10
|
/**
|
11
11
|
* Executes multiple graphs sequentially
|
12
12
|
* @param graphs - Array of GraphFlow instances to execute
|
@@ -0,0 +1,288 @@
|
|
1
|
+
import { Observable, Subject, filter } from "rxjs";
|
2
|
+
import { ZodSchema } from "zod";
|
3
|
+
import { IEventEmitter } from "../interfaces";
|
4
|
+
import { GraphContext, GraphEvent, Node } from "../types";
|
5
|
+
import { GraphNode } from "./node";
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Manages event handling and routing for a graph
|
9
|
+
* Coordinates event emission, listening, and execution of event-driven nodes
|
10
|
+
* @template T - The Zod schema type for validation
|
11
|
+
*/
|
12
|
+
export class GraphEventManager<T extends ZodSchema> {
|
13
|
+
private eventSubject: Subject<GraphEvent<T>> = new Subject();
|
14
|
+
private nodeStreams: Map<string, Observable<GraphEvent<T>>> = new Map();
|
15
|
+
private context: GraphContext<T>;
|
16
|
+
private name: string;
|
17
|
+
private graphEvents?: string[];
|
18
|
+
private entryNode?: string;
|
19
|
+
private globalErrorHandler?: (error: Error, context: GraphContext<T>) => void;
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Creates a new GraphEventManager instance
|
23
|
+
* @param eventEmitter - The event emitter implementation to use
|
24
|
+
* @param nodes - Map of all nodes in the graph
|
25
|
+
* @param name - Name of the graph
|
26
|
+
* @param context - Initial graph context
|
27
|
+
* @param graphEvents - List of events the graph should listen to
|
28
|
+
* @param entryNode - Name of the entry node for graph events
|
29
|
+
* @param globalErrorHandler - Global error handling function
|
30
|
+
* @param nodeExecutor - GraphNode instance for executing nodes
|
31
|
+
*/
|
32
|
+
constructor(
|
33
|
+
private eventEmitter: IEventEmitter,
|
34
|
+
private nodes: Map<string, Node<T, any>>,
|
35
|
+
name: string,
|
36
|
+
context: GraphContext<T>,
|
37
|
+
graphEvents?: string[],
|
38
|
+
entryNode?: string,
|
39
|
+
globalErrorHandler?: (error: Error, context: GraphContext<T>) => void,
|
40
|
+
private nodeExecutor?: GraphNode<T>
|
41
|
+
) {
|
42
|
+
this.name = name;
|
43
|
+
this.context = context;
|
44
|
+
this.graphEvents = graphEvents;
|
45
|
+
this.entryNode = entryNode;
|
46
|
+
this.globalErrorHandler = globalErrorHandler;
|
47
|
+
this.setupEventStreams();
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Sets up event streams for all nodes that listen to events
|
52
|
+
*/
|
53
|
+
public setupEventStreams(): void {
|
54
|
+
for (const [nodeName, node] of this.nodes.entries()) {
|
55
|
+
if (node.events && node.events.length > 0) {
|
56
|
+
const nodeStream = this.eventSubject.pipe(
|
57
|
+
filter((event) => node.events!.includes(event.type))
|
58
|
+
);
|
59
|
+
this.nodeStreams.set(nodeName, nodeStream);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Emits an event with optional payload and context
|
66
|
+
* @param type - The type of event to emit
|
67
|
+
* @param payload - Optional payload data
|
68
|
+
* @param context - Optional graph context
|
69
|
+
*/
|
70
|
+
public emitEvent<P = any>(
|
71
|
+
type: string,
|
72
|
+
payload?: P,
|
73
|
+
context?: GraphContext<T>
|
74
|
+
): void {
|
75
|
+
const event: GraphEvent<T> = { type, payload, timestamp: Date.now() };
|
76
|
+
this.eventSubject.next(event);
|
77
|
+
this.eventEmitter.emit(type, event);
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Sets up event listeners for all nodes in the graph
|
82
|
+
* Handles cleanup and re-registration of event listeners
|
83
|
+
*/
|
84
|
+
setupEventListeners(): void {
|
85
|
+
// First remove only the existing node-based listeners that we might have created previously
|
86
|
+
// We do NOT remove, for example, "nodeStarted" or "nodeCompleted" listeners that test code added.
|
87
|
+
for (const [eventName, listener] of this.eventEmitter
|
88
|
+
.rawListeners("*")
|
89
|
+
.entries()) {
|
90
|
+
// This can be tricky—EventEmitter doesn't directly let you remove by "type" of listener.
|
91
|
+
// Alternatively, we can store references in a separate structure.
|
92
|
+
// For simplicity, let's do a full removeAllListeners() on node-specified events (only),
|
93
|
+
// then re-add them below, but keep the test-based events like "nodeStarted" or "nodeCompleted".
|
94
|
+
}
|
95
|
+
|
96
|
+
// The simplest approach: removeAllListeners for each event that is declared as a node event
|
97
|
+
// so we don't stack up duplicates:
|
98
|
+
const allEvents = new Set<string>();
|
99
|
+
for (const node of this.nodes.values()) {
|
100
|
+
if (node.events) {
|
101
|
+
node.events.forEach((evt) => allEvents.add(evt));
|
102
|
+
}
|
103
|
+
}
|
104
|
+
for (const evt of allEvents) {
|
105
|
+
// remove only those events that are used by nodes
|
106
|
+
this.eventEmitter.removeAllListeners(evt);
|
107
|
+
}
|
108
|
+
|
109
|
+
// Now re-add the node-based event triggers
|
110
|
+
for (const node of this.nodes.values()) {
|
111
|
+
if (node.events && node.events.length > 0) {
|
112
|
+
node.events.forEach((event) => {
|
113
|
+
this.eventEmitter.on(
|
114
|
+
event,
|
115
|
+
async (data?: Partial<GraphContext<T>>) => {
|
116
|
+
const freshContext = structuredClone(this.context);
|
117
|
+
if (data) Object.assign(freshContext, data);
|
118
|
+
|
119
|
+
// If triggered by an event, we pass "true" so event-driven node will skip `next`.
|
120
|
+
await this.executeNode(
|
121
|
+
node.name,
|
122
|
+
freshContext,
|
123
|
+
undefined,
|
124
|
+
/* triggeredByEvent= */ true
|
125
|
+
);
|
126
|
+
}
|
127
|
+
);
|
128
|
+
});
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Sets up listeners for graph-level events
|
135
|
+
* Handles graph start, completion, and error events
|
136
|
+
*/
|
137
|
+
setupGraphEventListeners(): void {
|
138
|
+
if (this.graphEvents && this.graphEvents.length > 0) {
|
139
|
+
this.graphEvents.forEach((event) => {
|
140
|
+
this.eventEmitter.on(event, async (data?: Partial<GraphContext<T>>) => {
|
141
|
+
const freshContext = this.createNewContext();
|
142
|
+
if (data) Object.assign(freshContext, data);
|
143
|
+
|
144
|
+
// Emit "graphStarted"
|
145
|
+
this.eventEmitter.emit("graphStarted", { name: this.name });
|
146
|
+
|
147
|
+
try {
|
148
|
+
// Execute the graph starting from the entry node
|
149
|
+
if (!this.entryNode) {
|
150
|
+
throw new Error("No entry node defined for graph event handling");
|
151
|
+
}
|
152
|
+
|
153
|
+
await this.executeNode(
|
154
|
+
this.entryNode,
|
155
|
+
freshContext,
|
156
|
+
undefined,
|
157
|
+
false
|
158
|
+
);
|
159
|
+
|
160
|
+
// Emit "graphCompleted"
|
161
|
+
this.eventEmitter.emit("graphCompleted", {
|
162
|
+
name: this.name,
|
163
|
+
context: this.context,
|
164
|
+
});
|
165
|
+
} catch (error) {
|
166
|
+
// Emit "graphError"
|
167
|
+
this.eventEmitter.emit("graphError", { name: this.name, error });
|
168
|
+
this.globalErrorHandler?.(error as Error, freshContext);
|
169
|
+
throw error;
|
170
|
+
}
|
171
|
+
});
|
172
|
+
});
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
/**
|
177
|
+
* Waits for a set of events to occur within a timeout period
|
178
|
+
* @param events - Array of event names to wait for
|
179
|
+
* @param timeout - Maximum time to wait in milliseconds
|
180
|
+
* @returns Promise that resolves with array of received events
|
181
|
+
* @throws Error if timeout occurs before all events are received
|
182
|
+
*/
|
183
|
+
async waitForEvents(
|
184
|
+
events: string[],
|
185
|
+
timeout: number = 30000
|
186
|
+
): Promise<any[]> {
|
187
|
+
return new Promise((resolve, reject) => {
|
188
|
+
const receivedEvents = new Map<string, any>();
|
189
|
+
const eventHandlers = new Map();
|
190
|
+
let isResolved = false;
|
191
|
+
|
192
|
+
const cleanup = () => {
|
193
|
+
events.forEach((event) => {
|
194
|
+
const handler = eventHandlers.get(event);
|
195
|
+
if (handler) {
|
196
|
+
this.eventEmitter.removeListener(event, handler);
|
197
|
+
}
|
198
|
+
});
|
199
|
+
};
|
200
|
+
|
201
|
+
events.forEach((event) => {
|
202
|
+
const handler = (eventData: any) => {
|
203
|
+
console.log(`Received event: ${event}`, eventData);
|
204
|
+
if (!isResolved) {
|
205
|
+
receivedEvents.set(event, eventData);
|
206
|
+
console.log(
|
207
|
+
"Current received events:",
|
208
|
+
Array.from(receivedEvents.keys())
|
209
|
+
);
|
210
|
+
|
211
|
+
if (events.every((e) => receivedEvents.has(e))) {
|
212
|
+
console.log("All events received, resolving");
|
213
|
+
isResolved = true;
|
214
|
+
clearTimeout(timeoutId);
|
215
|
+
cleanup();
|
216
|
+
resolve(Array.from(receivedEvents.values()));
|
217
|
+
}
|
218
|
+
}
|
219
|
+
};
|
220
|
+
|
221
|
+
eventHandlers.set(event, handler);
|
222
|
+
this.eventEmitter.on(event, handler);
|
223
|
+
});
|
224
|
+
|
225
|
+
const timeoutId = setTimeout(() => {
|
226
|
+
if (!isResolved) {
|
227
|
+
isResolved = true;
|
228
|
+
cleanup();
|
229
|
+
reject(new Error(`Timeout waiting for events: ${events.join(", ")}`));
|
230
|
+
}
|
231
|
+
}, timeout);
|
232
|
+
});
|
233
|
+
}
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Registers an event handler
|
237
|
+
* @param eventName - Name of the event to listen for
|
238
|
+
* @param handler - Function to handle the event
|
239
|
+
*/
|
240
|
+
on(eventName: string, handler: (...args: any[]) => void): void {
|
241
|
+
this.eventEmitter.on(eventName, handler);
|
242
|
+
}
|
243
|
+
|
244
|
+
/**
|
245
|
+
* Emits an event through the event emitter
|
246
|
+
* @param eventName - Name of the event to emit
|
247
|
+
* @param data - Optional data to include with the event
|
248
|
+
*/
|
249
|
+
emit(eventName: string, data?: any): void {
|
250
|
+
this.eventEmitter.emit(eventName, data);
|
251
|
+
}
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Creates a new context object by cloning the current context
|
255
|
+
* @returns A new graph context instance
|
256
|
+
* @private
|
257
|
+
*/
|
258
|
+
private createNewContext(): GraphContext<T> {
|
259
|
+
return structuredClone(this.context);
|
260
|
+
}
|
261
|
+
|
262
|
+
/**
|
263
|
+
* Executes a node with the given parameters
|
264
|
+
* @param nodeName - Name of the node to execute
|
265
|
+
* @param context - Graph context for execution
|
266
|
+
* @param inputs - Input data for the node
|
267
|
+
* @param triggeredByEvent - Whether execution was triggered by an event
|
268
|
+
* @returns Promise that resolves when execution is complete
|
269
|
+
* @throws Error if nodeExecutor is not initialized
|
270
|
+
* @private
|
271
|
+
*/
|
272
|
+
private async executeNode(
|
273
|
+
nodeName: string,
|
274
|
+
context: GraphContext<T>,
|
275
|
+
inputs: any,
|
276
|
+
triggeredByEvent: boolean
|
277
|
+
): Promise<void> {
|
278
|
+
if (!this.nodeExecutor) {
|
279
|
+
throw new Error("NodeExecutor not initialized");
|
280
|
+
}
|
281
|
+
return this.nodeExecutor.executeNode(
|
282
|
+
nodeName,
|
283
|
+
context,
|
284
|
+
inputs,
|
285
|
+
triggeredByEvent
|
286
|
+
);
|
287
|
+
}
|
288
|
+
}
|