@falai/agent 0.6.9 → 0.8.0
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 +78 -82
- package/dist/adapters/MemoryAdapter.d.ts +1 -1
- package/dist/adapters/MemoryAdapter.js +3 -3
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.d.ts +1 -1
- package/dist/adapters/MongoAdapter.js +3 -3
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +7 -7
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +10 -10
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.d.ts +1 -1
- package/dist/adapters/PrismaAdapter.js +4 -4
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts +1 -1
- package/dist/adapters/RedisAdapter.js +3 -3
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts +4 -4
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +12 -12
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.d.ts +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +3 -3
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.d.ts +1 -1
- package/dist/cjs/adapters/MongoAdapter.js +3 -3
- package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js +7 -7
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +10 -10
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.d.ts +1 -1
- package/dist/cjs/adapters/PrismaAdapter.js +4 -4
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +3 -3
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +4 -4
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +12 -12
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/index.d.ts +1 -1
- package/dist/cjs/adapters/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.d.ts +4 -4
- package/dist/cjs/constants/index.js +5 -5
- package/dist/cjs/core/Agent.d.ts +22 -22
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +168 -160
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/Events.d.ts +6 -6
- package/dist/cjs/core/Events.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.d.ts +13 -13
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +24 -24
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/ResponseEngine.d.ts +3 -8
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +8 -8
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/Route.d.ts +17 -17
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +33 -33
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +31 -31
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +196 -196
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/Step.d.ts +72 -0
- package/dist/cjs/core/Step.d.ts.map +1 -0
- package/dist/cjs/core/Step.js +150 -0
- package/dist/cjs/core/Step.js.map +1 -0
- package/dist/cjs/core/ToolExecutor.d.ts +5 -5
- package/dist/cjs/core/ToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/ToolExecutor.js +8 -8
- package/dist/cjs/core/ToolExecutor.js.map +1 -1
- package/dist/cjs/core/Transition.d.ts +14 -14
- package/dist/cjs/core/Transition.d.ts.map +1 -1
- package/dist/cjs/core/Transition.js +48 -19
- package/dist/cjs/core/Transition.js.map +1 -1
- package/dist/cjs/index.d.ts +7 -7
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +8 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +9 -9
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/ai.d.ts +2 -2
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/history.d.ts +3 -3
- package/dist/cjs/types/history.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/persistence.d.ts +5 -5
- package/dist/cjs/types/persistence.d.ts.map +1 -1
- package/dist/cjs/types/route.d.ts +57 -52
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +27 -27
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/session.js +48 -50
- package/dist/cjs/types/session.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +13 -13
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/utils/id.d.ts +8 -3
- package/dist/cjs/utils/id.d.ts.map +1 -1
- package/dist/cjs/utils/id.js +16 -7
- package/dist/cjs/utils/id.js.map +1 -1
- package/dist/constants/index.d.ts +4 -4
- package/dist/constants/index.js +4 -4
- package/dist/core/Agent.d.ts +22 -22
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +170 -162
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/Events.d.ts +6 -6
- package/dist/core/Events.d.ts.map +1 -1
- package/dist/core/PersistenceManager.d.ts +13 -13
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +25 -25
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/ResponseEngine.d.ts +3 -8
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +8 -8
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/Route.d.ts +17 -17
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +33 -33
- package/dist/core/Route.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +31 -31
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +197 -197
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/Step.d.ts +72 -0
- package/dist/core/Step.d.ts.map +1 -0
- package/dist/core/Step.js +146 -0
- package/dist/core/Step.js.map +1 -0
- package/dist/core/ToolExecutor.d.ts +5 -5
- package/dist/core/ToolExecutor.d.ts.map +1 -1
- package/dist/core/ToolExecutor.js +8 -8
- package/dist/core/ToolExecutor.js.map +1 -1
- package/dist/core/Transition.d.ts +14 -14
- package/dist/core/Transition.d.ts.map +1 -1
- package/dist/core/Transition.js +48 -19
- package/dist/core/Transition.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +9 -9
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/ai.d.ts +2 -2
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/history.d.ts +3 -3
- package/dist/types/history.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/persistence.d.ts +5 -5
- package/dist/types/persistence.d.ts.map +1 -1
- package/dist/types/route.d.ts +57 -52
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/session.d.ts +27 -27
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +44 -46
- package/dist/types/session.js.map +1 -1
- package/dist/types/tool.d.ts +13 -13
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/id.d.ts +8 -3
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +14 -6
- package/dist/utils/id.js.map +1 -1
- package/docs/ADAPTERS.md +21 -21
- package/docs/AGENT.md +63 -61
- package/docs/API_REFERENCE.md +218 -220
- package/docs/ARCHITECTURE.md +99 -104
- package/docs/CONTEXT_MANAGEMENT.md +84 -91
- package/docs/CONTRIBUTING.md +3 -3
- package/docs/DOCS.md +18 -18
- package/docs/DOMAINS.md +18 -18
- package/docs/EXAMPLES.md +44 -44
- package/docs/GETTING_STARTED.md +63 -66
- package/docs/PERSISTENCE.md +70 -74
- package/docs/PROVIDERS.md +6 -6
- package/docs/README.md +6 -6
- package/docs/ROUTES.md +218 -220
- package/docs/STEPS.md +883 -0
- package/examples/business-onboarding.ts +85 -82
- package/examples/company-qna-agent.ts +69 -68
- package/examples/custom-database-persistence.ts +89 -91
- package/examples/declarative-agent.ts +33 -33
- package/examples/domain-scoping.ts +20 -20
- package/examples/extracted-data-modification.ts +93 -98
- package/examples/healthcare-agent.ts +90 -92
- package/examples/openai-agent.ts +30 -33
- package/examples/opensearch-persistence.ts +46 -48
- package/examples/persistent-onboarding.ts +67 -68
- package/examples/prisma-persistence.ts +111 -115
- package/examples/prisma-schema.example.prisma +3 -3
- package/examples/redis-persistence.ts +70 -76
- package/examples/route-transitions.ts +72 -48
- package/examples/rules-prohibitions.ts +31 -31
- package/examples/streaming-agent.ts +29 -29
- package/examples/travel-agent.ts +95 -110
- package/package.json +1 -1
- package/src/adapters/MemoryAdapter.ts +4 -4
- package/src/adapters/MongoAdapter.ts +4 -4
- package/src/adapters/OpenSearchAdapter.ts +8 -8
- package/src/adapters/PostgreSQLAdapter.ts +11 -11
- package/src/adapters/PrismaAdapter.ts +5 -5
- package/src/adapters/RedisAdapter.ts +4 -4
- package/src/adapters/SQLiteAdapter.ts +16 -16
- package/src/adapters/index.ts +1 -1
- package/src/constants/index.ts +4 -4
- package/src/core/Agent.ts +218 -214
- package/src/core/Events.ts +12 -12
- package/src/core/PersistenceManager.ts +32 -36
- package/src/core/ResponseEngine.ts +11 -17
- package/src/core/Route.ts +55 -49
- package/src/core/RoutingEngine.ts +252 -258
- package/src/core/Step.ts +197 -0
- package/src/core/ToolExecutor.ts +11 -11
- package/src/core/Transition.ts +72 -26
- package/src/index.ts +8 -8
- package/src/types/agent.ts +9 -9
- package/src/types/ai.ts +2 -2
- package/src/types/history.ts +3 -3
- package/src/types/index.ts +1 -1
- package/src/types/persistence.ts +6 -6
- package/src/types/route.ts +77 -61
- package/src/types/session.ts +75 -78
- package/src/types/tool.ts +17 -17
- package/src/utils/id.ts +15 -6
- package/dist/cjs/core/State.d.ts +0 -72
- package/dist/cjs/core/State.d.ts.map +0 -1
- package/dist/cjs/core/State.js +0 -148
- package/dist/cjs/core/State.js.map +0 -1
- package/dist/core/State.d.ts +0 -72
- package/dist/core/State.d.ts.map +0 -1
- package/dist/core/State.js +0 -144
- package/dist/core/State.js.map +0 -1
- package/docs/STATES.md +0 -888
- package/src/core/State.ts +0 -212
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Event } from "../types/history";
|
|
2
2
|
import type { Route } from "./Route";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Step } from "./Step";
|
|
4
4
|
import type { StructuredSchema } from "../types/schema";
|
|
5
5
|
import type { RoutingDecision } from "../types/routing";
|
|
6
|
-
import type {
|
|
6
|
+
import type { SessionStep } from "../types/session";
|
|
7
7
|
import type { AiProvider } from "../types/ai";
|
|
8
|
-
import { enterRoute,
|
|
8
|
+
import { enterRoute, mergeCollected } from "../types/session";
|
|
9
9
|
import { PromptComposer } from "./PromptComposer";
|
|
10
10
|
import { getLastMessageFromHistory } from "../utils/event";
|
|
11
11
|
import { logger } from "../utils/logger";
|
|
@@ -13,8 +13,8 @@ import { logger } from "../utils/logger";
|
|
|
13
13
|
export interface RoutingDecisionOutput {
|
|
14
14
|
context: string;
|
|
15
15
|
routes: Record<string, number>;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
selectedStepId?: string; // For active route, which step to transition to
|
|
17
|
+
stepReasoning?: string; // Why this step was selected
|
|
18
18
|
responseDirectives?: string[];
|
|
19
19
|
extractions?: Array<{
|
|
20
20
|
name: string;
|
|
@@ -37,12 +37,12 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Optimized decision for single-route scenarios
|
|
40
|
-
* Skips route scoring and only does
|
|
40
|
+
* Skips route scoring and only does step selection
|
|
41
41
|
* @private
|
|
42
42
|
*/
|
|
43
|
-
private async
|
|
43
|
+
private async decideSingleRouteStep(params: {
|
|
44
44
|
route: Route<TContext, unknown>;
|
|
45
|
-
session:
|
|
45
|
+
session: SessionStep;
|
|
46
46
|
history: Event[];
|
|
47
47
|
agentMeta?: {
|
|
48
48
|
name?: string;
|
|
@@ -50,17 +50,18 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
50
50
|
description?: string;
|
|
51
51
|
personality?: string;
|
|
52
52
|
};
|
|
53
|
-
|
|
53
|
+
provider: AiProvider;
|
|
54
54
|
context: TContext;
|
|
55
55
|
signal?: AbortSignal;
|
|
56
56
|
}): Promise<{
|
|
57
57
|
selectedRoute?: Route<TContext>;
|
|
58
|
-
|
|
58
|
+
selectedStep?: Step<TContext>;
|
|
59
59
|
responseDirectives?: string[];
|
|
60
|
-
session:
|
|
60
|
+
session: SessionStep;
|
|
61
61
|
isRouteComplete?: boolean;
|
|
62
62
|
}> {
|
|
63
|
-
const { route, session, history, agentMeta,
|
|
63
|
+
const { route, session, history, agentMeta, provider, context, signal } =
|
|
64
|
+
params;
|
|
64
65
|
|
|
65
66
|
let updatedSession = session;
|
|
66
67
|
const selectedRoute = route;
|
|
@@ -69,7 +70,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
69
70
|
if (!session.currentRoute || session.currentRoute.id !== route.id) {
|
|
70
71
|
updatedSession = enterRoute(session, route.id, route.title);
|
|
71
72
|
if (route.initialData) {
|
|
72
|
-
updatedSession =
|
|
73
|
+
updatedSession = mergeCollected(updatedSession, route.initialData);
|
|
73
74
|
logger.debug(
|
|
74
75
|
`[RoutingEngine] Single-route: Merged initial data:`,
|
|
75
76
|
route.initialData
|
|
@@ -80,18 +81,18 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
// Get candidate
|
|
84
|
-
const
|
|
85
|
-
? route.
|
|
84
|
+
// Get candidate steps
|
|
85
|
+
const currentStep = updatedSession.currentStep
|
|
86
|
+
? route.getStep(updatedSession.currentStep.id)
|
|
86
87
|
: undefined;
|
|
87
|
-
const candidates = this.
|
|
88
|
+
const candidates = this.getCandidateSteps(
|
|
88
89
|
route,
|
|
89
|
-
|
|
90
|
-
updatedSession.
|
|
90
|
+
currentStep,
|
|
91
|
+
updatedSession.data || {}
|
|
91
92
|
);
|
|
92
93
|
|
|
93
94
|
if (candidates.length === 0) {
|
|
94
|
-
logger.warn(`[RoutingEngine] Single-route: No valid
|
|
95
|
+
logger.warn(`[RoutingEngine] Single-route: No valid steps found`);
|
|
95
96
|
return { selectedRoute, session: updatedSession };
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -100,233 +101,229 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
100
101
|
const isRouteComplete = candidates[0].isRouteComplete;
|
|
101
102
|
if (isRouteComplete) {
|
|
102
103
|
logger.debug(
|
|
103
|
-
`[RoutingEngine] Single-route: Route complete - all data collected,
|
|
104
|
+
`[RoutingEngine] Single-route: Route complete - all data collected, END_ROUTE reached`
|
|
104
105
|
);
|
|
105
|
-
// Don't return a
|
|
106
|
+
// Don't return a selectedStep when route is complete - there's no step to enter
|
|
106
107
|
return {
|
|
107
108
|
selectedRoute,
|
|
108
|
-
|
|
109
|
+
selectedStep: undefined,
|
|
109
110
|
session: updatedSession,
|
|
110
111
|
isRouteComplete: true,
|
|
111
112
|
};
|
|
112
113
|
} else {
|
|
113
114
|
logger.debug(
|
|
114
|
-
`[RoutingEngine] Single-route: Only one valid
|
|
115
|
+
`[RoutingEngine] Single-route: Only one valid step: ${candidates[0].step.id}`
|
|
115
116
|
);
|
|
116
117
|
return {
|
|
117
118
|
selectedRoute,
|
|
118
|
-
|
|
119
|
+
selectedStep: candidates[0].step,
|
|
119
120
|
session: updatedSession,
|
|
120
121
|
isRouteComplete: false,
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
// Multiple candidates - use AI to select best
|
|
126
|
+
// Multiple candidates - use AI to select best step
|
|
126
127
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
127
|
-
const
|
|
128
|
+
const stepPrompt = this.buildStepSelectionPrompt(
|
|
128
129
|
route,
|
|
129
|
-
|
|
130
|
+
currentStep,
|
|
130
131
|
candidates,
|
|
131
|
-
updatedSession.
|
|
132
|
+
updatedSession.data || {},
|
|
132
133
|
history,
|
|
133
134
|
lastUserMessage,
|
|
134
135
|
agentMeta
|
|
135
136
|
);
|
|
136
137
|
|
|
137
|
-
const
|
|
138
|
-
candidates.map((c) => c.
|
|
138
|
+
const stepSchema = this.buildStepSelectionSchema(
|
|
139
|
+
candidates.map((c) => c.step.id)
|
|
139
140
|
);
|
|
140
141
|
|
|
141
|
-
const
|
|
142
|
+
const stepResult = await provider.generateMessage<
|
|
142
143
|
TContext,
|
|
143
144
|
{
|
|
144
145
|
reasoning: string;
|
|
145
|
-
|
|
146
|
+
selectedStepId: string;
|
|
146
147
|
responseDirectives?: string[];
|
|
147
148
|
}
|
|
148
149
|
>({
|
|
149
|
-
prompt:
|
|
150
|
+
prompt: stepPrompt,
|
|
150
151
|
history,
|
|
151
152
|
context,
|
|
152
153
|
signal,
|
|
153
154
|
parameters: {
|
|
154
|
-
jsonSchema:
|
|
155
|
-
schemaName: "
|
|
155
|
+
jsonSchema: stepSchema,
|
|
156
|
+
schemaName: "step_selection",
|
|
156
157
|
},
|
|
157
158
|
});
|
|
158
159
|
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
(c) => c.
|
|
162
|
-
)?.
|
|
160
|
+
const selectedStepId = stepResult.structured?.selectedStepId;
|
|
161
|
+
const selectedStep = candidates.find(
|
|
162
|
+
(c) => c.step.id === selectedStepId
|
|
163
|
+
)?.step;
|
|
163
164
|
|
|
164
|
-
if (
|
|
165
|
+
if (selectedStep) {
|
|
165
166
|
logger.debug(
|
|
166
|
-
`[RoutingEngine] Single-route: AI selected
|
|
167
|
+
`[RoutingEngine] Single-route: AI selected step: ${selectedStep.id}`
|
|
167
168
|
);
|
|
168
169
|
logger.debug(
|
|
169
|
-
`[RoutingEngine] Single-route: Reasoning: ${
|
|
170
|
+
`[RoutingEngine] Single-route: Reasoning: ${stepResult.structured?.reasoning}`
|
|
170
171
|
);
|
|
171
172
|
} else {
|
|
172
173
|
logger.warn(
|
|
173
|
-
`[RoutingEngine] Single-route: Invalid
|
|
174
|
+
`[RoutingEngine] Single-route: Invalid step ID returned, using first candidate`
|
|
174
175
|
);
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
return {
|
|
178
179
|
selectedRoute,
|
|
179
|
-
|
|
180
|
-
responseDirectives:
|
|
180
|
+
selectedStep: selectedStep || candidates[0].step,
|
|
181
|
+
responseDirectives: stepResult.structured?.responseDirectives,
|
|
181
182
|
session: updatedSession,
|
|
182
183
|
};
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
/**
|
|
186
|
-
* Recursively traverse
|
|
187
|
+
* Recursively traverse step chain to find first non-skipped step or END_ROUTE
|
|
187
188
|
* @private
|
|
188
189
|
*/
|
|
189
|
-
private
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
private findFirstValidStepRecursive<TData = unknown>(
|
|
191
|
+
currentStep: Step<TContext, TData>,
|
|
192
|
+
data: Partial<TData>,
|
|
192
193
|
visited: Set<string>
|
|
193
194
|
): {
|
|
194
|
-
|
|
195
|
+
step?: Step<TContext, TData>;
|
|
195
196
|
condition?: string;
|
|
196
197
|
isRouteComplete?: boolean;
|
|
197
198
|
} {
|
|
198
199
|
// Prevent infinite loops
|
|
199
|
-
if (visited.has(
|
|
200
|
+
if (visited.has(currentStep.id)) {
|
|
200
201
|
return {};
|
|
201
202
|
}
|
|
202
|
-
visited.add(
|
|
203
|
+
visited.add(currentStep.id);
|
|
203
204
|
|
|
204
|
-
const transitions =
|
|
205
|
+
const transitions = currentStep.getTransitions();
|
|
205
206
|
|
|
206
207
|
for (const transition of transitions) {
|
|
207
208
|
const target = transition.getTarget();
|
|
208
209
|
|
|
209
|
-
// Check for
|
|
210
|
+
// Check for END_ROUTE transition
|
|
210
211
|
if (
|
|
211
212
|
!target &&
|
|
212
|
-
transition.spec.
|
|
213
|
-
typeof transition.spec.
|
|
213
|
+
transition.spec.step &&
|
|
214
|
+
typeof transition.spec.step === "symbol"
|
|
214
215
|
) {
|
|
215
|
-
// Found
|
|
216
|
+
// Found END_ROUTE - route is complete
|
|
216
217
|
return {
|
|
217
|
-
isRouteComplete: true
|
|
218
|
+
isRouteComplete: true,
|
|
218
219
|
};
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
if (!target) continue;
|
|
222
223
|
|
|
223
|
-
// If target should NOT be skipped, we found our
|
|
224
|
-
if (!target.shouldSkip(
|
|
224
|
+
// If target should NOT be skipped, we found our step
|
|
225
|
+
if (!target.shouldSkip(data)) {
|
|
225
226
|
logger.debug(
|
|
226
|
-
`[RoutingEngine] Found valid
|
|
227
|
+
`[RoutingEngine] Found valid step after skipping: ${target.id}`
|
|
227
228
|
);
|
|
228
229
|
return {
|
|
229
|
-
|
|
230
|
+
step: target,
|
|
230
231
|
condition: transition.condition,
|
|
231
232
|
};
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
// Target should be skipped too - recurse deeper
|
|
235
236
|
logger.debug(
|
|
236
|
-
`[RoutingEngine] Skipping
|
|
237
|
-
);
|
|
238
|
-
const result = this.findFirstValidStateRecursive(
|
|
239
|
-
target,
|
|
240
|
-
extracted,
|
|
241
|
-
visited
|
|
237
|
+
`[RoutingEngine] Skipping step ${target.id} (skipIf condition met), continuing traversal...`
|
|
242
238
|
);
|
|
239
|
+
const result = this.findFirstValidStepRecursive(target, data, visited);
|
|
243
240
|
|
|
244
|
-
// If we found something (a valid
|
|
245
|
-
if (result.
|
|
241
|
+
// If we found something (a valid step or END_ROUTE), return it
|
|
242
|
+
if (result.step || result.isRouteComplete) {
|
|
246
243
|
return result;
|
|
247
244
|
}
|
|
248
245
|
}
|
|
249
246
|
|
|
250
|
-
// No valid
|
|
247
|
+
// No valid steps or END_ROUTE found in this branch
|
|
251
248
|
return {};
|
|
252
249
|
}
|
|
253
250
|
|
|
254
251
|
/**
|
|
255
|
-
* Identify valid next candidate
|
|
256
|
-
* Returns
|
|
252
|
+
* Identify valid next candidate steps based on current step and collected data
|
|
253
|
+
* Returns step with isRouteComplete flag if route is complete (all steps skipped + has END_ROUTE transition)
|
|
257
254
|
*/
|
|
258
|
-
|
|
259
|
-
route: Route<TContext,
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
getCandidateSteps<TData = unknown>(
|
|
256
|
+
route: Route<TContext, TData>,
|
|
257
|
+
currentStep: Step<TContext, TData> | undefined,
|
|
258
|
+
data: Partial<TData>
|
|
262
259
|
): Array<{
|
|
263
|
-
|
|
260
|
+
step: Step<TContext, TData>;
|
|
264
261
|
condition?: string;
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
requires?: string[];
|
|
263
|
+
collectFields?: string[];
|
|
267
264
|
isRouteComplete?: boolean;
|
|
268
265
|
}> {
|
|
269
266
|
const candidates: Array<{
|
|
270
|
-
|
|
267
|
+
step: Step<TContext, TData>;
|
|
271
268
|
condition?: string;
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
requires?: string[];
|
|
270
|
+
collectFields?: string[];
|
|
274
271
|
isRouteComplete?: boolean;
|
|
275
272
|
}> = [];
|
|
276
273
|
|
|
277
|
-
if (!
|
|
278
|
-
const
|
|
279
|
-
if (
|
|
280
|
-
// Initial
|
|
281
|
-
const result = this.
|
|
282
|
-
|
|
283
|
-
|
|
274
|
+
if (!currentStep) {
|
|
275
|
+
const initialStep = route.initialStep;
|
|
276
|
+
if (initialStep.shouldSkip(data)) {
|
|
277
|
+
// Initial step should be skipped - recursively traverse to find first non-skipped step or END_ROUTE
|
|
278
|
+
const result = this.findFirstValidStepRecursive(
|
|
279
|
+
initialStep,
|
|
280
|
+
data,
|
|
284
281
|
new Set<string>()
|
|
285
282
|
);
|
|
286
283
|
|
|
287
284
|
if (result.isRouteComplete) {
|
|
288
|
-
// All
|
|
285
|
+
// All steps are skipped and we reached END_ROUTE
|
|
289
286
|
logger.debug(
|
|
290
|
-
`[RoutingEngine] Route complete on entry: all
|
|
287
|
+
`[RoutingEngine] Route complete on entry: all steps skipped, END_ROUTE reached`
|
|
291
288
|
);
|
|
292
289
|
return [
|
|
293
290
|
{
|
|
294
|
-
|
|
291
|
+
step: initialStep,
|
|
295
292
|
condition: "Route complete - all data collected on entry",
|
|
296
293
|
isRouteComplete: true,
|
|
297
294
|
},
|
|
298
295
|
];
|
|
299
|
-
} else if (result.
|
|
300
|
-
// Found a non-skipped
|
|
296
|
+
} else if (result.step) {
|
|
297
|
+
// Found a non-skipped step
|
|
301
298
|
candidates.push({
|
|
302
|
-
|
|
299
|
+
step: result.step,
|
|
303
300
|
condition: result.condition,
|
|
304
|
-
|
|
305
|
-
|
|
301
|
+
requires: result.step.requires,
|
|
302
|
+
collectFields: result.step.collectFields,
|
|
306
303
|
});
|
|
307
304
|
}
|
|
308
|
-
// If no
|
|
305
|
+
// If no step found and not complete, fall through to return empty candidates
|
|
309
306
|
} else {
|
|
310
307
|
candidates.push({
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
308
|
+
step: initialStep,
|
|
309
|
+
requires: initialStep.requires,
|
|
310
|
+
collectFields: initialStep.collectFields,
|
|
314
311
|
});
|
|
315
312
|
}
|
|
316
313
|
return candidates;
|
|
317
314
|
}
|
|
318
315
|
|
|
319
|
-
const transitions =
|
|
316
|
+
const transitions = currentStep.getTransitions();
|
|
320
317
|
let hasEndRoute = false;
|
|
321
318
|
|
|
322
319
|
for (const transition of transitions) {
|
|
323
320
|
const target = transition.getTarget();
|
|
324
321
|
|
|
325
|
-
// Check for
|
|
322
|
+
// Check for END_ROUTE transition (no target step)
|
|
326
323
|
if (
|
|
327
324
|
!target &&
|
|
328
|
-
transition.spec.
|
|
329
|
-
typeof transition.spec.
|
|
325
|
+
transition.spec.step &&
|
|
326
|
+
typeof transition.spec.step === "symbol"
|
|
330
327
|
) {
|
|
331
328
|
hasEndRoute = true;
|
|
332
329
|
continue;
|
|
@@ -334,64 +331,64 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
334
331
|
|
|
335
332
|
if (!target) continue;
|
|
336
333
|
|
|
337
|
-
if (target.shouldSkip(
|
|
334
|
+
if (target.shouldSkip(data)) {
|
|
338
335
|
logger.debug(
|
|
339
|
-
`[RoutingEngine] Skipping
|
|
336
|
+
`[RoutingEngine] Skipping step ${target.id} (skipIf condition met)`
|
|
340
337
|
);
|
|
341
338
|
|
|
342
|
-
// Recursively traverse to find next valid
|
|
343
|
-
const result = this.
|
|
339
|
+
// Recursively traverse to find next valid step or END_ROUTE
|
|
340
|
+
const result = this.findFirstValidStepRecursive(
|
|
344
341
|
target,
|
|
345
|
-
|
|
346
|
-
new Set<string>([
|
|
342
|
+
data,
|
|
343
|
+
new Set<string>([currentStep.id]) // Already visited current step
|
|
347
344
|
);
|
|
348
345
|
|
|
349
346
|
if (result.isRouteComplete) {
|
|
350
347
|
hasEndRoute = true;
|
|
351
|
-
} else if (result.
|
|
352
|
-
// Found a non-skipped
|
|
348
|
+
} else if (result.step) {
|
|
349
|
+
// Found a non-skipped step deeper in the chain
|
|
353
350
|
candidates.push({
|
|
354
|
-
|
|
351
|
+
step: result.step,
|
|
355
352
|
condition: result.condition || transition.condition,
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
requires: result.step.requires,
|
|
354
|
+
collectFields: result.step.collectFields,
|
|
358
355
|
});
|
|
359
356
|
}
|
|
360
357
|
continue;
|
|
361
358
|
}
|
|
362
359
|
|
|
363
360
|
candidates.push({
|
|
364
|
-
|
|
361
|
+
step: target,
|
|
365
362
|
condition: transition.condition,
|
|
366
|
-
|
|
367
|
-
|
|
363
|
+
requires: target.requires,
|
|
364
|
+
collectFields: target.collectFields,
|
|
368
365
|
});
|
|
369
366
|
}
|
|
370
367
|
|
|
371
368
|
// If no valid candidates found
|
|
372
369
|
if (candidates.length === 0) {
|
|
373
|
-
// If current
|
|
370
|
+
// If current step has END_ROUTE transition, the route is complete
|
|
374
371
|
if (hasEndRoute) {
|
|
375
372
|
logger.debug(
|
|
376
|
-
`[RoutingEngine] Route complete: all
|
|
373
|
+
`[RoutingEngine] Route complete: all steps processed, END_ROUTE reached`
|
|
377
374
|
);
|
|
378
|
-
// Return current
|
|
375
|
+
// Return current step with completion flag
|
|
379
376
|
return [
|
|
380
377
|
{
|
|
381
|
-
|
|
378
|
+
step: currentStep,
|
|
382
379
|
condition: "Route complete - all data collected",
|
|
383
380
|
isRouteComplete: true,
|
|
384
381
|
},
|
|
385
382
|
];
|
|
386
383
|
}
|
|
387
384
|
|
|
388
|
-
// Otherwise, stay in current
|
|
389
|
-
if (!
|
|
385
|
+
// Otherwise, stay in current step if it's still valid
|
|
386
|
+
if (!currentStep.shouldSkip(data)) {
|
|
390
387
|
candidates.push({
|
|
391
|
-
|
|
392
|
-
condition: "Continue in current
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
step: currentStep,
|
|
389
|
+
condition: "Continue in current step (no valid transitions)",
|
|
390
|
+
requires: currentStep.requires,
|
|
391
|
+
collectFields: currentStep.collectFields,
|
|
395
392
|
});
|
|
396
393
|
}
|
|
397
394
|
}
|
|
@@ -400,14 +397,14 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
400
397
|
}
|
|
401
398
|
|
|
402
399
|
/**
|
|
403
|
-
* Full routing orchestration: builds prompt and schema, calls AI, selects route/
|
|
400
|
+
* Full routing orchestration: builds prompt and schema, calls AI, selects route/step,
|
|
404
401
|
* and updates the session (including initialData merge when entering a new route).
|
|
405
402
|
*
|
|
406
|
-
* OPTIMIZATION: If there's only 1 route, skips route scoring and only does
|
|
403
|
+
* OPTIMIZATION: If there's only 1 route, skips route scoring and only does step selection.
|
|
407
404
|
*/
|
|
408
|
-
async
|
|
405
|
+
async decideRouteAndStep(params: {
|
|
409
406
|
routes: Route<TContext, unknown>[];
|
|
410
|
-
session:
|
|
407
|
+
session: SessionStep;
|
|
411
408
|
history: Event[];
|
|
412
409
|
agentMeta?: {
|
|
413
410
|
name?: string;
|
|
@@ -415,30 +412,31 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
415
412
|
description?: string;
|
|
416
413
|
personality?: string;
|
|
417
414
|
};
|
|
418
|
-
|
|
415
|
+
provider: AiProvider;
|
|
419
416
|
context: TContext;
|
|
420
417
|
signal?: AbortSignal;
|
|
421
418
|
}): Promise<{
|
|
422
419
|
selectedRoute?: Route<TContext>;
|
|
423
|
-
|
|
420
|
+
selectedStep?: Step<TContext>;
|
|
424
421
|
responseDirectives?: string[];
|
|
425
|
-
session:
|
|
422
|
+
session: SessionStep;
|
|
426
423
|
isRouteComplete?: boolean;
|
|
427
424
|
}> {
|
|
428
|
-
const { routes, session, history, agentMeta,
|
|
425
|
+
const { routes, session, history, agentMeta, provider, context, signal } =
|
|
426
|
+
params;
|
|
429
427
|
|
|
430
428
|
if (routes.length === 0) {
|
|
431
429
|
return { session };
|
|
432
430
|
}
|
|
433
431
|
|
|
434
|
-
// OPTIMIZATION: Single route - skip route scoring, only do
|
|
432
|
+
// OPTIMIZATION: Single route - skip route scoring, only do step selection
|
|
435
433
|
if (routes.length === 1) {
|
|
436
|
-
return this.
|
|
434
|
+
return this.decideSingleRouteStep({
|
|
437
435
|
route: routes[0],
|
|
438
436
|
session,
|
|
439
437
|
history,
|
|
440
438
|
agentMeta,
|
|
441
|
-
|
|
439
|
+
provider,
|
|
442
440
|
context,
|
|
443
441
|
signal,
|
|
444
442
|
});
|
|
@@ -446,13 +444,13 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
446
444
|
|
|
447
445
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
448
446
|
|
|
449
|
-
let
|
|
447
|
+
let activeRouteSteps:
|
|
450
448
|
| Array<{
|
|
451
|
-
|
|
449
|
+
stepId: string;
|
|
452
450
|
description: string;
|
|
453
451
|
condition?: string;
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
requires?: string[];
|
|
453
|
+
collectFields?: string[];
|
|
456
454
|
}>
|
|
457
455
|
| undefined;
|
|
458
456
|
let activeRoute: Route<TContext> | undefined;
|
|
@@ -461,13 +459,13 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
461
459
|
if (session.currentRoute) {
|
|
462
460
|
activeRoute = routes.find((r) => r.id === session.currentRoute?.id);
|
|
463
461
|
if (activeRoute) {
|
|
464
|
-
const
|
|
465
|
-
? activeRoute.
|
|
462
|
+
const currentStep = session.currentStep
|
|
463
|
+
? activeRoute.getStep(session.currentStep.id)
|
|
466
464
|
: undefined;
|
|
467
|
-
const candidates = this.
|
|
465
|
+
const candidates = this.getCandidateSteps(
|
|
468
466
|
activeRoute,
|
|
469
|
-
|
|
470
|
-
session.
|
|
467
|
+
currentStep,
|
|
468
|
+
session.data || {}
|
|
471
469
|
);
|
|
472
470
|
|
|
473
471
|
// Check if route is complete
|
|
@@ -476,18 +474,18 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
476
474
|
logger.debug(
|
|
477
475
|
`[RoutingEngine] Route ${activeRoute.title} is complete - all data collected`
|
|
478
476
|
);
|
|
479
|
-
// Don't include
|
|
480
|
-
|
|
477
|
+
// Don't include steps in routing if route is complete
|
|
478
|
+
activeRouteSteps = undefined;
|
|
481
479
|
} else {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
description: c.
|
|
480
|
+
activeRouteSteps = candidates.map((c) => ({
|
|
481
|
+
stepId: c.step.id,
|
|
482
|
+
description: c.step.description || "",
|
|
485
483
|
condition: c.condition,
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
requires: c.requires,
|
|
485
|
+
collectFields: c.collectFields,
|
|
488
486
|
}));
|
|
489
487
|
logger.debug(
|
|
490
|
-
`[RoutingEngine] Found ${
|
|
488
|
+
`[RoutingEngine] Found ${activeRouteSteps.length} candidate steps for active route`
|
|
491
489
|
);
|
|
492
490
|
}
|
|
493
491
|
}
|
|
@@ -496,7 +494,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
496
494
|
const routingSchema = this.buildDynamicRoutingSchema(
|
|
497
495
|
routes,
|
|
498
496
|
undefined,
|
|
499
|
-
|
|
497
|
+
activeRouteSteps
|
|
500
498
|
);
|
|
501
499
|
|
|
502
500
|
const routingPrompt = this.buildRoutingPrompt(
|
|
@@ -505,10 +503,10 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
505
503
|
lastUserMessage,
|
|
506
504
|
agentMeta,
|
|
507
505
|
session,
|
|
508
|
-
|
|
506
|
+
activeRouteSteps
|
|
509
507
|
);
|
|
510
508
|
|
|
511
|
-
const routingResult = await
|
|
509
|
+
const routingResult = await provider.generateMessage<
|
|
512
510
|
TContext,
|
|
513
511
|
RoutingDecisionOutput
|
|
514
512
|
>({
|
|
@@ -523,7 +521,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
523
521
|
});
|
|
524
522
|
|
|
525
523
|
let selectedRoute: Route<TContext> | undefined;
|
|
526
|
-
let
|
|
524
|
+
let selectedStep: Step<TContext> | undefined;
|
|
527
525
|
let responseDirectives: string[] | undefined;
|
|
528
526
|
let updatedSession = session;
|
|
529
527
|
|
|
@@ -538,18 +536,18 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
538
536
|
|
|
539
537
|
if (
|
|
540
538
|
selectedRoute === activeRoute &&
|
|
541
|
-
routingResult.structured.
|
|
539
|
+
routingResult.structured.selectedStepId &&
|
|
542
540
|
activeRoute
|
|
543
541
|
) {
|
|
544
|
-
|
|
545
|
-
routingResult.structured.
|
|
542
|
+
selectedStep = activeRoute.getStep(
|
|
543
|
+
routingResult.structured.selectedStepId
|
|
546
544
|
);
|
|
547
|
-
if (
|
|
545
|
+
if (selectedStep) {
|
|
548
546
|
logger.debug(
|
|
549
|
-
`[RoutingEngine] AI selected
|
|
547
|
+
`[RoutingEngine] AI selected step: ${selectedStep.id} in active route`
|
|
550
548
|
);
|
|
551
549
|
logger.debug(
|
|
552
|
-
`[RoutingEngine]
|
|
550
|
+
`[RoutingEngine] Step reasoning: ${routingResult.structured.stepReasoning}`
|
|
553
551
|
);
|
|
554
552
|
}
|
|
555
553
|
}
|
|
@@ -566,7 +564,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
566
564
|
selectedRoute.title
|
|
567
565
|
);
|
|
568
566
|
if (selectedRoute.initialData) {
|
|
569
|
-
updatedSession =
|
|
567
|
+
updatedSession = mergeCollected(
|
|
570
568
|
updatedSession,
|
|
571
569
|
selectedRoute.initialData
|
|
572
570
|
);
|
|
@@ -582,7 +580,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
582
580
|
|
|
583
581
|
return {
|
|
584
582
|
selectedRoute,
|
|
585
|
-
|
|
583
|
+
selectedStep,
|
|
586
584
|
responseDirectives,
|
|
587
585
|
session: updatedSession,
|
|
588
586
|
isRouteComplete,
|
|
@@ -590,19 +588,19 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
590
588
|
}
|
|
591
589
|
|
|
592
590
|
/**
|
|
593
|
-
* Build prompt for
|
|
591
|
+
* Build prompt for step selection within a single route
|
|
594
592
|
* @private
|
|
595
593
|
*/
|
|
596
|
-
private
|
|
594
|
+
private buildStepSelectionPrompt(
|
|
597
595
|
route: Route<TContext>,
|
|
598
|
-
|
|
596
|
+
currentStep: Step<TContext> | undefined,
|
|
599
597
|
candidates: Array<{
|
|
600
|
-
|
|
598
|
+
step: Step<TContext>;
|
|
601
599
|
condition?: string;
|
|
602
|
-
|
|
603
|
-
|
|
600
|
+
requires?: string[];
|
|
601
|
+
collectFields?: string[];
|
|
604
602
|
}>,
|
|
605
|
-
|
|
603
|
+
data: Partial<unknown>,
|
|
606
604
|
history: Event[],
|
|
607
605
|
lastMessage: string,
|
|
608
606
|
agentMeta?: {
|
|
@@ -632,71 +630,71 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
632
630
|
`Active Route: ${route.title}\nDescription: ${route.description || "N/A"}`
|
|
633
631
|
);
|
|
634
632
|
|
|
635
|
-
// Add current
|
|
636
|
-
if (
|
|
633
|
+
// Add current step context
|
|
634
|
+
if (currentStep) {
|
|
637
635
|
pc.addInstruction(
|
|
638
|
-
`Current
|
|
639
|
-
|
|
636
|
+
`Current Step: ${currentStep.id}\nDescription: ${
|
|
637
|
+
currentStep.description || "N/A"
|
|
640
638
|
}`
|
|
641
639
|
);
|
|
642
640
|
} else {
|
|
643
|
-
pc.addInstruction("Current
|
|
641
|
+
pc.addInstruction("Current Step: None (entering route)");
|
|
644
642
|
}
|
|
645
643
|
|
|
646
|
-
// Add
|
|
647
|
-
if (Object.keys(
|
|
644
|
+
// Add collected data context
|
|
645
|
+
if (Object.keys(data).length > 0) {
|
|
648
646
|
pc.addInstruction(
|
|
649
|
-
`
|
|
647
|
+
`Collected Data So Far:\n${JSON.stringify(data, null, 2)}`
|
|
650
648
|
);
|
|
651
649
|
} else {
|
|
652
|
-
pc.addInstruction("
|
|
650
|
+
pc.addInstruction("Collected Data: None yet");
|
|
653
651
|
}
|
|
654
652
|
|
|
655
653
|
// Add conversation history
|
|
656
654
|
pc.addInteractionHistory(history);
|
|
657
655
|
pc.addLastMessage(lastMessage);
|
|
658
656
|
|
|
659
|
-
// Add candidate
|
|
660
|
-
const
|
|
657
|
+
// Add candidate steps
|
|
658
|
+
const stepDescriptions = candidates.map((candidate, idx) => {
|
|
661
659
|
const parts = [
|
|
662
|
-
`${idx + 1}.
|
|
663
|
-
` Description: ${candidate.
|
|
660
|
+
`${idx + 1}. Step ID: ${candidate.step.id}`,
|
|
661
|
+
` Description: ${candidate.step.description || "N/A"}`,
|
|
664
662
|
];
|
|
665
663
|
|
|
666
664
|
if (candidate.condition) {
|
|
667
665
|
parts.push(` Condition: ${candidate.condition}`);
|
|
668
666
|
}
|
|
669
667
|
|
|
670
|
-
if (candidate.
|
|
671
|
-
parts.push(` Required Data: ${candidate.
|
|
668
|
+
if (candidate.requires && candidate.requires.length > 0) {
|
|
669
|
+
parts.push(` Required Data: ${candidate.requires.join(", ")}`);
|
|
672
670
|
}
|
|
673
671
|
|
|
674
|
-
if (candidate.
|
|
675
|
-
parts.push(`
|
|
672
|
+
if (candidate.collectFields && candidate.collectFields.length > 0) {
|
|
673
|
+
parts.push(` Collects: ${candidate.collectFields.join(", ")}`);
|
|
676
674
|
}
|
|
677
675
|
|
|
678
676
|
return parts.join("\n");
|
|
679
677
|
});
|
|
680
678
|
|
|
681
679
|
pc.addInstruction(
|
|
682
|
-
`Available
|
|
680
|
+
`Available Steps to Transition To:\n${stepDescriptions.join("\n\n")}`
|
|
683
681
|
);
|
|
684
682
|
|
|
685
683
|
// Add decision instructions
|
|
686
684
|
pc.addInstruction(
|
|
687
685
|
[
|
|
688
|
-
"Task: Decide which
|
|
686
|
+
"Task: Decide which step to transition to based on:",
|
|
689
687
|
"1. The user's current message and intent",
|
|
690
688
|
"2. The conversation history and context",
|
|
691
|
-
"3. The
|
|
692
|
-
"4. The conditions and requirements of each
|
|
689
|
+
"3. The collected data we already have",
|
|
690
|
+
"4. The conditions and requirements of each step",
|
|
693
691
|
"5. The logical flow of the conversation",
|
|
694
692
|
"",
|
|
695
693
|
"Rules:",
|
|
696
|
-
"- If a
|
|
697
|
-
"- If a
|
|
698
|
-
"- Choose the
|
|
699
|
-
"-
|
|
694
|
+
"- If a step has a condition, evaluate whether it's met based on context",
|
|
695
|
+
"- If a step requires data we don't have, consider if we should collect it now",
|
|
696
|
+
"- Choose the step that makes the most sense for moving the conversation forward",
|
|
697
|
+
"- Steps with skipIf conditions that are met have already been filtered out",
|
|
700
698
|
"",
|
|
701
699
|
"Return ONLY JSON matching the provided schema.",
|
|
702
700
|
].join("\n")
|
|
@@ -706,25 +704,25 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
706
704
|
}
|
|
707
705
|
|
|
708
706
|
/**
|
|
709
|
-
* Build schema for
|
|
707
|
+
* Build schema for step selection
|
|
710
708
|
* @private
|
|
711
709
|
*/
|
|
712
|
-
private
|
|
710
|
+
private buildStepSelectionSchema(validStepIds: string[]): StructuredSchema {
|
|
713
711
|
return {
|
|
714
712
|
description:
|
|
715
|
-
"
|
|
713
|
+
"Step transition decision based on conversation context and collected data",
|
|
716
714
|
type: "object",
|
|
717
715
|
properties: {
|
|
718
716
|
reasoning: {
|
|
719
717
|
type: "string",
|
|
720
718
|
nullable: false,
|
|
721
|
-
description: "Brief explanation of why this
|
|
719
|
+
description: "Brief explanation of why this step was selected",
|
|
722
720
|
},
|
|
723
|
-
|
|
721
|
+
selectedStepId: {
|
|
724
722
|
type: "string",
|
|
725
723
|
nullable: false,
|
|
726
|
-
description: "The ID of the selected
|
|
727
|
-
enum:
|
|
724
|
+
description: "The ID of the selected step to transition to",
|
|
725
|
+
enum: validStepIds,
|
|
728
726
|
},
|
|
729
727
|
responseDirectives: {
|
|
730
728
|
type: "array",
|
|
@@ -733,7 +731,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
733
731
|
"Optional bullet points the response should address (concise)",
|
|
734
732
|
},
|
|
735
733
|
},
|
|
736
|
-
required: ["reasoning", "
|
|
734
|
+
required: ["reasoning", "selectedStepId"],
|
|
737
735
|
additionalProperties: false,
|
|
738
736
|
};
|
|
739
737
|
}
|
|
@@ -741,7 +739,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
741
739
|
buildDynamicRoutingSchema(
|
|
742
740
|
routes: Route<TContext>[],
|
|
743
741
|
extrasSchema?: StructuredSchema,
|
|
744
|
-
|
|
742
|
+
activeRouteSteps?: { stepId: string; description: string }[]
|
|
745
743
|
): StructuredSchema {
|
|
746
744
|
const routeIds = routes.map((r) => r.id);
|
|
747
745
|
const routeProperties: Record<string, StructuredSchema> = {};
|
|
@@ -783,25 +781,25 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
783
781
|
additionalProperties: false,
|
|
784
782
|
};
|
|
785
783
|
|
|
786
|
-
// Add
|
|
787
|
-
if (
|
|
784
|
+
// Add step selection fields if there's an active route with steps
|
|
785
|
+
if (activeRouteSteps && activeRouteSteps.length > 0) {
|
|
788
786
|
base.properties = base.properties || {};
|
|
789
|
-
base.properties.
|
|
787
|
+
base.properties.selectedStepId = {
|
|
790
788
|
type: "string",
|
|
791
789
|
nullable: false,
|
|
792
790
|
description:
|
|
793
|
-
"The
|
|
794
|
-
enum:
|
|
791
|
+
"The step ID to transition to within the active route (required if continuing in current route)",
|
|
792
|
+
enum: activeRouteSteps.map((s) => s.stepId),
|
|
795
793
|
};
|
|
796
|
-
base.properties.
|
|
794
|
+
base.properties.stepReasoning = {
|
|
797
795
|
type: "string",
|
|
798
796
|
nullable: false,
|
|
799
|
-
description: "Brief explanation of why this
|
|
797
|
+
description: "Brief explanation of why this step was selected",
|
|
800
798
|
};
|
|
801
799
|
base.required = [
|
|
802
800
|
...(base.required || []),
|
|
803
|
-
"
|
|
804
|
-
"
|
|
801
|
+
"selectedStepId",
|
|
802
|
+
"stepReasoning",
|
|
805
803
|
];
|
|
806
804
|
}
|
|
807
805
|
|
|
@@ -823,13 +821,13 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
823
821
|
description?: string;
|
|
824
822
|
personality?: string;
|
|
825
823
|
},
|
|
826
|
-
session?:
|
|
827
|
-
|
|
828
|
-
|
|
824
|
+
session?: SessionStep,
|
|
825
|
+
activeRouteSteps?: Array<{
|
|
826
|
+
stepId: string;
|
|
829
827
|
description: string;
|
|
830
828
|
condition?: string;
|
|
831
|
-
|
|
832
|
-
|
|
829
|
+
requires?: string[];
|
|
830
|
+
collectFields?: string[];
|
|
833
831
|
}>
|
|
834
832
|
): string {
|
|
835
833
|
const pc = new PromptComposer();
|
|
@@ -853,54 +851,50 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
853
851
|
"Current conversation context:",
|
|
854
852
|
`- Active route: ${session.currentRoute.title} (${session.currentRoute.id})`,
|
|
855
853
|
];
|
|
856
|
-
if (session.
|
|
857
|
-
sessionInfo.push(`- Current
|
|
858
|
-
if (session.
|
|
859
|
-
sessionInfo.push(` "${session.
|
|
854
|
+
if (session.currentStep) {
|
|
855
|
+
sessionInfo.push(`- Current step: ${session.currentStep.id}`);
|
|
856
|
+
if (session.currentStep.description) {
|
|
857
|
+
sessionInfo.push(` "${session.currentStep.description}"`);
|
|
860
858
|
}
|
|
861
859
|
}
|
|
862
|
-
if (session.
|
|
863
|
-
sessionInfo.push(
|
|
864
|
-
`- Extracted data: ${JSON.stringify(session.extracted)}`
|
|
865
|
-
);
|
|
860
|
+
if (session.data && Object.keys(session.data).length > 0) {
|
|
861
|
+
sessionInfo.push(`- Collected data: ${JSON.stringify(session.data)}`);
|
|
866
862
|
}
|
|
867
863
|
sessionInfo.push(
|
|
868
864
|
"Note: User is mid-conversation. They may want to continue current route or switch to a new one based on their intent."
|
|
869
865
|
);
|
|
870
866
|
pc.addInstruction(sessionInfo.join("\n"));
|
|
871
867
|
|
|
872
|
-
// Add available
|
|
873
|
-
if (
|
|
874
|
-
const
|
|
868
|
+
// Add available steps for the active route
|
|
869
|
+
if (activeRouteSteps && activeRouteSteps.length > 0) {
|
|
870
|
+
const stepInfo = [
|
|
875
871
|
"",
|
|
876
|
-
"Available
|
|
872
|
+
"Available steps in active route (choose one to transition to):",
|
|
877
873
|
];
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if (
|
|
881
|
-
|
|
874
|
+
activeRouteSteps.forEach((step, idx) => {
|
|
875
|
+
stepInfo.push(`${idx + 1}. Step: ${step.stepId}`);
|
|
876
|
+
if (step.description) {
|
|
877
|
+
stepInfo.push(` Description: ${step.description}`);
|
|
882
878
|
}
|
|
883
|
-
if (
|
|
884
|
-
|
|
879
|
+
if (step.condition) {
|
|
880
|
+
stepInfo.push(` Condition: ${step.condition}`);
|
|
885
881
|
}
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
` Required data: ${state.requiredData.join(", ")}`
|
|
889
|
-
);
|
|
882
|
+
if (step.requires && step.requires.length > 0) {
|
|
883
|
+
stepInfo.push(` Required data: ${step.requires.join(", ")}`);
|
|
890
884
|
}
|
|
891
|
-
if (
|
|
892
|
-
|
|
885
|
+
if (step.collectFields && step.collectFields.length > 0) {
|
|
886
|
+
stepInfo.push(` Will collect: ${step.collectFields.join(", ")}`);
|
|
893
887
|
}
|
|
894
888
|
});
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
"IMPORTANT: You MUST select a
|
|
889
|
+
stepInfo.push("");
|
|
890
|
+
stepInfo.push(
|
|
891
|
+
"IMPORTANT: You MUST select a step to transition to. Evaluate which step makes the most sense based on:"
|
|
898
892
|
);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
pc.addInstruction(
|
|
893
|
+
stepInfo.push("- The conversation flow and what's been collected");
|
|
894
|
+
stepInfo.push("- What data is still needed vs already present");
|
|
895
|
+
stepInfo.push("- The logical next step in the conversation");
|
|
896
|
+
stepInfo.push("- Whether conditions for steps are met");
|
|
897
|
+
pc.addInstruction(stepInfo.join("\n"));
|
|
904
898
|
}
|
|
905
899
|
}
|
|
906
900
|
|