@falai/agent 0.5.3 → 0.5.5
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 +20 -4
- package/dist/cjs/core/Agent.d.ts +0 -5
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +75 -157
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +68 -2
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +416 -2
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/State.js +2 -1
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/types/route.d.ts +2 -0
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/utils/event.d.ts +6 -0
- package/dist/cjs/utils/event.d.ts.map +1 -0
- package/dist/cjs/utils/event.js +20 -0
- package/dist/cjs/utils/event.js.map +1 -0
- package/dist/core/Agent.d.ts +0 -5
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +74 -156
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +68 -2
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +416 -2
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/State.js +2 -1
- package/dist/core/State.js.map +1 -1
- package/dist/types/route.d.ts +2 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/utils/event.d.ts +6 -0
- package/dist/utils/event.d.ts.map +1 -0
- package/dist/utils/event.js +17 -0
- package/dist/utils/event.js.map +1 -0
- package/docs/API_REFERENCE.md +107 -26
- package/docs/ARCHITECTURE.md +83 -12
- package/docs/PERSISTENCE.md +18 -6
- package/examples/business-onboarding.ts +11 -0
- package/examples/custom-database-persistence.ts +533 -0
- package/examples/healthcare-agent.ts +28 -16
- package/examples/persistent-onboarding.ts +16 -10
- package/examples/prisma-persistence.ts +13 -2
- package/examples/travel-agent.ts +94 -52
- package/package.json +1 -1
- package/src/core/Agent.ts +78 -227
- package/src/core/RoutingEngine.ts +663 -2
- package/src/core/State.ts +1 -1
- package/src/types/route.ts +2 -0
- package/src/utils/event.ts +16 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Custom Database Integration (Manual Session State Management)
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to manually manage session state when using your own
|
|
5
|
+
* database structure instead of the built-in persistence adapters.
|
|
6
|
+
*
|
|
7
|
+
* Use this approach if you:
|
|
8
|
+
* - Have an existing database schema you want to integrate with
|
|
9
|
+
* - Need custom data structures beyond what adapters provide
|
|
10
|
+
* - Want full control over database operations
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Agent,
|
|
15
|
+
GeminiProvider,
|
|
16
|
+
createMessageEvent,
|
|
17
|
+
EventSource,
|
|
18
|
+
createSession,
|
|
19
|
+
SessionState,
|
|
20
|
+
MessageEventData,
|
|
21
|
+
Event,
|
|
22
|
+
} from "../src/index";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Example: Your existing database structure
|
|
26
|
+
* This could be any ORM (Prisma, TypeORM, Drizzle) or query builder
|
|
27
|
+
*/
|
|
28
|
+
interface CustomDatabaseSession {
|
|
29
|
+
id: string;
|
|
30
|
+
userId: string;
|
|
31
|
+
currentRoute?: string;
|
|
32
|
+
currentState?: string;
|
|
33
|
+
collectedData?: {
|
|
34
|
+
extracted?: Record<string, unknown>;
|
|
35
|
+
routeHistory?: unknown[];
|
|
36
|
+
currentRouteTitle?: string;
|
|
37
|
+
currentStateDescription?: string;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
updatedAt: Date;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface CustomDatabaseMessage {
|
|
45
|
+
id: string;
|
|
46
|
+
sessionId: string;
|
|
47
|
+
userId: string;
|
|
48
|
+
role: "user" | "agent" | "system";
|
|
49
|
+
content: string;
|
|
50
|
+
route?: string;
|
|
51
|
+
state?: string;
|
|
52
|
+
createdAt: Date;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Mock database - replace with your actual database
|
|
56
|
+
class CustomDatabase {
|
|
57
|
+
private sessions: Map<string, CustomDatabaseSession> = new Map();
|
|
58
|
+
private messages: Map<string, CustomDatabaseMessage[]> = new Map();
|
|
59
|
+
|
|
60
|
+
async findSession(id: string): Promise<CustomDatabaseSession | null> {
|
|
61
|
+
return this.sessions.get(id) || null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createSession(
|
|
65
|
+
data: Omit<CustomDatabaseSession, "id" | "createdAt" | "updatedAt">
|
|
66
|
+
): Promise<CustomDatabaseSession> {
|
|
67
|
+
const session: CustomDatabaseSession = {
|
|
68
|
+
id: `session_${Date.now()}`,
|
|
69
|
+
...data,
|
|
70
|
+
createdAt: new Date(),
|
|
71
|
+
updatedAt: new Date(),
|
|
72
|
+
};
|
|
73
|
+
this.sessions.set(session.id, session);
|
|
74
|
+
return session;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async updateSession(
|
|
78
|
+
id: string,
|
|
79
|
+
data: Partial<CustomDatabaseSession>
|
|
80
|
+
): Promise<CustomDatabaseSession | null> {
|
|
81
|
+
const session = this.sessions.get(id);
|
|
82
|
+
if (!session) return null;
|
|
83
|
+
|
|
84
|
+
const updated = {
|
|
85
|
+
...session,
|
|
86
|
+
...data,
|
|
87
|
+
updatedAt: new Date(),
|
|
88
|
+
};
|
|
89
|
+
this.sessions.set(id, updated);
|
|
90
|
+
return updated;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async createMessage(
|
|
94
|
+
data: Omit<CustomDatabaseMessage, "id" | "createdAt">
|
|
95
|
+
): Promise<CustomDatabaseMessage> {
|
|
96
|
+
const message: CustomDatabaseMessage = {
|
|
97
|
+
id: `msg_${Date.now()}`,
|
|
98
|
+
...data,
|
|
99
|
+
createdAt: new Date(),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const messages = this.messages.get(data.sessionId) || [];
|
|
103
|
+
messages.push(message);
|
|
104
|
+
this.messages.set(data.sessionId, messages);
|
|
105
|
+
|
|
106
|
+
return message;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async getSessionMessages(
|
|
110
|
+
sessionId: string
|
|
111
|
+
): Promise<CustomDatabaseMessage[]> {
|
|
112
|
+
return this.messages.get(sessionId) || [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Example data type for a customer onboarding route
|
|
117
|
+
interface OnboardingData {
|
|
118
|
+
fullName: string;
|
|
119
|
+
email: string;
|
|
120
|
+
companyName: string;
|
|
121
|
+
phoneNumber?: string;
|
|
122
|
+
industry?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function example() {
|
|
126
|
+
const db = new CustomDatabase();
|
|
127
|
+
const userId = "user_123";
|
|
128
|
+
|
|
129
|
+
// Create agent
|
|
130
|
+
const agent = new Agent({
|
|
131
|
+
name: "Onboarding Assistant",
|
|
132
|
+
description: "Help new customers get started",
|
|
133
|
+
goal: "Collect customer information efficiently",
|
|
134
|
+
ai: new GeminiProvider({
|
|
135
|
+
apiKey: process.env.GEMINI_API_KEY!,
|
|
136
|
+
model: "models/gemini-2.0-flash-exp",
|
|
137
|
+
}),
|
|
138
|
+
// NOTE: No persistence adapter - we handle it manually!
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Create onboarding route
|
|
142
|
+
const onboardingRoute = agent.createRoute<OnboardingData>({
|
|
143
|
+
title: "Customer Onboarding",
|
|
144
|
+
description: "Collect customer information",
|
|
145
|
+
conditions: [
|
|
146
|
+
"User is a new customer",
|
|
147
|
+
"User needs to set up their account",
|
|
148
|
+
],
|
|
149
|
+
gatherSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
fullName: { type: "string" },
|
|
153
|
+
email: { type: "string" },
|
|
154
|
+
companyName: { type: "string" },
|
|
155
|
+
phoneNumber: { type: "string" },
|
|
156
|
+
industry: { type: "string" },
|
|
157
|
+
},
|
|
158
|
+
required: ["fullName", "email", "companyName"],
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Define states with custom IDs
|
|
163
|
+
onboardingRoute.initialState
|
|
164
|
+
.transitionTo({
|
|
165
|
+
id: "ask_name",
|
|
166
|
+
chatState: "Ask for full name",
|
|
167
|
+
gather: ["fullName"],
|
|
168
|
+
skipIf: (data) => !!data.fullName,
|
|
169
|
+
})
|
|
170
|
+
.transitionTo({
|
|
171
|
+
id: "ask_email",
|
|
172
|
+
chatState: "Ask for email address",
|
|
173
|
+
gather: ["email"],
|
|
174
|
+
skipIf: (data) => !!data.email,
|
|
175
|
+
})
|
|
176
|
+
.transitionTo({
|
|
177
|
+
id: "ask_company",
|
|
178
|
+
chatState: "Ask for company name",
|
|
179
|
+
gather: ["companyName"],
|
|
180
|
+
skipIf: (data) => !!data.companyName,
|
|
181
|
+
})
|
|
182
|
+
.transitionTo({
|
|
183
|
+
id: "ask_phone",
|
|
184
|
+
chatState: "Ask for phone number (optional)",
|
|
185
|
+
gather: ["phoneNumber"],
|
|
186
|
+
})
|
|
187
|
+
.transitionTo({
|
|
188
|
+
id: "ask_industry",
|
|
189
|
+
chatState: "Ask for industry",
|
|
190
|
+
gather: ["industry"],
|
|
191
|
+
})
|
|
192
|
+
.transitionTo({
|
|
193
|
+
id: "confirm_details",
|
|
194
|
+
chatState: "Confirm all details",
|
|
195
|
+
requiredData: ["fullName", "email", "companyName"],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create or load session from your custom database
|
|
200
|
+
*/
|
|
201
|
+
let dbSession = await db.findSession("existing_session_id");
|
|
202
|
+
|
|
203
|
+
if (!dbSession) {
|
|
204
|
+
// Create new session in your database
|
|
205
|
+
dbSession = await db.createSession({
|
|
206
|
+
userId,
|
|
207
|
+
collectedData: {},
|
|
208
|
+
});
|
|
209
|
+
console.log("✨ Created new database session:", dbSession.id);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Convert database session to agent SessionState
|
|
214
|
+
*/
|
|
215
|
+
let agentSession: SessionState<OnboardingData>;
|
|
216
|
+
|
|
217
|
+
if (dbSession.currentRoute && dbSession.collectedData) {
|
|
218
|
+
// Restore existing session from database
|
|
219
|
+
console.log("📥 Restoring session from database...");
|
|
220
|
+
|
|
221
|
+
agentSession = {
|
|
222
|
+
currentRoute: {
|
|
223
|
+
id: dbSession.currentRoute,
|
|
224
|
+
title:
|
|
225
|
+
dbSession.collectedData?.currentRouteTitle || dbSession.currentRoute,
|
|
226
|
+
enteredAt: new Date(),
|
|
227
|
+
},
|
|
228
|
+
currentState: dbSession.currentState
|
|
229
|
+
? {
|
|
230
|
+
id: dbSession.currentState,
|
|
231
|
+
description: dbSession.collectedData?.currentStateDescription,
|
|
232
|
+
enteredAt: new Date(),
|
|
233
|
+
}
|
|
234
|
+
: undefined,
|
|
235
|
+
extracted:
|
|
236
|
+
(dbSession.collectedData?.extracted as Partial<OnboardingData>) || {},
|
|
237
|
+
routeHistory:
|
|
238
|
+
(dbSession.collectedData
|
|
239
|
+
?.routeHistory as SessionState<OnboardingData>["routeHistory"]) || [],
|
|
240
|
+
metadata: {
|
|
241
|
+
sessionId: dbSession.id,
|
|
242
|
+
userId,
|
|
243
|
+
createdAt: dbSession.createdAt,
|
|
244
|
+
lastUpdatedAt: dbSession.updatedAt,
|
|
245
|
+
...(dbSession.collectedData?.metadata as Record<string, unknown>),
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
console.log("✅ Session restored:", {
|
|
250
|
+
sessionId: agentSession.metadata?.sessionId,
|
|
251
|
+
currentRoute: agentSession.currentRoute?.title,
|
|
252
|
+
currentState: agentSession.currentState?.id,
|
|
253
|
+
extracted: agentSession.extracted,
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
// Create new session state
|
|
257
|
+
console.log("🆕 Creating new session state...");
|
|
258
|
+
|
|
259
|
+
agentSession = createSession<OnboardingData>(dbSession.id, {
|
|
260
|
+
sessionId: dbSession.id,
|
|
261
|
+
userId,
|
|
262
|
+
createdAt: dbSession.createdAt,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Simulate conversation
|
|
268
|
+
*/
|
|
269
|
+
const history: Event<MessageEventData>[] = [];
|
|
270
|
+
|
|
271
|
+
// Turn 1: User provides name and email
|
|
272
|
+
console.log("\n--- Turn 1 ---");
|
|
273
|
+
const userMessage1 = createMessageEvent(
|
|
274
|
+
EventSource.CUSTOMER,
|
|
275
|
+
"User",
|
|
276
|
+
"Hi! I'm John Smith and my email is john@acme.com"
|
|
277
|
+
);
|
|
278
|
+
history.push(userMessage1);
|
|
279
|
+
|
|
280
|
+
// Save user message to database
|
|
281
|
+
await db.createMessage({
|
|
282
|
+
sessionId: dbSession.id,
|
|
283
|
+
userId,
|
|
284
|
+
role: "user",
|
|
285
|
+
content: userMessage1.data.message,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const response1 = await agent.respond({
|
|
289
|
+
history,
|
|
290
|
+
session: agentSession,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
console.log("🤖 Agent:", response1.message);
|
|
294
|
+
console.log("📊 Extracted so far:", response1.session?.extracted);
|
|
295
|
+
|
|
296
|
+
// Save agent message to database
|
|
297
|
+
await db.createMessage({
|
|
298
|
+
sessionId: dbSession.id,
|
|
299
|
+
userId,
|
|
300
|
+
role: "agent",
|
|
301
|
+
content: response1.message,
|
|
302
|
+
route: response1.session?.currentRoute?.id,
|
|
303
|
+
state: response1.session?.currentState?.id,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Manually save session state back to database
|
|
307
|
+
await db.updateSession(dbSession.id, {
|
|
308
|
+
currentRoute: response1.session?.currentRoute?.id,
|
|
309
|
+
currentState: response1.session?.currentState?.id,
|
|
310
|
+
collectedData: {
|
|
311
|
+
extracted: response1.session?.extracted,
|
|
312
|
+
routeHistory: response1.session?.routeHistory,
|
|
313
|
+
currentRouteTitle: response1.session?.currentRoute?.title,
|
|
314
|
+
currentStateDescription: response1.session?.currentState?.description,
|
|
315
|
+
metadata: response1.session?.metadata,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
console.log("💾 Session saved to database");
|
|
320
|
+
|
|
321
|
+
// Update session for next turn
|
|
322
|
+
agentSession = response1.session!;
|
|
323
|
+
|
|
324
|
+
// Turn 2: User provides company
|
|
325
|
+
console.log("\n--- Turn 2 ---");
|
|
326
|
+
history.push(
|
|
327
|
+
createMessageEvent(EventSource.AI_AGENT, "Agent", response1.message)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const userMessage2 = createMessageEvent(
|
|
331
|
+
EventSource.CUSTOMER,
|
|
332
|
+
"User",
|
|
333
|
+
"I work for Acme Corporation"
|
|
334
|
+
);
|
|
335
|
+
history.push(userMessage2);
|
|
336
|
+
|
|
337
|
+
await db.createMessage({
|
|
338
|
+
sessionId: dbSession.id,
|
|
339
|
+
userId,
|
|
340
|
+
role: "user",
|
|
341
|
+
content: userMessage2.data.message,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const response2 = await agent.respond({
|
|
345
|
+
history,
|
|
346
|
+
session: agentSession,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
console.log("🤖 Agent:", response2.message);
|
|
350
|
+
console.log("📊 Extracted so far:", response2.session?.extracted);
|
|
351
|
+
|
|
352
|
+
await db.createMessage({
|
|
353
|
+
sessionId: dbSession.id,
|
|
354
|
+
userId,
|
|
355
|
+
role: "agent",
|
|
356
|
+
content: response2.message,
|
|
357
|
+
route: response2.session?.currentRoute?.id,
|
|
358
|
+
state: response2.session?.currentState?.id,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Save session state
|
|
362
|
+
await db.updateSession(dbSession.id, {
|
|
363
|
+
currentRoute: response2.session?.currentRoute?.id,
|
|
364
|
+
currentState: response2.session?.currentState?.id,
|
|
365
|
+
collectedData: {
|
|
366
|
+
extracted: response2.session?.extracted,
|
|
367
|
+
routeHistory: response2.session?.routeHistory,
|
|
368
|
+
currentRouteTitle: response2.session?.currentRoute?.title,
|
|
369
|
+
currentStateDescription: response2.session?.currentState?.description,
|
|
370
|
+
metadata: response2.session?.metadata,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
console.log("💾 Session saved to database");
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Demonstrate session recovery
|
|
378
|
+
*/
|
|
379
|
+
console.log("\n--- Session Recovery ---");
|
|
380
|
+
console.log("Simulating app restart...\n");
|
|
381
|
+
|
|
382
|
+
// Load session from database again
|
|
383
|
+
const reloadedDbSession = await db.findSession(dbSession.id);
|
|
384
|
+
if (!reloadedDbSession) throw new Error("Session not found");
|
|
385
|
+
|
|
386
|
+
// Reconstruct session state
|
|
387
|
+
const recoveredSession: SessionState<OnboardingData> = {
|
|
388
|
+
currentRoute: reloadedDbSession.currentRoute
|
|
389
|
+
? {
|
|
390
|
+
id: reloadedDbSession.currentRoute,
|
|
391
|
+
title:
|
|
392
|
+
reloadedDbSession.collectedData?.currentRouteTitle ||
|
|
393
|
+
reloadedDbSession.currentRoute,
|
|
394
|
+
enteredAt: new Date(),
|
|
395
|
+
}
|
|
396
|
+
: undefined,
|
|
397
|
+
currentState: reloadedDbSession.currentState
|
|
398
|
+
? {
|
|
399
|
+
id: reloadedDbSession.currentState,
|
|
400
|
+
description: reloadedDbSession.collectedData?.currentStateDescription,
|
|
401
|
+
enteredAt: new Date(),
|
|
402
|
+
}
|
|
403
|
+
: undefined,
|
|
404
|
+
extracted:
|
|
405
|
+
(reloadedDbSession.collectedData?.extracted as Partial<OnboardingData>) ||
|
|
406
|
+
{},
|
|
407
|
+
routeHistory:
|
|
408
|
+
(reloadedDbSession.collectedData
|
|
409
|
+
?.routeHistory as SessionState<OnboardingData>["routeHistory"]) || [],
|
|
410
|
+
metadata: {
|
|
411
|
+
sessionId: reloadedDbSession.id,
|
|
412
|
+
userId,
|
|
413
|
+
createdAt: reloadedDbSession.createdAt,
|
|
414
|
+
lastUpdatedAt: reloadedDbSession.updatedAt,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
console.log("✅ Session recovered from database:", {
|
|
419
|
+
sessionId: recoveredSession.metadata?.sessionId,
|
|
420
|
+
currentRoute: recoveredSession.currentRoute?.title,
|
|
421
|
+
currentState: recoveredSession.currentState?.id,
|
|
422
|
+
extracted: recoveredSession.extracted,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Load message history
|
|
426
|
+
const messages = await db.getSessionMessages(dbSession.id);
|
|
427
|
+
console.log(`📜 Loaded ${messages.length} messages from history`);
|
|
428
|
+
|
|
429
|
+
console.log("\n✅ Example complete!");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Advanced Example: With validation hooks
|
|
434
|
+
*/
|
|
435
|
+
async function advancedExample() {
|
|
436
|
+
const db = new CustomDatabase();
|
|
437
|
+
const userId = "user_456";
|
|
438
|
+
|
|
439
|
+
const agent = new Agent({
|
|
440
|
+
name: "Smart Onboarding",
|
|
441
|
+
ai: new GeminiProvider({
|
|
442
|
+
apiKey: process.env.GEMINI_API_KEY!,
|
|
443
|
+
model: "models/gemini-2.0-flash-exp",
|
|
444
|
+
}),
|
|
445
|
+
hooks: {
|
|
446
|
+
// Validate and enrich extracted data
|
|
447
|
+
onExtractedUpdate: async (extracted, previous) => {
|
|
448
|
+
console.log("🔄 Data extracted, validating...");
|
|
449
|
+
|
|
450
|
+
// Normalize email
|
|
451
|
+
if (extracted.email) {
|
|
452
|
+
extracted.email = extracted.email.toLowerCase().trim();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Normalize phone
|
|
456
|
+
if (extracted.phoneNumber) {
|
|
457
|
+
extracted.phoneNumber = extracted.phoneNumber.replace(/\D/g, "");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return extracted;
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const route = agent.createRoute<OnboardingData>({
|
|
466
|
+
title: "Onboarding",
|
|
467
|
+
gatherSchema: {
|
|
468
|
+
type: "object",
|
|
469
|
+
properties: {
|
|
470
|
+
fullName: { type: "string" },
|
|
471
|
+
email: { type: "string" },
|
|
472
|
+
companyName: { type: "string" },
|
|
473
|
+
phoneNumber: { type: "string" },
|
|
474
|
+
},
|
|
475
|
+
required: ["fullName", "email", "companyName"],
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
route.initialState.transitionTo({
|
|
480
|
+
id: "collect_all",
|
|
481
|
+
chatState: "Collect all information",
|
|
482
|
+
gather: ["fullName", "email", "companyName", "phoneNumber"],
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Create database session
|
|
486
|
+
const dbSession = await db.createSession({
|
|
487
|
+
userId,
|
|
488
|
+
collectedData: {},
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Create agent session
|
|
492
|
+
let agentSession = createSession<OnboardingData>(dbSession.id, {
|
|
493
|
+
userId,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Simulate conversation
|
|
497
|
+
const response = await agent.respond({
|
|
498
|
+
history: [
|
|
499
|
+
createMessageEvent(
|
|
500
|
+
EventSource.CUSTOMER,
|
|
501
|
+
"User",
|
|
502
|
+
"I'm Alice Johnson, alice@EXAMPLE.COM, working for TechCorp. Phone: (555) 123-4567"
|
|
503
|
+
),
|
|
504
|
+
],
|
|
505
|
+
session: agentSession,
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
console.log("🤖 Agent:", response.message);
|
|
509
|
+
console.log("📊 Normalized data:", response.session?.extracted);
|
|
510
|
+
// Shows: { email: "alice@example.com", phoneNumber: "5551234567", ... }
|
|
511
|
+
|
|
512
|
+
// Save to database
|
|
513
|
+
await db.updateSession(dbSession.id, {
|
|
514
|
+
currentRoute: response.session?.currentRoute?.id,
|
|
515
|
+
currentState: response.session?.currentState?.id,
|
|
516
|
+
collectedData: {
|
|
517
|
+
extracted: response.session?.extracted,
|
|
518
|
+
routeHistory: response.session?.routeHistory,
|
|
519
|
+
currentRouteTitle: response.session?.currentRoute?.title,
|
|
520
|
+
currentStateDescription: response.session?.currentState?.description,
|
|
521
|
+
metadata: response.session?.metadata,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
console.log("✅ Validated data saved to custom database!");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Run the example
|
|
529
|
+
if (require.main === module) {
|
|
530
|
+
example().catch(console.error);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export { example, advancedExample };
|
|
@@ -161,19 +161,25 @@ async function createHealthcareAgent() {
|
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
// State 1: Gather appointment reason
|
|
164
|
-
const gatherReason = schedulingRoute.initialState.transitionTo(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
const gatherReason = schedulingRoute.initialState.transitionTo(
|
|
165
|
+
{
|
|
166
|
+
chatState: "Ask what the patient needs an appointment for",
|
|
167
|
+
gather: ["appointmentReason"],
|
|
168
|
+
skipIf: (extracted) => !!extracted.appointmentReason,
|
|
169
|
+
},
|
|
170
|
+
"Patient hasn't specified reason for appointment yet"
|
|
171
|
+
);
|
|
169
172
|
|
|
170
173
|
// State 2: Check urgency and show available slots
|
|
171
|
-
const checkUrgency = gatherReason.transitionTo(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
const checkUrgency = gatherReason.transitionTo(
|
|
175
|
+
{
|
|
176
|
+
chatState: "Check if this is urgent and show available slots",
|
|
177
|
+
gather: ["urgency"],
|
|
178
|
+
skipIf: (extracted) => !!extracted.urgency,
|
|
179
|
+
requiredData: ["appointmentReason"],
|
|
180
|
+
},
|
|
181
|
+
"Reason provided, now assess urgency level"
|
|
182
|
+
);
|
|
177
183
|
|
|
178
184
|
const showSlots = checkUrgency.transitionTo({
|
|
179
185
|
toolState: getUpcomingSlots,
|
|
@@ -200,16 +206,22 @@ async function createHealthcareAgent() {
|
|
|
200
206
|
requiredData: ["appointmentReason", "preferredTime", "preferredDate"],
|
|
201
207
|
});
|
|
202
208
|
|
|
203
|
-
const schedule = confirmDetails.transitionTo(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
const schedule = confirmDetails.transitionTo(
|
|
210
|
+
{
|
|
211
|
+
toolState: scheduleAppointment,
|
|
212
|
+
requiredData: ["appointmentReason", "preferredTime", "preferredDate"],
|
|
213
|
+
},
|
|
214
|
+
"All details confirmed, book the appointment"
|
|
215
|
+
);
|
|
207
216
|
|
|
208
217
|
const confirmation = schedule.transitionTo({
|
|
209
218
|
chatState: "Confirm the appointment has been scheduled",
|
|
210
219
|
});
|
|
211
220
|
|
|
212
|
-
confirmation.transitionTo(
|
|
221
|
+
confirmation.transitionTo(
|
|
222
|
+
{ state: END_ROUTE },
|
|
223
|
+
"Appointment booked successfully"
|
|
224
|
+
);
|
|
213
225
|
|
|
214
226
|
// Alternative path: no times work - show later slots
|
|
215
227
|
const laterSlots = presentTimes.transitionTo({
|
|
@@ -269,18 +269,24 @@ async function createPersistentOnboardingAgent(sessionId: string) {
|
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
// State 1: Gather business name and description
|
|
272
|
-
const gatherBusinessInfo = onboardingRoute.initialState.transitionTo(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
const gatherBusinessInfo = onboardingRoute.initialState.transitionTo(
|
|
273
|
+
{
|
|
274
|
+
chatState: "Ask for business name and a brief description",
|
|
275
|
+
gather: ["businessName", "businessDescription"],
|
|
276
|
+
skipIf: (extracted) =>
|
|
277
|
+
!!extracted.businessName && !!extracted.businessDescription,
|
|
278
|
+
},
|
|
279
|
+
"Need to collect basic business information first"
|
|
280
|
+
);
|
|
278
281
|
|
|
279
282
|
// State 2: Save business info (tool execution)
|
|
280
|
-
const saveBusiness = gatherBusinessInfo.transitionTo(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
const saveBusiness = gatherBusinessInfo.transitionTo(
|
|
284
|
+
{
|
|
285
|
+
toolState: saveBusinessInfo,
|
|
286
|
+
requiredData: ["businessName", "businessDescription"],
|
|
287
|
+
},
|
|
288
|
+
"Business name and description provided, save to database"
|
|
289
|
+
);
|
|
284
290
|
|
|
285
291
|
// State 3: Gather industry
|
|
286
292
|
const gatherIndustry = saveBusiness.transitionTo({
|
|
@@ -128,14 +128,16 @@ async function example() {
|
|
|
128
128
|
},
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
// State flow with smart data gathering
|
|
131
|
+
// State flow with smart data gathering and custom IDs
|
|
132
132
|
const askDestination = flightRoute.initialState.transitionTo({
|
|
133
|
+
id: "ask_destination", // Custom state ID for easier tracking
|
|
133
134
|
chatState: "Ask where they want to fly",
|
|
134
135
|
gather: ["destination"],
|
|
135
136
|
skipIf: (extracted) => !!extracted.destination,
|
|
136
137
|
});
|
|
137
138
|
|
|
138
139
|
const askDates = askDestination.transitionTo({
|
|
140
|
+
id: "ask_dates", // Custom state ID
|
|
139
141
|
chatState: "Ask about travel dates",
|
|
140
142
|
gather: ["departureDate", "returnDate"],
|
|
141
143
|
skipIf: (extracted) => !!extracted.departureDate,
|
|
@@ -143,6 +145,7 @@ async function example() {
|
|
|
143
145
|
});
|
|
144
146
|
|
|
145
147
|
const askPassengers = askDates.transitionTo({
|
|
148
|
+
id: "ask_passengers", // Custom state ID
|
|
146
149
|
chatState: "Ask how many passengers",
|
|
147
150
|
gather: ["passengers"],
|
|
148
151
|
skipIf: (extracted) => !!extracted.passengers,
|
|
@@ -150,6 +153,7 @@ async function example() {
|
|
|
150
153
|
});
|
|
151
154
|
|
|
152
155
|
const askCabinClass = askPassengers.transitionTo({
|
|
156
|
+
id: "ask_cabin_class", // Custom state ID
|
|
153
157
|
chatState: "Ask about cabin class preference",
|
|
154
158
|
gather: ["cabinClass"],
|
|
155
159
|
skipIf: (extracted) => !!extracted.cabinClass,
|
|
@@ -157,6 +161,7 @@ async function example() {
|
|
|
157
161
|
});
|
|
158
162
|
|
|
159
163
|
const confirmBooking = askCabinClass.transitionTo({
|
|
164
|
+
id: "confirm_booking", // Custom state ID
|
|
160
165
|
chatState: "Present options and confirm booking details",
|
|
161
166
|
requiredData: ["destination", "departureDate", "passengers", "cabinClass"],
|
|
162
167
|
});
|
|
@@ -186,6 +191,10 @@ async function example() {
|
|
|
186
191
|
const dbSessionId = sessionResult.sessionData.id;
|
|
187
192
|
|
|
188
193
|
console.log("✨ Created new session:", dbSessionId);
|
|
194
|
+
console.log("📊 Session metadata:", {
|
|
195
|
+
sessionId: session.metadata?.sessionId, // Same as dbSessionId
|
|
196
|
+
createdAt: session.metadata?.createdAt,
|
|
197
|
+
});
|
|
189
198
|
console.log("📊 Initial session state:", {
|
|
190
199
|
currentRoute: session.currentRoute,
|
|
191
200
|
extracted: session.extracted,
|
|
@@ -216,8 +225,10 @@ async function example() {
|
|
|
216
225
|
|
|
217
226
|
console.log("🤖 Agent:", response1.message);
|
|
218
227
|
console.log("📊 Session state after turn 1:", {
|
|
228
|
+
sessionId: response1.session?.metadata?.sessionId,
|
|
219
229
|
currentRoute: response1.session?.currentRoute?.title,
|
|
220
|
-
|
|
230
|
+
currentStateId: response1.session?.currentState?.id, // Custom ID like "ask_destination"
|
|
231
|
+
currentStateDescription: response1.session?.currentState?.description,
|
|
221
232
|
extracted: response1.session?.extracted,
|
|
222
233
|
});
|
|
223
234
|
|