@falai/agent 1.0.0 → 1.0.2

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.
Files changed (71) hide show
  1. package/README.md +19 -4
  2. package/dist/cjs/core/Agent.d.ts +10 -0
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +22 -3
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/BatchExecutor.d.ts.map +1 -1
  7. package/dist/cjs/core/BatchExecutor.js +8 -0
  8. package/dist/cjs/core/BatchExecutor.js.map +1 -1
  9. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -1
  10. package/dist/cjs/core/BatchPromptBuilder.js +16 -0
  11. package/dist/cjs/core/BatchPromptBuilder.js.map +1 -1
  12. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  13. package/dist/cjs/core/ResponseEngine.js +71 -62
  14. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  15. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  16. package/dist/cjs/core/ResponseModal.js +71 -13
  17. package/dist/cjs/core/ResponseModal.js.map +1 -1
  18. package/dist/cjs/core/RoutingEngine.d.ts +8 -9
  19. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  20. package/dist/cjs/core/RoutingEngine.js +29 -23
  21. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  22. package/dist/cjs/types/agent.d.ts +11 -0
  23. package/dist/cjs/types/agent.d.ts.map +1 -1
  24. package/dist/cjs/types/routing.d.ts +0 -4
  25. package/dist/cjs/types/routing.d.ts.map +1 -1
  26. package/dist/core/Agent.d.ts +10 -0
  27. package/dist/core/Agent.d.ts.map +1 -1
  28. package/dist/core/Agent.js +22 -3
  29. package/dist/core/Agent.js.map +1 -1
  30. package/dist/core/BatchExecutor.d.ts.map +1 -1
  31. package/dist/core/BatchExecutor.js +8 -0
  32. package/dist/core/BatchExecutor.js.map +1 -1
  33. package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
  34. package/dist/core/BatchPromptBuilder.js +17 -1
  35. package/dist/core/BatchPromptBuilder.js.map +1 -1
  36. package/dist/core/ResponseEngine.d.ts.map +1 -1
  37. package/dist/core/ResponseEngine.js +71 -62
  38. package/dist/core/ResponseEngine.js.map +1 -1
  39. package/dist/core/ResponseModal.d.ts.map +1 -1
  40. package/dist/core/ResponseModal.js +71 -13
  41. package/dist/core/ResponseModal.js.map +1 -1
  42. package/dist/core/RoutingEngine.d.ts +8 -9
  43. package/dist/core/RoutingEngine.d.ts.map +1 -1
  44. package/dist/core/RoutingEngine.js +29 -23
  45. package/dist/core/RoutingEngine.js.map +1 -1
  46. package/dist/types/agent.d.ts +11 -0
  47. package/dist/types/agent.d.ts.map +1 -1
  48. package/dist/types/routing.d.ts +0 -4
  49. package/dist/types/routing.d.ts.map +1 -1
  50. package/docs/README.md +7 -17
  51. package/docs/api/README.md +28 -16
  52. package/docs/api/overview.md +4 -0
  53. package/docs/architecture/data-extraction-flow.md +0 -1
  54. package/docs/core/agent/README.md +36 -0
  55. package/docs/core/agent/context-management.md +6 -6
  56. package/docs/core/agent/rules-and-prohibitions.md +113 -0
  57. package/docs/core/agent/session-management.md +3 -4
  58. package/docs/core/routing/intelligent-routing.md +12 -8
  59. package/docs/guides/getting-started/README.md +10 -13
  60. package/docs/guides/migration/README.md +6 -2
  61. package/docs/guides/migration/multi-step-execution.md +70 -0
  62. package/docs/guides/migration/response-modal-refactor.md +2 -2
  63. package/package.json +1 -1
  64. package/src/core/Agent.ts +25 -3
  65. package/src/core/BatchExecutor.ts +10 -0
  66. package/src/core/BatchPromptBuilder.ts +19 -1
  67. package/src/core/ResponseEngine.ts +91 -91
  68. package/src/core/ResponseModal.ts +85 -27
  69. package/src/core/RoutingEngine.ts +63 -50
  70. package/src/types/agent.ts +11 -0
  71. package/src/types/routing.ts +0 -5
@@ -2,7 +2,6 @@ import type {
2
2
  Event,
3
3
  AgentOptions,
4
4
  StructuredSchema,
5
- RoutingDecision,
6
5
  SessionState,
7
6
  AiProvider,
8
7
  TemplateContext,
@@ -36,9 +35,12 @@ export interface RoutingDecisionOutput {
36
35
  }
37
36
 
38
37
  export interface RoutingEngineOptions {
39
- allowRouteSwitch?: boolean;
40
- switchThreshold?: number; // 0-100
41
- maxCandidates?: number;
38
+ /**
39
+ * Score margin the best alternative route must exceed the current route's score
40
+ * by before the agent switches routes. Prevents flip-flopping on marginal differences.
41
+ * @default 15
42
+ */
43
+ routeSwitchMargin?: number;
42
44
  }
43
45
 
44
46
  export interface BuildStepSelectionPromptParams<
@@ -746,7 +748,8 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
746
748
  const optimalRoute = this.selectOptimalRoute(
747
749
  eligibleRoutes,
748
750
  updatedSession.data || {},
749
- routingResult.structured.routes
751
+ routingResult.structured.routes,
752
+ updatedSession.currentRoute?.id
750
753
  );
751
754
 
752
755
  // If no optimal route found, check why
@@ -908,42 +911,68 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
908
911
  * @returns Route that should be prioritized for continuation
909
912
  */
910
913
  selectOptimalRoute(
911
- routes: Route<TContext, TData>[],
912
- data: Partial<TData>,
913
- routeScores: Record<string, number>
914
- ): Route<TContext, TData> | undefined {
915
- const completionStatus = this.getRouteCompletionStatus(routes, data);
916
-
917
- // Create weighted scores combining AI intent scores with completion progress
918
- const weightedScores: Array<{ route: Route<TContext, TData>; score: number }> = [];
914
+ routes: Route<TContext, TData>[],
915
+ data: Partial<TData>,
916
+ routeScores: Record<string, number>,
917
+ currentRouteId?: string
918
+ ): Route<TContext, TData> | undefined {
919
+ const completionStatus = this.getRouteCompletionStatus(routes, data);
920
+ const switchMargin = this.options?.routeSwitchMargin ?? 15;
921
+
922
+ // Create weighted scores combining AI intent scores with completion progress
923
+ const weightedScores: Array<{ route: Route<TContext, TData>; score: number }> = [];
924
+
925
+ for (const route of routes) {
926
+ const aiScore = routeScores[route.id] || 0;
927
+ const completionProgress = completionStatus.get(route.id) || 0;
928
+
929
+ // ALWAYS skip fully completed routes to prevent re-entering finished tasks
930
+ if (completionProgress >= 1.0) {
931
+ logger.debug(
932
+ `[RoutingEngine] Excluding completed route: ${route.title} (100% complete)`
933
+ );
934
+ continue;
935
+ }
919
936
 
920
- for (const route of routes) {
921
- const aiScore = routeScores[route.id] || 0;
922
- const completionProgress = completionStatus.get(route.id) || 0;
937
+ // Boost partially complete routes that match user intent
938
+ let weightedScore = aiScore;
939
+ if (completionProgress > 0 && completionProgress < 1.0) {
940
+ weightedScore += (completionProgress * 20); // Up to 20 point boost
941
+ }
923
942
 
924
- // ALWAYS skip fully completed routes to prevent re-entering finished tasks
925
- // Users should not be forced back into completed routes
926
- if (completionProgress >= 1.0) {
927
- logger.debug(
928
- `[RoutingEngine] Excluding completed route: ${route.title} (100% complete)`
929
- );
930
- continue;
943
+ weightedScores.push({ route, score: weightedScore });
931
944
  }
932
945
 
933
- // Boost partially complete routes that match user intent
934
- let weightedScore = aiScore;
935
- if (completionProgress > 0 && completionProgress < 1.0) {
936
- // Boost score for partially complete routes
937
- weightedScore += (completionProgress * 20); // Up to 20 point boost
938
- }
946
+ // Sort by weighted score descending
947
+ weightedScores.sort((a, b) => b.score - a.score);
939
948
 
940
- weightedScores.push({ route, score: weightedScore });
941
- }
949
+ if (weightedScores.length === 0) {
950
+ return undefined;
951
+ }
942
952
 
943
- // Sort by weighted score and return the best option
944
- weightedScores.sort((a, b) => b.score - a.score);
953
+ // Apply sticky routing: if there's a current route, only switch if the
954
+ // best alternative exceeds the current route's score by the configured margin
955
+ if (currentRouteId) {
956
+ const currentEntry = weightedScores.find(e => e.route.id === currentRouteId);
957
+ const bestEntry = weightedScores[0];
958
+
959
+ if (currentEntry && bestEntry.route.id !== currentRouteId) {
960
+ if (bestEntry.score < currentEntry.score + switchMargin) {
961
+ logger.debug(
962
+ `[RoutingEngine] Staying on current route: ${currentEntry.route.title} ` +
963
+ `(current: ${currentEntry.score}, best alternative: ${bestEntry.score}, ` +
964
+ `margin required: ${switchMargin})`
965
+ );
966
+ return currentEntry.route;
967
+ }
968
+ logger.debug(
969
+ `[RoutingEngine] Switching route: ${currentEntry.route.title} → ${bestEntry.route.title} ` +
970
+ `(current: ${currentEntry.score}, alternative: ${bestEntry.score}, ` +
971
+ `margin: ${switchMargin})`
972
+ );
973
+ }
974
+ }
945
975
 
946
- if (weightedScores.length > 0) {
947
976
  logger.debug(
948
977
  `[RoutingEngine] Selected optimal route: ${weightedScores[0].route.title} ` +
949
978
  `(AI: ${routeScores[weightedScores[0].route.id]}, ` +
@@ -953,9 +982,6 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
953
982
  return weightedScores[0].route;
954
983
  }
955
984
 
956
- return undefined;
957
- }
958
-
959
985
  /**
960
986
  * Build prompt for step selection within a single route
961
987
  * @private
@@ -1380,17 +1406,4 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
1380
1406
  return pc.build();
1381
1407
  }
1382
1408
 
1383
- decideRouteFromScores(output: RoutingDecision): {
1384
- routeId: string;
1385
- maxScore: number;
1386
- } {
1387
- // Optionally limit candidates and apply switching threshold
1388
- const entries = Object.entries(output.routes).sort((a, b) => b[1] - a[1]);
1389
- const limited = this.options?.maxCandidates
1390
- ? entries.slice(0, this.options.maxCandidates)
1391
- : entries;
1392
- const [topId, topScore] = limited[0] || ["", 0];
1393
- // switchThreshold is enforced by caller when a current route exists
1394
- return { routeId: topId, maxScore: topScore };
1395
- }
1396
1409
  }
@@ -106,10 +106,21 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
106
106
  persistence?: PersistenceConfig<TData>;
107
107
  /** Knowledge base containing any JSON structure the AI should know */
108
108
  knowledgeBase?: Record<string, unknown>;
109
+ /** Absolute rules the agent must follow across all routes */
110
+ rules?: Template<TContext, TData>[];
111
+ /** Absolute prohibitions the agent must never do across all routes */
112
+ prohibitions?: Template<TContext, TData>[];
109
113
  /** Agent-level data schema defining the complete data structure for collection */
110
114
  schema?: StructuredSchema;
111
115
  /** Initial data to pre-populate when creating the agent */
112
116
  initialData?: Partial<TData>;
117
+ /**
118
+ * Margin (0-100) the best alternative route must exceed the current route's score
119
+ * by before the agent switches. Higher values make the agent "stickier" to the
120
+ * current route. Set to 0 to switch whenever any route scores higher.
121
+ * @default 15
122
+ */
123
+ routeSwitchMargin?: number;
113
124
  }
114
125
 
115
126
  /**
@@ -8,11 +8,6 @@ export interface RoutingDecision {
8
8
  contextUpdate?: Record<string, unknown>;
9
9
  }
10
10
 
11
- export interface RoutingDecisionWithRoute extends RoutingDecision {
12
- selectedRouteId: string;
13
- maxScore: number;
14
- }
15
-
16
11
  export interface RoutingSchemaOptions {
17
12
  extrasSchema?: StructuredSchema;
18
13
  }