@wundr.io/langgraph-orchestrator 1.0.2-dev.20260530174250.ef0ec927
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/README.md +842 -0
- package/package.json +60 -0
- package/src/checkpointing.ts +702 -0
- package/src/edges/conditional-edge.ts +518 -0
- package/src/edges/loop-edge.ts +623 -0
- package/src/index.ts +416 -0
- package/src/nodes/decision-node.ts +538 -0
- package/src/nodes/human-node.ts +572 -0
- package/src/nodes/llm-node.ts +448 -0
- package/src/nodes/tool-node.ts +525 -0
- package/src/prebuilt-graphs/plan-execute-refine.ts +769 -0
- package/src/state-graph.ts +990 -0
- package/src/types.ts +729 -0
package/README.md
ADDED
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
# @wundr.io/langgraph-orchestrator
|
|
2
|
+
|
|
3
|
+
LangGraph-style cyclic, state-driven workflow orchestration for AI agents. This package provides a powerful framework for building complex AI workflows with conditional branching, state management, checkpointing, and human-in-the-loop capabilities.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @wundr.io/langgraph-orchestrator
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @wundr.io/langgraph-orchestrator
|
|
11
|
+
# or
|
|
12
|
+
yarn add @wundr.io/langgraph-orchestrator
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Package Overview
|
|
16
|
+
|
|
17
|
+
The `@wundr.io/langgraph-orchestrator` package enables you to build sophisticated AI agent workflows using a state graph paradigm. Key features include:
|
|
18
|
+
|
|
19
|
+
- **State Graph Architecture**: Define workflows as directed graphs with nodes and edges
|
|
20
|
+
- **Cyclic Workflows**: Support for loops and iterative processing patterns
|
|
21
|
+
- **Decision Nodes**: Conditional branching based on state values
|
|
22
|
+
- **Checkpointing**: Save and restore workflow state for fault tolerance
|
|
23
|
+
- **Human-in-the-Loop**: Pause workflows for human input or approval
|
|
24
|
+
- **Type Safety**: Full TypeScript support with Zod schema validation
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import {
|
|
30
|
+
StateGraph,
|
|
31
|
+
createLLMNode,
|
|
32
|
+
createToolNode,
|
|
33
|
+
createDecisionNode,
|
|
34
|
+
MemoryCheckpointer
|
|
35
|
+
} from '@wundr.io/langgraph-orchestrator';
|
|
36
|
+
|
|
37
|
+
// Create a workflow graph
|
|
38
|
+
const graph = new StateGraph('my-workflow')
|
|
39
|
+
.addNode('agent', createLLMNode({
|
|
40
|
+
id: 'agent',
|
|
41
|
+
name: 'Agent',
|
|
42
|
+
config: { model: 'claude-3-sonnet-20240229' }
|
|
43
|
+
}))
|
|
44
|
+
.addNode('tools', createToolNode({
|
|
45
|
+
id: 'tools',
|
|
46
|
+
name: 'Tools'
|
|
47
|
+
}))
|
|
48
|
+
.addEdge('agent', 'tools')
|
|
49
|
+
.addConditionalEdge('tools', 'agent', {
|
|
50
|
+
type: 'exists',
|
|
51
|
+
field: 'data.pendingToolCalls'
|
|
52
|
+
})
|
|
53
|
+
.setEntryPoint('agent')
|
|
54
|
+
.setCheckpointer(new MemoryCheckpointer());
|
|
55
|
+
|
|
56
|
+
// Execute the workflow
|
|
57
|
+
const result = await graph.execute({
|
|
58
|
+
initialState: {
|
|
59
|
+
data: { task: 'Research AI developments' }
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Core Concepts
|
|
65
|
+
|
|
66
|
+
### StateGraph
|
|
67
|
+
|
|
68
|
+
The `StateGraph` class is the foundation for defining workflows. It manages nodes, edges, and execution flow.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const graph = new StateGraph('workflow-name', {
|
|
72
|
+
maxIterations: 100, // Cycle protection
|
|
73
|
+
timeout: 300000, // 5 minute timeout
|
|
74
|
+
checkpointEnabled: true, // Enable state persistence
|
|
75
|
+
logLevel: 'info'
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### AgentState
|
|
80
|
+
|
|
81
|
+
All workflows operate on an `AgentState` object that maintains:
|
|
82
|
+
|
|
83
|
+
- `id`: Unique state identifier
|
|
84
|
+
- `messages`: Conversation history
|
|
85
|
+
- `data`: Arbitrary key-value store for workflow data
|
|
86
|
+
- `currentStep`: Current node in execution
|
|
87
|
+
- `history`: State history for debugging
|
|
88
|
+
- `metadata`: Execution tracking metadata
|
|
89
|
+
|
|
90
|
+
### Nodes
|
|
91
|
+
|
|
92
|
+
Nodes are the processing units in your workflow. Each node receives state, performs operations, and returns updated state with optional next node specification.
|
|
93
|
+
|
|
94
|
+
### Edges
|
|
95
|
+
|
|
96
|
+
Edges define transitions between nodes:
|
|
97
|
+
|
|
98
|
+
- **Direct edges**: Always follow to the target node
|
|
99
|
+
- **Conditional edges**: Follow based on state conditions
|
|
100
|
+
- **Loop edges**: Enable cyclic workflows
|
|
101
|
+
- **Parallel edges**: Branch to multiple nodes
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Decision Nodes
|
|
106
|
+
|
|
107
|
+
Decision nodes enable conditional branching in your workflows based on state values. The package provides five decision node factory functions, each suited for different routing patterns.
|
|
108
|
+
|
|
109
|
+
### Condition Types
|
|
110
|
+
|
|
111
|
+
All decision nodes use condition types to evaluate state values:
|
|
112
|
+
|
|
113
|
+
| Type | Description | Example |
|
|
114
|
+
|------|-------------|---------|
|
|
115
|
+
| `equals` | Exact match comparison | `{ type: 'equals', field: 'data.status', value: 'complete' }` |
|
|
116
|
+
| `not_equals` | Inverse of equals | `{ type: 'not_equals', field: 'data.error', value: null }` |
|
|
117
|
+
| `contains` | Array includes or string contains | `{ type: 'contains', field: 'data.tags', value: 'urgent' }` |
|
|
118
|
+
| `greater_than` | Numeric comparison (>) | `{ type: 'greater_than', field: 'data.score', value: 0.8 }` |
|
|
119
|
+
| `less_than` | Numeric comparison (<) | `{ type: 'less_than', field: 'data.retries', value: 3 }` |
|
|
120
|
+
| `exists` | Value is not null/undefined | `{ type: 'exists', field: 'data.result' }` |
|
|
121
|
+
| `not_exists` | Value is null/undefined | `{ type: 'not_exists', field: 'error' }` |
|
|
122
|
+
| `custom` | Custom evaluation function | `{ type: 'custom', evaluate: async (state) => state.data.x > 10 }` |
|
|
123
|
+
|
|
124
|
+
### Field Path Notation
|
|
125
|
+
|
|
126
|
+
Conditions use dot notation to access nested state values:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Access state.data.user.role
|
|
130
|
+
{ type: 'equals', field: 'data.user.role', value: 'admin' }
|
|
131
|
+
|
|
132
|
+
// Access state.messages
|
|
133
|
+
{ type: 'exists', field: 'messages' }
|
|
134
|
+
|
|
135
|
+
// Access state.metadata.stepCount
|
|
136
|
+
{ type: 'greater_than', field: 'metadata.stepCount', value: 5 }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### createDecisionNode
|
|
142
|
+
|
|
143
|
+
The most flexible decision node, allowing multiple branches with priority-based evaluation.
|
|
144
|
+
|
|
145
|
+
**API:**
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
function createDecisionNode<TState extends AgentState = AgentState>(options: {
|
|
149
|
+
id: string; // Unique node identifier
|
|
150
|
+
name: string; // Human-readable name
|
|
151
|
+
config: DecisionNodeConfig; // Decision configuration
|
|
152
|
+
nodeConfig?: NodeConfig; // Optional node settings
|
|
153
|
+
}): NodeDefinition<TState>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**DecisionNodeConfig:**
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface DecisionNodeConfig {
|
|
160
|
+
branches: DecisionBranch[]; // Decision branches
|
|
161
|
+
defaultBranch?: string; // Fallback target node
|
|
162
|
+
throwOnNoMatch?: boolean; // Error if no match (default: false)
|
|
163
|
+
decide?: (state: AgentState) => string | Promise<string>; // Custom function
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface DecisionBranch {
|
|
167
|
+
name: string; // Branch identifier
|
|
168
|
+
target: string; // Target node name
|
|
169
|
+
condition: EdgeCondition; // Branch condition
|
|
170
|
+
priority?: number; // Evaluation order (higher first)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Example:**
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { createDecisionNode } from '@wundr.io/langgraph-orchestrator';
|
|
178
|
+
|
|
179
|
+
const router = createDecisionNode({
|
|
180
|
+
id: 'router',
|
|
181
|
+
name: 'Task Router',
|
|
182
|
+
config: {
|
|
183
|
+
branches: [
|
|
184
|
+
{
|
|
185
|
+
name: 'search',
|
|
186
|
+
target: 'search-node',
|
|
187
|
+
condition: { type: 'equals', field: 'data.action', value: 'search' },
|
|
188
|
+
priority: 2
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'answer',
|
|
192
|
+
target: 'answer-node',
|
|
193
|
+
condition: { type: 'equals', field: 'data.action', value: 'answer' },
|
|
194
|
+
priority: 1
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
defaultBranch: 'fallback-node'
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
graph.addNode('router', router);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**With Custom Decision Function:**
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const customRouter = createDecisionNode({
|
|
208
|
+
id: 'smart-router',
|
|
209
|
+
name: 'Smart Router',
|
|
210
|
+
config: {
|
|
211
|
+
branches: [], // Not used when decide function is provided
|
|
212
|
+
decide: async (state) => {
|
|
213
|
+
const score = state.data.confidence as number;
|
|
214
|
+
if (score > 0.9) return 'high-confidence-handler';
|
|
215
|
+
if (score > 0.5) return 'medium-confidence-handler';
|
|
216
|
+
return 'low-confidence-handler';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### createSwitchNode
|
|
225
|
+
|
|
226
|
+
A switch-case style decision node for routing based on a single field value. Ideal for enumerated routing scenarios.
|
|
227
|
+
|
|
228
|
+
**API:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
function createSwitchNode<TState extends AgentState = AgentState>(options: {
|
|
232
|
+
id: string; // Unique node identifier
|
|
233
|
+
name: string; // Human-readable name
|
|
234
|
+
field: string; // State field to switch on
|
|
235
|
+
cases: Record<string, string>; // Value-to-target mapping
|
|
236
|
+
default?: string; // Default target if no case matches
|
|
237
|
+
nodeConfig?: NodeConfig; // Optional node settings
|
|
238
|
+
}): NodeDefinition<TState>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Example:**
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { createSwitchNode } from '@wundr.io/langgraph-orchestrator';
|
|
245
|
+
|
|
246
|
+
const typeSwitch = createSwitchNode({
|
|
247
|
+
id: 'type-switch',
|
|
248
|
+
name: 'Message Type Switch',
|
|
249
|
+
field: 'data.messageType',
|
|
250
|
+
cases: {
|
|
251
|
+
'question': 'question-handler',
|
|
252
|
+
'command': 'command-handler',
|
|
253
|
+
'feedback': 'feedback-handler'
|
|
254
|
+
},
|
|
255
|
+
default: 'unknown-handler'
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
graph.addNode('type-switch', typeSwitch);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Practical Use Case - Support Ticket Routing:**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const ticketRouter = createSwitchNode({
|
|
265
|
+
id: 'ticket-router',
|
|
266
|
+
name: 'Support Ticket Router',
|
|
267
|
+
field: 'data.department',
|
|
268
|
+
cases: {
|
|
269
|
+
'billing': 'billing-team',
|
|
270
|
+
'technical': 'tech-support',
|
|
271
|
+
'sales': 'sales-team',
|
|
272
|
+
'legal': 'legal-team'
|
|
273
|
+
},
|
|
274
|
+
default: 'general-support'
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
### createThresholdNode
|
|
281
|
+
|
|
282
|
+
Routes based on numeric thresholds, perfect for confidence scores, priority levels, or any numeric classification.
|
|
283
|
+
|
|
284
|
+
**API:**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
function createThresholdNode<TState extends AgentState = AgentState>(options: {
|
|
288
|
+
id: string; // Unique node identifier
|
|
289
|
+
name: string; // Human-readable name
|
|
290
|
+
field: string; // Numeric field to evaluate
|
|
291
|
+
thresholds: Array<{ // Threshold definitions
|
|
292
|
+
value: number; // Threshold value
|
|
293
|
+
target: string; // Target node
|
|
294
|
+
}>;
|
|
295
|
+
default?: string; // Target for values below all thresholds
|
|
296
|
+
nodeConfig?: NodeConfig; // Optional node settings
|
|
297
|
+
}): NodeDefinition<TState>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Threshold Evaluation:**
|
|
301
|
+
|
|
302
|
+
Thresholds are automatically sorted in descending order. A value matches a threshold if it is greater than or equal to the threshold value but less than the next higher threshold.
|
|
303
|
+
|
|
304
|
+
**Example:**
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { createThresholdNode } from '@wundr.io/langgraph-orchestrator';
|
|
308
|
+
|
|
309
|
+
const confidenceRouter = createThresholdNode({
|
|
310
|
+
id: 'confidence-router',
|
|
311
|
+
name: 'Confidence Router',
|
|
312
|
+
field: 'data.confidence',
|
|
313
|
+
thresholds: [
|
|
314
|
+
{ value: 0.9, target: 'high-confidence' }, // >= 0.9
|
|
315
|
+
{ value: 0.7, target: 'medium-confidence' }, // >= 0.7 and < 0.9
|
|
316
|
+
{ value: 0.5, target: 'low-confidence' } // >= 0.5 and < 0.7
|
|
317
|
+
],
|
|
318
|
+
default: 'very-low-confidence' // < 0.5
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
graph.addNode('confidence-router', confidenceRouter);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Practical Use Case - Priority Queue:**
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
const priorityRouter = createThresholdNode({
|
|
328
|
+
id: 'priority-router',
|
|
329
|
+
name: 'Task Priority Router',
|
|
330
|
+
field: 'data.priority',
|
|
331
|
+
thresholds: [
|
|
332
|
+
{ value: 90, target: 'critical-queue' },
|
|
333
|
+
{ value: 70, target: 'high-priority-queue' },
|
|
334
|
+
{ value: 40, target: 'normal-queue' },
|
|
335
|
+
{ value: 10, target: 'low-priority-queue' }
|
|
336
|
+
],
|
|
337
|
+
default: 'backlog-queue'
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
### createIfElseNode
|
|
344
|
+
|
|
345
|
+
A simple binary decision node for true/false branching based on a single condition.
|
|
346
|
+
|
|
347
|
+
**API:**
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
function createIfElseNode<TState extends AgentState = AgentState>(options: {
|
|
351
|
+
id: string; // Unique node identifier
|
|
352
|
+
name: string; // Human-readable name
|
|
353
|
+
condition: EdgeCondition; // Condition to evaluate
|
|
354
|
+
ifTrue: string; // Target node if condition is true
|
|
355
|
+
ifFalse: string; // Target node if condition is false
|
|
356
|
+
nodeConfig?: NodeConfig; // Optional node settings
|
|
357
|
+
}): NodeDefinition<TState>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Example:**
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { createIfElseNode } from '@wundr.io/langgraph-orchestrator';
|
|
364
|
+
|
|
365
|
+
const errorCheck = createIfElseNode({
|
|
366
|
+
id: 'has-error',
|
|
367
|
+
name: 'Error Check',
|
|
368
|
+
condition: {
|
|
369
|
+
type: 'exists',
|
|
370
|
+
field: 'error'
|
|
371
|
+
},
|
|
372
|
+
ifTrue: 'error-handler',
|
|
373
|
+
ifFalse: 'success-handler'
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
graph.addNode('has-error', errorCheck);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Practical Use Cases:**
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// Authentication check
|
|
383
|
+
const authCheck = createIfElseNode({
|
|
384
|
+
id: 'auth-check',
|
|
385
|
+
name: 'Authentication Check',
|
|
386
|
+
condition: { type: 'exists', field: 'data.userId' },
|
|
387
|
+
ifTrue: 'authenticated-flow',
|
|
388
|
+
ifFalse: 'login-required'
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Retry decision
|
|
392
|
+
const retryCheck = createIfElseNode({
|
|
393
|
+
id: 'retry-check',
|
|
394
|
+
name: 'Retry Decision',
|
|
395
|
+
condition: { type: 'less_than', field: 'data.retryCount', value: 3 },
|
|
396
|
+
ifTrue: 'retry-operation',
|
|
397
|
+
ifFalse: 'max-retries-reached'
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Feature flag
|
|
401
|
+
const featureFlag = createIfElseNode({
|
|
402
|
+
id: 'feature-flag',
|
|
403
|
+
name: 'New Feature Check',
|
|
404
|
+
condition: { type: 'equals', field: 'data.features.newUI', value: true },
|
|
405
|
+
ifTrue: 'new-ui-flow',
|
|
406
|
+
ifFalse: 'legacy-ui-flow'
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### createMultiConditionNode
|
|
413
|
+
|
|
414
|
+
Routes based on multiple conditions combined with AND/OR logic. Ideal for complex business rules.
|
|
415
|
+
|
|
416
|
+
**API:**
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
function createMultiConditionNode<TState extends AgentState = AgentState>(options: {
|
|
420
|
+
id: string; // Unique node identifier
|
|
421
|
+
name: string; // Human-readable name
|
|
422
|
+
branches: Array<{
|
|
423
|
+
name: string; // Branch identifier
|
|
424
|
+
target: string; // Target node
|
|
425
|
+
conditions: EdgeCondition[]; // Array of conditions
|
|
426
|
+
logic: 'AND' | 'OR'; // How to combine conditions
|
|
427
|
+
priority?: number; // Evaluation order
|
|
428
|
+
}>;
|
|
429
|
+
default?: string; // Default target if no branch matches
|
|
430
|
+
nodeConfig?: NodeConfig; // Optional node settings
|
|
431
|
+
}): NodeDefinition<TState>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Logic Evaluation:**
|
|
435
|
+
|
|
436
|
+
- `AND`: All conditions must be true for the branch to match
|
|
437
|
+
- `OR`: At least one condition must be true for the branch to match
|
|
438
|
+
|
|
439
|
+
**Example:**
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { createMultiConditionNode } from '@wundr.io/langgraph-orchestrator';
|
|
443
|
+
|
|
444
|
+
const complexRouter = createMultiConditionNode({
|
|
445
|
+
id: 'complex-router',
|
|
446
|
+
name: 'Complex Router',
|
|
447
|
+
branches: [
|
|
448
|
+
{
|
|
449
|
+
name: 'premium-user',
|
|
450
|
+
target: 'premium-flow',
|
|
451
|
+
conditions: [
|
|
452
|
+
{ type: 'equals', field: 'data.userType', value: 'premium' },
|
|
453
|
+
{ type: 'greater_than', field: 'data.credits', value: 0 }
|
|
454
|
+
],
|
|
455
|
+
logic: 'AND', // Must be premium AND have credits
|
|
456
|
+
priority: 2
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: 'needs-upgrade',
|
|
460
|
+
target: 'upgrade-flow',
|
|
461
|
+
conditions: [
|
|
462
|
+
{ type: 'equals', field: 'data.userType', value: 'free' },
|
|
463
|
+
{ type: 'less_than', field: 'data.credits', value: 1 }
|
|
464
|
+
],
|
|
465
|
+
logic: 'OR', // Either free user OR out of credits
|
|
466
|
+
priority: 1
|
|
467
|
+
}
|
|
468
|
+
],
|
|
469
|
+
default: 'standard-flow'
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
graph.addNode('complex-router', complexRouter);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Practical Use Case - Access Control:**
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
const accessControl = createMultiConditionNode({
|
|
479
|
+
id: 'access-control',
|
|
480
|
+
name: 'Access Control',
|
|
481
|
+
branches: [
|
|
482
|
+
{
|
|
483
|
+
name: 'admin-access',
|
|
484
|
+
target: 'admin-dashboard',
|
|
485
|
+
conditions: [
|
|
486
|
+
{ type: 'equals', field: 'data.role', value: 'admin' },
|
|
487
|
+
{ type: 'equals', field: 'data.verified', value: true }
|
|
488
|
+
],
|
|
489
|
+
logic: 'AND',
|
|
490
|
+
priority: 3
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: 'power-user',
|
|
494
|
+
target: 'advanced-features',
|
|
495
|
+
conditions: [
|
|
496
|
+
{ type: 'equals', field: 'data.role', value: 'power-user' },
|
|
497
|
+
{ type: 'greater_than', field: 'data.accountAge', value: 30 }
|
|
498
|
+
],
|
|
499
|
+
logic: 'AND',
|
|
500
|
+
priority: 2
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'suspended-or-unverified',
|
|
504
|
+
target: 'restricted-access',
|
|
505
|
+
conditions: [
|
|
506
|
+
{ type: 'equals', field: 'data.suspended', value: true },
|
|
507
|
+
{ type: 'equals', field: 'data.verified', value: false }
|
|
508
|
+
],
|
|
509
|
+
logic: 'OR',
|
|
510
|
+
priority: 1
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
default: 'standard-user-flow'
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## State Management
|
|
520
|
+
|
|
521
|
+
### Creating Initial State
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
const result = await graph.execute({
|
|
525
|
+
initialState: {
|
|
526
|
+
data: {
|
|
527
|
+
task: 'Process user request',
|
|
528
|
+
userId: '12345',
|
|
529
|
+
config: { maxRetries: 3 }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### State Updates in Nodes
|
|
536
|
+
|
|
537
|
+
Nodes return updated state in their `NodeResult`:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
const processNode: NodeDefinition = {
|
|
541
|
+
id: 'process',
|
|
542
|
+
name: 'Process Node',
|
|
543
|
+
type: 'transform',
|
|
544
|
+
config: {},
|
|
545
|
+
execute: async (state, context) => {
|
|
546
|
+
// Immutably update state
|
|
547
|
+
const newState = {
|
|
548
|
+
...state,
|
|
549
|
+
data: {
|
|
550
|
+
...state.data,
|
|
551
|
+
processed: true,
|
|
552
|
+
result: 'Some computation result'
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
state: newState,
|
|
558
|
+
next: 'next-node' // Optional: specify next node
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### State History
|
|
565
|
+
|
|
566
|
+
The graph automatically tracks state changes in the `history` array:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// Access execution history
|
|
570
|
+
result.state.history.forEach(entry => {
|
|
571
|
+
console.log(`Step: ${entry.step}`);
|
|
572
|
+
console.log(`Changes: ${JSON.stringify(entry.changes)}`);
|
|
573
|
+
});
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Node Execution Flow
|
|
579
|
+
|
|
580
|
+
### Node Result
|
|
581
|
+
|
|
582
|
+
Every node returns a `NodeResult`:
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
interface NodeResult<TState> {
|
|
586
|
+
state: TState; // Updated state
|
|
587
|
+
next?: string | string[]; // Next node(s) - optional
|
|
588
|
+
terminate?: boolean; // Stop execution
|
|
589
|
+
metadata?: {
|
|
590
|
+
duration: number; // Execution time
|
|
591
|
+
tokensUsed?: number; // LLM tokens consumed
|
|
592
|
+
toolCalls?: ToolCall[]; // Tools executed
|
|
593
|
+
retryCount?: number; // Retry attempts
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Execution Handlers
|
|
599
|
+
|
|
600
|
+
Monitor workflow execution with event handlers:
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
const result = await graph.execute({
|
|
604
|
+
handlers: {
|
|
605
|
+
onStart: (state) => console.log('Started:', state.id),
|
|
606
|
+
onNodeEnter: (nodeName, state) => console.log(`Entering: ${nodeName}`),
|
|
607
|
+
onNodeExit: (nodeName, result) => console.log(`Exiting: ${nodeName}`),
|
|
608
|
+
onCheckpoint: (checkpoint) => console.log('Checkpoint:', checkpoint.id),
|
|
609
|
+
onError: (error) => console.error('Error:', error.message),
|
|
610
|
+
onComplete: (state) => console.log('Complete:', state.id)
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Event Emitter
|
|
616
|
+
|
|
617
|
+
The `StateGraph` class extends `EventEmitter` for reactive patterns:
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
graph.on('execution:start', (state) => {
|
|
621
|
+
console.log('Workflow started');
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
graph.on('node:enter', (nodeName, state) => {
|
|
625
|
+
metrics.trackNodeEntry(nodeName);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
graph.on('state:updated', (state) => {
|
|
629
|
+
saveToDatabase(state);
|
|
630
|
+
});
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Checkpointing
|
|
636
|
+
|
|
637
|
+
### Memory Checkpointer
|
|
638
|
+
|
|
639
|
+
For development and testing:
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
import { MemoryCheckpointer } from '@wundr.io/langgraph-orchestrator';
|
|
643
|
+
|
|
644
|
+
const checkpointer = new MemoryCheckpointer();
|
|
645
|
+
graph.setCheckpointer(checkpointer);
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### File Checkpointer
|
|
649
|
+
|
|
650
|
+
For persistent storage:
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { FileCheckpointer } from '@wundr.io/langgraph-orchestrator';
|
|
654
|
+
|
|
655
|
+
const checkpointer = new FileCheckpointer({
|
|
656
|
+
directory: './checkpoints',
|
|
657
|
+
format: 'json'
|
|
658
|
+
});
|
|
659
|
+
graph.setCheckpointer(checkpointer);
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Resuming from Checkpoint
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
// Resume execution from a specific checkpoint
|
|
666
|
+
const result = await graph.execute({
|
|
667
|
+
resumeFrom: 'checkpoint-id-here'
|
|
668
|
+
});
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## Complete Example: AI Research Assistant
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
import {
|
|
677
|
+
StateGraph,
|
|
678
|
+
createLLMNode,
|
|
679
|
+
createToolNode,
|
|
680
|
+
createDecisionNode,
|
|
681
|
+
createIfElseNode,
|
|
682
|
+
createThresholdNode,
|
|
683
|
+
MemoryCheckpointer
|
|
684
|
+
} from '@wundr.io/langgraph-orchestrator';
|
|
685
|
+
|
|
686
|
+
// Define custom state
|
|
687
|
+
interface ResearchState extends AgentState {
|
|
688
|
+
data: {
|
|
689
|
+
query: string;
|
|
690
|
+
searchResults?: string[];
|
|
691
|
+
analysis?: string;
|
|
692
|
+
confidence?: number;
|
|
693
|
+
needsMoreInfo?: boolean;
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Create the research workflow
|
|
698
|
+
const researchGraph = new StateGraph<ResearchState>('research-assistant', {
|
|
699
|
+
maxIterations: 20
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Add nodes
|
|
703
|
+
researchGraph
|
|
704
|
+
// Initial planning node
|
|
705
|
+
.addNode('planner', createLLMNode({
|
|
706
|
+
id: 'planner',
|
|
707
|
+
name: 'Research Planner',
|
|
708
|
+
config: { systemPrompt: 'Plan the research strategy for the given query.' }
|
|
709
|
+
}))
|
|
710
|
+
|
|
711
|
+
// Search execution
|
|
712
|
+
.addNode('searcher', createToolNode({
|
|
713
|
+
id: 'searcher',
|
|
714
|
+
name: 'Web Searcher',
|
|
715
|
+
config: { tools: [webSearchTool] }
|
|
716
|
+
}))
|
|
717
|
+
|
|
718
|
+
// Analyze search results
|
|
719
|
+
.addNode('analyzer', createLLMNode({
|
|
720
|
+
id: 'analyzer',
|
|
721
|
+
name: 'Result Analyzer',
|
|
722
|
+
config: { systemPrompt: 'Analyze the search results and assess confidence.' }
|
|
723
|
+
}))
|
|
724
|
+
|
|
725
|
+
// Decision: enough information?
|
|
726
|
+
.addNode('confidence-check', createThresholdNode({
|
|
727
|
+
id: 'confidence-check',
|
|
728
|
+
name: 'Confidence Check',
|
|
729
|
+
field: 'data.confidence',
|
|
730
|
+
thresholds: [
|
|
731
|
+
{ value: 0.8, target: 'synthesizer' },
|
|
732
|
+
{ value: 0.5, target: 'refiner' }
|
|
733
|
+
],
|
|
734
|
+
default: 'searcher' // Low confidence, search more
|
|
735
|
+
}))
|
|
736
|
+
|
|
737
|
+
// Refine search query
|
|
738
|
+
.addNode('refiner', createLLMNode({
|
|
739
|
+
id: 'refiner',
|
|
740
|
+
name: 'Query Refiner',
|
|
741
|
+
config: { systemPrompt: 'Refine the search query based on gaps identified.' }
|
|
742
|
+
}))
|
|
743
|
+
|
|
744
|
+
// Synthesize final answer
|
|
745
|
+
.addNode('synthesizer', createLLMNode({
|
|
746
|
+
id: 'synthesizer',
|
|
747
|
+
name: 'Answer Synthesizer',
|
|
748
|
+
config: { systemPrompt: 'Synthesize a comprehensive answer from all research.' }
|
|
749
|
+
}))
|
|
750
|
+
|
|
751
|
+
// Define edges
|
|
752
|
+
.addEdge('planner', 'searcher')
|
|
753
|
+
.addEdge('searcher', 'analyzer')
|
|
754
|
+
.addEdge('analyzer', 'confidence-check')
|
|
755
|
+
.addEdge('refiner', 'searcher')
|
|
756
|
+
|
|
757
|
+
// Set entry point and checkpointer
|
|
758
|
+
.setEntryPoint('planner')
|
|
759
|
+
.setCheckpointer(new MemoryCheckpointer());
|
|
760
|
+
|
|
761
|
+
// Execute research
|
|
762
|
+
const result = await researchGraph.execute({
|
|
763
|
+
initialState: {
|
|
764
|
+
data: {
|
|
765
|
+
query: 'What are the latest advances in quantum computing?'
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
console.log('Research complete:', result.state.data.analysis);
|
|
771
|
+
console.log('Confidence:', result.state.data.confidence);
|
|
772
|
+
console.log('Path taken:', result.path.join(' -> '));
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## API Reference
|
|
778
|
+
|
|
779
|
+
### Exports
|
|
780
|
+
|
|
781
|
+
#### Core Classes
|
|
782
|
+
|
|
783
|
+
- `StateGraph` - Main graph class for workflow definition and execution
|
|
784
|
+
- `StateGraphEvents` - Event types emitted by StateGraph
|
|
785
|
+
|
|
786
|
+
#### Node Factories
|
|
787
|
+
|
|
788
|
+
- `createLLMNode` - LLM-based processing node
|
|
789
|
+
- `createToolNode` - Tool execution node
|
|
790
|
+
- `createDecisionNode` - Multi-branch decision node
|
|
791
|
+
- `createSwitchNode` - Switch-case style routing
|
|
792
|
+
- `createThresholdNode` - Numeric threshold routing
|
|
793
|
+
- `createIfElseNode` - Binary decision node
|
|
794
|
+
- `createMultiConditionNode` - Complex multi-condition routing
|
|
795
|
+
- `createHumanNode` - Human-in-the-loop node
|
|
796
|
+
|
|
797
|
+
#### Edge Builders
|
|
798
|
+
|
|
799
|
+
- `conditionalEdge` - Conditional edge builder
|
|
800
|
+
- `loopEdge` - Loop edge builder
|
|
801
|
+
- `createRouter` - Dynamic routing helper
|
|
802
|
+
|
|
803
|
+
#### Checkpointing
|
|
804
|
+
|
|
805
|
+
- `MemoryCheckpointer` - In-memory checkpoint storage
|
|
806
|
+
- `FileCheckpointer` - File-based checkpoint storage
|
|
807
|
+
- `TimeTravelDebugger` - Debug tool for state history
|
|
808
|
+
|
|
809
|
+
#### Prebuilt Graphs
|
|
810
|
+
|
|
811
|
+
- `createAgentWorkflow` - Simple agent with tools
|
|
812
|
+
- `createChatWorkflow` - Conversational workflow
|
|
813
|
+
- `createDecisionTree` - Multi-level decision tree
|
|
814
|
+
- `createPlanExecuteRefineGraph` - Plan-execute-refine pattern
|
|
815
|
+
|
|
816
|
+
### Types
|
|
817
|
+
|
|
818
|
+
All TypeScript types are exported for use in your projects:
|
|
819
|
+
|
|
820
|
+
```typescript
|
|
821
|
+
import type {
|
|
822
|
+
AgentState,
|
|
823
|
+
NodeDefinition,
|
|
824
|
+
NodeResult,
|
|
825
|
+
EdgeCondition,
|
|
826
|
+
ConditionType,
|
|
827
|
+
ExecutionResult,
|
|
828
|
+
ExecutionOptions
|
|
829
|
+
} from '@wundr.io/langgraph-orchestrator';
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## License
|
|
835
|
+
|
|
836
|
+
MIT
|
|
837
|
+
|
|
838
|
+
## Links
|
|
839
|
+
|
|
840
|
+
- [Repository](https://github.com/adapticai/wundr)
|
|
841
|
+
- [Issues](https://github.com/adapticai/wundr/issues)
|
|
842
|
+
- [Homepage](https://wundr.io)
|