@falai/agent 0.6.2 → 0.6.4
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 +89 -56
- package/dist/cjs/constants/index.d.ts +6 -1
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +8 -3
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +22 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +108 -21
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/Events.d.ts +13 -0
- package/dist/cjs/core/Events.d.ts.map +1 -1
- package/dist/cjs/core/Events.js +28 -14
- package/dist/cjs/core/Events.js.map +1 -1
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +4 -4
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +6 -1
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +112 -37
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/State.d.ts +15 -5
- package/dist/cjs/core/State.d.ts.map +1 -1
- package/dist/cjs/core/State.js +24 -5
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/core/Tool.d.ts +8 -1
- package/dist/cjs/core/Tool.d.ts.map +1 -1
- package/dist/cjs/core/Tool.js +25 -28
- package/dist/cjs/core/Tool.js.map +1 -1
- package/dist/cjs/core/Transition.js +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +5 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/route.d.ts +7 -1
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +12 -1
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/session.js +26 -5
- package/dist/cjs/types/session.js.map +1 -1
- package/dist/cjs/utils/logger.d.ts +10 -0
- package/dist/cjs/utils/logger.d.ts.map +1 -0
- package/dist/cjs/utils/logger.js +23 -0
- package/dist/cjs/utils/logger.js.map +1 -0
- package/dist/constants/index.d.ts +6 -1
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +6 -1
- package/dist/constants/index.js.map +1 -1
- package/dist/core/Agent.d.ts +22 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +108 -21
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/Events.d.ts +13 -0
- package/dist/core/Events.d.ts.map +1 -1
- package/dist/core/Events.js +28 -14
- package/dist/core/Events.js.map +1 -1
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +4 -4
- package/dist/core/Route.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +6 -1
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +112 -37
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/State.d.ts +15 -5
- package/dist/core/State.d.ts.map +1 -1
- package/dist/core/State.js +25 -6
- package/dist/core/State.js.map +1 -1
- package/dist/core/Tool.d.ts +8 -1
- package/dist/core/Tool.d.ts.map +1 -1
- package/dist/core/Tool.js +25 -28
- package/dist/core/Tool.js.map +1 -1
- package/dist/core/Transition.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +5 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js.map +1 -1
- package/dist/types/route.d.ts +7 -1
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/session.d.ts +12 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +26 -5
- package/dist/types/session.js.map +1 -1
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +17 -0
- package/dist/utils/logger.js.map +1 -0
- package/docs/{CONSTRUCTOR_OPTIONS.md → AGENT.md} +79 -7
- package/docs/API_REFERENCE.md +309 -18
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/DOCS.md +46 -22
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/README.md +13 -5
- package/docs/ROUTES.md +743 -0
- package/docs/STATES.md +798 -0
- package/examples/business-onboarding.ts +46 -5
- package/examples/company-qna-agent.ts +107 -1
- package/examples/custom-database-persistence.ts +44 -1
- package/examples/declarative-agent.ts +80 -37
- package/examples/domain-scoping.ts +91 -21
- package/examples/extracted-data-modification.ts +64 -2
- package/examples/healthcare-agent.ts +61 -4
- package/examples/openai-agent.ts +24 -2
- package/examples/opensearch-persistence.ts +26 -1
- package/examples/persistent-onboarding.ts +84 -18
- package/examples/prisma-persistence.ts +90 -16
- package/examples/redis-persistence.ts +89 -17
- package/examples/rules-prohibitions.ts +300 -139
- package/examples/streaming-agent.ts +60 -0
- package/examples/travel-agent.ts +66 -24
- package/package.json +3 -2
- package/src/constants/index.ts +6 -1
- package/src/core/Agent.ts +135 -21
- package/src/core/Events.ts +73 -10
- package/src/core/Route.ts +8 -4
- package/src/core/RoutingEngine.ts +150 -39
- package/src/core/State.ts +35 -10
- package/src/core/Tool.ts +67 -10
- package/src/core/Transition.ts +1 -1
- package/src/index.ts +1 -1
- package/src/types/agent.ts +5 -0
- package/src/types/route.ts +10 -1
- package/src/types/session.ts +42 -6
- package/src/utils/logger.ts +19 -0
package/docs/ROUTES.md
ADDED
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
# Routes Guide
|
|
2
|
+
|
|
3
|
+
A complete guide to creating and managing conversational routes in `@falai/agent`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [What is a Route?](#what-is-a-route)
|
|
10
|
+
- [Creating Routes](#creating-routes)
|
|
11
|
+
- [Initial State Configuration](#initial-state-configuration)
|
|
12
|
+
- [Data Extraction](#data-extraction)
|
|
13
|
+
- [Sequential Steps](#sequential-steps)
|
|
14
|
+
- [Route Properties](#route-properties)
|
|
15
|
+
- [Route Security](#route-security)
|
|
16
|
+
- [Advanced Patterns](#advanced-patterns)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## What is a Route?
|
|
21
|
+
|
|
22
|
+
A **Route** (also called a "Journey") represents a specific conversational flow in your agent. Think of it as a state machine that guides the conversation through a series of steps to accomplish a specific goal.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// Example: A route for booking flights
|
|
26
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
27
|
+
title: "Book Flight",
|
|
28
|
+
description: "Help user book a flight",
|
|
29
|
+
conditions: ["User wants to book a flight"],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Key Concepts:**
|
|
34
|
+
|
|
35
|
+
- Each route has a **goal** (e.g., "book a flight", "answer FAQ", "collect feedback")
|
|
36
|
+
- Routes contain **states** that represent conversation steps
|
|
37
|
+
- Routes can extract and track **typed data** throughout the conversation
|
|
38
|
+
- Routes have their own **rules**, **prohibitions**, and **guidelines**
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Creating Routes
|
|
43
|
+
|
|
44
|
+
### Basic Route
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface FlightData {
|
|
48
|
+
destination: string;
|
|
49
|
+
departureDate: string;
|
|
50
|
+
passengers: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
54
|
+
title: "Book Flight",
|
|
55
|
+
description: "Help user book a flight",
|
|
56
|
+
conditions: [
|
|
57
|
+
"User wants to book a flight",
|
|
58
|
+
"User mentions flying or traveling",
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Route with Data Extraction Schema
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
67
|
+
title: "Book Flight",
|
|
68
|
+
description: "Help user book a flight",
|
|
69
|
+
conditions: ["User wants to book a flight"],
|
|
70
|
+
|
|
71
|
+
// Define what data to extract
|
|
72
|
+
extractionSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
destination: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Where the user wants to fly",
|
|
78
|
+
},
|
|
79
|
+
departureDate: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "When they want to depart",
|
|
82
|
+
},
|
|
83
|
+
passengers: {
|
|
84
|
+
type: "number",
|
|
85
|
+
minimum: 1,
|
|
86
|
+
maximum: 9,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
required: ["destination", "departureDate", "passengers"],
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Route with Rules and Prohibitions
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const paymentRoute = agent.createRoute({
|
|
98
|
+
title: "Process Payment",
|
|
99
|
+
conditions: ["User wants to make a payment"],
|
|
100
|
+
|
|
101
|
+
// Absolute rules the agent MUST follow
|
|
102
|
+
rules: [
|
|
103
|
+
"Always confirm the payment amount before processing",
|
|
104
|
+
"Verify payment method is supported",
|
|
105
|
+
"Provide a receipt after successful payment",
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
// Things the agent must NEVER do
|
|
109
|
+
prohibitions: [
|
|
110
|
+
"Never store credit card numbers",
|
|
111
|
+
"Never process payments without explicit confirmation",
|
|
112
|
+
"Never share payment details with third parties",
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Initial State Configuration
|
|
120
|
+
|
|
121
|
+
Every route starts with an initial state. You can now configure it in two ways:
|
|
122
|
+
|
|
123
|
+
### Option 1: Configure at Route Creation
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
127
|
+
title: "Book Flight",
|
|
128
|
+
|
|
129
|
+
// Configure the initial state
|
|
130
|
+
initialState: {
|
|
131
|
+
id: "welcome_state",
|
|
132
|
+
chatState:
|
|
133
|
+
"Welcome! I'll help you book a flight. Where would you like to go?",
|
|
134
|
+
gather: ["destination"],
|
|
135
|
+
skipIf: (extracted) => !!extracted.destination,
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
extractionSchema: {
|
|
139
|
+
// ... schema definition
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Option 2: Configure After Route Creation
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
148
|
+
title: "Book Flight",
|
|
149
|
+
// ... other options
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Configure initial state later
|
|
153
|
+
bookingRoute.initialState.configure({
|
|
154
|
+
description: "Welcome! Let's book your flight",
|
|
155
|
+
gatherFields: ["destination"],
|
|
156
|
+
skipIf: (extracted) => !!extracted.destination,
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Initial State Options
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
initialState: {
|
|
164
|
+
// Custom ID for the initial state
|
|
165
|
+
id?: string;
|
|
166
|
+
|
|
167
|
+
// Description/prompt for the initial state
|
|
168
|
+
chatState?: string;
|
|
169
|
+
|
|
170
|
+
// Fields to extract in this state
|
|
171
|
+
gather?: string[];
|
|
172
|
+
|
|
173
|
+
// Skip this state if condition is met
|
|
174
|
+
skipIf?: (extracted: Partial<TExtracted>) => boolean;
|
|
175
|
+
|
|
176
|
+
// Prerequisites that must be met
|
|
177
|
+
requiredData?: string[];
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Data Extraction
|
|
184
|
+
|
|
185
|
+
### Defining Extraction Schema
|
|
186
|
+
|
|
187
|
+
The extraction schema defines what data your route will collect:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
const onboardingRoute = agent.createRoute<OnboardingData>({
|
|
191
|
+
title: "User Onboarding",
|
|
192
|
+
|
|
193
|
+
extractionSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
firstName: { type: "string" },
|
|
197
|
+
lastName: { type: "string" },
|
|
198
|
+
email: { type: "string", format: "email" },
|
|
199
|
+
company: { type: "string" },
|
|
200
|
+
role: {
|
|
201
|
+
type: "string",
|
|
202
|
+
enum: ["developer", "designer", "manager", "other"],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
required: ["firstName", "lastName", "email"],
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Getting Extracted Data
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Get extracted data from the route
|
|
214
|
+
const extracted = bookingRoute.getExtractedData(session);
|
|
215
|
+
|
|
216
|
+
console.log(extracted);
|
|
217
|
+
// { destination: "Paris", departureDate: "2025-06-15", passengers: 2 }
|
|
218
|
+
|
|
219
|
+
// Only returns data if session is in this route
|
|
220
|
+
const otherRouteData = otherRoute.getExtractedData(session);
|
|
221
|
+
// {} - empty if session is in a different route
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Pre-populating Data
|
|
225
|
+
|
|
226
|
+
You can pre-populate data when entering a route:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
230
|
+
title: "Book Flight",
|
|
231
|
+
|
|
232
|
+
// Pre-fill known information
|
|
233
|
+
initialData: {
|
|
234
|
+
destination: "Paris", // User mentioned this earlier
|
|
235
|
+
passengers: 1, // Default value
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
extractionSchema: {
|
|
239
|
+
// ... schema
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// States with skipIf will automatically bypass if data exists
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Sequential Steps
|
|
249
|
+
|
|
250
|
+
For simple linear flows, use the `steps` option:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const feedbackRoute = agent.createRoute({
|
|
254
|
+
title: "Collect Feedback",
|
|
255
|
+
|
|
256
|
+
steps: [
|
|
257
|
+
{
|
|
258
|
+
id: "ask_rating",
|
|
259
|
+
chatState: "How would you rate your experience? (1-5 stars)",
|
|
260
|
+
gather: ["rating"],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "ask_liked",
|
|
264
|
+
chatState: "What did you like most?",
|
|
265
|
+
gather: ["likedMost"],
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "ask_improve",
|
|
269
|
+
chatState: "What could we improve?",
|
|
270
|
+
gather: ["improvements"],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "thank_you",
|
|
274
|
+
chatState: "Thank you for your feedback! 🙏",
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Automatically chains: initialState → ask_rating → ask_liked → ask_improve → thank_you → END_STATE
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**When to use steps vs manual chaining:**
|
|
283
|
+
|
|
284
|
+
- ✅ Use `steps` for: Linear flows, simple wizards, sequential data collection
|
|
285
|
+
- ✅ Use manual chaining for: Branching logic, conditional flows, complex state machines
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Route Properties
|
|
290
|
+
|
|
291
|
+
### Accessing Route Properties
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
const route = agent.createRoute({ ... });
|
|
295
|
+
|
|
296
|
+
// Route identification
|
|
297
|
+
console.log(route.id); // "route_book_flight_abc123"
|
|
298
|
+
console.log(route.title); // "Book Flight"
|
|
299
|
+
console.log(route.description); // "Help user book a flight"
|
|
300
|
+
|
|
301
|
+
// Activation conditions
|
|
302
|
+
console.log(route.conditions); // ["User wants to book a flight"]
|
|
303
|
+
|
|
304
|
+
// Rules and guidelines
|
|
305
|
+
console.log(route.getRules()); // ["Always confirm...", ...]
|
|
306
|
+
console.log(route.getProhibitions()); // ["Never store...", ...]
|
|
307
|
+
console.log(route.getGuidelines()); // [{ condition: "...", action: "..." }]
|
|
308
|
+
|
|
309
|
+
// Domain restrictions
|
|
310
|
+
console.log(route.getDomains()); // ["payment", "booking"] or undefined
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Route Reference
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const routeRef = route.getRef();
|
|
317
|
+
console.log(routeRef); // { id: "route_book_flight_abc123" }
|
|
318
|
+
|
|
319
|
+
// Use reference to jump to specific routes
|
|
320
|
+
state.transitionTo({ state: routeRef });
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Route Structure
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// Get all states in the route
|
|
327
|
+
const states = route.getAllStates();
|
|
328
|
+
console.log(states); // [State, State, State, ...]
|
|
329
|
+
|
|
330
|
+
// Get specific state by ID
|
|
331
|
+
const state = route.getState("ask_destination");
|
|
332
|
+
|
|
333
|
+
// Describe route structure
|
|
334
|
+
console.log(route.describe());
|
|
335
|
+
// Output:
|
|
336
|
+
// Route: Book Flight
|
|
337
|
+
// ID: route_book_flight_abc123
|
|
338
|
+
// Description: Help user book a flight
|
|
339
|
+
// Conditions: User wants to book a flight
|
|
340
|
+
//
|
|
341
|
+
// States:
|
|
342
|
+
// - initial_state: Initial state
|
|
343
|
+
// -> ask_destination
|
|
344
|
+
// - ask_destination: Ask where they want to fly
|
|
345
|
+
// -> ask_dates
|
|
346
|
+
// ...
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Route Security
|
|
352
|
+
|
|
353
|
+
### Domain Scoping
|
|
354
|
+
|
|
355
|
+
Restrict which tools/domains a route can access:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Define domains
|
|
359
|
+
agent.addDomain("payment", {
|
|
360
|
+
processPayment: async (amount: number) => {
|
|
361
|
+
/* ... */
|
|
362
|
+
},
|
|
363
|
+
refund: async (transactionId: string) => {
|
|
364
|
+
/* ... */
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
agent.addDomain("booking", {
|
|
369
|
+
searchFlights: async (dest: string) => {
|
|
370
|
+
/* ... */
|
|
371
|
+
},
|
|
372
|
+
createReservation: async (data: any) => {
|
|
373
|
+
/* ... */
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
agent.addDomain("database", {
|
|
378
|
+
saveUser: async (user: any) => {
|
|
379
|
+
/* ... */
|
|
380
|
+
},
|
|
381
|
+
deleteUser: async (userId: string) => {
|
|
382
|
+
/* ... */
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Payment route: only access payment tools
|
|
387
|
+
const paymentRoute = agent.createRoute({
|
|
388
|
+
title: "Process Payment",
|
|
389
|
+
domains: ["payment"], // ONLY payment domain
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Booking route: access booking AND payment
|
|
393
|
+
const bookingRoute = agent.createRoute({
|
|
394
|
+
title: "Book Flight",
|
|
395
|
+
domains: ["booking", "payment"], // Both domains
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Admin route: access everything
|
|
399
|
+
const adminRoute = agent.createRoute({
|
|
400
|
+
title: "Admin Actions",
|
|
401
|
+
// domains: undefined = all domains allowed
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Security Benefits:**
|
|
406
|
+
|
|
407
|
+
- ✅ Prevents accidental tool execution in wrong context
|
|
408
|
+
- ✅ Reduces attack surface for each route
|
|
409
|
+
- ✅ Makes permissions explicit and auditable
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Advanced Patterns
|
|
414
|
+
|
|
415
|
+
### Pattern 1: Branching Logic
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const supportRoute = agent.createRoute<SupportData>({
|
|
419
|
+
title: "Customer Support",
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Ask issue type
|
|
423
|
+
const askIssueType = supportRoute.initialState.transitionTo({
|
|
424
|
+
chatState: "What type of issue are you experiencing?",
|
|
425
|
+
gather: ["issueType"],
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Branch 1: Technical issues
|
|
429
|
+
const technicalFlow = askIssueType.transitionTo({
|
|
430
|
+
chatState: "Let me help with your technical issue...",
|
|
431
|
+
condition: "Issue type is technical",
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Branch 2: Billing issues
|
|
435
|
+
const billingFlow = askIssueType.transitionTo({
|
|
436
|
+
chatState: "Let me help with your billing issue...",
|
|
437
|
+
condition: "Issue type is billing",
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Branch 3: General inquiries
|
|
441
|
+
const generalFlow = askIssueType.transitionTo({
|
|
442
|
+
chatState: "Let me help with your inquiry...",
|
|
443
|
+
condition: "Issue type is general",
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Pattern 2: Conditional Skip States
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
const checkoutRoute = agent.createRoute<CheckoutData>({
|
|
451
|
+
title: "Checkout",
|
|
452
|
+
extractionSchema: {
|
|
453
|
+
properties: {
|
|
454
|
+
hasAccount: { type: "boolean" },
|
|
455
|
+
email: { type: "string" },
|
|
456
|
+
shippingAddress: { type: "object" },
|
|
457
|
+
billingAddress: { type: "object" },
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Skip login if user already has account
|
|
463
|
+
const login = checkoutRoute.initialState.transitionTo({
|
|
464
|
+
chatState: "Please log in or continue as guest",
|
|
465
|
+
gather: ["hasAccount", "email"],
|
|
466
|
+
skipIf: (extracted) => extracted.hasAccount === true,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Skip billing address if same as shipping
|
|
470
|
+
const shippingAddress = login.transitionTo({
|
|
471
|
+
chatState: "What's your shipping address?",
|
|
472
|
+
gather: ["shippingAddress"],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const billingAddress = shippingAddress.transitionTo({
|
|
476
|
+
chatState: "Is your billing address the same as shipping?",
|
|
477
|
+
gather: ["billingAddress"],
|
|
478
|
+
skipIf: (extracted) => extracted.billingAddress !== undefined,
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Pattern 3: Route-Specific Guidelines
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
const bookingRoute = agent.createRoute({
|
|
486
|
+
title: "Book Flight",
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Add guidelines specific to this route
|
|
490
|
+
bookingRoute.createGuideline({
|
|
491
|
+
condition: "User asks about cancellation policy",
|
|
492
|
+
action: "Explain that cancellations must be made 24 hours in advance",
|
|
493
|
+
tags: ["policy"],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
bookingRoute.createGuideline({
|
|
497
|
+
condition: "User provides invalid date",
|
|
498
|
+
action: "Politely ask for a valid future date in YYYY-MM-DD format",
|
|
499
|
+
tags: ["validation"],
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Pattern 4: Tool Integration in Routes
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { defineTool } from "@falai/agent";
|
|
507
|
+
|
|
508
|
+
const searchFlights = defineTool<MyContext, [], FlightResults>(
|
|
509
|
+
"search_flights",
|
|
510
|
+
async ({ context, extracted }) => {
|
|
511
|
+
const flights = await api.searchFlights({
|
|
512
|
+
destination: extracted.destination,
|
|
513
|
+
date: extracted.departureDate,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
data: flights,
|
|
518
|
+
contextUpdate: { availableFlights: flights },
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
524
|
+
title: "Book Flight",
|
|
525
|
+
domains: ["booking"], // Ensure tool is in this domain
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Gather data
|
|
529
|
+
const gatherDetails = bookingRoute.initialState.transitionTo({
|
|
530
|
+
chatState: "Where and when would you like to fly?",
|
|
531
|
+
gather: ["destination", "departureDate", "passengers"],
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Execute tool
|
|
535
|
+
const searchState = gatherDetails.transitionTo({
|
|
536
|
+
toolState: searchFlights,
|
|
537
|
+
requiredData: ["destination", "departureDate", "passengers"],
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Present results
|
|
541
|
+
const presentFlights = searchState.transitionTo({
|
|
542
|
+
chatState: "Here are available flights based on your search",
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Route Completion
|
|
549
|
+
|
|
550
|
+
When a route reaches its final state and transitions to `END_STATE`, the agent returns `isRouteComplete: true` to signal that all required data has been collected.
|
|
551
|
+
|
|
552
|
+
### Ending a Route
|
|
553
|
+
|
|
554
|
+
Use the `END_STATE` symbol to mark the end of a route:
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { END_STATE } from "@falai/agent";
|
|
558
|
+
|
|
559
|
+
const onboardingRoute = agent.createRoute<OnboardingData>({
|
|
560
|
+
title: "User Onboarding",
|
|
561
|
+
extractionSchema: ONBOARDING_SCHEMA,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const askName = onboardingRoute.initialState.transitionTo({
|
|
565
|
+
chatState: "What's your name?",
|
|
566
|
+
gather: ["name"],
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const askEmail = askName.transitionTo({
|
|
570
|
+
chatState: "What's your email?",
|
|
571
|
+
gather: ["email"],
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const thankYou = askEmail.transitionTo({
|
|
575
|
+
chatState: "Thank you! Your profile is complete.",
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// End the route
|
|
579
|
+
thankYou.transitionTo({ state: END_STATE });
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Handling Completion
|
|
583
|
+
|
|
584
|
+
There are **two ways** to check if a route has completed:
|
|
585
|
+
|
|
586
|
+
#### Method 1: Using `isRouteComplete` (Recommended)
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
const response = await agent.respond({
|
|
590
|
+
history,
|
|
591
|
+
session,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
if (response.isRouteComplete) {
|
|
595
|
+
// ✅ Route is complete! All data has been collected
|
|
596
|
+
|
|
597
|
+
// Get the collected data
|
|
598
|
+
const data = agent.getExtractedData(response.session!);
|
|
599
|
+
console.log("Collected data:", data);
|
|
600
|
+
|
|
601
|
+
// Process the data
|
|
602
|
+
await saveToDatabase(data);
|
|
603
|
+
await sendConfirmationEmail(data.email);
|
|
604
|
+
|
|
605
|
+
// Show custom completion message
|
|
606
|
+
return "Thank you! Your information has been saved.";
|
|
607
|
+
} else {
|
|
608
|
+
// ⏳ Route still in progress
|
|
609
|
+
return response.message;
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### Method 2: Using `END_STATE_ID` Constant
|
|
614
|
+
|
|
615
|
+
For users who prefer a symbol-based pattern consistent with building routes:
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import { END_STATE_ID } from "@falai/agent";
|
|
619
|
+
|
|
620
|
+
const response = await agent.respond({
|
|
621
|
+
history,
|
|
622
|
+
session,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
if (response.session?.currentState?.id === END_STATE_ID) {
|
|
626
|
+
// ✅ Route completed - currentState is now END_STATE
|
|
627
|
+
const data = agent.getExtractedData(response.session!);
|
|
628
|
+
await handleCompletion(data);
|
|
629
|
+
return "Complete!";
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return response.message;
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Which method should you use?**
|
|
636
|
+
|
|
637
|
+
- ✅ **Use `isRouteComplete`** for simplicity and clarity
|
|
638
|
+
- ✅ **Use `END_STATE_ID`** if you want consistency with how you build routes (`END_STATE` symbol)
|
|
639
|
+
|
|
640
|
+
### Immediate Completion
|
|
641
|
+
|
|
642
|
+
Routes can complete **immediately** if all states are skipped due to `skipIf` conditions. This is useful when:
|
|
643
|
+
|
|
644
|
+
- Resuming a partially completed route
|
|
645
|
+
- Pre-filling data from an existing session
|
|
646
|
+
- User provides all information upfront
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
const onboardingRoute = agent.createRoute<OnboardingData>({
|
|
650
|
+
title: "User Onboarding",
|
|
651
|
+
extractionSchema: ONBOARDING_SCHEMA,
|
|
652
|
+
initialData: existingUserData, // Pre-fill with existing data
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const askName = onboardingRoute.initialState.transitionTo({
|
|
656
|
+
chatState: "What's your name?",
|
|
657
|
+
gather: ["name"],
|
|
658
|
+
skipIf: (data) => !!data.name, // Skip if name exists
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const askEmail = askName.transitionTo({
|
|
662
|
+
chatState: "What's your email?",
|
|
663
|
+
gather: ["email"],
|
|
664
|
+
skipIf: (data) => !!data.email, // Skip if email exists
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const complete = askEmail.transitionTo({
|
|
668
|
+
chatState: "All done!",
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
complete.transitionTo({ state: END_STATE });
|
|
672
|
+
|
|
673
|
+
// In your handler:
|
|
674
|
+
const response = await agent.respond({ history, session });
|
|
675
|
+
|
|
676
|
+
if (response.isRouteComplete) {
|
|
677
|
+
// If existingUserData had all fields, route completes immediately!
|
|
678
|
+
// The routing engine recursively skips all states and reaches END_STATE
|
|
679
|
+
console.log("Profile already complete!");
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Important Notes
|
|
684
|
+
|
|
685
|
+
- **`isRouteComplete: true`** indicates the route has reached `END_STATE`
|
|
686
|
+
- **`currentState.id`** is set to `END_STATE_ID` when the route completes
|
|
687
|
+
- **`response.message`** will be empty (`""`) when route is complete
|
|
688
|
+
- **The routing engine** recursively traverses skipped states to detect completion
|
|
689
|
+
- **You can check either** `isRouteComplete` or `currentState.id === END_STATE_ID`
|
|
690
|
+
- **Session state** still contains all the collected data via `agent.getExtractedData()`
|
|
691
|
+
- **The route** (`currentRoute.id`) remains the completed route (e.g., "onboarding"), not END_STATE
|
|
692
|
+
|
|
693
|
+
### With Streaming
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
for await (const chunk of agent.respondStream({ history, session })) {
|
|
697
|
+
if (chunk.delta) {
|
|
698
|
+
process.stdout.write(chunk.delta);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (chunk.done && chunk.isRouteComplete) {
|
|
702
|
+
console.log("\n🎉 Route completed!");
|
|
703
|
+
const data = agent.getExtractedData(chunk.session!);
|
|
704
|
+
await processCompletedData(data);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Best Practices
|
|
712
|
+
|
|
713
|
+
### ✅ Do's
|
|
714
|
+
|
|
715
|
+
- **Use descriptive titles and descriptions** - Makes routing more accurate
|
|
716
|
+
- **Define extraction schemas** - Type-safe data collection
|
|
717
|
+
- **Configure initial state** - Set up proper entry point
|
|
718
|
+
- **Use skipIf for known data** - Avoid redundant questions
|
|
719
|
+
- **Scope domains** - Limit tool access per route
|
|
720
|
+
- **Add route-specific guidelines** - Context-aware behavior
|
|
721
|
+
- **Use steps for linear flows** - Cleaner code for simple paths
|
|
722
|
+
|
|
723
|
+
### ❌ Don'ts
|
|
724
|
+
|
|
725
|
+
- **Don't use vague conditions** - Be specific about when to activate
|
|
726
|
+
- **Don't skip extraction schemas** - Loses type safety
|
|
727
|
+
- **Don't create overly complex routes** - Split into multiple routes
|
|
728
|
+
- **Don't forget requiredData** - Prevent states from executing too early
|
|
729
|
+
- **Don't mix concerns** - One route = one goal
|
|
730
|
+
- **Don't hardcode state IDs** - Let framework generate deterministic IDs
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
## See Also
|
|
735
|
+
|
|
736
|
+
- [States Guide](./STATES.md) - Deep dive into state management
|
|
737
|
+
- [API Reference - Route](./API_REFERENCE.md#route) - Complete API docs
|
|
738
|
+
- [Examples](../examples/) - Real-world route implementations
|
|
739
|
+
- [Architecture Guide](./ARCHITECTURE.md) - How routes fit in the system
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
**Made with ❤️ for the community**
|