@falai/agent 0.9.0-alpha-1 → 0.9.0-alpha-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.
- package/README.md +34 -22
- package/dist/cjs/src/core/Agent.d.ts +52 -24
- package/dist/cjs/src/core/Agent.d.ts.map +1 -1
- package/dist/cjs/src/core/Agent.js +266 -39
- package/dist/cjs/src/core/Agent.js.map +1 -1
- package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/src/core/PersistenceManager.js +48 -25
- package/dist/cjs/src/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/src/core/PromptComposer.d.ts +1 -1
- package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/src/core/PromptComposer.js.map +1 -1
- package/dist/cjs/src/core/ResponseEngine.d.ts +13 -12
- package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponseEngine.js +4 -4
- package/dist/cjs/src/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/src/core/ResponsePipeline.d.ts +66 -38
- package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponsePipeline.js +71 -3
- package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/src/core/Route.d.ts +24 -5
- package/dist/cjs/src/core/Route.d.ts.map +1 -1
- package/dist/cjs/src/core/Route.js +45 -1
- package/dist/cjs/src/core/Route.js.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.d.ts +31 -6
- package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.js +113 -9
- package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/src/core/SessionManager.d.ts +14 -4
- package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/src/core/SessionManager.js +25 -5
- package/dist/cjs/src/core/SessionManager.js.map +1 -1
- package/dist/cjs/src/core/Step.d.ts +10 -10
- package/dist/cjs/src/core/Step.d.ts.map +1 -1
- package/dist/cjs/src/core/Step.js.map +1 -1
- package/dist/cjs/src/core/ToolExecutor.d.ts +4 -2
- package/dist/cjs/src/core/ToolExecutor.d.ts.map +1 -1
- package/dist/cjs/src/core/ToolExecutor.js +13 -3
- package/dist/cjs/src/core/ToolExecutor.js.map +1 -1
- package/dist/cjs/src/types/agent.d.ts +41 -21
- package/dist/cjs/src/types/agent.d.ts.map +1 -1
- package/dist/cjs/src/types/agent.js.map +1 -1
- package/dist/cjs/src/types/index.d.ts +1 -1
- package/dist/cjs/src/types/index.d.ts.map +1 -1
- package/dist/cjs/src/types/index.js.map +1 -1
- package/dist/cjs/src/types/persistence.d.ts +0 -1
- package/dist/cjs/src/types/persistence.d.ts.map +1 -1
- package/dist/cjs/src/types/route.d.ts +22 -16
- package/dist/cjs/src/types/route.d.ts.map +1 -1
- package/dist/cjs/src/types/session.d.ts +6 -11
- package/dist/cjs/src/types/session.d.ts.map +1 -1
- package/dist/cjs/src/types/tool.d.ts +12 -6
- package/dist/cjs/src/types/tool.d.ts.map +1 -1
- package/dist/cjs/src/utils/session.d.ts +2 -2
- package/dist/cjs/src/utils/session.d.ts.map +1 -1
- package/dist/cjs/src/utils/session.js +6 -26
- package/dist/cjs/src/utils/session.js.map +1 -1
- package/dist/src/core/Agent.d.ts +52 -24
- package/dist/src/core/Agent.d.ts.map +1 -1
- package/dist/src/core/Agent.js +266 -39
- package/dist/src/core/Agent.js.map +1 -1
- package/dist/src/core/PersistenceManager.d.ts.map +1 -1
- package/dist/src/core/PersistenceManager.js +48 -25
- package/dist/src/core/PersistenceManager.js.map +1 -1
- package/dist/src/core/PromptComposer.d.ts +1 -1
- package/dist/src/core/PromptComposer.d.ts.map +1 -1
- package/dist/src/core/PromptComposer.js.map +1 -1
- package/dist/src/core/ResponseEngine.d.ts +13 -12
- package/dist/src/core/ResponseEngine.d.ts.map +1 -1
- package/dist/src/core/ResponseEngine.js +4 -4
- package/dist/src/core/ResponseEngine.js.map +1 -1
- package/dist/src/core/ResponsePipeline.d.ts +66 -38
- package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/src/core/ResponsePipeline.js +71 -3
- package/dist/src/core/ResponsePipeline.js.map +1 -1
- package/dist/src/core/Route.d.ts +24 -5
- package/dist/src/core/Route.d.ts.map +1 -1
- package/dist/src/core/Route.js +45 -1
- package/dist/src/core/Route.js.map +1 -1
- package/dist/src/core/RoutingEngine.d.ts +31 -6
- package/dist/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/src/core/RoutingEngine.js +113 -9
- package/dist/src/core/RoutingEngine.js.map +1 -1
- package/dist/src/core/SessionManager.d.ts +14 -4
- package/dist/src/core/SessionManager.d.ts.map +1 -1
- package/dist/src/core/SessionManager.js +25 -5
- package/dist/src/core/SessionManager.js.map +1 -1
- package/dist/src/core/Step.d.ts +10 -10
- package/dist/src/core/Step.d.ts.map +1 -1
- package/dist/src/core/Step.js.map +1 -1
- package/dist/src/core/ToolExecutor.d.ts +4 -2
- package/dist/src/core/ToolExecutor.d.ts.map +1 -1
- package/dist/src/core/ToolExecutor.js +13 -3
- package/dist/src/core/ToolExecutor.js.map +1 -1
- package/dist/src/types/agent.d.ts +41 -21
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/persistence.d.ts +0 -1
- package/dist/src/types/persistence.d.ts.map +1 -1
- package/dist/src/types/route.d.ts +22 -16
- package/dist/src/types/route.d.ts.map +1 -1
- package/dist/src/types/session.d.ts +6 -11
- package/dist/src/types/session.d.ts.map +1 -1
- package/dist/src/types/tool.d.ts +12 -6
- package/dist/src/types/tool.d.ts.map +1 -1
- package/dist/src/utils/session.d.ts +2 -2
- package/dist/src/utils/session.d.ts.map +1 -1
- package/dist/src/utils/session.js +6 -26
- package/dist/src/utils/session.js.map +1 -1
- package/docs/README.md +3 -3
- package/docs/api/README.md +35 -4
- package/docs/api/overview.md +166 -12
- package/docs/core/agent/README.md +162 -17
- package/docs/core/agent/context-management.md +39 -15
- package/docs/core/agent/session-management.md +49 -16
- package/docs/core/ai-integration/prompt-composition.md +38 -14
- package/docs/core/ai-integration/response-processing.md +28 -17
- package/docs/core/conversation-flows/data-collection.md +103 -25
- package/docs/core/conversation-flows/route-dsl.md +45 -22
- package/docs/core/conversation-flows/routes.md +74 -18
- package/docs/core/conversation-flows/step-transitions.md +3 -3
- package/docs/core/conversation-flows/steps.md +39 -15
- package/docs/core/routing/intelligent-routing.md +18 -9
- package/docs/core/tools/tool-definition.md +8 -8
- package/docs/core/tools/tool-execution.md +26 -26
- package/docs/core/tools/tool-scoping.md +5 -5
- package/docs/guides/getting-started/README.md +54 -32
- package/examples/advanced-patterns/knowledge-based-agent.ts +37 -28
- package/examples/advanced-patterns/persistent-onboarding.ts +70 -41
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +28 -2
- package/examples/advanced-patterns/streaming-responses.ts +28 -23
- package/examples/ai-providers/anthropic-integration.ts +40 -33
- package/examples/ai-providers/openai-integration.ts +25 -25
- package/examples/conversation-flows/completion-transitions.ts +36 -32
- package/examples/core-concepts/basic-agent.ts +76 -78
- package/examples/core-concepts/schema-driven-extraction.ts +20 -16
- package/examples/core-concepts/session-management.ts +65 -53
- package/examples/integrations/database-integration.ts +49 -34
- package/examples/integrations/healthcare-integration.ts +96 -91
- package/examples/integrations/search-integration.ts +79 -82
- package/examples/integrations/server-session-management.ts +25 -17
- package/examples/persistence/database-persistence.ts +61 -45
- package/examples/persistence/memory-sessions.ts +52 -63
- package/examples/persistence/redis-persistence.ts +81 -95
- package/examples/tools/basic-tools.ts +73 -62
- package/examples/tools/data-enrichment-tools.ts +52 -44
- package/package.json +1 -1
- package/src/core/Agent.ts +418 -128
- package/src/core/PersistenceManager.ts +51 -27
- package/src/core/PromptComposer.ts +1 -1
- package/src/core/ResponseEngine.ts +21 -19
- package/src/core/ResponsePipeline.ts +174 -59
- package/src/core/Route.ts +58 -6
- package/src/core/RoutingEngine.ts +174 -27
- package/src/core/SessionManager.ts +32 -8
- package/src/core/Step.ts +20 -12
- package/src/core/ToolExecutor.ts +19 -5
- package/src/types/agent.ts +46 -23
- package/src/types/index.ts +2 -0
- package/src/types/persistence.ts +0 -1
- package/src/types/route.ts +22 -16
- package/src/types/session.ts +6 -12
- package/src/types/tool.ts +15 -9
- package/src/utils/session.ts +6 -31
package/src/core/Route.ts
CHANGED
|
@@ -39,8 +39,9 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
39
39
|
"step" | "condition" | "skipIf"
|
|
40
40
|
>;
|
|
41
41
|
public readonly responseOutputSchema?: StructuredSchema;
|
|
42
|
-
public readonly schema?: StructuredSchema;
|
|
43
42
|
public readonly initialData?: Partial<TData>;
|
|
43
|
+
public readonly requiredFields?: (keyof TData)[];
|
|
44
|
+
public readonly optionalFields?: (keyof TData)[];
|
|
44
45
|
public readonly onComplete?:
|
|
45
46
|
| string
|
|
46
47
|
| RouteTransitionConfig<TContext, TData>
|
|
@@ -49,7 +50,7 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
49
50
|
public routingExtrasSchema?: StructuredSchema;
|
|
50
51
|
public guidelines: Guideline<TContext>[] = [];
|
|
51
52
|
public terms: Term<TContext>[] = [];
|
|
52
|
-
public tools: Tool<TContext, unknown[], unknown
|
|
53
|
+
public tools: Tool<TContext, TData, unknown[], unknown>[] = [];
|
|
53
54
|
public knowledgeBase: Record<string, unknown> = {};
|
|
54
55
|
|
|
55
56
|
constructor(options: RouteOptions<TContext, TData>) {
|
|
@@ -87,8 +88,9 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
87
88
|
};
|
|
88
89
|
this.routingExtrasSchema = options.routingExtrasSchema;
|
|
89
90
|
this.responseOutputSchema = options.responseOutputSchema;
|
|
90
|
-
this.schema = options.schema;
|
|
91
91
|
this.initialData = options.initialData;
|
|
92
|
+
this.requiredFields = options.requiredFields;
|
|
93
|
+
this.optionalFields = options.optionalFields;
|
|
92
94
|
this.onComplete = options.onComplete;
|
|
93
95
|
this.hooks = options.hooks;
|
|
94
96
|
|
|
@@ -166,7 +168,7 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
166
168
|
/**
|
|
167
169
|
* Register a tool for this route
|
|
168
170
|
*/
|
|
169
|
-
createTool(tool: Tool<TContext, unknown[], unknown
|
|
171
|
+
createTool(tool: Tool<TContext, TData, unknown[], unknown>): this {
|
|
170
172
|
this.tools.push(tool);
|
|
171
173
|
return this;
|
|
172
174
|
}
|
|
@@ -174,7 +176,7 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
174
176
|
/**
|
|
175
177
|
* Register multiple tools for this route
|
|
176
178
|
*/
|
|
177
|
-
registerTools(tools: Tool<TContext, unknown[], unknown
|
|
179
|
+
registerTools(tools: Tool<TContext, TData, unknown[], unknown>[]): this {
|
|
178
180
|
tools.forEach((tool) => this.createTool(tool));
|
|
179
181
|
return this;
|
|
180
182
|
}
|
|
@@ -196,7 +198,7 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
196
198
|
/**
|
|
197
199
|
* Get all tools for this route
|
|
198
200
|
*/
|
|
199
|
-
getTools(): Tool<TContext, unknown[], unknown
|
|
201
|
+
getTools(): Tool<TContext, TData, unknown[], unknown>[] {
|
|
200
202
|
return [...this.tools];
|
|
201
203
|
}
|
|
202
204
|
|
|
@@ -353,6 +355,56 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
353
355
|
}
|
|
354
356
|
}
|
|
355
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Check if this route is complete based on the provided data
|
|
360
|
+
* @param data - Currently collected agent-level data
|
|
361
|
+
* @returns true if all required fields are present, false otherwise
|
|
362
|
+
*/
|
|
363
|
+
isComplete(data: Partial<TData>): boolean {
|
|
364
|
+
if (!this.requiredFields || this.requiredFields.length === 0) {
|
|
365
|
+
return true; // No required fields means route is always complete
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return this.requiredFields.every(field => {
|
|
369
|
+
const value = data[field];
|
|
370
|
+
return value !== undefined && value !== null && value !== '';
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Get the list of missing required fields for this route
|
|
376
|
+
* @param data - Currently collected agent-level data
|
|
377
|
+
* @returns Array of missing required field keys
|
|
378
|
+
*/
|
|
379
|
+
getMissingRequiredFields(data: Partial<TData>): (keyof TData)[] {
|
|
380
|
+
if (!this.requiredFields || this.requiredFields.length === 0) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return this.requiredFields.filter(field => {
|
|
385
|
+
const value = data[field];
|
|
386
|
+
return value === undefined || value === null || value === '';
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get the completion progress for this route as a percentage
|
|
392
|
+
* @param data - Currently collected agent-level data
|
|
393
|
+
* @returns Completion progress as a number between 0 and 1
|
|
394
|
+
*/
|
|
395
|
+
getCompletionProgress(data: Partial<TData>): number {
|
|
396
|
+
if (!this.requiredFields || this.requiredFields.length === 0) {
|
|
397
|
+
return 1; // No required fields means 100% complete
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const completedFields = this.requiredFields.filter(field => {
|
|
401
|
+
const value = data[field];
|
|
402
|
+
return value !== undefined && value !== null && value !== '';
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return completedFields.length / this.requiredFields.length;
|
|
406
|
+
}
|
|
407
|
+
|
|
356
408
|
/**
|
|
357
409
|
* Evaluate the onComplete handler and return transition config
|
|
358
410
|
* @param session - Current session step
|
|
@@ -52,7 +52,7 @@ export interface BuildStepSelectionPromptParams<
|
|
|
52
52
|
data: Partial<TData>;
|
|
53
53
|
history: Event[];
|
|
54
54
|
lastMessage: string;
|
|
55
|
-
agentOptions?: AgentOptions<TContext>;
|
|
55
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
56
56
|
context?: TContext;
|
|
57
57
|
session?: SessionState<TData>;
|
|
58
58
|
}
|
|
@@ -61,14 +61,14 @@ export interface BuildRoutingPromptParams<TContext = unknown, TData = unknown> {
|
|
|
61
61
|
history: Event[];
|
|
62
62
|
routes: Route<TContext, TData>[];
|
|
63
63
|
lastMessage: string;
|
|
64
|
-
agentOptions?: AgentOptions<TContext>;
|
|
64
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
65
65
|
session?: SessionState<TData>;
|
|
66
66
|
activeRouteSteps?: Step<TContext, TData>[];
|
|
67
67
|
context?: TContext;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
71
|
-
constructor(private readonly options?: RoutingEngineOptions) {}
|
|
71
|
+
constructor(private readonly options?: RoutingEngineOptions) { }
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Optimized decision for single-route scenarios
|
|
@@ -79,16 +79,17 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
79
79
|
route: Route<TContext, TData>;
|
|
80
80
|
session: SessionState<TData>;
|
|
81
81
|
history: Event[];
|
|
82
|
-
agentOptions?: AgentOptions<TContext>;
|
|
82
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
83
83
|
provider: AiProvider;
|
|
84
84
|
context: TContext;
|
|
85
85
|
signal?: AbortSignal;
|
|
86
86
|
}): Promise<{
|
|
87
|
-
selectedRoute?: Route<TContext>;
|
|
88
|
-
selectedStep?: Step<TContext>;
|
|
87
|
+
selectedRoute?: Route<TContext, TData>;
|
|
88
|
+
selectedStep?: Step<TContext, TData>;
|
|
89
89
|
responseDirectives?: string[];
|
|
90
90
|
session: SessionState<TData>;
|
|
91
91
|
isRouteComplete?: boolean;
|
|
92
|
+
completedRoutes?: Route<TContext, TData>[];
|
|
92
93
|
}> {
|
|
93
94
|
const { route, session, history, agentOptions, provider, context, signal } =
|
|
94
95
|
params;
|
|
@@ -96,6 +97,9 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
96
97
|
let updatedSession = session;
|
|
97
98
|
const selectedRoute = route;
|
|
98
99
|
|
|
100
|
+
// Check if this single route is complete
|
|
101
|
+
const completedRoutes = route.isComplete(session.data || {}) ? [route] : [];
|
|
102
|
+
|
|
99
103
|
// Enter route if not already in it
|
|
100
104
|
if (!session.currentRoute || session.currentRoute.id !== route.id) {
|
|
101
105
|
updatedSession = enterRoute(session, route.id, route.title);
|
|
@@ -139,6 +143,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
139
143
|
selectedStep: undefined,
|
|
140
144
|
session: updatedSession,
|
|
141
145
|
isRouteComplete: true,
|
|
146
|
+
completedRoutes,
|
|
142
147
|
};
|
|
143
148
|
} else {
|
|
144
149
|
logger.debug(
|
|
@@ -149,6 +154,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
149
154
|
selectedStep: candidates[0].step,
|
|
150
155
|
session: updatedSession,
|
|
151
156
|
isRouteComplete: false,
|
|
157
|
+
completedRoutes,
|
|
152
158
|
};
|
|
153
159
|
}
|
|
154
160
|
}
|
|
@@ -210,6 +216,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
210
216
|
selectedStep: selectedStep?.step || candidates[0].step,
|
|
211
217
|
responseDirectives: stepResult.structured?.responseDirectives,
|
|
212
218
|
session: updatedSession,
|
|
219
|
+
completedRoutes,
|
|
213
220
|
};
|
|
214
221
|
}
|
|
215
222
|
|
|
@@ -217,7 +224,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
217
224
|
* Recursively traverse step chain to find first non-skipped step or END_ROUTE
|
|
218
225
|
* @private
|
|
219
226
|
*/
|
|
220
|
-
private findFirstValidStepRecursive
|
|
227
|
+
private findFirstValidStepRecursive(
|
|
221
228
|
currentStep: Step<TContext, TData>,
|
|
222
229
|
data: Partial<TData>,
|
|
223
230
|
visited: Set<string>
|
|
@@ -277,7 +284,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
277
284
|
* Identify valid next candidate steps based on current step and collected data
|
|
278
285
|
* Returns step with isRouteComplete flag if route is complete (all steps skipped + has END_ROUTE transition)
|
|
279
286
|
*/
|
|
280
|
-
getCandidateSteps
|
|
287
|
+
getCandidateSteps(
|
|
281
288
|
route: Route<TContext, TData>,
|
|
282
289
|
currentStep: Step<TContext, TData> | undefined,
|
|
283
290
|
data: Partial<TData>
|
|
@@ -397,21 +404,23 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
397
404
|
* and updates the session (including initialData merge when entering a new route).
|
|
398
405
|
*
|
|
399
406
|
* OPTIMIZATION: If there's only 1 route, skips route scoring and only does step selection.
|
|
407
|
+
* CROSS-ROUTE COMPLETION: Evaluates all routes for completion based on collected data.
|
|
400
408
|
*/
|
|
401
409
|
async decideRouteAndStep(params: {
|
|
402
410
|
routes: Route<TContext, TData>[];
|
|
403
411
|
session: SessionState<TData>;
|
|
404
412
|
history: Event[];
|
|
405
|
-
agentOptions?: AgentOptions<TContext>;
|
|
413
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
406
414
|
provider: AiProvider;
|
|
407
415
|
context: TContext;
|
|
408
416
|
signal?: AbortSignal;
|
|
409
417
|
}): Promise<{
|
|
410
|
-
selectedRoute?: Route<TContext>;
|
|
411
|
-
selectedStep?: Step<TContext>;
|
|
418
|
+
selectedRoute?: Route<TContext, TData>;
|
|
419
|
+
selectedStep?: Step<TContext, TData>;
|
|
412
420
|
responseDirectives?: string[];
|
|
413
421
|
session: SessionState<TData>;
|
|
414
422
|
isRouteComplete?: boolean;
|
|
423
|
+
completedRoutes?: Route<TContext, TData>[];
|
|
415
424
|
}> {
|
|
416
425
|
const {
|
|
417
426
|
routes,
|
|
@@ -427,9 +436,19 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
427
436
|
return { session };
|
|
428
437
|
}
|
|
429
438
|
|
|
439
|
+
// CROSS-ROUTE COMPLETION EVALUATION: Check all routes for completion
|
|
440
|
+
const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
|
|
441
|
+
|
|
442
|
+
// Log completed routes
|
|
443
|
+
if (completedRoutes.length > 0) {
|
|
444
|
+
logger.debug(
|
|
445
|
+
`[RoutingEngine] Found ${completedRoutes.length} completed routes: ${completedRoutes.map(r => r.title).join(', ')}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
430
449
|
// OPTIMIZATION: Single route - skip route scoring, only do step selection
|
|
431
450
|
if (routes.length === 1) {
|
|
432
|
-
|
|
451
|
+
const result = await this.decideSingleRouteStep({
|
|
433
452
|
route: routes[0],
|
|
434
453
|
session,
|
|
435
454
|
history,
|
|
@@ -438,6 +457,10 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
438
457
|
context,
|
|
439
458
|
signal,
|
|
440
459
|
});
|
|
460
|
+
return {
|
|
461
|
+
...result,
|
|
462
|
+
completedRoutes,
|
|
463
|
+
};
|
|
441
464
|
}
|
|
442
465
|
|
|
443
466
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
@@ -505,18 +528,29 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
505
528
|
},
|
|
506
529
|
});
|
|
507
530
|
|
|
508
|
-
let selectedRoute: Route<TContext> | undefined;
|
|
509
|
-
let selectedStep: Step<TContext> | undefined;
|
|
531
|
+
let selectedRoute: Route<TContext, TData> | undefined;
|
|
532
|
+
let selectedStep: Step<TContext, TData> | undefined;
|
|
510
533
|
let responseDirectives: string[] | undefined;
|
|
511
534
|
let updatedSession = session;
|
|
512
535
|
|
|
513
536
|
if (routingResult.structured?.routes) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
routes
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
537
|
+
// Use cross-route completion evaluation to select optimal route
|
|
538
|
+
const optimalRoute = this.selectOptimalRoute(
|
|
539
|
+
routes,
|
|
540
|
+
updatedSession.data || {},
|
|
541
|
+
routingResult.structured.routes
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
// Fall back to traditional scoring if no optimal route found
|
|
545
|
+
selectedRoute = optimalRoute || (() => {
|
|
546
|
+
const decision = this.decideRouteFromScores({
|
|
547
|
+
context: routingResult.structured.context,
|
|
548
|
+
routes: routingResult.structured.routes,
|
|
549
|
+
responseDirectives: routingResult.structured.responseDirectives,
|
|
550
|
+
});
|
|
551
|
+
return routes.find((r) => r.id === decision.routeId);
|
|
552
|
+
})();
|
|
553
|
+
|
|
520
554
|
responseDirectives = routingResult.structured.responseDirectives;
|
|
521
555
|
|
|
522
556
|
if (
|
|
@@ -569,14 +603,95 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
569
603
|
responseDirectives,
|
|
570
604
|
session: updatedSession,
|
|
571
605
|
isRouteComplete,
|
|
606
|
+
completedRoutes,
|
|
572
607
|
};
|
|
573
608
|
}
|
|
574
609
|
|
|
610
|
+
/**
|
|
611
|
+
* Evaluate all routes for completion based on collected data
|
|
612
|
+
* @param routes - All available routes
|
|
613
|
+
* @param data - Currently collected agent-level data
|
|
614
|
+
* @returns Array of routes that are complete
|
|
615
|
+
*/
|
|
616
|
+
evaluateRouteCompletions(routes: Route<TContext, TData>[], data: Partial<TData>): Route<TContext, TData>[] {
|
|
617
|
+
return routes.filter(route => route.isComplete(data));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Get completion status for all routes
|
|
622
|
+
* @param routes - All available routes
|
|
623
|
+
* @param data - Currently collected agent-level data
|
|
624
|
+
* @returns Map of route ID to completion progress (0-1)
|
|
625
|
+
*/
|
|
626
|
+
getRouteCompletionStatus(routes: Route<TContext, TData>[], data: Partial<TData>): Map<string, number> {
|
|
627
|
+
const completionStatus = new Map<string, number>();
|
|
628
|
+
|
|
629
|
+
for (const route of routes) {
|
|
630
|
+
const progress = route.getCompletionProgress(data);
|
|
631
|
+
completionStatus.set(route.id, progress);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return completionStatus;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Find the best route to continue based on completion status and user intent
|
|
639
|
+
* Prioritizes routes that are partially complete but not finished
|
|
640
|
+
* @param routes - All available routes
|
|
641
|
+
* @param data - Currently collected agent-level data
|
|
642
|
+
* @param routeScores - AI-generated route scores from routing decision
|
|
643
|
+
* @returns Route that should be prioritized for continuation
|
|
644
|
+
*/
|
|
645
|
+
selectOptimalRoute(
|
|
646
|
+
routes: Route<TContext, TData>[],
|
|
647
|
+
data: Partial<TData>,
|
|
648
|
+
routeScores: Record<string, number>
|
|
649
|
+
): Route<TContext, TData> | undefined {
|
|
650
|
+
const completionStatus = this.getRouteCompletionStatus(routes, data);
|
|
651
|
+
|
|
652
|
+
// Create weighted scores combining AI intent scores with completion progress
|
|
653
|
+
const weightedScores: Array<{ route: Route<TContext, TData>; score: number }> = [];
|
|
654
|
+
|
|
655
|
+
for (const route of routes) {
|
|
656
|
+
const aiScore = routeScores[route.id] || 0;
|
|
657
|
+
const completionProgress = completionStatus.get(route.id) || 0;
|
|
658
|
+
|
|
659
|
+
// Skip fully completed routes unless they have very high AI scores
|
|
660
|
+
if (completionProgress >= 1.0 && aiScore < 80) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Boost partially complete routes that match user intent
|
|
665
|
+
let weightedScore = aiScore;
|
|
666
|
+
if (completionProgress > 0 && completionProgress < 1.0) {
|
|
667
|
+
// Boost score for partially complete routes
|
|
668
|
+
weightedScore += (completionProgress * 20); // Up to 20 point boost
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
weightedScores.push({ route, score: weightedScore });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Sort by weighted score and return the best option
|
|
675
|
+
weightedScores.sort((a, b) => b.score - a.score);
|
|
676
|
+
|
|
677
|
+
if (weightedScores.length > 0) {
|
|
678
|
+
logger.debug(
|
|
679
|
+
`[RoutingEngine] Selected optimal route: ${weightedScores[0].route.title} ` +
|
|
680
|
+
`(AI: ${routeScores[weightedScores[0].route.id]}, ` +
|
|
681
|
+
`Completion: ${(completionStatus.get(weightedScores[0].route.id) || 0) * 100}%, ` +
|
|
682
|
+
`Weighted: ${weightedScores[0].score})`
|
|
683
|
+
);
|
|
684
|
+
return weightedScores[0].route;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
|
|
575
690
|
/**
|
|
576
691
|
* Build prompt for step selection within a single route
|
|
577
692
|
* @private
|
|
578
693
|
*/
|
|
579
|
-
private async buildStepSelectionPrompt
|
|
694
|
+
private async buildStepSelectionPrompt(
|
|
580
695
|
params: BuildStepSelectionPromptParams<TContext, TData>
|
|
581
696
|
): Promise<string> {
|
|
582
697
|
const {
|
|
@@ -591,7 +706,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
591
706
|
session,
|
|
592
707
|
} = params;
|
|
593
708
|
const templateContext = { context, session, history };
|
|
594
|
-
const pc = new PromptComposer(templateContext);
|
|
709
|
+
const pc = new PromptComposer<TContext, TData>(templateContext);
|
|
595
710
|
|
|
596
711
|
// Add agent metadata
|
|
597
712
|
if (agentOptions) {
|
|
@@ -606,8 +721,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
606
721
|
// Add current step context
|
|
607
722
|
if (currentStep) {
|
|
608
723
|
await pc.addInstruction(
|
|
609
|
-
`Current Step: ${currentStep.id}\nDescription: ${
|
|
610
|
-
currentStep.description || "N/A"
|
|
724
|
+
`Current Step: ${currentStep.id}\nDescription: ${currentStep.description || "N/A"
|
|
611
725
|
}`
|
|
612
726
|
);
|
|
613
727
|
} else {
|
|
@@ -802,7 +916,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
802
916
|
context,
|
|
803
917
|
} = params;
|
|
804
918
|
const templateContext = { context, session, history };
|
|
805
|
-
const pc = new PromptComposer(templateContext);
|
|
919
|
+
const pc = new PromptComposer<TContext, TData>(templateContext);
|
|
806
920
|
if (agentOptions) {
|
|
807
921
|
await pc.addAgentMeta(agentOptions);
|
|
808
922
|
}
|
|
@@ -830,6 +944,41 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
830
944
|
);
|
|
831
945
|
await pc.addInstruction(sessionInfo.join("\n"));
|
|
832
946
|
|
|
947
|
+
// Add cross-route completion status
|
|
948
|
+
const completionStatus = this.getRouteCompletionStatus(routes, session.data || {});
|
|
949
|
+
const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
|
|
950
|
+
|
|
951
|
+
if (completionStatus.size > 0) {
|
|
952
|
+
const statusInfo = [
|
|
953
|
+
"",
|
|
954
|
+
"Route completion status based on collected data:",
|
|
955
|
+
];
|
|
956
|
+
|
|
957
|
+
for (const route of routes) {
|
|
958
|
+
const progress = completionStatus.get(route.id) || 0;
|
|
959
|
+
const isComplete = completedRoutes.includes(route);
|
|
960
|
+
const progressPercent = Math.round(progress * 100);
|
|
961
|
+
|
|
962
|
+
statusInfo.push(
|
|
963
|
+
`- ${route.title}: ${progressPercent}% complete${isComplete ? ' ✓ COMPLETE' : ''}`
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
if (!isComplete && route.requiredFields) {
|
|
967
|
+
const missingFields = route.getMissingRequiredFields(session.data || {});
|
|
968
|
+
if (missingFields.length > 0) {
|
|
969
|
+
statusInfo.push(` Missing: ${missingFields.join(', ')}`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
statusInfo.push(
|
|
975
|
+
"",
|
|
976
|
+
"Consider route completion status when scoring. Partially complete routes may be good candidates for continuation."
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
await pc.addInstruction(statusInfo.join("\n"));
|
|
980
|
+
}
|
|
981
|
+
|
|
833
982
|
// Add available steps for the active route
|
|
834
983
|
if (activeRouteSteps && activeRouteSteps.length > 0) {
|
|
835
984
|
const stepInfo = [
|
|
@@ -869,8 +1018,6 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
869
1018
|
|
|
870
1019
|
await pc.addInteractionHistory(history);
|
|
871
1020
|
await pc.addLastMessage(lastMessage);
|
|
872
|
-
// Cast to unknown to satisfy generic constraints in composer
|
|
873
|
-
// This is safe because PromptComposer only reads route metadata (id, title, description)
|
|
874
1021
|
await pc.addRoutingOverview(routes);
|
|
875
1022
|
await pc.addInstruction(
|
|
876
1023
|
[
|
|
@@ -75,8 +75,7 @@ export class SessionManager<TData = unknown> {
|
|
|
75
75
|
|
|
76
76
|
const session: SessionState<TData> = {
|
|
77
77
|
id,
|
|
78
|
-
data: {} as Partial<TData>,
|
|
79
|
-
dataByRoute: {},
|
|
78
|
+
data: {} as Partial<TData>, // Agent-level data structure
|
|
80
79
|
routeHistory: [],
|
|
81
80
|
history: [], // Session manages its own history
|
|
82
81
|
metadata: {
|
|
@@ -186,25 +185,50 @@ export class SessionManager<TData = unknown> {
|
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
/**
|
|
189
|
-
* Get collected data from the current session
|
|
188
|
+
* Get agent-level collected data from the current session
|
|
190
189
|
*/
|
|
191
|
-
getData
|
|
192
|
-
return this.currentSession?.data
|
|
190
|
+
getData(): Partial<TData> {
|
|
191
|
+
return this.currentSession?.data || ({} as Partial<TData>);
|
|
193
192
|
}
|
|
194
193
|
|
|
195
194
|
/**
|
|
196
|
-
* Set/merge data into the current session
|
|
195
|
+
* Set/merge agent-level data into the current session
|
|
196
|
+
* This updates the single source of truth for all collected data
|
|
197
197
|
*/
|
|
198
|
-
async setData
|
|
198
|
+
async setData(data: Partial<TData>): Promise<void> {
|
|
199
199
|
// Ensure session exists
|
|
200
200
|
await this.getOrCreate();
|
|
201
201
|
|
|
202
202
|
if (this.currentSession && data) {
|
|
203
203
|
this.currentSession.data = {
|
|
204
204
|
...this.currentSession.data,
|
|
205
|
-
...
|
|
205
|
+
...data,
|
|
206
206
|
};
|
|
207
207
|
this.currentSession.metadata!.lastUpdatedAt = new Date();
|
|
208
|
+
|
|
209
|
+
// Auto-save to persistence
|
|
210
|
+
await this.save();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update specific fields in the agent-level data
|
|
216
|
+
* Provides a more explicit method for data updates
|
|
217
|
+
*/
|
|
218
|
+
async updateData(updates: Partial<TData>): Promise<void> {
|
|
219
|
+
await this.setData(updates);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Clear all collected data while preserving session structure
|
|
224
|
+
*/
|
|
225
|
+
async clearData(): Promise<void> {
|
|
226
|
+
if (this.currentSession) {
|
|
227
|
+
this.currentSession.data = {} as Partial<TData>;
|
|
228
|
+
this.currentSession.metadata!.lastUpdatedAt = new Date();
|
|
229
|
+
|
|
230
|
+
// Auto-save to persistence
|
|
231
|
+
await this.save();
|
|
208
232
|
}
|
|
209
233
|
}
|
|
210
234
|
|
package/src/core/Step.ts
CHANGED
|
@@ -24,27 +24,31 @@ export class Step<TContext = unknown, TData = unknown> {
|
|
|
24
24
|
private nextSteps: Step<TContext, TData>[] = [];
|
|
25
25
|
private guidelines: Guideline<TContext>[] = [];
|
|
26
26
|
public readonly routeId: string;
|
|
27
|
-
public collect?:
|
|
27
|
+
public collect?: (keyof TData)[];
|
|
28
28
|
public description?: string;
|
|
29
29
|
public when?: Template<TContext, TData>;
|
|
30
30
|
public skipIf?: (data: Partial<TData>) => boolean;
|
|
31
|
-
public requires?:
|
|
31
|
+
public requires?: (keyof TData)[];
|
|
32
32
|
public prompt?: Template<TContext, TData>;
|
|
33
33
|
public prepare?:
|
|
34
34
|
| string
|
|
35
|
-
| Tool<TContext, unknown[], unknown
|
|
35
|
+
| Tool<TContext, TData, unknown[], unknown>
|
|
36
36
|
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>);
|
|
37
37
|
public finalize?:
|
|
38
38
|
| string
|
|
39
|
-
| Tool<TContext, unknown[], unknown
|
|
39
|
+
| Tool<TContext, TData, unknown[], unknown>
|
|
40
40
|
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>);
|
|
41
|
-
public tools?: (string | Tool<TContext, unknown[], unknown
|
|
41
|
+
public tools?: (string | Tool<TContext, TData, unknown[], unknown>)[];
|
|
42
42
|
|
|
43
|
-
constructor(
|
|
43
|
+
constructor(
|
|
44
|
+
routeId: string,
|
|
45
|
+
options: StepOptions<TContext, TData> = {}
|
|
46
|
+
) {
|
|
44
47
|
// Use provided ID or generate a deterministic one
|
|
45
48
|
this.id = options.id || generateStepId(routeId, options.description);
|
|
46
49
|
this.routeId = routeId;
|
|
47
50
|
this.description = options.description;
|
|
51
|
+
|
|
48
52
|
this.collect = options.collect;
|
|
49
53
|
this.skipIf = options.skipIf;
|
|
50
54
|
this.requires = options.requires;
|
|
@@ -61,32 +65,36 @@ export class Step<TContext = unknown, TData = unknown> {
|
|
|
61
65
|
*/
|
|
62
66
|
configure(config: {
|
|
63
67
|
description?: string;
|
|
64
|
-
collect?:
|
|
68
|
+
collect?: (keyof TData)[];
|
|
65
69
|
skipIf?: (data: Partial<TData>) => boolean;
|
|
66
|
-
requires?:
|
|
70
|
+
requires?: (keyof TData)[];
|
|
67
71
|
prompt?: Template<TContext, TData>;
|
|
68
72
|
prepare?:
|
|
69
73
|
| string
|
|
70
|
-
| Tool<TContext, unknown[], unknown
|
|
74
|
+
| Tool<TContext, TData, unknown[], unknown>
|
|
71
75
|
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>);
|
|
72
76
|
finalize?:
|
|
73
77
|
| string
|
|
74
|
-
| Tool<TContext, unknown[], unknown
|
|
78
|
+
| Tool<TContext, TData, unknown[], unknown>
|
|
75
79
|
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>);
|
|
76
|
-
tools?: (string | Tool<TContext, unknown[], unknown
|
|
80
|
+
tools?: (string | Tool<TContext, TData, unknown[], unknown>)[];
|
|
77
81
|
}): this {
|
|
78
82
|
if (config.description !== undefined) {
|
|
79
83
|
this.description = config.description;
|
|
80
84
|
}
|
|
85
|
+
|
|
81
86
|
if (config.collect !== undefined) {
|
|
82
87
|
this.collect = config.collect;
|
|
83
88
|
}
|
|
89
|
+
|
|
84
90
|
if (config.skipIf !== undefined) {
|
|
85
91
|
this.skipIf = config.skipIf;
|
|
86
92
|
}
|
|
93
|
+
|
|
87
94
|
if (config.requires !== undefined) {
|
|
88
95
|
this.requires = config.requires;
|
|
89
96
|
}
|
|
97
|
+
|
|
90
98
|
if (config.prompt !== undefined) {
|
|
91
99
|
this.prompt = config.prompt;
|
|
92
100
|
}
|
|
@@ -214,7 +222,7 @@ export class Step<TContext = unknown, TData = unknown> {
|
|
|
214
222
|
*/
|
|
215
223
|
hasRequires(data: Partial<TData>): boolean {
|
|
216
224
|
if (!this.requires || this.requires.length === 0) return true;
|
|
217
|
-
return this.requires.every((key) => data[key
|
|
225
|
+
return this.requires.every((key) => data[key] !== undefined);
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
/**
|