@falai/agent 0.5.4 → 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 +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/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/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/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 +24 -13
- package/docs/ARCHITECTURE.md +38 -14
- package/examples/business-onboarding.ts +11 -0
- package/examples/healthcare-agent.ts +28 -16
- package/examples/persistent-onboarding.ts +16 -10
- 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/utils/event.ts +16 -0
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
|
|