@falai/agent 0.5.4 → 0.6.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 +9 -4
- package/dist/cjs/core/Agent.d.ts +0 -5
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +75 -157
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +2 -2
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/Route.d.ts +6 -1
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +19 -1
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +68 -2
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +416 -2
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/State.d.ts +1 -2
- package/dist/cjs/core/State.d.ts.map +1 -1
- package/dist/cjs/core/State.js +5 -6
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/core/Transition.d.ts +2 -2
- package/dist/cjs/core/Transition.d.ts.map +1 -1
- package/dist/cjs/core/Transition.js +3 -2
- package/dist/cjs/core/Transition.js.map +1 -1
- package/dist/cjs/types/route.d.ts +15 -4
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/utils/event.d.ts +6 -0
- package/dist/cjs/utils/event.d.ts.map +1 -0
- package/dist/cjs/utils/event.js +20 -0
- package/dist/cjs/utils/event.js.map +1 -0
- package/dist/core/Agent.d.ts +0 -5
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +74 -156
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/ResponseEngine.js +2 -2
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/Route.d.ts +6 -1
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +19 -1
- package/dist/core/Route.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +68 -2
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +416 -2
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/State.d.ts +1 -2
- package/dist/core/State.d.ts.map +1 -1
- package/dist/core/State.js +5 -6
- package/dist/core/State.js.map +1 -1
- package/dist/core/Transition.d.ts +2 -2
- package/dist/core/Transition.d.ts.map +1 -1
- package/dist/core/Transition.js +3 -2
- package/dist/core/Transition.js.map +1 -1
- package/dist/types/route.d.ts +15 -4
- package/dist/types/route.d.ts.map +1 -1
- package/dist/utils/event.d.ts +6 -0
- package/dist/utils/event.d.ts.map +1 -0
- package/dist/utils/event.js +17 -0
- package/dist/utils/event.js.map +1 -0
- package/docs/ADAPTERS.md +1 -1
- package/docs/API_REFERENCE.md +15 -7
- package/docs/ARCHITECTURE.md +25 -5
- package/docs/CONSTRUCTOR_OPTIONS.md +2 -2
- package/docs/CONTEXT_MANAGEMENT.md +1 -1
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/PERSISTENCE.md +3 -3
- package/examples/business-onboarding.ts +97 -70
- package/examples/company-qna-agent.ts +4 -4
- package/examples/custom-database-persistence.ts +2 -2
- package/examples/declarative-agent.ts +3 -3
- package/examples/extracted-data-modification.ts +1 -1
- package/examples/healthcare-agent.ts +9 -3
- package/examples/openai-agent.ts +1 -1
- package/examples/opensearch-persistence.ts +2 -2
- package/examples/persistent-onboarding.ts +18 -12
- package/examples/prisma-persistence.ts +3 -3
- package/examples/redis-persistence.ts +3 -3
- package/examples/travel-agent.ts +23 -4
- package/package.json +1 -1
- package/src/core/Agent.ts +78 -227
- package/src/core/ResponseEngine.ts +2 -2
- package/src/core/Route.ts +34 -3
- package/src/core/RoutingEngine.ts +663 -2
- package/src/core/State.ts +6 -13
- package/src/core/Transition.ts +6 -3
- package/src/types/route.ts +15 -5
- package/src/utils/event.ts +16 -0
package/examples/travel-agent.ts
CHANGED
|
@@ -223,7 +223,7 @@ async function createTravelAgent() {
|
|
|
223
223
|
description:
|
|
224
224
|
"Helps the customer find and book a flight to their desired destination.",
|
|
225
225
|
conditions: ["The customer wants to book a flight"],
|
|
226
|
-
|
|
226
|
+
extractionSchema: {
|
|
227
227
|
type: "object",
|
|
228
228
|
properties: {
|
|
229
229
|
destination: {
|
|
@@ -274,11 +274,13 @@ async function createTravelAgent() {
|
|
|
274
274
|
chatState: "Ask about the destination",
|
|
275
275
|
gather: ["destination"],
|
|
276
276
|
skipIf: (extracted) => !!extracted.destination,
|
|
277
|
+
condition: "Customer needs to specify their travel destination",
|
|
277
278
|
});
|
|
278
279
|
|
|
279
280
|
const enrichDestination = askDestination.transitionTo({
|
|
280
281
|
toolState: lookupDestinationCode,
|
|
281
282
|
requiredData: ["destination"],
|
|
283
|
+
condition: "Destination provided, lookup airport code",
|
|
282
284
|
});
|
|
283
285
|
|
|
284
286
|
const askDates = enrichDestination.transitionTo({
|
|
@@ -286,6 +288,7 @@ async function createTravelAgent() {
|
|
|
286
288
|
gather: ["departureDate"],
|
|
287
289
|
skipIf: (extracted) => !!extracted.departureDate,
|
|
288
290
|
requiredData: ["destination"],
|
|
291
|
+
condition: "Destination confirmed, need travel dates",
|
|
289
292
|
});
|
|
290
293
|
|
|
291
294
|
const askPassengers = askDates.transitionTo({
|
|
@@ -293,32 +296,41 @@ async function createTravelAgent() {
|
|
|
293
296
|
gather: ["passengers"],
|
|
294
297
|
skipIf: (extracted) => !!extracted.passengers,
|
|
295
298
|
requiredData: ["destination", "departureDate"],
|
|
299
|
+
condition: "Dates confirmed, need passenger count",
|
|
296
300
|
});
|
|
297
301
|
|
|
298
302
|
const searchFlightsState = askPassengers.transitionTo({
|
|
299
303
|
toolState: searchFlights,
|
|
300
304
|
// Triggered when shouldSearchFlights flag is set by hook
|
|
305
|
+
condition: "All basic info gathered, search for available flights",
|
|
301
306
|
});
|
|
302
307
|
|
|
303
308
|
const presentFlights = searchFlightsState.transitionTo({
|
|
304
309
|
chatState: "Present available flights and ask which one works for them",
|
|
310
|
+
condition: "Flight search complete, present options to customer",
|
|
305
311
|
});
|
|
306
312
|
|
|
307
313
|
// Happy path: customer selects a flight
|
|
308
314
|
const confirmBooking = presentFlights.transitionTo({
|
|
309
315
|
chatState: "Confirm booking details before proceeding",
|
|
310
316
|
gather: ["cabinClass", "urgency"], // Additional optional data
|
|
317
|
+
condition: "Customer interested in a flight, confirm booking details",
|
|
311
318
|
});
|
|
312
319
|
|
|
313
320
|
const bookFlightState = confirmBooking.transitionTo({
|
|
314
321
|
toolState: bookFlight,
|
|
322
|
+
condition: "Customer confirmed, proceed with booking",
|
|
315
323
|
});
|
|
316
324
|
|
|
317
325
|
const provideConfirmation = bookFlightState.transitionTo({
|
|
318
326
|
chatState: "Provide confirmation number and booking summary",
|
|
327
|
+
condition: "Booking completed successfully",
|
|
319
328
|
});
|
|
320
329
|
|
|
321
|
-
provideConfirmation.transitionTo({
|
|
330
|
+
provideConfirmation.transitionTo({
|
|
331
|
+
state: END_ROUTE,
|
|
332
|
+
condition: "Customer has confirmation, booking flow complete",
|
|
333
|
+
});
|
|
322
334
|
|
|
323
335
|
// Add route-specific guidelines
|
|
324
336
|
flightBookingRoute.createGuideline({
|
|
@@ -340,7 +352,7 @@ async function createTravelAgent() {
|
|
|
340
352
|
description:
|
|
341
353
|
"Retrieves the customer's booking status and provides relevant information.",
|
|
342
354
|
conditions: ["The customer wants to check their booking status"],
|
|
343
|
-
|
|
355
|
+
extractionSchema: {
|
|
344
356
|
type: "object",
|
|
345
357
|
properties: {
|
|
346
358
|
confirmationNumber: {
|
|
@@ -360,18 +372,25 @@ async function createTravelAgent() {
|
|
|
360
372
|
chatState: "Ask for the confirmation number or booking reference",
|
|
361
373
|
gather: ["confirmationNumber"],
|
|
362
374
|
skipIf: (extracted) => !!extracted.confirmationNumber,
|
|
375
|
+
condition:
|
|
376
|
+
"Customer wants to check booking status but hasn't provided confirmation number",
|
|
363
377
|
});
|
|
364
378
|
|
|
365
379
|
const checkStatus = askConfirmation.transitionTo({
|
|
366
380
|
toolState: getBookingStatus,
|
|
367
381
|
requiredData: ["confirmationNumber"],
|
|
382
|
+
condition: "Confirmation number provided, look up booking details",
|
|
368
383
|
});
|
|
369
384
|
|
|
370
385
|
const provideStatus = checkStatus.transitionTo({
|
|
371
386
|
chatState: "Provide booking status and relevant information",
|
|
387
|
+
condition: "Booking status retrieved successfully",
|
|
372
388
|
});
|
|
373
389
|
|
|
374
|
-
provideStatus.transitionTo({
|
|
390
|
+
provideStatus.transitionTo({
|
|
391
|
+
state: END_ROUTE,
|
|
392
|
+
condition: "Booking information provided to customer",
|
|
393
|
+
});
|
|
375
394
|
|
|
376
395
|
// Global guidelines
|
|
377
396
|
agent.createGuideline({
|
package/package.json
CHANGED
package/src/core/Agent.ts
CHANGED
|
@@ -3,18 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { AgentOptions, Term, Guideline, Capability } from "../types/agent";
|
|
6
|
-
import type { Event, StateRef
|
|
6
|
+
import type { Event, StateRef } from "../types/index";
|
|
7
7
|
import type { RouteOptions } from "../types/route";
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
import type { SessionState } from "../types/session";
|
|
10
10
|
import type { AgentStructuredResponse } from "../types/ai";
|
|
11
|
-
import {
|
|
12
|
-
createSession,
|
|
13
|
-
enterRoute,
|
|
14
|
-
enterState,
|
|
15
|
-
mergeExtracted,
|
|
16
|
-
} from "../types/session";
|
|
17
|
-
import { EventKind } from "../types/history";
|
|
11
|
+
import { createSession, enterState, mergeExtracted } from "../types/session";
|
|
18
12
|
import { PromptComposer } from "./PromptComposer";
|
|
19
13
|
|
|
20
14
|
import { Route } from "./Route";
|
|
@@ -24,19 +18,7 @@ import { PersistenceManager } from "./PersistenceManager";
|
|
|
24
18
|
import { RoutingEngine } from "./RoutingEngine";
|
|
25
19
|
import { ResponseEngine } from "./ResponseEngine";
|
|
26
20
|
import { ToolExecutor } from "./ToolExecutor";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Helper to extract last message from history
|
|
30
|
-
*/
|
|
31
|
-
function getLastMessageFromHistory(history: Event[]): string {
|
|
32
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
33
|
-
const event = history[i];
|
|
34
|
-
if (event.kind === EventKind.MESSAGE) {
|
|
35
|
-
return (event.data as MessageEventData).message;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return "";
|
|
39
|
-
}
|
|
21
|
+
import { getLastMessageFromHistory } from "../utils/event";
|
|
40
22
|
|
|
41
23
|
/**
|
|
42
24
|
* Main Agent class with generic context support
|
|
@@ -275,57 +257,6 @@ export class Agent<TContext = unknown> {
|
|
|
275
257
|
return this.context;
|
|
276
258
|
}
|
|
277
259
|
|
|
278
|
-
/**
|
|
279
|
-
* Determine the next state in a route based on extracted data
|
|
280
|
-
* @internal
|
|
281
|
-
*/
|
|
282
|
-
private getNextState<TExtracted = unknown>(
|
|
283
|
-
route: Route<TContext, TExtracted>,
|
|
284
|
-
currentState: State<TContext, TExtracted> | undefined,
|
|
285
|
-
extracted: Partial<TExtracted>
|
|
286
|
-
): State<TContext, TExtracted> {
|
|
287
|
-
// If no current state, start from initial state
|
|
288
|
-
if (!currentState) {
|
|
289
|
-
// Check if initial state should be skipped
|
|
290
|
-
if (route.initialState.shouldSkip(extracted)) {
|
|
291
|
-
return this.getNextState(route, route.initialState, extracted);
|
|
292
|
-
}
|
|
293
|
-
return route.initialState;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Get transitions from current state
|
|
297
|
-
const transitions = currentState.getTransitions();
|
|
298
|
-
|
|
299
|
-
// If no transitions, stay in current state
|
|
300
|
-
if (transitions.length === 0) {
|
|
301
|
-
return currentState;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Try to find the next state to transition to
|
|
305
|
-
for (const transition of transitions) {
|
|
306
|
-
const target = transition.getTarget();
|
|
307
|
-
if (!target) continue;
|
|
308
|
-
|
|
309
|
-
// Check if target state should be skipped
|
|
310
|
-
if (target.shouldSkip(extracted)) {
|
|
311
|
-
// Recursively find next non-skipped state
|
|
312
|
-
return this.getNextState(route, target, extracted);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Check if target state has required data
|
|
316
|
-
if (!target.hasRequiredData(extracted)) {
|
|
317
|
-
// Cannot enter this state yet, stay in current state
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Found valid next state
|
|
322
|
-
return target;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// No valid transition found, stay in current state
|
|
326
|
-
return currentState;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
260
|
/**
|
|
330
261
|
* Generate a response based on history and context as a stream
|
|
331
262
|
*/
|
|
@@ -414,106 +345,66 @@ export class Agent<TContext = unknown> {
|
|
|
414
345
|
}
|
|
415
346
|
}
|
|
416
347
|
|
|
417
|
-
// PHASE 2: ROUTING - Determine which route to use
|
|
348
|
+
// PHASE 2: ROUTING + STATE SELECTION - Determine which route and state to use (combined)
|
|
418
349
|
let selectedRoute: Route<TContext> | undefined;
|
|
419
350
|
let responseDirectives: string[] | undefined;
|
|
351
|
+
let selectedState: State<TContext> | undefined;
|
|
420
352
|
|
|
421
353
|
if (this.routes.length > 0) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// Build routing schema
|
|
426
|
-
const routingSchema = this.routingEngine.buildDynamicRoutingSchema(
|
|
427
|
-
this.routes
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
// Build routing prompt with session context
|
|
431
|
-
const routingPrompt = this.routingEngine.buildRoutingPrompt(
|
|
354
|
+
const orchestration = await this.routingEngine.decideRouteAndState({
|
|
355
|
+
routes: this.routes,
|
|
356
|
+
session,
|
|
432
357
|
history,
|
|
433
|
-
|
|
434
|
-
lastUserMessage,
|
|
435
|
-
{
|
|
358
|
+
agentMeta: {
|
|
436
359
|
name: this.options.name,
|
|
437
360
|
goal: this.options.goal,
|
|
438
361
|
description: this.options.description,
|
|
439
362
|
personality: this.options.personality,
|
|
440
363
|
},
|
|
441
|
-
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
// Call AI to score routes (non-streaming for routing decision)
|
|
445
|
-
const routingResult = await this.options.ai.generateMessage<
|
|
446
|
-
TContext,
|
|
447
|
-
RoutingDecisionOutput
|
|
448
|
-
>({
|
|
449
|
-
prompt: routingPrompt,
|
|
450
|
-
history,
|
|
364
|
+
ai: this.options.ai,
|
|
451
365
|
context: effectiveContext,
|
|
452
366
|
signal,
|
|
453
|
-
parameters: {
|
|
454
|
-
jsonSchema: routingSchema,
|
|
455
|
-
schemaName: "routing_output",
|
|
456
|
-
},
|
|
457
367
|
});
|
|
458
368
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
responseDirectives: routingResult.structured.responseDirectives,
|
|
465
|
-
});
|
|
466
|
-
selectedRoute = this.routes.find((r) => r.id === decision.routeId);
|
|
467
|
-
responseDirectives = routingResult.structured.responseDirectives;
|
|
369
|
+
selectedRoute = orchestration.selectedRoute;
|
|
370
|
+
selectedState = orchestration.selectedState;
|
|
371
|
+
responseDirectives = orchestration.responseDirectives;
|
|
372
|
+
session = orchestration.session;
|
|
373
|
+
}
|
|
468
374
|
|
|
469
|
-
|
|
375
|
+
// PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
|
|
376
|
+
if (selectedRoute) {
|
|
377
|
+
let nextState: State<TContext>;
|
|
378
|
+
|
|
379
|
+
// If we have a selected state from the combined routing decision, use it
|
|
380
|
+
if (selectedState) {
|
|
381
|
+
nextState = selectedState;
|
|
382
|
+
} else {
|
|
383
|
+
// New route or no state selected - get initial state or first valid state
|
|
384
|
+
const candidates = this.routingEngine.getCandidateStates(
|
|
385
|
+
selectedRoute,
|
|
386
|
+
undefined,
|
|
387
|
+
session.extracted
|
|
388
|
+
);
|
|
389
|
+
if (candidates.length > 0) {
|
|
390
|
+
nextState = candidates[0].state;
|
|
470
391
|
console.log(
|
|
471
|
-
`[Agent]
|
|
392
|
+
`[Agent] Using first valid state: ${nextState.id} for new route`
|
|
393
|
+
);
|
|
394
|
+
} else {
|
|
395
|
+
// Fallback to initial state even if it should be skipped
|
|
396
|
+
nextState = selectedRoute.initialState;
|
|
397
|
+
console.warn(
|
|
398
|
+
`[Agent] No valid states found, using initial state: ${nextState.id}`
|
|
472
399
|
);
|
|
473
|
-
|
|
474
|
-
// Update session with selected route (if changed)
|
|
475
|
-
if (
|
|
476
|
-
!session.currentRoute ||
|
|
477
|
-
session.currentRoute.id !== selectedRoute.id
|
|
478
|
-
) {
|
|
479
|
-
session = enterRoute(
|
|
480
|
-
session,
|
|
481
|
-
selectedRoute.id,
|
|
482
|
-
selectedRoute.title
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
// Merge initial data if provided by the route
|
|
486
|
-
if (selectedRoute.initialData) {
|
|
487
|
-
session = mergeExtracted(session, selectedRoute.initialData);
|
|
488
|
-
console.log(
|
|
489
|
-
`[Agent] Merged initial data:`,
|
|
490
|
-
selectedRoute.initialData
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
console.log(`[Agent] Entered route: ${selectedRoute.title}`);
|
|
495
|
-
}
|
|
496
400
|
}
|
|
497
401
|
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// PHASE 3: RESPONSE - Stream message using selected route
|
|
501
|
-
if (selectedRoute) {
|
|
502
|
-
// Determine next state based on current extracted data
|
|
503
|
-
const currentStateRef = session.currentState;
|
|
504
|
-
const currentState = currentStateRef
|
|
505
|
-
? selectedRoute.getState(currentStateRef.id)
|
|
506
|
-
: undefined;
|
|
507
|
-
const nextState = this.getNextState(
|
|
508
|
-
selectedRoute,
|
|
509
|
-
currentState,
|
|
510
|
-
session.extracted
|
|
511
|
-
);
|
|
512
402
|
|
|
513
403
|
// Update session with next state
|
|
514
404
|
session = enterState(session, nextState.id, nextState.description);
|
|
515
405
|
console.log(`[Agent] Entered state: ${nextState.id}`);
|
|
516
406
|
|
|
407
|
+
// PHASE 4: RESPONSE GENERATION - Stream message using selected route and state
|
|
517
408
|
// Get last user message
|
|
518
409
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
519
410
|
|
|
@@ -742,111 +633,71 @@ export class Agent<TContext = unknown> {
|
|
|
742
633
|
}
|
|
743
634
|
}
|
|
744
635
|
|
|
745
|
-
// PHASE 2: ROUTING - Determine which route to use
|
|
636
|
+
// PHASE 2: ROUTING + STATE SELECTION - Determine which route and state to use (combined)
|
|
746
637
|
let selectedRoute: Route<TContext> | undefined;
|
|
747
638
|
let responseDirectives: string[] | undefined;
|
|
639
|
+
let selectedState: State<TContext> | undefined;
|
|
748
640
|
|
|
749
641
|
if (this.routes.length > 0) {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
// Build routing schema
|
|
754
|
-
const routingSchema = this.routingEngine.buildDynamicRoutingSchema(
|
|
755
|
-
this.routes
|
|
756
|
-
);
|
|
757
|
-
|
|
758
|
-
// Build routing prompt with session context
|
|
759
|
-
const routingPrompt = this.routingEngine.buildRoutingPrompt(
|
|
642
|
+
const orchestration = await this.routingEngine.decideRouteAndState({
|
|
643
|
+
routes: this.routes,
|
|
644
|
+
session,
|
|
760
645
|
history,
|
|
761
|
-
|
|
762
|
-
lastUserMessage,
|
|
763
|
-
{
|
|
646
|
+
agentMeta: {
|
|
764
647
|
name: this.options.name,
|
|
765
648
|
goal: this.options.goal,
|
|
766
649
|
description: this.options.description,
|
|
767
650
|
personality: this.options.personality,
|
|
768
651
|
},
|
|
769
|
-
|
|
770
|
-
);
|
|
771
|
-
|
|
772
|
-
// Call AI to score routes
|
|
773
|
-
const routingResult = await this.options.ai.generateMessage<
|
|
774
|
-
TContext,
|
|
775
|
-
RoutingDecisionOutput
|
|
776
|
-
>({
|
|
777
|
-
prompt: routingPrompt,
|
|
778
|
-
history,
|
|
652
|
+
ai: this.options.ai,
|
|
779
653
|
context: effectiveContext,
|
|
780
654
|
signal,
|
|
781
|
-
parameters: {
|
|
782
|
-
jsonSchema: routingSchema,
|
|
783
|
-
schemaName: "routing_output",
|
|
784
|
-
},
|
|
785
655
|
});
|
|
786
656
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
routes: routingResult.structured.routes,
|
|
792
|
-
responseDirectives: routingResult.structured.responseDirectives,
|
|
793
|
-
});
|
|
794
|
-
selectedRoute = this.routes.find((r) => r.id === decision.routeId);
|
|
795
|
-
responseDirectives = routingResult.structured.responseDirectives;
|
|
796
|
-
|
|
797
|
-
if (selectedRoute) {
|
|
798
|
-
console.log(
|
|
799
|
-
`[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`
|
|
800
|
-
);
|
|
801
|
-
|
|
802
|
-
// Update session with selected route (if changed)
|
|
803
|
-
if (
|
|
804
|
-
!session.currentRoute ||
|
|
805
|
-
session.currentRoute.id !== selectedRoute.id
|
|
806
|
-
) {
|
|
807
|
-
session = enterRoute(
|
|
808
|
-
session,
|
|
809
|
-
selectedRoute.id,
|
|
810
|
-
selectedRoute.title
|
|
811
|
-
);
|
|
812
|
-
|
|
813
|
-
// Merge initial data if provided by the route
|
|
814
|
-
if (selectedRoute.initialData) {
|
|
815
|
-
session = mergeExtracted(session, selectedRoute.initialData);
|
|
816
|
-
console.log(
|
|
817
|
-
`[Agent] Merged initial data:`,
|
|
818
|
-
selectedRoute.initialData
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
console.log(`[Agent] Entered route: ${selectedRoute.title}`);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
}
|
|
657
|
+
selectedRoute = orchestration.selectedRoute;
|
|
658
|
+
selectedState = orchestration.selectedState;
|
|
659
|
+
responseDirectives = orchestration.responseDirectives;
|
|
660
|
+
session = orchestration.session;
|
|
826
661
|
}
|
|
827
662
|
|
|
828
|
-
// PHASE 3:
|
|
663
|
+
// PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
|
|
829
664
|
let message: string;
|
|
830
665
|
const toolCalls:
|
|
831
666
|
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
832
667
|
| undefined = undefined;
|
|
833
668
|
|
|
834
669
|
if (selectedRoute) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
670
|
+
let nextState: State<TContext>;
|
|
671
|
+
|
|
672
|
+
// If we have a selected state from the combined routing decision, use it
|
|
673
|
+
if (selectedState) {
|
|
674
|
+
nextState = selectedState;
|
|
675
|
+
} else {
|
|
676
|
+
// New route or no state selected - get initial state or first valid state
|
|
677
|
+
const candidates = this.routingEngine.getCandidateStates(
|
|
678
|
+
selectedRoute,
|
|
679
|
+
undefined,
|
|
680
|
+
session.extracted
|
|
681
|
+
);
|
|
682
|
+
if (candidates.length > 0) {
|
|
683
|
+
nextState = candidates[0].state;
|
|
684
|
+
console.log(
|
|
685
|
+
`[Agent] Using first valid state: ${nextState.id} for new route`
|
|
686
|
+
);
|
|
687
|
+
} else {
|
|
688
|
+
// Fallback to initial state even if it should be skipped
|
|
689
|
+
nextState = selectedRoute.initialState;
|
|
690
|
+
console.warn(
|
|
691
|
+
`[Agent] No valid states found, using initial state: ${nextState.id}`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
845
695
|
|
|
846
696
|
// Update session with next state
|
|
847
697
|
session = enterState(session, nextState.id, nextState.description);
|
|
848
698
|
console.log(`[Agent] Entered state: ${nextState.id}`);
|
|
849
699
|
|
|
700
|
+
// PHASE 4: RESPONSE GENERATION - Generate message using selected route and state
|
|
850
701
|
// Get last user message
|
|
851
702
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
852
703
|
|
|
@@ -30,9 +30,9 @@ export class ResponseEngine<TContext = unknown> {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// Add gather fields from current state
|
|
33
|
-
if (currentState?.gatherFields && route.
|
|
33
|
+
if (currentState?.gatherFields && route.extractionSchema?.properties) {
|
|
34
34
|
for (const field of currentState.gatherFields) {
|
|
35
|
-
const fieldSchema = route.
|
|
35
|
+
const fieldSchema = route.extractionSchema.properties[field];
|
|
36
36
|
if (fieldSchema) {
|
|
37
37
|
base.properties![field] = fieldSchema;
|
|
38
38
|
}
|
package/src/core/Route.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* Route (Journey) DSL implementation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
RouteOptions,
|
|
7
|
+
RouteRef,
|
|
8
|
+
TransitionSpec,
|
|
9
|
+
TransitionResult,
|
|
10
|
+
} from "../types/route";
|
|
6
11
|
import type { StructuredSchema } from "../types/schema";
|
|
7
12
|
import type { Guideline } from "../types/agent";
|
|
8
13
|
|
|
@@ -22,7 +27,7 @@ export class Route<TContext = unknown, TExtracted = unknown> {
|
|
|
22
27
|
public readonly prohibitions: string[];
|
|
23
28
|
public readonly initialState: State<TContext, TExtracted>;
|
|
24
29
|
public readonly responseOutputSchema?: StructuredSchema;
|
|
25
|
-
public readonly
|
|
30
|
+
public readonly extractionSchema?: StructuredSchema;
|
|
26
31
|
public readonly initialData?: Partial<TExtracted>;
|
|
27
32
|
private routingExtrasSchema?: StructuredSchema;
|
|
28
33
|
private guidelines: Guideline[] = [];
|
|
@@ -42,7 +47,7 @@ export class Route<TContext = unknown, TExtracted = unknown> {
|
|
|
42
47
|
);
|
|
43
48
|
this.routingExtrasSchema = options.routingExtrasSchema;
|
|
44
49
|
this.responseOutputSchema = options.responseOutputSchema;
|
|
45
|
-
this.
|
|
50
|
+
this.extractionSchema = options.extractionSchema;
|
|
46
51
|
this.initialData = options.initialData;
|
|
47
52
|
|
|
48
53
|
// Initialize guidelines from options
|
|
@@ -51,6 +56,32 @@ export class Route<TContext = unknown, TExtracted = unknown> {
|
|
|
51
56
|
this.createGuideline(guideline);
|
|
52
57
|
});
|
|
53
58
|
}
|
|
59
|
+
|
|
60
|
+
// Build sequential steps if provided
|
|
61
|
+
if (options.steps && options.steps.length > 0) {
|
|
62
|
+
this.buildSequentialSteps(options.steps);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build a sequential state machine from an array of steps
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
private buildSequentialSteps(
|
|
71
|
+
steps: Array<TransitionSpec<TContext, TExtracted>>
|
|
72
|
+
): void {
|
|
73
|
+
// Import END_ROUTE dynamically to avoid circular dependency
|
|
74
|
+
const END_ROUTE = Symbol.for("END_ROUTE");
|
|
75
|
+
|
|
76
|
+
let currentState: TransitionResult<TContext, TExtracted> =
|
|
77
|
+
this.initialState;
|
|
78
|
+
|
|
79
|
+
for (const step of steps) {
|
|
80
|
+
currentState = currentState.transitionTo(step);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// End the route
|
|
84
|
+
currentState.transitionTo({ state: END_ROUTE });
|
|
54
85
|
}
|
|
55
86
|
|
|
56
87
|
/**
|