@ellyco/agentic 0.1.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +560 -0
  3. package/dist/graphs/graph.d.ts +218 -0
  4. package/dist/graphs/graph.d.ts.map +1 -0
  5. package/dist/graphs/graph.js +334 -0
  6. package/dist/graphs/graph.js.map +1 -0
  7. package/dist/graphs/index.d.ts +7 -0
  8. package/dist/graphs/index.d.ts.map +1 -0
  9. package/dist/graphs/index.js +15 -0
  10. package/dist/graphs/index.js.map +1 -0
  11. package/dist/graphs/iterator.d.ts +138 -0
  12. package/dist/graphs/iterator.d.ts.map +1 -0
  13. package/dist/graphs/iterator.js +184 -0
  14. package/dist/graphs/iterator.js.map +1 -0
  15. package/dist/graphs/merge-state.d.ts +22 -0
  16. package/dist/graphs/merge-state.d.ts.map +1 -0
  17. package/dist/graphs/merge-state.js +56 -0
  18. package/dist/graphs/merge-state.js.map +1 -0
  19. package/dist/graphs/node-sequence.d.ts +63 -0
  20. package/dist/graphs/node-sequence.d.ts.map +1 -0
  21. package/dist/graphs/node-sequence.js +84 -0
  22. package/dist/graphs/node-sequence.js.map +1 -0
  23. package/dist/graphs/registry.d.ts +5 -0
  24. package/dist/graphs/registry.d.ts.map +1 -0
  25. package/dist/graphs/registry.js +6 -0
  26. package/dist/graphs/registry.js.map +1 -0
  27. package/dist/graphs/runtime-context.d.ts +189 -0
  28. package/dist/graphs/runtime-context.d.ts.map +1 -0
  29. package/dist/graphs/runtime-context.js +254 -0
  30. package/dist/graphs/runtime-context.js.map +1 -0
  31. package/dist/graphs/state-machine.d.ts +105 -0
  32. package/dist/graphs/state-machine.d.ts.map +1 -0
  33. package/dist/graphs/state-machine.js +130 -0
  34. package/dist/graphs/state-machine.js.map +1 -0
  35. package/dist/graphs/store/base-store.d.ts +90 -0
  36. package/dist/graphs/store/base-store.d.ts.map +1 -0
  37. package/dist/graphs/store/base-store.js +50 -0
  38. package/dist/graphs/store/base-store.js.map +1 -0
  39. package/dist/graphs/store/sqlite-store.d.ts +88 -0
  40. package/dist/graphs/store/sqlite-store.d.ts.map +1 -0
  41. package/dist/graphs/store/sqlite-store.js +109 -0
  42. package/dist/graphs/store/sqlite-store.js.map +1 -0
  43. package/dist/graphs/store/stored-run.d.ts +77 -0
  44. package/dist/graphs/store/stored-run.d.ts.map +1 -0
  45. package/dist/graphs/store/stored-run.js +88 -0
  46. package/dist/graphs/store/stored-run.js.map +1 -0
  47. package/dist/graphs/types.d.ts +15 -0
  48. package/dist/graphs/types.d.ts.map +1 -0
  49. package/dist/graphs/types.js +3 -0
  50. package/dist/graphs/types.js.map +1 -0
  51. package/dist/messages/index.d.ts +6 -0
  52. package/dist/messages/index.d.ts.map +1 -0
  53. package/dist/messages/index.js +19 -0
  54. package/dist/messages/index.js.map +1 -0
  55. package/dist/messages/message.d.ts +143 -0
  56. package/dist/messages/message.d.ts.map +1 -0
  57. package/dist/messages/message.js +172 -0
  58. package/dist/messages/message.js.map +1 -0
  59. package/dist/messages/tool.d.ts +160 -0
  60. package/dist/messages/tool.d.ts.map +1 -0
  61. package/dist/messages/tool.js +173 -0
  62. package/dist/messages/tool.js.map +1 -0
  63. package/dist/models/BaseModel.d.ts +232 -0
  64. package/dist/models/BaseModel.d.ts.map +1 -0
  65. package/dist/models/BaseModel.js +247 -0
  66. package/dist/models/BaseModel.js.map +1 -0
  67. package/dist/models/BedrockModel.d.ts +112 -0
  68. package/dist/models/BedrockModel.d.ts.map +1 -0
  69. package/dist/models/BedrockModel.js +315 -0
  70. package/dist/models/BedrockModel.js.map +1 -0
  71. package/dist/models/TestModel.d.ts +135 -0
  72. package/dist/models/TestModel.d.ts.map +1 -0
  73. package/dist/models/TestModel.js +191 -0
  74. package/dist/models/TestModel.js.map +1 -0
  75. package/dist/nodes/function-node.d.ts +59 -0
  76. package/dist/nodes/function-node.d.ts.map +1 -0
  77. package/dist/nodes/function-node.js +72 -0
  78. package/dist/nodes/function-node.js.map +1 -0
  79. package/dist/nodes/index.d.ts +4 -0
  80. package/dist/nodes/index.d.ts.map +1 -0
  81. package/dist/nodes/index.js +9 -0
  82. package/dist/nodes/index.js.map +1 -0
  83. package/dist/nodes/interrupt-node.d.ts +51 -0
  84. package/dist/nodes/interrupt-node.d.ts.map +1 -0
  85. package/dist/nodes/interrupt-node.js +65 -0
  86. package/dist/nodes/interrupt-node.js.map +1 -0
  87. package/dist/nodes/model-node.d.ts +72 -0
  88. package/dist/nodes/model-node.d.ts.map +1 -0
  89. package/dist/nodes/model-node.js +80 -0
  90. package/dist/nodes/model-node.js.map +1 -0
  91. package/dist/nodes/types.d.ts +5 -0
  92. package/dist/nodes/types.d.ts.map +1 -0
  93. package/dist/nodes/types.js +3 -0
  94. package/dist/nodes/types.js.map +1 -0
  95. package/dist/tools.d.ts +65 -0
  96. package/dist/tools.d.ts.map +1 -0
  97. package/dist/tools.js +56 -0
  98. package/dist/tools.js.map +1 -0
  99. package/dist/types.d.ts +17 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +3 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +32 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Elina Garcia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,560 @@
1
+ # Ellyco Agentic
2
+
3
+ A powerful TypeScript framework for building stateful, agentic workflows with built-in support for AI model orchestration, tool usage, interruptions, and persistent checkpointing.
4
+
5
+ ## Features
6
+
7
+ ✨ **Graph-Based Execution Engine** - Define complex workflows as directed graphs with nodes and edges
8
+ 🤖 **AI Model Integration** - Built-in support for AWS Bedrock and custom model implementations
9
+ 🔧 **Tool Calling** - Seamless tool definition and execution with automatic validation
10
+ ⏸️ **Interrupts & Resumption** - Pause execution for human input or external events, then resume from checkpoint
11
+ 💾 **Persistent Checkpointing** - SQLite-based state persistence for long-running workflows
12
+ 🔄 **State Management** - Declarative state merging with support for custom merge strategies
13
+ 🔀 **Flexible Graphs** - State machines, linear sequences, and iterators for different workflow patterns
14
+ 📦 **Fully Typed** - Complete TypeScript support with Zod schema validation
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install ellyco-agentic
20
+ ```
21
+
22
+ ### Dependencies
23
+ - `zod` - Schema validation
24
+ - `@aws-sdk/client-bedrock-runtime` - For Bedrock model support
25
+ - `better-sqlite3` - For persistent storage
26
+ - `@paralleldrive/cuid2` - For run ID generation
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Define Your Messages
31
+
32
+ ```typescript
33
+ import { SystemMessage, UserMessage, AgentMessage } from 'ellyco-agentic';
34
+
35
+ const systemMsg = new SystemMessage(
36
+ "You are a helpful assistant that processes data."
37
+ );
38
+
39
+ const userMsg = new UserMessage("Process this data: {data}");
40
+
41
+ // Interpolate template variables
42
+ userMsg.interpolate({ data: "important info" });
43
+ ```
44
+
45
+ ### 2. Define Your Tools
46
+
47
+ ```typescript
48
+ import { defineTool, tool } from 'ellyco-agentic';
49
+ import { z } from 'zod';
50
+
51
+ const searchTool = defineTool(
52
+ "search",
53
+ "Search for information",
54
+ z.object({
55
+ query: z.string(),
56
+ limit: z.number().optional()
57
+ })
58
+ );
59
+
60
+ const searchImplementation = tool(
61
+ searchTool,
62
+ async (input) => {
63
+ // Implement search logic
64
+ return { results: [...] };
65
+ }
66
+ );
67
+ ```
68
+
69
+ ### 3. Configure Your Model
70
+
71
+ ```typescript
72
+ import { BedrockModel } from 'ellyco-agentic';
73
+
74
+ const model = new BedrockModel({
75
+ modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
76
+ temperature: 0.7,
77
+ maxTokens: 2048
78
+ })
79
+ .withSystemMessage(systemMsg)
80
+ .withTools([searchTool]);
81
+
82
+ const response = await model.invoke([userMsg]);
83
+ ```
84
+
85
+ ### 4. Build a Graph
86
+
87
+ ```typescript
88
+ import { StateMachine, makeNode } from 'ellyco-agentic';
89
+ import { z } from 'zod';
90
+
91
+ const schema = z.object({
92
+ input: z.string(),
93
+ output: z.string().optional(),
94
+ iterations: z.number().default(0)
95
+ });
96
+
97
+ const graph = new StateMachine(schema);
98
+
99
+ // Add nodes
100
+ graph.addNode("process", makeNode((state) => ({
101
+ output: state.input.toUpperCase(),
102
+ iterations: state.iterations + 1
103
+ })));
104
+
105
+ graph.addNode("validate", makeNode((state) => {
106
+ if (state.output && state.output.length > 0) {
107
+ return { output: state.output };
108
+ }
109
+ throw new Error("Invalid output");
110
+ }));
111
+
112
+ // Add edges
113
+ graph.addEdge("start", "process");
114
+ graph.addEdge("process", "validate");
115
+ graph.addEdge("validate", "end");
116
+
117
+ // Execute
118
+ const result = await graph.invoke({ input: "hello world" });
119
+ console.log(result.state.output); // "HELLO WORLD"
120
+ ```
121
+
122
+ ## Core Concepts
123
+
124
+ ### Graphs
125
+
126
+ Graphs represent workflows as directed acyclic graphs (DAGs) where execution flows from node to node. Three main types:
127
+
128
+ #### StateMachine
129
+ The most flexible graph type - manually define nodes and edges with conditional routing.
130
+
131
+ ```typescript
132
+ const sm = new StateMachine(schema);
133
+ sm.addNode("decision", decisionNode);
134
+ sm.addNode("path1", path1Node);
135
+ sm.addNode("path2", path2Node);
136
+
137
+ // Conditional edge - route based on state
138
+ sm.addConditionalEdge(
139
+ "decision",
140
+ ["path1", "path2", "end"],
141
+ (state) => state.priority > 5 ? "path1" : "path2"
142
+ );
143
+ ```
144
+
145
+ #### NodeSequence
146
+ Execute nodes linearly, one after another.
147
+
148
+ ```typescript
149
+ const sequence = new NodeSequence(schema);
150
+ sequence
151
+ .next(node1)
152
+ .next(node2)
153
+ .next(node3);
154
+ ```
155
+
156
+ #### Iterator
157
+ Loop over an array in state, executing a node for each item.
158
+
159
+ ```typescript
160
+ const schema = z.object({
161
+ items: z.array(z.object({ value: z.number() }))
162
+ });
163
+
164
+ const iterator = new Iterator(schema, "item", "items");
165
+ iterator.setLoopedNode(loopedNode);
166
+
167
+ const result = await iterator.invoke({
168
+ items: [{ value: 1 }, { value: 2 }, { value: 3 }]
169
+ });
170
+ ```
171
+
172
+ ### Nodes
173
+
174
+ Nodes are the building blocks of graphs - they execute logic and return partial state updates.
175
+
176
+ #### FunctionNode
177
+ Simple synchronous or asynchronous functions.
178
+
179
+ ```typescript
180
+ import { makeNode } from 'ellyco-agentic';
181
+
182
+ const node = makeNode((state, context) => ({
183
+ processed: true,
184
+ timestamp: Date.now()
185
+ }));
186
+ ```
187
+
188
+ #### ModelNode
189
+ Invoke an AI model and capture the response.
190
+
191
+ ```typescript
192
+ const modelNode = new ModelNode(model, {
193
+ messages: (state, context) => [
194
+ new UserMessage(state.userInput)
195
+ ],
196
+ output: "modelOutput"
197
+ });
198
+ ```
199
+
200
+ #### InterruptNode
201
+ Pause execution for human input or external intervention.
202
+
203
+ ```typescript
204
+ const confirmNode = new InterruptNode(
205
+ "Please confirm the action before proceeding"
206
+ );
207
+ ```
208
+
209
+ ### Messages
210
+
211
+ Messages represent communication in the system with different roles:
212
+
213
+ ```typescript
214
+ import { SystemMessage, UserMessage, AgentMessage } from 'ellyco-agentic';
215
+
216
+ // System messages set context
217
+ const system = new SystemMessage("You are a data analyst");
218
+
219
+ // User messages are requests
220
+ const user = new UserMessage("Analyze {dataset_name}");
221
+ user.interpolate({ dataset_name: "sales_data" });
222
+
223
+ // Agent messages are responses
224
+ const agent = new AgentMessage("The analysis shows...");
225
+ ```
226
+
227
+ ### Tool Usage
228
+
229
+ Tools enable models to request external operations:
230
+
231
+ ```typescript
232
+ import { ToolRequest, ToolResponse, ToolError } from 'ellyco-agentic';
233
+
234
+ // Model requests a tool
235
+ const request = new ToolRequest(
236
+ "call_123",
237
+ "search",
238
+ { query: "latest news" }
239
+ );
240
+
241
+ // Tool execution succeeds
242
+ const response = new ToolResponse(
243
+ "call_123",
244
+ "search",
245
+ { results: [...] }
246
+ );
247
+
248
+ // Or fails
249
+ const error = new ToolError(
250
+ "call_123",
251
+ "search",
252
+ "API rate limit exceeded"
253
+ );
254
+ ```
255
+
256
+ ### State Management
257
+
258
+ State flows through graphs, with each node returning partial updates that are merged using the schema:
259
+
260
+ ```typescript
261
+ const base = { count: 5, items: [1, 2, 3] };
262
+ const changes = { count: 10, items: [4, 5] };
263
+
264
+ // Merged state
265
+ const merged = { count: 10, items: [4, 5] };
266
+ ```
267
+
268
+ ## Advanced Features
269
+
270
+ ### Interrupts and Resumption
271
+
272
+ Pause execution for human input and resume from the checkpoint:
273
+
274
+ ```typescript
275
+ // Start execution
276
+ let result = await graph.invoke({ data: "..." });
277
+
278
+ if (result.exitReason === "interrupt") {
279
+ console.log("Paused:", result.exitMessage);
280
+ console.log("Run ID:", result.runId);
281
+ console.log("Cursor:", result.cursor);
282
+
283
+ // Get user confirmation...
284
+
285
+ // Resume from checkpoint
286
+ const result2 = await graph.invoke(
287
+ result.state,
288
+ { resumeFrom: result.cursor }
289
+ );
290
+ }
291
+ ```
292
+
293
+ ### Persistent Storage
294
+
295
+ Use SQLite to persist and resume runs across sessions:
296
+
297
+ ```typescript
298
+ import { SQLiteStore } from 'ellyco-agentic';
299
+ import Database from 'better-sqlite3';
300
+
301
+ // Setup database
302
+ const db = new Database("runs.db");
303
+ const store = new SQLiteStore(db, "graph_runs");
304
+
305
+ // Run with persistence
306
+ let result = await graph.invoke(initialState, { store });
307
+
308
+ if (result.exitReason === "interrupt") {
309
+ // Later, in a different process:
310
+ const db2 = new Database("runs.db");
311
+ const store2 = new SQLiteStore(db2);
312
+
313
+ // Resume from stored checkpoint
314
+ const result2 = await graph.invoke(result.state, {
315
+ store: store2,
316
+ runId: result.runId
317
+ });
318
+ }
319
+
320
+ await store.dispose();
321
+ ```
322
+
323
+ ### Structured Output
324
+
325
+ Force models to return data in a specific format:
326
+
327
+ ```typescript
328
+ import { z } from 'zod';
329
+
330
+ const schema = z.object({
331
+ sentiment: z.enum(["positive", "negative", "neutral"]),
332
+ confidence: z.number().min(0).max(1),
333
+ explanation: z.string()
334
+ });
335
+
336
+ const wrapper = model.withStructuredOutput(schema);
337
+ const result = await wrapper.invoke([userMessage]);
338
+
339
+ // TypeScript knows result matches the schema
340
+ console.log(result.sentiment); // string enum
341
+ console.log(result.confidence); // number 0-1
342
+ console.log(result.explanation); // string
343
+ ```
344
+
345
+ ### Custom Models
346
+
347
+ Implement your own model provider:
348
+
349
+ ```typescript
350
+ import { BaseModel, InvokeResponse, ModelMessages } from 'ellyco-agentic';
351
+
352
+ class MyCustomModel extends BaseModel {
353
+ protected async runModel(
354
+ messages: ModelMessages[]
355
+ ): Promise<InvokeResponse> {
356
+ // Your API integration here
357
+ const response = await fetch("your-api/v1/chat", {
358
+ method: "POST",
359
+ body: JSON.stringify({ messages })
360
+ });
361
+
362
+ return {
363
+ messages: [...],
364
+ usage: { inputTokens: 100, outputTokens: 50 },
365
+ stopReason: InvokeResponseStopReason.END_TURN
366
+ };
367
+ }
368
+ }
369
+
370
+ const model = new MyCustomModel({ temperature: 0.7 });
371
+ ```
372
+
373
+ ### Testing with TestModel
374
+
375
+ Use the mock model for testing without hitting real APIs:
376
+
377
+ ```typescript
378
+ import { TestModel, TestResponseConfig } from 'ellyco-agentic';
379
+
380
+ const testModel = new TestModel({ temperature: 0.7 });
381
+
382
+ // Configure expected responses
383
+ const config = new TestResponseConfig()
384
+ .userSends([new UserMessage("Hello")])
385
+ .respondWith([new AgentMessage("Hi there!")]);
386
+
387
+ testModel.addTestConfig(config);
388
+
389
+ // In tests
390
+ const response = await testModel.invoke([new UserMessage("Hello")]);
391
+ expect(response.messages[0].text).toBe("Hi there!");
392
+ ```
393
+
394
+ ## Architecture
395
+
396
+ ### Execution Flow
397
+
398
+ ```
399
+ Graph.invoke()
400
+
401
+ Create RuntimeContext
402
+
403
+ Loop:
404
+ - Get current node
405
+ - Execute node.run(state, context)
406
+ - Merge returned state
407
+ - Check for interrupts
408
+ - Transition to next node
409
+
410
+ Return result (end or interrupt)
411
+ ```
412
+
413
+ ### State Transformation
414
+
415
+ ```
416
+ Graph State
417
+
418
+ stateToNodeState() ← Node gets specific state type
419
+
420
+ Node.run() ← Node executes
421
+
422
+ nodeStateToState() ← Convert back to graph state
423
+
424
+ mergeState() ← Merge with existing state
425
+
426
+ Updated Graph State
427
+ ```
428
+
429
+ ### Context Layers
430
+
431
+ Nested graphs create a stack of context layers for tracking execution position:
432
+
433
+ ```typescript
434
+ RuntimeContext
435
+ └─ ContextLayer 0 (root)
436
+ ├─ currentNode: "node1"
437
+ ├─ custom: { ... }
438
+ └─ ContextLayer 1 (nested graph)
439
+ ├─ currentNode: "subnode"
440
+ └─ custom: { ... }
441
+ ```
442
+
443
+ ## API Reference
444
+
445
+ ### Graph Classes
446
+
447
+ - **`Graph<Z, S, NS>`** - Abstract base class for all graphs
448
+ - **`StateMachine<T, S>`** - Flexible graph with manual node/edge definition
449
+ - **`NodeSequence<T, S>`** - Linear graph executing nodes in sequence
450
+ - **`Iterator<Item, T, Prefix, S, NS>`** - Loop over array items
451
+
452
+ ### Node Classes
453
+
454
+ - **`FunctionNode<T>`** - Execute a function
455
+ - **`ModelNode<T>`** - Invoke an AI model
456
+ - **`InterruptNode<T>`** - Pause execution
457
+
458
+ ### Message Classes
459
+
460
+ - **`BaseMessage`** - Abstract message base
461
+ - **`SystemMessage`** - System context message
462
+ - **`UserMessage`** - User request message
463
+ - **`AgentMessage`** - Agent response message
464
+ - **`ToolRequest<T>`** - Tool invocation request
465
+ - **`ToolResponse<T>`** - Tool execution result
466
+ - **`ToolError`** - Tool execution error
467
+
468
+ ### Model Classes
469
+
470
+ - **`BaseModel`** - Abstract model base class
471
+ - **`BedrockModel`** - AWS Bedrock integration
472
+ - **`TestModel`** - Mock model for testing
473
+
474
+ ### Storage Classes
475
+
476
+ - **`BaseStore`** - Abstract store interface
477
+ - **`SQLiteStore`** - SQLite-based persistence
478
+ - **`StoredRun`** - Single run checkpoint wrapper
479
+
480
+ ## Complete Example
481
+
482
+ Here's a complete example combining all concepts:
483
+
484
+ ```typescript
485
+ import {
486
+ StateMachine,
487
+ BedrockModel,
488
+ ModelNode,
489
+ makeNode,
490
+ UserMessage,
491
+ SystemMessage,
492
+ SQLiteStore,
493
+ defineTool,
494
+ tool
495
+ } from 'ellyco-agentic';
496
+ import { z } from 'zod';
497
+ import Database from 'better-sqlite3';
498
+
499
+ // Define schema
500
+ const schema = z.object({
501
+ query: z.string(),
502
+ searchResults: z.array(z.string()).default([]),
503
+ summary: z.string().optional(),
504
+ attempts: z.number().default(0)
505
+ });
506
+
507
+ // Define tool
508
+ const searchTool = defineTool(
509
+ "search",
510
+ "Search for information",
511
+ z.object({ query: z.string() })
512
+ );
513
+
514
+ // Setup model
515
+ const model = new BedrockModel({
516
+ modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
517
+ temperature: 0.7
518
+ })
519
+ .withSystemMessage("You are a research assistant.")
520
+ .withTools([searchTool]);
521
+
522
+ // Build graph
523
+ const graph = new StateMachine(schema);
524
+
525
+ graph.addNode("search", makeNode((state) => ({
526
+ searchResults: ["Result 1", "Result 2", "Result 3"],
527
+ attempts: state.attempts + 1
528
+ })));
529
+
530
+ graph.addNode("analyze", new ModelNode(model, {
531
+ messages: (state) => [
532
+ new UserMessage(`Summarize these results: ${state.searchResults.join(", ")}`)
533
+ ],
534
+ output: "summary"
535
+ }));
536
+
537
+ graph.addEdge("start", "search");
538
+ graph.addEdge("search", "analyze");
539
+ graph.addEdge("analyze", "end");
540
+
541
+ // Setup storage
542
+ const db = new Database("research.db");
543
+ const store = new SQLiteStore(db);
544
+
545
+ // Execute
546
+ const result = await graph.invoke(
547
+ { query: "climate change" },
548
+ { store }
549
+ );
550
+
551
+ console.log("Status:", result.exitReason);
552
+ console.log("Results:", result.state.searchResults);
553
+ console.log("Summary:", result.state.summary);
554
+
555
+ db.close();
556
+ ```
557
+
558
+
559
+
560
+ **Questions?** Check out the comprehensive JSDoc comments throughout the codebase for detailed API documentation and examples!