@nextsparkjs/plugin-langchain 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +41 -0
- package/api/observability/metrics/route.ts +110 -0
- package/api/observability/traces/[traceId]/route.ts +398 -0
- package/api/observability/traces/route.ts +205 -0
- package/api/sessions/route.ts +332 -0
- package/components/observability/CollapsibleJson.tsx +71 -0
- package/components/observability/CompactTimeline.tsx +75 -0
- package/components/observability/ConversationFlow.tsx +271 -0
- package/components/observability/DisabledMessage.tsx +21 -0
- package/components/observability/FiltersPanel.tsx +82 -0
- package/components/observability/ObservabilityDashboard.tsx +230 -0
- package/components/observability/SpansList.tsx +210 -0
- package/components/observability/TraceDetail.tsx +335 -0
- package/components/observability/TraceStatusBadge.tsx +39 -0
- package/components/observability/TracesTable.tsx +97 -0
- package/components/observability/index.ts +7 -0
- package/docs/01-getting-started/01-overview.md +196 -0
- package/docs/01-getting-started/02-installation.md +368 -0
- package/docs/01-getting-started/03-configuration.md +794 -0
- package/docs/02-core-concepts/01-architecture.md +566 -0
- package/docs/02-core-concepts/02-agents.md +597 -0
- package/docs/02-core-concepts/03-tools.md +689 -0
- package/docs/03-orchestration/01-graph-orchestrator.md +809 -0
- package/docs/03-orchestration/02-legacy-react.md +650 -0
- package/docs/04-advanced/01-observability.md +645 -0
- package/docs/04-advanced/02-token-tracking.md +469 -0
- package/docs/04-advanced/03-streaming.md +476 -0
- package/docs/04-advanced/04-guardrails.md +597 -0
- package/docs/05-reference/01-api-reference.md +1403 -0
- package/docs/05-reference/02-customization.md +646 -0
- package/docs/05-reference/03-examples.md +881 -0
- package/docs/index.md +85 -0
- package/hooks/observability/useMetrics.ts +31 -0
- package/hooks/observability/useTraceDetail.ts +48 -0
- package/hooks/observability/useTraces.ts +59 -0
- package/lib/agent-factory.ts +354 -0
- package/lib/agent-helpers.ts +201 -0
- package/lib/db-memory-store.ts +417 -0
- package/lib/graph/index.ts +58 -0
- package/lib/graph/nodes/combiner.ts +399 -0
- package/lib/graph/nodes/router.ts +440 -0
- package/lib/graph/orchestrator-graph.ts +386 -0
- package/lib/graph/prompts/combiner.md +131 -0
- package/lib/graph/prompts/router.md +193 -0
- package/lib/graph/types.ts +365 -0
- package/lib/guardrails.ts +230 -0
- package/lib/index.ts +44 -0
- package/lib/logger.ts +70 -0
- package/lib/memory-store.ts +168 -0
- package/lib/message-serializer.ts +110 -0
- package/lib/prompt-renderer.ts +94 -0
- package/lib/providers.ts +226 -0
- package/lib/streaming.ts +232 -0
- package/lib/token-tracker.ts +298 -0
- package/lib/tools-builder.ts +192 -0
- package/lib/tracer-callbacks.ts +342 -0
- package/lib/tracer.ts +350 -0
- package/migrations/001_langchain_memory.sql +83 -0
- package/migrations/002_token_usage.sql +127 -0
- package/migrations/003_observability.sql +257 -0
- package/package.json +28 -0
- package/plugin.config.ts +170 -0
- package/presets/lib/langchain.config.ts.preset +142 -0
- package/presets/templates/sector7/ai-observability/[traceId]/page.tsx +91 -0
- package/presets/templates/sector7/ai-observability/page.tsx +54 -0
- package/types/langchain.types.ts +274 -0
- package/types/observability.types.ts +270 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
# Graph-Based Orchestration
|
|
2
|
+
|
|
3
|
+
This guide covers the **LangGraph-based orchestrator**, the recommended approach for multi-agent systems. It replaces inefficient ReAct loops with an explicit state machine, achieving 25-50x faster execution with deterministic flow.
|
|
4
|
+
|
|
5
|
+
> **Note**: For the legacy ReAct-based approach, see [Legacy Orchestration](./02-legacy-react.md) (deprecated).
|
|
6
|
+
|
|
7
|
+
## Why Graph-Based Orchestration?
|
|
8
|
+
|
|
9
|
+
The traditional ReAct (Reasoning + Acting) pattern has significant drawbacks at scale:
|
|
10
|
+
|
|
11
|
+
### The Problem with ReAct Loops
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
User: "Show my tasks and find StartupXYZ account number"
|
|
15
|
+
|
|
16
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
17
|
+
│ ORCHESTRATOR ReAct Loop (~10-20 iterations) │
|
|
18
|
+
│ ├─ Think → Act → Observe → Think → Act... │
|
|
19
|
+
│ └─ Decide: route_to_task │
|
|
20
|
+
│ ↓ │
|
|
21
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
22
|
+
│ │ TASK-ASSISTANT ReAct Loop (~10-15 iterations) │ │
|
|
23
|
+
│ │ └─ Think → Act → Observe → Think... │ │
|
|
24
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
25
|
+
│ ↓ (returns to orchestrator) │
|
|
26
|
+
│ ├─ Think → decide second part │
|
|
27
|
+
│ └─ Decide: route_to_customer │
|
|
28
|
+
│ ↓ │
|
|
29
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
30
|
+
│ │ CUSTOMER-ASSISTANT ReAct Loop (~10-15 iter) │ │
|
|
31
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
32
|
+
└─────────────────────────────────────────────────────────────┘
|
|
33
|
+
|
|
34
|
+
TOTAL: 50+ LLM iterations → TIMEOUT (2-5 minutes)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Issues:**
|
|
38
|
+
- **50+ LLM calls** for a simple multi-intent request
|
|
39
|
+
- **Recursion limit errors** from nested loops
|
|
40
|
+
- **Non-deterministic** - different paths each time
|
|
41
|
+
- **Expensive** - each iteration costs tokens
|
|
42
|
+
- **Slow** - 2-5 minute response times
|
|
43
|
+
|
|
44
|
+
### The Solution: Explicit State Machine
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
User: "Show my tasks and find StartupXYZ account number"
|
|
48
|
+
|
|
49
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
50
|
+
│ START │
|
|
51
|
+
│ ↓ │
|
|
52
|
+
│ ┌─────────────┐ │
|
|
53
|
+
│ │ ROUTER │ ← 1 LLM call │
|
|
54
|
+
│ │ (Intent) │ (structured output) │
|
|
55
|
+
│ └──────┬──────┘ │
|
|
56
|
+
│ ↓ │
|
|
57
|
+
│ ┌───────────────┴───────────────┐ │
|
|
58
|
+
│ ↓ ↓ │
|
|
59
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
60
|
+
│ │ TASK_HANDLER │ │ CUST_HANDLER │ │
|
|
61
|
+
│ │ (0 LLM) │ │ (0 LLM) │ │
|
|
62
|
+
│ └───────┬──────┘ └───────┬──────┘ │
|
|
63
|
+
│ ↓ ↓ │
|
|
64
|
+
│ └─────────────┬───────────────┘ │
|
|
65
|
+
│ ↓ │
|
|
66
|
+
│ ┌─────────────┐ │
|
|
67
|
+
│ │ COMBINER │ ← 1 LLM call │
|
|
68
|
+
│ │ (Response) │ (optional) │
|
|
69
|
+
│ └──────┬──────┘ │
|
|
70
|
+
│ ↓ │
|
|
71
|
+
│ END │
|
|
72
|
+
└─────────────────────────────────────────────────────────────┘
|
|
73
|
+
|
|
74
|
+
TOTAL: 2 LLM calls → 2-3 seconds
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Benefits:**
|
|
78
|
+
- **1-2 LLM calls** instead of 50+
|
|
79
|
+
- **No recursion** - explicit transitions
|
|
80
|
+
- **Deterministic** - same input = same path
|
|
81
|
+
- **Cost effective** - 25-50x fewer tokens
|
|
82
|
+
- **Fast** - 2-10 second responses
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Theoretical Foundations
|
|
87
|
+
|
|
88
|
+
### What is LangGraph?
|
|
89
|
+
|
|
90
|
+
LangGraph is a framework for building **stateful, multi-step AI applications** using explicit graphs. Unlike ReAct patterns where the LLM decides what to do next in a loop, LangGraph defines explicit states and transitions.
|
|
91
|
+
|
|
92
|
+
Key concepts:
|
|
93
|
+
|
|
94
|
+
| Concept | Description |
|
|
95
|
+
|---------|-------------|
|
|
96
|
+
| **State** | The data that flows through the graph |
|
|
97
|
+
| **Node** | A function that transforms state |
|
|
98
|
+
| **Edge** | Connection between nodes |
|
|
99
|
+
| **Conditional Edge** | Edge that chooses next node based on state |
|
|
100
|
+
| **Channel** | How state updates are merged |
|
|
101
|
+
|
|
102
|
+
### State Machines vs ReAct Loops
|
|
103
|
+
|
|
104
|
+
| Aspect | ReAct Loop | State Machine |
|
|
105
|
+
|--------|------------|---------------|
|
|
106
|
+
| **Control Flow** | LLM decides | Code defines |
|
|
107
|
+
| **Predictability** | Variable | Deterministic |
|
|
108
|
+
| **LLM Calls** | O(n) per step | O(1) total |
|
|
109
|
+
| **Debugging** | Hard (black box) | Easy (explicit) |
|
|
110
|
+
| **Parallelism** | Limited | Native support |
|
|
111
|
+
|
|
112
|
+
### Why Deterministic Flow Matters
|
|
113
|
+
|
|
114
|
+
1. **Reproducibility**: Same input produces same execution path
|
|
115
|
+
2. **Debugging**: You can trace exactly which nodes executed
|
|
116
|
+
3. **Testing**: Unit test each node independently
|
|
117
|
+
4. **Cost Control**: Predictable token usage
|
|
118
|
+
5. **SLA Compliance**: Guaranteed response times
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Architecture
|
|
123
|
+
|
|
124
|
+
### Graph Overview
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
┌──────────────────────────────────────────────────────┐
|
|
128
|
+
│ ORCHESTRATOR GRAPH │
|
|
129
|
+
│ │
|
|
130
|
+
User Input ──────┤ │
|
|
131
|
+
│ ┌─────────┐ │
|
|
132
|
+
│ │ START │ │
|
|
133
|
+
│ └────┬────┘ │
|
|
134
|
+
│ │ │
|
|
135
|
+
│ ▼ │
|
|
136
|
+
│ ┌──────────┐ Structured Output │
|
|
137
|
+
│ │ ROUTER │ ◄── (Zod Schema) │
|
|
138
|
+
│ │ (LLM) │ Intent Classification │
|
|
139
|
+
│ └────┬─────┘ │
|
|
140
|
+
│ │ │
|
|
141
|
+
│ ┌────┴────┬────────────┬─────────────┐ │
|
|
142
|
+
│ ▼ ▼ ▼ ▼ │
|
|
143
|
+
│ greeting clarify single multi │
|
|
144
|
+
│ │ │ intent intent │
|
|
145
|
+
│ │ │ │ │ │
|
|
146
|
+
│ │ │ ▼ ▼ │
|
|
147
|
+
│ │ │ ┌──────────┐ ┌──────────┐ │
|
|
148
|
+
│ │ │ │ HANDLER │ │ HANDLER │ ... │
|
|
149
|
+
│ │ │ │ (No LLM) │ │ (No LLM) │ │
|
|
150
|
+
│ │ │ └────┬─────┘ └────┬─────┘ │
|
|
151
|
+
│ │ │ │ │ │
|
|
152
|
+
│ │ │ └──────┬──────┘ │
|
|
153
|
+
│ │ │ ▼ │
|
|
154
|
+
│ │ │ ┌──────────┐ │
|
|
155
|
+
│ └─────────┴─────────►│ COMBINER │ │
|
|
156
|
+
│ │ (LLM) │ │
|
|
157
|
+
│ └────┬─────┘ │
|
|
158
|
+
│ │ │
|
|
159
|
+
│ ▼ │
|
|
160
|
+
│ ┌────────┐ │
|
|
161
|
+
Response ◄───────┤ │ END │ │
|
|
162
|
+
│ └────────┘ │
|
|
163
|
+
└──────────────────────────────────────────────────────┘
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Node Responsibilities
|
|
167
|
+
|
|
168
|
+
| Node | Purpose | LLM Calls | Input | Output |
|
|
169
|
+
|------|---------|-----------|-------|--------|
|
|
170
|
+
| **Router** | Classify user intent | 1 | User message | Intent[] |
|
|
171
|
+
| **Task Handler** | Execute task operations | 0 | Intent | JSON result |
|
|
172
|
+
| **Customer Handler** | Execute customer operations | 0 | Intent | JSON result |
|
|
173
|
+
| **Page Handler** | Execute page operations | 0 | Intent | JSON result |
|
|
174
|
+
| **Combiner** | Synthesize response | 0-1 | All results | User text |
|
|
175
|
+
|
|
176
|
+
### Data Flow
|
|
177
|
+
|
|
178
|
+
1. **User Input** → Raw message string
|
|
179
|
+
2. **Router** → Parses to structured `Intent[]`
|
|
180
|
+
3. **Handlers** → Execute operations, return JSON
|
|
181
|
+
4. **Combiner** → Converts JSON to natural language
|
|
182
|
+
5. **Response** → Text for user
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Nodes Reference
|
|
187
|
+
|
|
188
|
+
### Router Node
|
|
189
|
+
|
|
190
|
+
The router is the "brain" that classifies user intent in a single LLM call.
|
|
191
|
+
|
|
192
|
+
**Location**: `lib/graph/nodes/router.ts`
|
|
193
|
+
|
|
194
|
+
**Features**:
|
|
195
|
+
- **Structured Output**: Uses Zod schema for reliable JSON extraction
|
|
196
|
+
- **Multi-Intent**: Detects multiple intents in one message
|
|
197
|
+
- **Retry Logic**: Handles malformed responses from local models
|
|
198
|
+
- **Provider Agnostic**: Works with OpenAI, Anthropic, Ollama, LM Studio
|
|
199
|
+
|
|
200
|
+
**Output Schema**:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const RouterOutputSchema = z.object({
|
|
204
|
+
intents: z.array(z.object({
|
|
205
|
+
type: z.enum(['task', 'customer', 'page', 'greeting', 'clarification']),
|
|
206
|
+
action: z.enum(['list', 'create', 'update', 'delete', 'search', 'get', 'unknown']),
|
|
207
|
+
parameters: z.record(z.unknown()),
|
|
208
|
+
originalText: z.string(),
|
|
209
|
+
})),
|
|
210
|
+
needsClarification: z.boolean(),
|
|
211
|
+
clarificationQuestion: z.string().nullable(),
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Examples**:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Input: "Show my tasks"
|
|
219
|
+
Output: {
|
|
220
|
+
intents: [{
|
|
221
|
+
type: "task",
|
|
222
|
+
action: "list",
|
|
223
|
+
parameters: {},
|
|
224
|
+
originalText: "Show my tasks"
|
|
225
|
+
}],
|
|
226
|
+
needsClarification: false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Input: "Create task 'Buy milk' high priority and find customer ABC"
|
|
230
|
+
Output: {
|
|
231
|
+
intents: [
|
|
232
|
+
{ type: "task", action: "create", parameters: { title: "Buy milk", priority: "high" } },
|
|
233
|
+
{ type: "customer", action: "search", parameters: { query: "ABC" } }
|
|
234
|
+
],
|
|
235
|
+
needsClarification: false
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Handler Nodes
|
|
240
|
+
|
|
241
|
+
Handlers execute operations **without LLM calls** - they directly call entity services.
|
|
242
|
+
|
|
243
|
+
#### Task Handler
|
|
244
|
+
|
|
245
|
+
**Location**: `lib/graph/nodes/task-handler.ts`
|
|
246
|
+
|
|
247
|
+
**Supported Actions**:
|
|
248
|
+
- `list` - Get all tasks with optional filters
|
|
249
|
+
- `get` - Get single task by ID
|
|
250
|
+
- `create` - Create new task
|
|
251
|
+
- `update` - Update task fields
|
|
252
|
+
- `search` - Find tasks by query
|
|
253
|
+
- `delete` - Delete task
|
|
254
|
+
|
|
255
|
+
**Example**:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Intent: { type: "task", action: "create", parameters: { title: "Buy milk", priority: "high" } }
|
|
259
|
+
|
|
260
|
+
// Handler calls:
|
|
261
|
+
await TasksService.create(userId, teamId, { title: "Buy milk", priority: "high" })
|
|
262
|
+
|
|
263
|
+
// Returns:
|
|
264
|
+
{
|
|
265
|
+
success: true,
|
|
266
|
+
operation: "create",
|
|
267
|
+
data: { id: "123", title: "Buy milk", priority: "high", status: "todo" },
|
|
268
|
+
message: "Created task: Buy milk"
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Customer Handler
|
|
273
|
+
|
|
274
|
+
**Location**: `lib/graph/nodes/customer-handler.ts`
|
|
275
|
+
|
|
276
|
+
**Supported Actions**:
|
|
277
|
+
- `list` - Get all customers
|
|
278
|
+
- `get` - Get single customer
|
|
279
|
+
- `create` - Create new customer
|
|
280
|
+
- `update` - Update customer fields
|
|
281
|
+
- `search` - Find customers by name, email, phone, account
|
|
282
|
+
- `delete` - Delete customer
|
|
283
|
+
|
|
284
|
+
#### Page Handler
|
|
285
|
+
|
|
286
|
+
**Location**: `lib/graph/nodes/page-handler.ts`
|
|
287
|
+
|
|
288
|
+
**Supported Actions**:
|
|
289
|
+
- `list` - Get published pages
|
|
290
|
+
- `get` - Get page by ID or slug
|
|
291
|
+
- `search` - Find pages by title
|
|
292
|
+
|
|
293
|
+
> **Note**: Create/Update/Delete for pages returns error directing to Page Builder UI.
|
|
294
|
+
|
|
295
|
+
### Combiner Node
|
|
296
|
+
|
|
297
|
+
The combiner converts JSON handler results into natural language.
|
|
298
|
+
|
|
299
|
+
**Location**: `lib/graph/nodes/combiner.ts`
|
|
300
|
+
|
|
301
|
+
**Optimization**: For simple single-intent operations, uses **template-based responses** without LLM:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// Template response (no LLM needed):
|
|
305
|
+
// Intent: list 3 tasks
|
|
306
|
+
"Found 3 task(s):
|
|
307
|
+
• Buy milk (high) - todo
|
|
308
|
+
• Call client (medium) - in-progress
|
|
309
|
+
• Review docs (low) - done"
|
|
310
|
+
|
|
311
|
+
// LLM response (multi-intent or complex):
|
|
312
|
+
// Uses GPT-4o-mini to synthesize natural response
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**When LLM is used**:
|
|
316
|
+
- Multiple handler results (task + customer)
|
|
317
|
+
- List/search with >5 items
|
|
318
|
+
- Complex data requiring summarization
|
|
319
|
+
|
|
320
|
+
**When templates are used**:
|
|
321
|
+
- Single intent operations
|
|
322
|
+
- Simple list/search with <=5 items
|
|
323
|
+
- CRUD confirmations
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## State Schema
|
|
328
|
+
|
|
329
|
+
### OrchestratorState
|
|
330
|
+
|
|
331
|
+
The complete state that flows through the graph:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
interface OrchestratorState {
|
|
335
|
+
// ---- Input (immutable) ----
|
|
336
|
+
input: string // User message
|
|
337
|
+
sessionId: string // Session identifier
|
|
338
|
+
context: AgentContext // userId, teamId
|
|
339
|
+
conversationHistory: BaseMessage[] // Recent messages (last 5)
|
|
340
|
+
|
|
341
|
+
// ---- Router Output ----
|
|
342
|
+
intents: Intent[] // Classified intents
|
|
343
|
+
needsClarification: boolean // If true, ask user
|
|
344
|
+
clarificationQuestion?: string // Question to ask
|
|
345
|
+
|
|
346
|
+
// ---- Handler Outputs ----
|
|
347
|
+
handlerResults: { // JSON from each handler
|
|
348
|
+
task?: TaskHandlerResult
|
|
349
|
+
customer?: CustomerHandlerResult
|
|
350
|
+
page?: PageHandlerResult
|
|
351
|
+
}
|
|
352
|
+
completedHandlers: IntentType[] // Which handlers ran
|
|
353
|
+
|
|
354
|
+
// ---- Final Output ----
|
|
355
|
+
finalResponse: string | null // Text for user
|
|
356
|
+
error: string | null // Error message
|
|
357
|
+
|
|
358
|
+
// ---- Tracing ----
|
|
359
|
+
traceId?: string // Observability trace
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Intent Type
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
interface Intent {
|
|
367
|
+
type: 'task' | 'customer' | 'page' | 'greeting' | 'clarification'
|
|
368
|
+
action: 'list' | 'create' | 'update' | 'delete' | 'search' | 'get' | 'unknown'
|
|
369
|
+
parameters: Record<string, unknown>
|
|
370
|
+
originalText: string
|
|
371
|
+
confidence?: number
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Handler Results
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
interface TaskHandlerResult {
|
|
379
|
+
success: boolean
|
|
380
|
+
operation: 'list' | 'create' | 'update' | 'delete' | 'search' | 'get'
|
|
381
|
+
data: TaskData[] | TaskData | null
|
|
382
|
+
count?: number
|
|
383
|
+
message: string
|
|
384
|
+
error?: string
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Routing Logic
|
|
391
|
+
|
|
392
|
+
### From Router to Handlers
|
|
393
|
+
|
|
394
|
+
The `routeByIntents` function determines execution path:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
function routeByIntents(state: OrchestratorState): RouterRoute {
|
|
398
|
+
const intentTypes = state.intents.map(i => i.type)
|
|
399
|
+
|
|
400
|
+
// Handle special cases
|
|
401
|
+
if (state.error) return 'error'
|
|
402
|
+
if (state.needsClarification) return 'clarification'
|
|
403
|
+
if (intentTypes.length === 1 && intentTypes[0] === 'greeting') return 'greeting'
|
|
404
|
+
|
|
405
|
+
// Determine handler combination
|
|
406
|
+
const hasTask = intentTypes.includes('task')
|
|
407
|
+
const hasCustomer = intentTypes.includes('customer')
|
|
408
|
+
const hasPage = intentTypes.includes('page')
|
|
409
|
+
|
|
410
|
+
if (hasTask && hasCustomer && hasPage) return 'all_handlers'
|
|
411
|
+
if (hasTask && hasCustomer) return 'task_and_customer'
|
|
412
|
+
if (hasTask && hasPage) return 'task_and_page'
|
|
413
|
+
if (hasCustomer && hasPage) return 'customer_and_page'
|
|
414
|
+
if (hasTask) return 'task_only'
|
|
415
|
+
if (hasCustomer) return 'customer_only'
|
|
416
|
+
if (hasPage) return 'page_only'
|
|
417
|
+
|
|
418
|
+
return 'clarification'
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Handler Sequencing
|
|
423
|
+
|
|
424
|
+
For multi-intent requests, handlers execute sequentially:
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
task_and_customer: Task Handler → Customer Handler → Combiner
|
|
428
|
+
task_and_page: Task Handler → Page Handler → Combiner
|
|
429
|
+
all_handlers: Task Handler → Customer Handler → Page Handler → Combiner
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Each handler's output is **merged** into `handlerResults`, preserving all results.
|
|
433
|
+
|
|
434
|
+
### Conditional Transitions
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// After task handler
|
|
438
|
+
function routeAfterTask(state): string {
|
|
439
|
+
if (needsCustomer && !completedCustomer) return 'customer_handler'
|
|
440
|
+
if (needsPage && !completedPage) return 'page_handler'
|
|
441
|
+
return 'combiner'
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Multi-Model Compatibility
|
|
448
|
+
|
|
449
|
+
### Structured Output Methods
|
|
450
|
+
|
|
451
|
+
Different LLM providers support different structured output methods:
|
|
452
|
+
|
|
453
|
+
| Provider | Method | Notes |
|
|
454
|
+
|----------|--------|-------|
|
|
455
|
+
| OpenAI API | `functionCalling` | Most reliable |
|
|
456
|
+
| Anthropic | `functionCalling` | Tool use |
|
|
457
|
+
| Ollama | `functionCalling` | Most models |
|
|
458
|
+
| LM Studio | `jsonSchema` | OpenAI-compatible servers |
|
|
459
|
+
|
|
460
|
+
The `getStructuredOutputMethod()` helper auto-detects the best method:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { getStructuredOutputMethod } from '../../providers'
|
|
464
|
+
|
|
465
|
+
const method = getStructuredOutputMethod({ provider: 'openai' })
|
|
466
|
+
// Returns: 'functionCalling' for real OpenAI, 'jsonSchema' for LM Studio
|
|
467
|
+
|
|
468
|
+
const structuredModel = model.withStructuredOutput(schema, { method })
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Retry Logic with Zod Validation
|
|
472
|
+
|
|
473
|
+
Local models (LM Studio, Ollama) may produce malformed JSON. The router includes retry logic:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
async function invokeRouterWithRetry(model, messages, method) {
|
|
477
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
478
|
+
// Try 1: Structured output
|
|
479
|
+
const result = await tryStructuredOutput(model, messages, method)
|
|
480
|
+
if (result) return result
|
|
481
|
+
|
|
482
|
+
// Try 2: Manual JSON parsing (fallback)
|
|
483
|
+
const manual = await tryManualJsonParsing(model, messages)
|
|
484
|
+
if (manual) return manual
|
|
485
|
+
|
|
486
|
+
// Exponential backoff before retry
|
|
487
|
+
await sleep(500 * Math.pow(2, attempt - 1))
|
|
488
|
+
}
|
|
489
|
+
throw new Error('Router failed after retries')
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function tryManualJsonParsing(model, messages) {
|
|
493
|
+
const result = await model.invoke(messages)
|
|
494
|
+
const content = result.content as string
|
|
495
|
+
|
|
496
|
+
// Extract JSON from markdown code blocks or raw text
|
|
497
|
+
const json = extractJsonFromResponse(content)
|
|
498
|
+
const parsed = JSON.parse(json)
|
|
499
|
+
|
|
500
|
+
// Validate with Zod
|
|
501
|
+
return RouterOutputSchema.safeParse(parsed)
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Provider Configuration
|
|
506
|
+
|
|
507
|
+
Configure providers in theme's `langchain.config.ts`:
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
'orchestrator': {
|
|
511
|
+
provider: 'anthropic', // Claude for router
|
|
512
|
+
model: 'claude-3-haiku-20240307',
|
|
513
|
+
temperature: 0.1, // Low for consistency
|
|
514
|
+
},
|
|
515
|
+
'task-assistant': {
|
|
516
|
+
provider: 'openai', // GPT-4o-mini for handlers
|
|
517
|
+
model: 'gpt-4o-mini',
|
|
518
|
+
temperature: 0.3,
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Configuration
|
|
525
|
+
|
|
526
|
+
### Environment Variables
|
|
527
|
+
|
|
528
|
+
```env
|
|
529
|
+
# Enable graph orchestrator (recommended)
|
|
530
|
+
LANGCHAIN_USE_GRAPH_ORCHESTRATOR=true
|
|
531
|
+
|
|
532
|
+
# Provider settings
|
|
533
|
+
LANGCHAIN_DEFAULT_PROVIDER=openai
|
|
534
|
+
|
|
535
|
+
# Debug mode
|
|
536
|
+
LANGCHAIN_PLUGIN_DEBUG=true
|
|
537
|
+
|
|
538
|
+
# Provider-specific
|
|
539
|
+
OPENAI_API_KEY=sk-...
|
|
540
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Graph Configuration
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
interface GraphConfig {
|
|
547
|
+
maxHistoryMessages: number // Default: 5
|
|
548
|
+
routerTemperature: number // Default: 0.1
|
|
549
|
+
combinerTemperature: number // Default: 0.3
|
|
550
|
+
parallelExecution: boolean // Default: true (future)
|
|
551
|
+
handlerTimeout: number // Default: 30000ms
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Usage
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
import { invokeOrchestrator } from '@/contents/plugins/langchain/lib/graph'
|
|
559
|
+
|
|
560
|
+
const result = await invokeOrchestrator(
|
|
561
|
+
message,
|
|
562
|
+
sessionId,
|
|
563
|
+
{ userId, teamId },
|
|
564
|
+
conversationHistory,
|
|
565
|
+
{
|
|
566
|
+
config: {
|
|
567
|
+
maxHistoryMessages: 10,
|
|
568
|
+
routerTemperature: 0.05,
|
|
569
|
+
},
|
|
570
|
+
traceId: 'trace-123',
|
|
571
|
+
}
|
|
572
|
+
)
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## Performance Comparison
|
|
578
|
+
|
|
579
|
+
### Benchmarks
|
|
580
|
+
|
|
581
|
+
| Metric | Graph Orchestrator | ReAct Loops | Improvement |
|
|
582
|
+
|--------|-------------------|-------------|-------------|
|
|
583
|
+
| **LLM Calls (single intent)** | 1 | 20-30 | 20-30x |
|
|
584
|
+
| **LLM Calls (multi intent)** | 2 | 50+ | 25-50x |
|
|
585
|
+
| **Response Time (single)** | 2-3s | 10-15s | 5x |
|
|
586
|
+
| **Response Time (multi)** | 3-5s | 60-120s | 20-30x |
|
|
587
|
+
| **Token Cost** | ~500 | ~15,000 | 30x |
|
|
588
|
+
| **Recursion Errors** | Never | Frequent | - |
|
|
589
|
+
| **Timeout Errors** | Never | Common | - |
|
|
590
|
+
|
|
591
|
+
### Real-World Example
|
|
592
|
+
|
|
593
|
+
**Query**: "Show my tasks and find the account number for StartupXYZ"
|
|
594
|
+
|
|
595
|
+
| Metric | Graph | ReAct |
|
|
596
|
+
|--------|-------|-------|
|
|
597
|
+
| Total LLM calls | 2 | 47 |
|
|
598
|
+
| Router calls | 1 | - |
|
|
599
|
+
| Orchestrator iterations | - | 15 |
|
|
600
|
+
| Task agent iterations | - | 12 |
|
|
601
|
+
| Customer agent iterations | - | 20 |
|
|
602
|
+
| Response time | 4.2s | 127s (timeout) |
|
|
603
|
+
| Tokens used | 892 | 23,450 |
|
|
604
|
+
| Estimated cost | $0.002 | $0.047 |
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Examples
|
|
609
|
+
|
|
610
|
+
### Single Intent Flow
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
// User: "List my tasks"
|
|
614
|
+
|
|
615
|
+
// 1. Router (1 LLM call)
|
|
616
|
+
{
|
|
617
|
+
intents: [{ type: "task", action: "list", parameters: {} }],
|
|
618
|
+
needsClarification: false
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// 2. Route: task_only → task_handler
|
|
622
|
+
|
|
623
|
+
// 3. Task Handler (0 LLM calls)
|
|
624
|
+
{
|
|
625
|
+
handlerResults: {
|
|
626
|
+
task: {
|
|
627
|
+
success: true,
|
|
628
|
+
operation: "list",
|
|
629
|
+
data: [{ id: "1", title: "Task A" }, ...],
|
|
630
|
+
count: 5
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// 4. Combiner (0 LLM calls - uses template)
|
|
636
|
+
{
|
|
637
|
+
finalResponse: "Found 5 task(s):\n• Task A\n• Task B..."
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// TOTAL: 1 LLM call
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Multi-Intent Flow
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
// User: "Show tasks and find StartupXYZ phone number"
|
|
647
|
+
|
|
648
|
+
// 1. Router (1 LLM call)
|
|
649
|
+
{
|
|
650
|
+
intents: [
|
|
651
|
+
{ type: "task", action: "list", parameters: {} },
|
|
652
|
+
{ type: "customer", action: "search", parameters: { query: "StartupXYZ", fields: ["phone"] } }
|
|
653
|
+
]
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// 2. Route: task_and_customer → task_handler → customer_handler → combiner
|
|
657
|
+
|
|
658
|
+
// 3. Task Handler (0 LLM)
|
|
659
|
+
// 4. Customer Handler (0 LLM)
|
|
660
|
+
{
|
|
661
|
+
handlerResults: {
|
|
662
|
+
task: { success: true, data: [...], count: 3 },
|
|
663
|
+
customer: { success: true, data: [{ name: "StartupXYZ", phone: "+1-555-1234" }] }
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// 5. Combiner (1 LLM call - multi-result)
|
|
668
|
+
{
|
|
669
|
+
finalResponse: "Found 3 tasks. StartupXYZ phone number is +1-555-1234."
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// TOTAL: 2 LLM calls
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Error Handling
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
// User: "Update task 999" (non-existent)
|
|
679
|
+
|
|
680
|
+
// Task Handler returns error:
|
|
681
|
+
{
|
|
682
|
+
handlerResults: {
|
|
683
|
+
task: {
|
|
684
|
+
success: false,
|
|
685
|
+
operation: "update",
|
|
686
|
+
data: null,
|
|
687
|
+
error: "Task not found",
|
|
688
|
+
message: "Could not find task with ID 999"
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Combiner generates friendly error:
|
|
694
|
+
{
|
|
695
|
+
finalResponse: "I couldn't find that task. Would you like to see your task list?"
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Debugging
|
|
702
|
+
|
|
703
|
+
### Enable Debug Mode
|
|
704
|
+
|
|
705
|
+
```env
|
|
706
|
+
LANGCHAIN_PLUGIN_DEBUG=true
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### Debug Output
|
|
710
|
+
|
|
711
|
+
```
|
|
712
|
+
[Router] Classifying intent for: Show my tasks
|
|
713
|
+
[Router] Classified intents: [{"type":"task","action":"list"}]
|
|
714
|
+
[Task Handler] Processing intent: list
|
|
715
|
+
[Task Handler] Found 5 tasks
|
|
716
|
+
[Combiner] Using template-based response
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### Observability
|
|
720
|
+
|
|
721
|
+
Use the [Observability Dashboard](../04-advanced/01-observability.md) to trace execution:
|
|
722
|
+
|
|
723
|
+
- View trace timeline with each node
|
|
724
|
+
- See provider/model used per node
|
|
725
|
+
- Inspect input/output at each step
|
|
726
|
+
- Track token usage and costs
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## Migration from ReAct
|
|
731
|
+
|
|
732
|
+
### Step 1: Enable Graph Mode
|
|
733
|
+
|
|
734
|
+
```env
|
|
735
|
+
LANGCHAIN_USE_GRAPH_ORCHESTRATOR=true
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### Step 2: Configure Providers
|
|
739
|
+
|
|
740
|
+
Ensure providers are configured in `langchain.config.ts`:
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
'orchestrator': {
|
|
744
|
+
provider: 'anthropic', // Claude for reliable classification
|
|
745
|
+
model: 'claude-3-haiku-20240307',
|
|
746
|
+
temperature: 0.1,
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Step 3: Test
|
|
751
|
+
|
|
752
|
+
```bash
|
|
753
|
+
# Run your test suite
|
|
754
|
+
pnpm test
|
|
755
|
+
|
|
756
|
+
# Monitor observability dashboard
|
|
757
|
+
# /sector7/ai-observability
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Fallback
|
|
761
|
+
|
|
762
|
+
The system supports automatic fallback:
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
// In orchestrator.ts
|
|
766
|
+
if (isGraphOrchestratorEnabled()) {
|
|
767
|
+
return processWithGraphOrchestrator(message, context)
|
|
768
|
+
}
|
|
769
|
+
return processWithOrchestrator(message, context) // Legacy ReAct
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
## Best Practices
|
|
775
|
+
|
|
776
|
+
### 1. Keep Router Focused
|
|
777
|
+
|
|
778
|
+
The router should only classify intent. Keep the system prompt simple and focused on intent extraction.
|
|
779
|
+
|
|
780
|
+
### 2. Use Low Temperature
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
routerTemperature: 0.1 // Consistent classification
|
|
784
|
+
combinerTemperature: 0.3 // Slightly creative responses
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### 3. Handle All Intent Types
|
|
788
|
+
|
|
789
|
+
Ensure your handlers cover all possible actions for each entity type.
|
|
790
|
+
|
|
791
|
+
### 4. Monitor Performance
|
|
792
|
+
|
|
793
|
+
Use observability to track:
|
|
794
|
+
- Intent classification accuracy
|
|
795
|
+
- Handler execution times
|
|
796
|
+
- Combiner usage (template vs LLM)
|
|
797
|
+
|
|
798
|
+
### 5. Test Multi-Intent
|
|
799
|
+
|
|
800
|
+
Always test with multi-intent queries to ensure proper sequencing.
|
|
801
|
+
|
|
802
|
+
---
|
|
803
|
+
|
|
804
|
+
## Related Documentation
|
|
805
|
+
|
|
806
|
+
- [Observability & Tracing](../04-advanced/01-observability.md) - Debug and monitor execution
|
|
807
|
+
- [Token Tracking](../04-advanced/02-token-tracking.md) - Cost management
|
|
808
|
+
- [Configuration](../01-getting-started/03-configuration.md) - Provider setup
|
|
809
|
+
- [Legacy Orchestration](./02-legacy-react.md) - ReAct-based approach (deprecated)
|