@falai/agent 0.5.3 → 0.5.5
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 +20 -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/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.js +2 -1
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/types/route.d.ts +2 -0
- 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/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.js +2 -1
- package/dist/core/State.js.map +1 -1
- package/dist/types/route.d.ts +2 -0
- 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/API_REFERENCE.md +107 -26
- package/docs/ARCHITECTURE.md +83 -12
- package/docs/PERSISTENCE.md +18 -6
- package/examples/business-onboarding.ts +11 -0
- package/examples/custom-database-persistence.ts +533 -0
- package/examples/healthcare-agent.ts +28 -16
- package/examples/persistent-onboarding.ts +16 -10
- package/examples/prisma-persistence.ts +13 -2
- package/examples/travel-agent.ts +94 -52
- package/package.json +1 -1
- package/src/core/Agent.ts +78 -227
- package/src/core/RoutingEngine.ts +663 -2
- package/src/core/State.ts +1 -1
- package/src/types/route.ts +2 -0
- package/src/utils/event.ts +16 -0
package/examples/travel-agent.ts
CHANGED
|
@@ -270,55 +270,85 @@ async function createTravelAgent() {
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
// Build the route flow with data extraction and smart state progression
|
|
273
|
-
const askDestination = flightBookingRoute.initialState.transitionTo(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
273
|
+
const askDestination = flightBookingRoute.initialState.transitionTo(
|
|
274
|
+
{
|
|
275
|
+
chatState: "Ask about the destination",
|
|
276
|
+
gather: ["destination"],
|
|
277
|
+
skipIf: (extracted) => !!extracted.destination,
|
|
278
|
+
},
|
|
279
|
+
"Customer needs to specify their travel destination"
|
|
280
|
+
);
|
|
278
281
|
|
|
279
|
-
const enrichDestination = askDestination.transitionTo(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
const enrichDestination = askDestination.transitionTo(
|
|
283
|
+
{
|
|
284
|
+
toolState: lookupDestinationCode,
|
|
285
|
+
requiredData: ["destination"],
|
|
286
|
+
},
|
|
287
|
+
"Destination provided, lookup airport code"
|
|
288
|
+
);
|
|
283
289
|
|
|
284
|
-
const askDates = enrichDestination.transitionTo(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
const askDates = enrichDestination.transitionTo(
|
|
291
|
+
{
|
|
292
|
+
chatState: "Ask about preferred travel dates",
|
|
293
|
+
gather: ["departureDate"],
|
|
294
|
+
skipIf: (extracted) => !!extracted.departureDate,
|
|
295
|
+
requiredData: ["destination"],
|
|
296
|
+
},
|
|
297
|
+
"Destination confirmed, need travel dates"
|
|
298
|
+
);
|
|
290
299
|
|
|
291
|
-
const askPassengers = askDates.transitionTo(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
300
|
+
const askPassengers = askDates.transitionTo(
|
|
301
|
+
{
|
|
302
|
+
chatState: "Ask for number of passengers",
|
|
303
|
+
gather: ["passengers"],
|
|
304
|
+
skipIf: (extracted) => !!extracted.passengers,
|
|
305
|
+
requiredData: ["destination", "departureDate"],
|
|
306
|
+
},
|
|
307
|
+
"Dates confirmed, need passenger count"
|
|
308
|
+
);
|
|
297
309
|
|
|
298
|
-
const searchFlightsState = askPassengers.transitionTo(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
const searchFlightsState = askPassengers.transitionTo(
|
|
311
|
+
{
|
|
312
|
+
toolState: searchFlights,
|
|
313
|
+
// Triggered when shouldSearchFlights flag is set by hook
|
|
314
|
+
},
|
|
315
|
+
"All basic info gathered, search for available flights"
|
|
316
|
+
);
|
|
302
317
|
|
|
303
|
-
const presentFlights = searchFlightsState.transitionTo(
|
|
304
|
-
|
|
305
|
-
|
|
318
|
+
const presentFlights = searchFlightsState.transitionTo(
|
|
319
|
+
{
|
|
320
|
+
chatState: "Present available flights and ask which one works for them",
|
|
321
|
+
},
|
|
322
|
+
"Flight search complete, present options to customer"
|
|
323
|
+
);
|
|
306
324
|
|
|
307
325
|
// Happy path: customer selects a flight
|
|
308
|
-
const confirmBooking = presentFlights.transitionTo(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
326
|
+
const confirmBooking = presentFlights.transitionTo(
|
|
327
|
+
{
|
|
328
|
+
chatState: "Confirm booking details before proceeding",
|
|
329
|
+
gather: ["cabinClass", "urgency"], // Additional optional data
|
|
330
|
+
},
|
|
331
|
+
"Customer interested in a flight, confirm booking details"
|
|
332
|
+
);
|
|
312
333
|
|
|
313
|
-
const bookFlightState = confirmBooking.transitionTo(
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
const bookFlightState = confirmBooking.transitionTo(
|
|
335
|
+
{
|
|
336
|
+
toolState: bookFlight,
|
|
337
|
+
},
|
|
338
|
+
"Customer confirmed, proceed with booking"
|
|
339
|
+
);
|
|
316
340
|
|
|
317
|
-
const provideConfirmation = bookFlightState.transitionTo(
|
|
318
|
-
|
|
319
|
-
|
|
341
|
+
const provideConfirmation = bookFlightState.transitionTo(
|
|
342
|
+
{
|
|
343
|
+
chatState: "Provide confirmation number and booking summary",
|
|
344
|
+
},
|
|
345
|
+
"Booking completed successfully"
|
|
346
|
+
);
|
|
320
347
|
|
|
321
|
-
provideConfirmation.transitionTo(
|
|
348
|
+
provideConfirmation.transitionTo(
|
|
349
|
+
{ state: END_ROUTE },
|
|
350
|
+
"Customer has confirmation, booking flow complete"
|
|
351
|
+
);
|
|
322
352
|
|
|
323
353
|
// Add route-specific guidelines
|
|
324
354
|
flightBookingRoute.createGuideline({
|
|
@@ -356,22 +386,34 @@ async function createTravelAgent() {
|
|
|
356
386
|
},
|
|
357
387
|
});
|
|
358
388
|
|
|
359
|
-
const askConfirmation = bookingStatusRoute.initialState.transitionTo(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
389
|
+
const askConfirmation = bookingStatusRoute.initialState.transitionTo(
|
|
390
|
+
{
|
|
391
|
+
chatState: "Ask for the confirmation number or booking reference",
|
|
392
|
+
gather: ["confirmationNumber"],
|
|
393
|
+
skipIf: (extracted) => !!extracted.confirmationNumber,
|
|
394
|
+
},
|
|
395
|
+
"Customer wants to check booking status but hasn't provided confirmation number"
|
|
396
|
+
);
|
|
364
397
|
|
|
365
|
-
const checkStatus = askConfirmation.transitionTo(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
398
|
+
const checkStatus = askConfirmation.transitionTo(
|
|
399
|
+
{
|
|
400
|
+
toolState: getBookingStatus,
|
|
401
|
+
requiredData: ["confirmationNumber"],
|
|
402
|
+
},
|
|
403
|
+
"Confirmation number provided, look up booking details"
|
|
404
|
+
);
|
|
369
405
|
|
|
370
|
-
const provideStatus = checkStatus.transitionTo(
|
|
371
|
-
|
|
372
|
-
|
|
406
|
+
const provideStatus = checkStatus.transitionTo(
|
|
407
|
+
{
|
|
408
|
+
chatState: "Provide booking status and relevant information",
|
|
409
|
+
},
|
|
410
|
+
"Booking status retrieved successfully"
|
|
411
|
+
);
|
|
373
412
|
|
|
374
|
-
provideStatus.transitionTo(
|
|
413
|
+
provideStatus.transitionTo(
|
|
414
|
+
{ state: END_ROUTE },
|
|
415
|
+
"Booking information provided to customer"
|
|
416
|
+
);
|
|
375
417
|
|
|
376
418
|
// Global guidelines
|
|
377
419
|
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
|
|