@ai.ntellect/core 0.6.20 → 0.7.0

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 +87 -148
  3. package/graph/controller.ts +1 -1
  4. package/graph/event-manager.ts +288 -0
  5. package/graph/index.ts +152 -384
  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/embedding/index.ts +3 -3
  12. package/package.json +12 -20
  13. package/test/graph/index.test.ts +296 -154
  14. package/test/graph/observer.test.ts +398 -0
  15. package/test/modules/agenda/node-cron.test.ts +37 -16
  16. package/test/modules/memory/adapters/in-memory.test.ts +2 -2
  17. package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
  18. package/test/modules/memory/base.test.ts +3 -3
  19. package/tsconfig.json +4 -2
  20. package/types/index.ts +23 -2
  21. package/utils/generate-action-schema.ts +8 -7
  22. package/.env.example +0 -2
  23. package/dist/graph/controller.js +0 -75
  24. package/dist/graph/index.js +0 -402
  25. package/dist/index.js +0 -41
  26. package/dist/interfaces/index.js +0 -17
  27. package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
  28. package/dist/modules/agenda/index.js +0 -140
  29. package/dist/modules/embedding/adapters/ai/index.js +0 -57
  30. package/dist/modules/embedding/index.js +0 -59
  31. package/dist/modules/memory/adapters/in-memory/index.js +0 -210
  32. package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
  33. package/dist/modules/memory/adapters/redis/index.js +0 -158
  34. package/dist/modules/memory/index.js +0 -103
  35. package/dist/types/index.js +0 -2
  36. package/dist/utils/generate-action-schema.js +0 -42
  37. package/dist/utils/header-builder.js +0 -34
  38. package/graph.ts +0 -74
  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
@@ -1,192 +1,131 @@
1
- # **@ai.ntellect/core Documentation**
1
+ # @ai.ntellect/core
2
2
 
3
- ## **1. Introduction**
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
- The **`@ai.ntellect/core`** framework is a powerful tool designed to **model, execute, and manage dynamic interaction flows** using **graph structures**. Unlike traditional **Directed Acyclic Graphs (DAGs)**, this framework supports cycles, enabling nodes to be executed multiple times and allowing for loop-based workflows.
5
+ ## Features
6
6
 
7
- ### **Key Features**
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
8
12
 
9
- - Dynamic workflow execution
10
- - Cyclic and acyclic graph support
11
- - Strong typing with Zod validation
12
- - Event-driven architecture
13
- - Built-in error handling and retries
14
- - Conditional execution paths
15
- - Parameter validation
16
- - State management
13
+ ## Installation
17
14
 
18
- ### **Common Use Cases**
15
+ ### Prerequisites
19
16
 
20
- - **AI Agents**: Building conversational AI systems that can maintain context and make decisions
21
- - **Transaction Processing**: Managing complex financial workflows with validation chains
22
- - **Task Orchestration**: Coordinating multiple dependent operations
23
- - **Decision Trees**: Implementing complex business logic with multiple branches
24
- - **State Machines**: Managing application state transitions
25
- - **Event Processing**: Handling and responding to system events
17
+ - Node.js (LTS version recommended)
18
+ - TypeScript
19
+ - Zod (for data validation)
26
20
 
27
- ## **2. Core Concepts**
21
+ Verify your installation:
28
22
 
29
- ### **2.1 Graph Theory Foundation**
30
-
31
- A directed graph in our framework is defined as **G = (V, E)** where:
32
-
33
- - **V**: Set of nodes (vertices)
34
- - **E**: Set of directed edges
35
-
36
- Each node represents an executable action, and edges represent conditional transitions between actions.
37
-
38
- #### Example Graph Structure:
39
-
40
- ```
41
- (ValidateInput) → (ProcessData) → (SaveResult)
42
- ↓ ↑
43
- (RetryInput) ──────────────────────
44
- ```
45
-
46
- ### **2.2 Node Types**
47
-
48
- #### **Basic Node**
49
-
50
- ```typescript
51
- const basicNode: Node<ContextType> = {
52
- name: "processData",
53
- execute: async (context) => {
54
- // Process data
55
- },
56
- next: ["saveResult"],
57
- };
58
- ```
59
-
60
- #### **Conditional Node**
61
-
62
- ```typescript
63
- const conditionalNode: Node<ContextType> = {
64
- name: "validateInput",
65
- condition: (context) => context.isValid,
66
- execute: async (context) => {
67
- // Validation logic
68
- },
69
- next: ["processData"],
70
- };
23
+ ```sh
24
+ node -v
25
+ npm -v
71
26
  ```
72
27
 
73
- ## **3. Advanced Features**
74
-
75
- ### **3.1 Event-Driven Execution**
76
-
77
- Nodes can respond to system events:
28
+ ### Installing the framework
78
29
 
79
- ```typescript
80
- const eventNode: Node<ContextType> = {
81
- name: "handleUserInput",
82
- events: ["userSubmitted"],
83
- execute: async (context) => {
84
- // Handle user input
85
- },
86
- };
30
+ ```sh
31
+ npm install @ai.ntellect/core zod
87
32
  ```
88
33
 
89
- ### **3.2 Retry Mechanisms**
90
-
91
- Built-in retry support for handling transient failures:
34
+ ## Example
92
35
 
93
36
  ```typescript
94
- const retryableNode: Node<ContextType> = {
95
- name: "apiCall",
96
- retry: {
97
- maxAttempts: 3,
98
- delay: 1000, // ms
99
- },
100
- execute: async (context) => {
101
- // API call logic
102
- },
103
- };
104
- ```
37
+ import { z } from "zod";
38
+ import { GraphFlow } from "@ai.ntellect/core";
105
39
 
106
- ## **4. Real-World Examples**
107
-
108
- ### **4.1 AI Agent Workflow**
40
+ // Define context schema
41
+ const ContextSchema = z.object({
42
+ message: z.string(),
43
+ counter: z.number(),
44
+ });
109
45
 
110
- ```typescript
111
- const aiAgentGraph = new Graph<AIContextType>("AIAgent", {
46
+ // Create graph instance
47
+ const graph = new GraphFlow<typeof ContextSchema>("MyGraph", {
48
+ name: "MyGraph",
49
+ schema: ContextSchema,
50
+ context: { message: "Hello", counter: 0 },
112
51
  nodes: [
113
52
  {
114
- name: "analyzeInput",
53
+ name: "incrementCounter",
115
54
  execute: async (context) => {
116
- context.intent = await analyzeUserIntent(context.input);
55
+ context.counter++;
117
56
  },
118
- next: ["selectAction"],
57
+ next: ["checkThreshold"],
119
58
  },
120
59
  {
121
- name: "selectAction",
60
+ name: "checkThreshold",
61
+ condition: (context) => context.counter < 5,
122
62
  execute: async (context) => {
123
- context.selectedAction = determineNextAction(context.intent);
63
+ if (context.counter >= 5) {
64
+ context.message = "Threshold reached!";
65
+ }
124
66
  },
125
- next: ["validateResponse"],
126
- },
127
- {
128
- name: "generateResponse",
129
- execute: async (context) => {
130
- context.response = await generateAIResponse(context);
67
+ next: ["incrementCounter"],
68
+ retry: {
69
+ maxAttempts: 3,
70
+ delay: 1000,
131
71
  },
132
- next: ["validateResponse"],
133
72
  },
134
73
  ],
135
74
  });
75
+
76
+ // Observe state changes
77
+ (async () => {
78
+ // Execute the graph
79
+ graph.execute("incrementCounter");
80
+ graph.observe().state().subscribe(console.log);
81
+ })();
136
82
  ```
137
83
 
138
- ### **4.2 Transaction Processing**
84
+ ## Features
85
+
86
+ ### Event handling
139
87
 
140
88
  ```typescript
141
- const transactionGraph = new Graph<TransactionContext>("TransactionProcessor", {
142
- nodes: [
143
- {
144
- name: "validateFunds",
145
- execute: async (context) => {
146
- context.hasSufficientFunds = await checkBalance(context.amount);
147
- },
148
- next: ["processPayment"],
149
- },
150
- {
151
- name: "processPayment",
152
- retry: {
153
- maxAttempts: 3,
154
- delay: 1000,
155
- },
156
- condition: (context) => context.hasSufficientFunds,
157
- execute: async (context) => {
158
- await processPayment(context.transactionData);
159
- },
160
- next: ["notifyUser"],
161
- },
162
- ],
163
- });
89
+ // Event-driven node
90
+ {
91
+ name: "waitForEvent",
92
+ events: ["dataReceived"],
93
+ execute: async (context, event) => {
94
+ context.data = event.payload;
95
+ }
96
+ }
97
+
98
+ // Emit events
99
+ graph.emit("dataReceived", { value: 42 });
164
100
  ```
165
101
 
166
- ## **5. Event Listeners**
102
+ ### State observation
167
103
 
168
104
  ```typescript
169
- graph.on("nodeStarted", ({ name, context }) => {
170
- console.log(`Node ${name} started with context:`, context);
171
- });
105
+ // Observe specific node
106
+ graph.observe().node("myNode").subscribe(console.log);
172
107
 
173
- graph.on("nodeCompleted", ({ name, context }) => {
174
- console.log(`Node ${name} completed with context:`, context);
175
- });
108
+ // Observe specific properties
109
+ graph.observe().property("counter").subscribe(console.log);
176
110
 
177
- graph.on("nodeError", ({ name, error }) => {
178
- console.error(`Error in node ${name}:`, error);
179
- });
111
+ // Observe events
112
+ graph.observe().event("nodeCompleted").subscribe(console.log);
180
113
  ```
181
114
 
182
- ## **6. Future Developments**
115
+ ## Documentation
116
+
117
+ For complete documentation, visit our [GitBook](https://ai-ntellect.gitbook.io/core).
118
+
119
+ ## Contributing
120
+
121
+ Contributions are welcome! To suggest an improvement or report an issue:
183
122
 
184
- Planned features include:
123
+ - Join our [Discord community](https://discord.gg/kEc5gWXJ)
124
+ - Explore the [GitBook documentation](https://ai-ntellect.gitbook.io/core)
125
+ - Open an issue on GitHub
185
126
 
186
- - Advanced memory management for AI agents
187
- - Graph composition and nesting
188
- - Real-time monitoring dashboard
189
- - Performance analytics
190
- - Distributed execution support
127
+ ## Useful links
191
128
 
192
- For more information and updates, visit the official documentation or join our community discussions.
129
+ - Documentation: [GitBook](https://ai-ntellect.gitbook.io/core)
130
+ - Community: [Discord](https://discord.gg/kEc5gWXJ)
131
+ - GitHub Repository: [@ai.ntellect/core](https://github.com/ai-ntellect/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
+ }