@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.
Files changed (41) hide show
  1. package/.mocharc.json +2 -1
  2. package/README.md +61 -85
  3. package/graph/controller.ts +1 -1
  4. package/graph/event-manager.ts +288 -0
  5. package/graph/index.ts +153 -367
  6. package/graph/logger.ts +70 -0
  7. package/graph/node.ts +398 -0
  8. package/graph/observer.ts +361 -0
  9. package/interfaces/index.ts +102 -1
  10. package/modules/agenda/index.ts +3 -16
  11. package/modules/memory/adapters/in-memory/index.ts +6 -2
  12. package/modules/memory/adapters/meilisearch/index.ts +6 -2
  13. package/modules/memory/adapters/redis/index.ts +2 -2
  14. package/modules/memory/index.ts +1 -1
  15. package/package.json +10 -5
  16. package/test/graph/index.test.ts +244 -113
  17. package/test/graph/observer.test.ts +398 -0
  18. package/test/modules/agenda/node-cron.test.ts +37 -16
  19. package/test/modules/memory/adapters/in-memory.test.ts +2 -2
  20. package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
  21. package/test/modules/memory/base.test.ts +3 -3
  22. package/tsconfig.json +4 -2
  23. package/types/index.ts +24 -3
  24. package/dist/graph/controller.js +0 -72
  25. package/dist/graph/index.js +0 -501
  26. package/dist/index.js +0 -41
  27. package/dist/interfaces/index.js +0 -17
  28. package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
  29. package/dist/modules/agenda/index.js +0 -140
  30. package/dist/modules/embedding/adapters/ai/index.js +0 -57
  31. package/dist/modules/embedding/index.js +0 -59
  32. package/dist/modules/memory/adapters/in-memory/index.js +0 -210
  33. package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
  34. package/dist/modules/memory/adapters/redis/index.js +0 -158
  35. package/dist/modules/memory/index.js +0 -103
  36. package/dist/types/index.js +0 -2
  37. package/dist/utils/generate-action-schema.js +0 -43
  38. package/dist/utils/header-builder.js +0 -34
  39. package/test/modules/embedding/ai.test.ts +0 -78
  40. package/test/modules/memory/adapters/redis.test.ts +0 -169
  41. package/test/services/agenda.test.ts +0 -279
package/.mocharc.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "extension": ["ts"],
3
- "require": ["ts-node/register"]
3
+ "require": ["./node_modules/ts-node/register"],
4
+ "timeout": 5000
4
5
  }
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
- ## Key features
5
+ ## Features
6
6
 
7
- - **GraphFlow** – A 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
- - **Scalable** – Manage multiple graphs in parallel with GraphController.
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
- ## Verifying the Installation
54
-
55
- Create a new file `index.ts`:
56
-
57
- ```sh
58
- touch index.ts
59
- ```
34
+ ## Example
60
35
 
61
- Add the following code to test a simple graph execution:
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
- type ContextSchema = typeof ContextSchema;
72
-
73
- const myGraph = new GraphFlow<ContextSchema>("TestGraph", {
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: "printMessage",
53
+ name: "incrementCounter",
80
54
  execute: async (context) => {
81
- console.log(context.message);
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
- await myGraph.execute("printMessage");
78
+ // Execute the graph
79
+ graph.execute("incrementCounter");
80
+ graph.observe().state().subscribe(console.log);
90
81
  })();
91
82
  ```
92
83
 
93
- Run the test:
84
+ ## Features
94
85
 
95
- ```sh
96
- npx ts-node index.ts
97
- ```
86
+ ### Event handling
98
87
 
99
- Expected output:
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
- ### GraphFlow
102
+ ### State observation
108
103
 
109
- GraphFlow is the core execution engine that automates workflows through graph-based logic. Each node in the graph can:
104
+ ```typescript
105
+ // Observe specific node
106
+ graph.observe().node("myNode").subscribe(console.log);
110
107
 
111
- - Execute a specific action.
112
- - Wait for an event before proceeding.
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
- ### GraphController
117
-
118
- GraphController orchestrates multiple GraphFlows, enabling:
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
- - Creating a simple graph
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
- Check out the complete documentation at [GitBook](https://ai-ntellect.gitbook.io/core).
117
+ For complete documentation, visit our [GitBook](https://ai-ntellect.gitbook.io/core).
142
118
 
143
119
  ## Contributing
144
120
 
145
- Contributions are welcome. To suggest an improvement or report an issue:
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)
@@ -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 GraphController {
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
+ }