@falai/agent 0.6.7 ā 0.6.8
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 +162 -30
- package/dist/cjs/core/Agent.d.ts +18 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +218 -15
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/Route.d.ts +12 -1
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +38 -1
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +3 -1
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/State.js +1 -1
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/route.d.ts +51 -0
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +17 -1
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/session.js.map +1 -1
- package/dist/core/Agent.d.ts +18 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +219 -16
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/Route.d.ts +12 -1
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +38 -1
- package/dist/core/Route.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +3 -1
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/State.js +1 -1
- package/dist/core/State.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/route.d.ts +51 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/session.d.ts +17 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/docs/EXAMPLES.md +51 -2
- package/docs/ROUTES.md +345 -1
- package/docs/STATES.md +97 -7
- package/examples/business-onboarding.ts +10 -12
- package/examples/company-qna-agent.ts +4 -5
- package/examples/healthcare-agent.ts +63 -0
- package/examples/persistent-onboarding.ts +6 -8
- package/examples/route-transitions.ts +242 -0
- package/examples/travel-agent.ts +60 -0
- package/package.json +1 -1
- package/src/core/Agent.ts +338 -16
- package/src/core/Route.ts +53 -1
- package/src/core/RoutingEngine.ts +3 -1
- package/src/core/State.ts +1 -1
- package/src/index.ts +3 -1
- package/src/types/route.ts +57 -0
- package/src/types/session.ts +20 -2
package/docs/ROUTES.md
CHANGED
|
@@ -9,6 +9,7 @@ A complete guide to creating and managing conversational routes in `@falai/agent
|
|
|
9
9
|
- [What is a Route?](#what-is-a-route)
|
|
10
10
|
- [Creating Routes](#creating-routes)
|
|
11
11
|
- [Initial State Configuration](#initial-state-configuration)
|
|
12
|
+
- [End State Configuration](#end-state-configuration)
|
|
12
13
|
- [Data Extraction](#data-extraction)
|
|
13
14
|
- [Sequential Steps](#sequential-steps)
|
|
14
15
|
- [Route Properties](#route-properties)
|
|
@@ -173,13 +174,140 @@ initialState: {
|
|
|
173
174
|
// Skip this state if condition is met
|
|
174
175
|
skipIf?: (extracted: Partial<TExtracted>) => boolean;
|
|
175
176
|
|
|
176
|
-
// Prerequisites that must be
|
|
177
|
+
// Prerequisites that must be metw
|
|
177
178
|
requiredData?: string[];
|
|
178
179
|
}
|
|
179
180
|
```
|
|
180
181
|
|
|
181
182
|
---
|
|
182
183
|
|
|
184
|
+
## End State Configuration
|
|
185
|
+
|
|
186
|
+
Every route ends when it reaches `END_STATE`. You can configure what happens at route completion:
|
|
187
|
+
|
|
188
|
+
### Configure End State at Route Creation
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { END_STATE } from "@falai/agent";
|
|
192
|
+
|
|
193
|
+
const bookingRoute = agent.createRoute<FlightData>({
|
|
194
|
+
title: "Book Flight",
|
|
195
|
+
|
|
196
|
+
// Configure what happens when route completes
|
|
197
|
+
endState: {
|
|
198
|
+
// Custom completion message
|
|
199
|
+
chatState: "Confirm the booking details and thank the user warmly!",
|
|
200
|
+
|
|
201
|
+
// Optional: Execute final actions (like sending confirmation emails)
|
|
202
|
+
toolState: sendConfirmationEmail,
|
|
203
|
+
|
|
204
|
+
// Optional: Gather final data before completing
|
|
205
|
+
gather: ["finalConfirmation"],
|
|
206
|
+
|
|
207
|
+
// Optional: Require certain data to be present
|
|
208
|
+
requiredData: ["destination", "departureDate"],
|
|
209
|
+
|
|
210
|
+
// Optional: Custom state ID
|
|
211
|
+
id: "booking_complete",
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
extractionSchema: {
|
|
215
|
+
// ... schema definition
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Then just transition to END_STATE
|
|
220
|
+
lastState.transitionTo({
|
|
221
|
+
state: END_STATE,
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Per-Transition Override
|
|
226
|
+
|
|
227
|
+
You can also override the endState configuration for specific transitions:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Use route-level endState
|
|
231
|
+
lastState.transitionTo({
|
|
232
|
+
state: END_STATE,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// OR override with custom chatState for this specific transition
|
|
236
|
+
lastState.transitionTo({
|
|
237
|
+
chatState: "Special completion message for this path!",
|
|
238
|
+
state: END_STATE,
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### End State Options
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
endState: {
|
|
246
|
+
// Completion message instruction (RECOMMENDED)
|
|
247
|
+
chatState?: string;
|
|
248
|
+
|
|
249
|
+
// Execute final tools/actions before completion
|
|
250
|
+
toolState?: ToolRef;
|
|
251
|
+
|
|
252
|
+
// Gather final data at completion
|
|
253
|
+
gather?: string[];
|
|
254
|
+
|
|
255
|
+
// Require specific data to be present
|
|
256
|
+
requiredData?: string[];
|
|
257
|
+
|
|
258
|
+
// Custom state ID for debugging
|
|
259
|
+
id?: string;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Default Behavior
|
|
264
|
+
|
|
265
|
+
If you don't configure `endState`, a smart default is used:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Default endState behavior:
|
|
269
|
+
{
|
|
270
|
+
chatState: "Summarize what was accomplished and confirm completion based on the conversation history and collected data"
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### End State with Tools
|
|
275
|
+
|
|
276
|
+
Execute final actions when route completes:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { defineTool, END_STATE } from "@falai/agent";
|
|
280
|
+
|
|
281
|
+
const sendConfirmation = defineTool(
|
|
282
|
+
"send_confirmation",
|
|
283
|
+
async ({ extracted }) => {
|
|
284
|
+
await emailService.send({
|
|
285
|
+
to: extracted.email,
|
|
286
|
+
subject: "Booking Confirmed",
|
|
287
|
+
body: `Your flight to ${extracted.destination} is confirmed!`,
|
|
288
|
+
});
|
|
289
|
+
return { data: "Confirmation sent" };
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const bookingRoute = agent.createRoute({
|
|
294
|
+
title: "Book Flight",
|
|
295
|
+
|
|
296
|
+
endState: {
|
|
297
|
+
toolState: sendConfirmation, // Executes when route completes
|
|
298
|
+
chatState: "Your booking is complete! Confirmation email sent.",
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Key Points:**
|
|
304
|
+
- ā
`endState` is configured once at the route level (DRY principle)
|
|
305
|
+
- ā
Can be overridden per-transition if needed
|
|
306
|
+
- ā
Supports full state capabilities: `chatState`, `toolState`, `gather`, `requiredData`
|
|
307
|
+
- ā
Falls back to smart default if not configured
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
183
311
|
## Data Extraction
|
|
184
312
|
|
|
185
313
|
### Defining Extraction Schema
|
|
@@ -637,6 +765,222 @@ return response.message;
|
|
|
637
765
|
- ā
**Use `isRouteComplete`** for simplicity and clarity
|
|
638
766
|
- ā
**Use `END_STATE_ID`** if you want consistency with how you build routes (`END_STATE` symbol)
|
|
639
767
|
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
## Route Transitions with `onComplete`
|
|
771
|
+
|
|
772
|
+
**NEW:** You can now automatically transition to another route when a route completes using the `onComplete` option. This is perfect for chaining workflows like:
|
|
773
|
+
|
|
774
|
+
- š Post-booking feedback collection
|
|
775
|
+
- š Upsell offers after purchase
|
|
776
|
+
- š Satisfaction surveys after support
|
|
777
|
+
- ā©ļø Error recovery flows
|
|
778
|
+
|
|
779
|
+
### Simple String Transition
|
|
780
|
+
|
|
781
|
+
The simplest form - just specify the target route ID or title:
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
const bookingRoute = agent.createRoute<BookingData>({
|
|
785
|
+
title: "Book Hotel",
|
|
786
|
+
conditions: ["User wants to book a hotel"],
|
|
787
|
+
extractionSchema: BOOKING_SCHEMA,
|
|
788
|
+
// Automatically transition to feedback when booking completes
|
|
789
|
+
onComplete: "Collect Feedback",
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const feedbackRoute = agent.createRoute<FeedbackData>({
|
|
793
|
+
title: "Collect Feedback",
|
|
794
|
+
conditions: ["Collect user feedback"],
|
|
795
|
+
extractionSchema: FEEDBACK_SCHEMA,
|
|
796
|
+
});
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### With AI-Evaluated Condition
|
|
800
|
+
|
|
801
|
+
Add an optional condition that the AI evaluates to determine if transition should happen:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
const bookingRoute = agent.createRoute<BookingData>({
|
|
805
|
+
title: "Book Hotel",
|
|
806
|
+
extractionSchema: BOOKING_SCHEMA,
|
|
807
|
+
onComplete: {
|
|
808
|
+
transitionTo: "Collect Feedback",
|
|
809
|
+
condition: "if booking was successful", // AI evaluates this
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### Dynamic Function-Based Transition
|
|
815
|
+
|
|
816
|
+
Use a function for complex logic based on extracted data or context:
|
|
817
|
+
|
|
818
|
+
```typescript
|
|
819
|
+
const bookingRoute = agent.createRoute<BookingData>({
|
|
820
|
+
title: "Book Hotel",
|
|
821
|
+
extractionSchema: BOOKING_SCHEMA,
|
|
822
|
+
// Function receives session and context
|
|
823
|
+
onComplete: (session, context) => {
|
|
824
|
+
// Conditional logic based on extracted data
|
|
825
|
+
if (session.extracted?.guests && session.extracted.guests > 5) {
|
|
826
|
+
return "VIP Feedback"; // Large groups get VIP treatment
|
|
827
|
+
}
|
|
828
|
+
if (session.extracted?.bookingFailed) {
|
|
829
|
+
return "Error Recovery"; // Handle failures gracefully
|
|
830
|
+
}
|
|
831
|
+
return "Collect Feedback"; // Standard feedback flow
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
Function can also return a config object:
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
onComplete: (session) => ({
|
|
840
|
+
transitionTo: "Collect Feedback",
|
|
841
|
+
condition: session.extracted?.vip ? "if user is satisfied" : undefined,
|
|
842
|
+
})
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### How It Works
|
|
846
|
+
|
|
847
|
+
1. **Route completes** ā reaches `END_STATE`
|
|
848
|
+
2. **Agent evaluates** `onComplete` handler
|
|
849
|
+
3. **Sets pending transition** in session state
|
|
850
|
+
4. **Next `respond()` call** ā automatically transitions to target route
|
|
851
|
+
5. **User sees seamless flow** ā no interruption in conversation
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
// User books hotel
|
|
855
|
+
const response1 = await agent.respond({ history, session });
|
|
856
|
+
console.log(response1.isRouteComplete); // true
|
|
857
|
+
console.log(response1.session?.pendingTransition); // { targetRouteId: "...", reason: "route_complete" }
|
|
858
|
+
|
|
859
|
+
// Next message automatically transitions to feedback route
|
|
860
|
+
history.push(createMessageEvent(EventSource.CUSTOMER, "User", "Yes!"));
|
|
861
|
+
const response2 = await agent.respond({ history, session: response1.session });
|
|
862
|
+
console.log(response2.session?.currentRoute?.title); // "Collect Feedback"
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Manual Transition Control
|
|
866
|
+
|
|
867
|
+
For more control, use `agent.transitionToRoute()` to manually set the transition:
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
const response = await agent.respond({ history, session });
|
|
871
|
+
|
|
872
|
+
if (response.isRouteComplete && shouldCollectFeedback) {
|
|
873
|
+
// Manually trigger transition instead of onComplete
|
|
874
|
+
const updatedSession = agent.transitionToRoute(
|
|
875
|
+
"Collect Feedback",
|
|
876
|
+
response.session
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
// Next respond() will transition automatically
|
|
880
|
+
const nextResponse = await agent.respond({
|
|
881
|
+
history,
|
|
882
|
+
session: updatedSession,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### Complete Example
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
interface BookingData {
|
|
891
|
+
hotelName: string;
|
|
892
|
+
date: string;
|
|
893
|
+
guests: number;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
interface FeedbackData {
|
|
897
|
+
rating: number;
|
|
898
|
+
comments?: string;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Booking route with automatic transition to feedback
|
|
902
|
+
const bookingRoute = agent.createRoute<BookingData>({
|
|
903
|
+
title: "Book Hotel",
|
|
904
|
+
conditions: ["User wants to book a hotel"],
|
|
905
|
+
extractionSchema: {
|
|
906
|
+
type: "object",
|
|
907
|
+
properties: {
|
|
908
|
+
hotelName: { type: "string" },
|
|
909
|
+
date: { type: "string" },
|
|
910
|
+
guests: { type: "number" },
|
|
911
|
+
},
|
|
912
|
+
required: ["hotelName", "date", "guests"],
|
|
913
|
+
},
|
|
914
|
+
onComplete: "Collect Feedback",
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
const askHotel = bookingRoute.initialState.transitionTo({
|
|
918
|
+
chatState: "Ask which hotel",
|
|
919
|
+
gather: ["hotelName"],
|
|
920
|
+
skipIf: (e) => !!e.hotelName,
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
const askDate = askHotel.transitionTo({
|
|
924
|
+
chatState: "Ask for date",
|
|
925
|
+
gather: ["date"],
|
|
926
|
+
skipIf: (e) => !!e.date,
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const askGuests = askDate.transitionTo({
|
|
930
|
+
chatState: "Ask for guests",
|
|
931
|
+
gather: ["guests"],
|
|
932
|
+
skipIf: (e) => !!e.guests,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
askGuests.transitionTo({
|
|
936
|
+
chatState: "Confirm booking",
|
|
937
|
+
state: END_STATE,
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// Feedback route
|
|
941
|
+
const feedbackRoute = agent.createRoute<FeedbackData>({
|
|
942
|
+
title: "Collect Feedback",
|
|
943
|
+
conditions: ["Collect user feedback"],
|
|
944
|
+
extractionSchema: {
|
|
945
|
+
type: "object",
|
|
946
|
+
properties: {
|
|
947
|
+
rating: { type: "number" },
|
|
948
|
+
comments: { type: "string" },
|
|
949
|
+
},
|
|
950
|
+
required: ["rating"],
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
const askRating = feedbackRoute.initialState.transitionTo({
|
|
955
|
+
chatState: "Ask for rating 1-5",
|
|
956
|
+
gather: ["rating"],
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
askRating.transitionTo({
|
|
960
|
+
chatState: "Thank user",
|
|
961
|
+
state: END_STATE,
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// Usage - seamless transition from booking to feedback
|
|
965
|
+
let session;
|
|
966
|
+
const response1 = await agent.respond({ history, session });
|
|
967
|
+
// Booking complete, pending transition set
|
|
968
|
+
|
|
969
|
+
history.push(createMessageEvent(EventSource.CUSTOMER, "User", "Yes!"));
|
|
970
|
+
const response2 = await agent.respond({ history, session: response1.session });
|
|
971
|
+
// Now in feedback route automatically
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
### Benefits
|
|
975
|
+
|
|
976
|
+
ā
**Seamless user experience** - No awkward pauses between flows
|
|
977
|
+
ā
**Predictable behavior** - Transitions defined at design time
|
|
978
|
+
ā
**Flexible** - Simple strings, conditions, or complex functions
|
|
979
|
+
ā
**Type-safe** - Full TypeScript inference for extracted data
|
|
980
|
+
ā
**Non-breaking** - Existing routes without `onComplete` work as before
|
|
981
|
+
|
|
982
|
+
See [examples/route-transitions.ts](../examples/route-transitions.ts) for a complete working example.
|
|
983
|
+
|
|
640
984
|
### Immediate Completion
|
|
641
985
|
|
|
642
986
|
Routes can complete **immediately** if all states are skipped due to `skipIf` conditions. This is useful when:
|
package/docs/STATES.md
CHANGED
|
@@ -503,21 +503,111 @@ route.initialState.configure({
|
|
|
503
503
|
- Set expectations
|
|
504
504
|
- Pre-populate data
|
|
505
505
|
|
|
506
|
-
### 4. Terminal State
|
|
506
|
+
### 4. End State (Terminal State)
|
|
507
507
|
|
|
508
|
-
|
|
508
|
+
Every route ends when it reaches `END_STATE`. You can configure what happens at completion:
|
|
509
|
+
|
|
510
|
+
#### Option A: Route-Level Configuration (Recommended)
|
|
509
511
|
|
|
510
512
|
```typescript
|
|
511
|
-
|
|
513
|
+
import { END_STATE } from "@falai/agent";
|
|
514
|
+
|
|
515
|
+
const bookingRoute = agent.createRoute({
|
|
516
|
+
title: "Book Flight",
|
|
517
|
+
|
|
518
|
+
// Configure end state behavior
|
|
519
|
+
endState: {
|
|
520
|
+
chatState: "Confirm booking and thank the user!",
|
|
521
|
+
toolState: sendConfirmationEmail, // Execute final actions
|
|
522
|
+
gather: ["finalConfirmation"], // Gather last data
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Later, just transition to END_STATE
|
|
527
|
+
finalStep.transitionTo({
|
|
512
528
|
state: END_STATE,
|
|
513
529
|
});
|
|
514
530
|
```
|
|
515
531
|
|
|
516
|
-
|
|
532
|
+
#### Option B: Per-Transition Override
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// Override endState for this specific path
|
|
536
|
+
finalStep.transitionTo({
|
|
537
|
+
chatState: "Special completion message for VIP users!",
|
|
538
|
+
state: END_STATE,
|
|
539
|
+
});
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### Option C: Default Behavior
|
|
543
|
+
|
|
544
|
+
If you don't configure `endState`, a smart default completion message is generated:
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
finalStep.transitionTo({
|
|
548
|
+
state: END_STATE,
|
|
549
|
+
});
|
|
550
|
+
// Uses default: "Summarize what was accomplished and confirm completion..."
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**End State Capabilities:**
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
endState: {
|
|
557
|
+
// Completion message instruction
|
|
558
|
+
chatState?: string;
|
|
559
|
+
|
|
560
|
+
// Execute final actions (emails, database updates, etc.)
|
|
561
|
+
toolState?: ToolRef;
|
|
562
|
+
|
|
563
|
+
// Gather final data before completion
|
|
564
|
+
gather?: string[];
|
|
565
|
+
|
|
566
|
+
// Require certain data to be present
|
|
567
|
+
requiredData?: string[];
|
|
568
|
+
|
|
569
|
+
// Custom state ID for debugging
|
|
570
|
+
id?: string;
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**When to use END_STATE:**
|
|
575
|
+
|
|
576
|
+
- ā
Route completion - all required data collected
|
|
577
|
+
- ā
Success outcomes - action completed successfully
|
|
578
|
+
- ā
Failure outcomes - error handling completed
|
|
579
|
+
- ā
Before transition - handoff to another route via `onComplete`
|
|
580
|
+
|
|
581
|
+
**End State with Tools:**
|
|
582
|
+
|
|
583
|
+
Execute final actions when route completes:
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
import { defineTool, END_STATE } from "@falai/agent";
|
|
587
|
+
|
|
588
|
+
const notifyTeam = defineTool("notify_team", async ({ extracted }) => {
|
|
589
|
+
await slack.send({
|
|
590
|
+
channel: "#bookings",
|
|
591
|
+
message: `New booking: ${extracted.hotelName} for ${extracted.guests} guests`,
|
|
592
|
+
});
|
|
593
|
+
return { data: "Team notified" };
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
const bookingRoute = agent.createRoute({
|
|
597
|
+
endState: {
|
|
598
|
+
toolState: notifyTeam, // Runs when route completes
|
|
599
|
+
chatState: "Booking complete! Our team has been notified.",
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Key Points:**
|
|
517
605
|
|
|
518
|
-
-
|
|
519
|
-
-
|
|
520
|
-
-
|
|
606
|
+
- ā
Configure once at route level (DRY principle)
|
|
607
|
+
- ā
Can be overridden per-transition if needed
|
|
608
|
+
- ā
Supports full state capabilities: `chatState`, `toolState`, `gather`, `requiredData`
|
|
609
|
+
- ā
Automatically generates message if not configured
|
|
610
|
+
- ā
Executes before `onComplete` route transitions
|
|
521
611
|
|
|
522
612
|
---
|
|
523
613
|
|
|
@@ -417,6 +417,9 @@ async function createBusinessOnboardingAgent(
|
|
|
417
417
|
title: "Business Onboarding",
|
|
418
418
|
description: "Complete onboarding process to configure personalized routes",
|
|
419
419
|
conditions: ["User is starting the onboarding process"],
|
|
420
|
+
endState: {
|
|
421
|
+
chatState: "š Perfect! Setup complete! Your WhatsApp assistant is ready and will use all this information to automatically serve your customers. If you have any questions or need adjustments, just let me know!",
|
|
422
|
+
},
|
|
420
423
|
});
|
|
421
424
|
|
|
422
425
|
// ==================== Build the Flow ====================
|
|
@@ -556,15 +559,12 @@ async function createBusinessOnboardingAgent(
|
|
|
556
559
|
// Loop back to summary after adding custom route
|
|
557
560
|
saveCustomRoute.transitionTo({ state: summary });
|
|
558
561
|
|
|
559
|
-
// Step 9b: Final confirmation
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
"š Perfect! Setup complete! Your WhatsApp assistant is ready and will use all this information to automatically serve your customers. If you have any questions or need adjustments, just let me know!",
|
|
562
|
+
// Step 9b: Final confirmation - transition to END_STATE (uses route-level endState)
|
|
563
|
+
summary.transitionTo({
|
|
564
|
+
state: END_STATE,
|
|
563
565
|
condition: "User confirmed everything is okay",
|
|
564
566
|
});
|
|
565
567
|
|
|
566
|
-
completion.transitionTo({ state: END_STATE });
|
|
567
|
-
|
|
568
568
|
// ==================== Alternative: Sequential Steps ====================
|
|
569
569
|
// For simpler linear flows, you can use the new sequential steps approach:
|
|
570
570
|
|
|
@@ -598,6 +598,9 @@ async function createBusinessOnboardingAgent(
|
|
|
598
598
|
title: "Manual Feedback Route",
|
|
599
599
|
description: "Same flow using traditional chaining",
|
|
600
600
|
conditions: ["User wants manual feedback flow"],
|
|
601
|
+
endState: {
|
|
602
|
+
chatState: "Thank you for your feedback! It helps us improve. š",
|
|
603
|
+
},
|
|
601
604
|
});
|
|
602
605
|
|
|
603
606
|
manualFeedbackRoute.initialState
|
|
@@ -614,12 +617,7 @@ async function createBusinessOnboardingAgent(
|
|
|
614
617
|
chatState: "Is there anything we could improve?",
|
|
615
618
|
condition: "User wants to provide feedback",
|
|
616
619
|
})
|
|
617
|
-
.transitionTo({
|
|
618
|
-
id: "thank_you",
|
|
619
|
-
chatState: "Thank you for your feedback! It helps us improve. š",
|
|
620
|
-
condition: "User provided feedback",
|
|
621
|
-
})
|
|
622
|
-
.transitionTo({ state: END_STATE });
|
|
620
|
+
.transitionTo({ state: END_STATE }); // Uses route-level endState
|
|
623
621
|
|
|
624
622
|
// ==================== Global Guidelines ====================
|
|
625
623
|
|
|
@@ -358,6 +358,9 @@ const feedbackRoute = agent.createRoute<FeedbackData>({
|
|
|
358
358
|
},
|
|
359
359
|
required: ["rating", "comments"],
|
|
360
360
|
},
|
|
361
|
+
endState: {
|
|
362
|
+
chatState: "Thank the user warmly for their valuable feedback and let them know we appreciate their time",
|
|
363
|
+
},
|
|
361
364
|
});
|
|
362
365
|
|
|
363
366
|
feedbackRoute.initialState
|
|
@@ -381,11 +384,7 @@ feedbackRoute.initialState
|
|
|
381
384
|
gather: ["contactPermission"],
|
|
382
385
|
requiredData: ["comments"],
|
|
383
386
|
})
|
|
384
|
-
.transitionTo({
|
|
385
|
-
id: "thank_you",
|
|
386
|
-
chatState: "Thank you for your valuable feedback!",
|
|
387
|
-
})
|
|
388
|
-
.transitionTo({ state: END_STATE });
|
|
387
|
+
.transitionTo({ state: END_STATE }); // Uses route-level endState configuration
|
|
389
388
|
|
|
390
389
|
// ==============================================================================
|
|
391
390
|
// USAGE EXAMPLES: Three-Phase Pipeline Demonstration
|
|
@@ -34,6 +34,12 @@ interface LabResultsData {
|
|
|
34
34
|
resultsNeeded?: boolean;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
interface SatisfactionData {
|
|
38
|
+
rating?: number;
|
|
39
|
+
easeOfScheduling?: number;
|
|
40
|
+
comments?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
// Tools
|
|
38
44
|
const getInsuranceProviders = defineTool<HealthcareContext, [], string[]>(
|
|
39
45
|
"get_insurance_providers",
|
|
@@ -126,6 +132,7 @@ async function createHealthcareAgent() {
|
|
|
126
132
|
});
|
|
127
133
|
|
|
128
134
|
// Create scheduling route with data extraction schema
|
|
135
|
+
// NEW: Added onComplete to automatically transition to satisfaction survey after booking
|
|
129
136
|
const schedulingRoute = agent.createRoute<AppointmentData>({
|
|
130
137
|
title: "Schedule an Appointment",
|
|
131
138
|
description: "Helps the patient find a time for their appointment.",
|
|
@@ -158,6 +165,8 @@ async function createHealthcareAgent() {
|
|
|
158
165
|
},
|
|
159
166
|
required: ["appointmentReason"],
|
|
160
167
|
},
|
|
168
|
+
// NEW: Automatically collect feedback after successful scheduling
|
|
169
|
+
onComplete: "Satisfaction Survey",
|
|
161
170
|
});
|
|
162
171
|
|
|
163
172
|
// State 1: Gather appointment reason
|
|
@@ -300,6 +309,50 @@ async function createHealthcareAgent() {
|
|
|
300
309
|
"Assertively tell them that you cannot help and they should call the office",
|
|
301
310
|
});
|
|
302
311
|
|
|
312
|
+
// NEW: Satisfaction Survey route - collects feedback after appointment scheduling
|
|
313
|
+
const satisfactionRoute = agent.createRoute<SatisfactionData>({
|
|
314
|
+
title: "Satisfaction Survey",
|
|
315
|
+
description: "Quick satisfaction survey after scheduling",
|
|
316
|
+
conditions: ["Collect patient satisfaction feedback"],
|
|
317
|
+
extractionSchema: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
rating: {
|
|
321
|
+
type: "number",
|
|
322
|
+
description: "Overall satisfaction rating 1-5",
|
|
323
|
+
},
|
|
324
|
+
easeOfScheduling: {
|
|
325
|
+
type: "number",
|
|
326
|
+
description: "Ease of scheduling process 1-5",
|
|
327
|
+
},
|
|
328
|
+
comments: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Optional feedback comments",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ["rating"],
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const askRating = satisfactionRoute.initialState.transitionTo({
|
|
338
|
+
chatState:
|
|
339
|
+
"Ask for overall satisfaction rating from 1 to 5 with the scheduling experience",
|
|
340
|
+
gather: ["rating"],
|
|
341
|
+
skipIf: (extracted) => !!extracted.rating,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const askComments = askRating.transitionTo({
|
|
345
|
+
chatState: "Ask if they have any additional comments or feedback (optional)",
|
|
346
|
+
gather: ["comments"],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const thankYou = askComments.transitionTo({
|
|
350
|
+
chatState:
|
|
351
|
+
"Thank them for their feedback and confirm their appointment details one more time",
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
thankYou.transitionTo({ state: END_STATE });
|
|
355
|
+
|
|
303
356
|
// Global guidelines
|
|
304
357
|
agent.createGuideline({
|
|
305
358
|
condition: "The patient asks about insurance",
|
|
@@ -384,6 +437,16 @@ async function main() {
|
|
|
384
437
|
// Update session again
|
|
385
438
|
session = response2.session!;
|
|
386
439
|
|
|
440
|
+
// NEW: Check if route is complete - will auto-transition to satisfaction survey
|
|
441
|
+
if (response2.isRouteComplete) {
|
|
442
|
+
console.log("\nā Appointment scheduling complete!");
|
|
443
|
+
console.log("Pending transition:", response2.session?.pendingTransition);
|
|
444
|
+
console.log(
|
|
445
|
+
"Next respond() will auto-transition to:",
|
|
446
|
+
response2.session?.pendingTransition?.targetRouteId
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
387
450
|
// Turn 3: Patient provides final details
|
|
388
451
|
const history3 = [
|
|
389
452
|
...history2,
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
defineTool,
|
|
9
9
|
GeminiProvider,
|
|
10
10
|
END_STATE,
|
|
11
|
-
END_STATE_ID,
|
|
12
11
|
EventSource,
|
|
13
12
|
createMessageEvent,
|
|
14
13
|
createSession,
|
|
@@ -102,7 +101,7 @@ async function createPersistentOnboardingAgent(sessionId: string) {
|
|
|
102
101
|
// Define lifecycle hooks for automatic persistence
|
|
103
102
|
const hooks = {
|
|
104
103
|
// Called after data extraction - validate and enrich extracted data
|
|
105
|
-
onExtractedUpdate: async (extracted
|
|
104
|
+
onExtractedUpdate: async (extracted: Partial<OnboardingData>) => {
|
|
106
105
|
console.log("š Processing extracted data...");
|
|
107
106
|
|
|
108
107
|
// Update completed steps based on what's been extracted
|
|
@@ -267,6 +266,9 @@ async function createPersistentOnboardingAgent(sessionId: string) {
|
|
|
267
266
|
},
|
|
268
267
|
required: ["businessName", "businessDescription"],
|
|
269
268
|
},
|
|
269
|
+
endState: {
|
|
270
|
+
chatState: "Summarize all collected information warmly and confirm onboarding is complete",
|
|
271
|
+
},
|
|
270
272
|
});
|
|
271
273
|
|
|
272
274
|
// State 1: Gather business name and description
|
|
@@ -311,12 +313,8 @@ async function createPersistentOnboardingAgent(sessionId: string) {
|
|
|
311
313
|
requiredData: ["contactEmail"],
|
|
312
314
|
});
|
|
313
315
|
|
|
314
|
-
// State 7: Confirmation
|
|
315
|
-
|
|
316
|
-
chatState: "Summarize all collected information and ask for confirmation",
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
confirm.transitionTo({ state: END_STATE });
|
|
316
|
+
// State 7: Confirmation - uses route-level endState
|
|
317
|
+
saveContact.transitionTo({ state: END_STATE });
|
|
320
318
|
|
|
321
319
|
// Guidelines
|
|
322
320
|
onboardingRoute.createGuideline({
|