@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.
- package/LICENSE +21 -0
- package/README.md +560 -0
- package/dist/graphs/graph.d.ts +218 -0
- package/dist/graphs/graph.d.ts.map +1 -0
- package/dist/graphs/graph.js +334 -0
- package/dist/graphs/graph.js.map +1 -0
- package/dist/graphs/index.d.ts +7 -0
- package/dist/graphs/index.d.ts.map +1 -0
- package/dist/graphs/index.js +15 -0
- package/dist/graphs/index.js.map +1 -0
- package/dist/graphs/iterator.d.ts +138 -0
- package/dist/graphs/iterator.d.ts.map +1 -0
- package/dist/graphs/iterator.js +184 -0
- package/dist/graphs/iterator.js.map +1 -0
- package/dist/graphs/merge-state.d.ts +22 -0
- package/dist/graphs/merge-state.d.ts.map +1 -0
- package/dist/graphs/merge-state.js +56 -0
- package/dist/graphs/merge-state.js.map +1 -0
- package/dist/graphs/node-sequence.d.ts +63 -0
- package/dist/graphs/node-sequence.d.ts.map +1 -0
- package/dist/graphs/node-sequence.js +84 -0
- package/dist/graphs/node-sequence.js.map +1 -0
- package/dist/graphs/registry.d.ts +5 -0
- package/dist/graphs/registry.d.ts.map +1 -0
- package/dist/graphs/registry.js +6 -0
- package/dist/graphs/registry.js.map +1 -0
- package/dist/graphs/runtime-context.d.ts +189 -0
- package/dist/graphs/runtime-context.d.ts.map +1 -0
- package/dist/graphs/runtime-context.js +254 -0
- package/dist/graphs/runtime-context.js.map +1 -0
- package/dist/graphs/state-machine.d.ts +105 -0
- package/dist/graphs/state-machine.d.ts.map +1 -0
- package/dist/graphs/state-machine.js +130 -0
- package/dist/graphs/state-machine.js.map +1 -0
- package/dist/graphs/store/base-store.d.ts +90 -0
- package/dist/graphs/store/base-store.d.ts.map +1 -0
- package/dist/graphs/store/base-store.js +50 -0
- package/dist/graphs/store/base-store.js.map +1 -0
- package/dist/graphs/store/sqlite-store.d.ts +88 -0
- package/dist/graphs/store/sqlite-store.d.ts.map +1 -0
- package/dist/graphs/store/sqlite-store.js +109 -0
- package/dist/graphs/store/sqlite-store.js.map +1 -0
- package/dist/graphs/store/stored-run.d.ts +77 -0
- package/dist/graphs/store/stored-run.d.ts.map +1 -0
- package/dist/graphs/store/stored-run.js +88 -0
- package/dist/graphs/store/stored-run.js.map +1 -0
- package/dist/graphs/types.d.ts +15 -0
- package/dist/graphs/types.d.ts.map +1 -0
- package/dist/graphs/types.js +3 -0
- package/dist/graphs/types.js.map +1 -0
- package/dist/messages/index.d.ts +6 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +19 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/message.d.ts +143 -0
- package/dist/messages/message.d.ts.map +1 -0
- package/dist/messages/message.js +172 -0
- package/dist/messages/message.js.map +1 -0
- package/dist/messages/tool.d.ts +160 -0
- package/dist/messages/tool.d.ts.map +1 -0
- package/dist/messages/tool.js +173 -0
- package/dist/messages/tool.js.map +1 -0
- package/dist/models/BaseModel.d.ts +232 -0
- package/dist/models/BaseModel.d.ts.map +1 -0
- package/dist/models/BaseModel.js +247 -0
- package/dist/models/BaseModel.js.map +1 -0
- package/dist/models/BedrockModel.d.ts +112 -0
- package/dist/models/BedrockModel.d.ts.map +1 -0
- package/dist/models/BedrockModel.js +315 -0
- package/dist/models/BedrockModel.js.map +1 -0
- package/dist/models/TestModel.d.ts +135 -0
- package/dist/models/TestModel.d.ts.map +1 -0
- package/dist/models/TestModel.js +191 -0
- package/dist/models/TestModel.js.map +1 -0
- package/dist/nodes/function-node.d.ts +59 -0
- package/dist/nodes/function-node.d.ts.map +1 -0
- package/dist/nodes/function-node.js +72 -0
- package/dist/nodes/function-node.js.map +1 -0
- package/dist/nodes/index.d.ts +4 -0
- package/dist/nodes/index.d.ts.map +1 -0
- package/dist/nodes/index.js +9 -0
- package/dist/nodes/index.js.map +1 -0
- package/dist/nodes/interrupt-node.d.ts +51 -0
- package/dist/nodes/interrupt-node.d.ts.map +1 -0
- package/dist/nodes/interrupt-node.js +65 -0
- package/dist/nodes/interrupt-node.js.map +1 -0
- package/dist/nodes/model-node.d.ts +72 -0
- package/dist/nodes/model-node.d.ts.map +1 -0
- package/dist/nodes/model-node.js +80 -0
- package/dist/nodes/model-node.js.map +1 -0
- package/dist/nodes/types.d.ts +5 -0
- package/dist/nodes/types.d.ts.map +1 -0
- package/dist/nodes/types.js +3 -0
- package/dist/nodes/types.js.map +1 -0
- package/dist/tools.d.ts +65 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +56 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- 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!
|