@statelyai/agent 2.0.0-next.4 → 2.0.0-next.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/.changeset/calm-beans-talk.md +5 -0
- package/.changeset/long-guests-explode.md +5 -0
- package/.changeset/odd-kiwis-compare.md +5 -0
- package/.changeset/pre.json +3 -0
- package/CHANGELOG.md +12 -0
- package/architecture.tldr +652 -30
- package/dist/index.d.mts +187 -136
- package/dist/index.d.ts +187 -136
- package/dist/index.js +4377 -103
- package/dist/index.mjs +4377 -104
- package/examples/chatbot.ts +9 -5
- package/examples/cot.ts +2 -4
- package/examples/jugs.ts +2 -2
- package/examples/learn-from-feedback.ts +5 -5
- package/examples/newspaper.ts +1 -1
- package/examples/rewoo.ts +62 -0
- package/examples/river-crossing.ts +2 -2
- package/examples/serverless.ts +71 -0
- package/examples/simple.ts +1 -1
- package/examples/ticTacToe.ts +6 -2
- package/examples/wiki.ts +2 -2
- package/package.json +14 -12
- package/readme.md +57 -0
- package/src/agent.test.ts +286 -27
- package/src/agent.ts +165 -51
- package/src/decide.test.ts +2 -2
- package/src/decide.ts +24 -66
- package/src/index.ts +1 -0
- package/src/{strategies/chainOfThought.ts → policies/chainOfThoughtPolicy.ts} +7 -9
- package/src/policies/index.ts +3 -0
- package/src/{strategies/shortestPath.test.ts → policies/shortestPathPolicy.test.ts} +2 -2
- package/src/{strategies/shortestPath.ts → policies/shortestPathPolicy.ts} +8 -8
- package/src/{strategies/simpleStrategy.ts → policies/toolPolicy.ts} +26 -25
- package/src/text.ts +17 -21
- package/src/types.ts +154 -150
- package/src/agent-experimental.ts +0 -221
package/src/agent.test.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { test, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AgentDecision,
|
|
4
|
+
AgentFeedbackInput,
|
|
5
|
+
createAgent,
|
|
6
|
+
TypesFromAgent,
|
|
7
|
+
} from './';
|
|
3
8
|
import { createActor, createMachine } from 'xstate';
|
|
4
9
|
import { LanguageModelV1CallOptions } from 'ai';
|
|
5
10
|
import { z } from 'zod';
|
|
@@ -76,12 +81,13 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
76
81
|
|
|
77
82
|
const decision: AgentDecision<typeof agent> = {
|
|
78
83
|
goal: 'Win the game',
|
|
84
|
+
decisionId: null,
|
|
79
85
|
episodeId: agent.episodeId,
|
|
80
86
|
goalState: { value: 'won' },
|
|
81
87
|
id: 'decision-1',
|
|
82
88
|
nextEvent: { type: 'play', position: 3 },
|
|
83
89
|
paths: [],
|
|
84
|
-
|
|
90
|
+
policy: 'simple',
|
|
85
91
|
timestamp: Date.now(),
|
|
86
92
|
};
|
|
87
93
|
|
|
@@ -93,16 +99,16 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
93
99
|
});
|
|
94
100
|
|
|
95
101
|
const feedback = agent.addFeedback({
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
reward: 0,
|
|
103
|
+
decisionId: decision.id,
|
|
98
104
|
});
|
|
99
105
|
|
|
100
106
|
expect(feedback.episodeId).toEqual(agent.episodeId);
|
|
101
107
|
|
|
102
108
|
expect(agent.getFeedback()).toContainEqual(
|
|
103
109
|
expect.objectContaining({
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
reward: 0,
|
|
111
|
+
decisionId: decision.id,
|
|
106
112
|
episodeId: expect.any(String),
|
|
107
113
|
timestamp: expect.any(Number),
|
|
108
114
|
})
|
|
@@ -159,7 +165,7 @@ test('agent.addObservation() adds to observations (initial state)', () => {
|
|
|
159
165
|
);
|
|
160
166
|
});
|
|
161
167
|
|
|
162
|
-
test('agent.addObservation() adds to observations with machine hash', () => {
|
|
168
|
+
test.skip('agent.addObservation() adds to observations with machine hash', () => {
|
|
163
169
|
const agent = createAgent({
|
|
164
170
|
id: 'test',
|
|
165
171
|
events: {},
|
|
@@ -198,7 +204,7 @@ test('agent.addObservation() adds to observations with machine hash', () => {
|
|
|
198
204
|
);
|
|
199
205
|
});
|
|
200
206
|
|
|
201
|
-
test('agent.
|
|
207
|
+
test('agent.addInsight() adds to insights (with observation)', () => {
|
|
202
208
|
const agent = createAgent({
|
|
203
209
|
id: 'test',
|
|
204
210
|
events: {},
|
|
@@ -212,24 +218,26 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
|
212
218
|
goal: 'Win the game',
|
|
213
219
|
});
|
|
214
220
|
|
|
215
|
-
const
|
|
216
|
-
score: 0,
|
|
221
|
+
const insight = agent.addInsight({
|
|
217
222
|
observationId: observation.id,
|
|
223
|
+
attributes: {
|
|
224
|
+
advantage: 50,
|
|
225
|
+
},
|
|
218
226
|
});
|
|
219
227
|
|
|
220
|
-
expect(
|
|
228
|
+
expect(insight.episodeId).toEqual(agent.episodeId);
|
|
221
229
|
|
|
222
|
-
expect(agent.
|
|
230
|
+
expect(agent.getInsights()).toContainEqual(
|
|
223
231
|
expect.objectContaining({
|
|
224
|
-
|
|
232
|
+
attributes: { advantage: 50 },
|
|
225
233
|
observationId: observation.id,
|
|
226
234
|
episodeId: expect.any(String),
|
|
227
235
|
timestamp: expect.any(Number),
|
|
228
236
|
})
|
|
229
237
|
);
|
|
230
|
-
expect(agent.
|
|
238
|
+
expect(agent.getInsights()).toContainEqual(
|
|
231
239
|
expect.objectContaining({
|
|
232
|
-
|
|
240
|
+
attributes: { advantage: 50 },
|
|
233
241
|
observationId: observation.id,
|
|
234
242
|
episodeId: expect.any(String),
|
|
235
243
|
timestamp: expect.any(Number),
|
|
@@ -292,14 +300,25 @@ test('You can listen for feedback events', () => {
|
|
|
292
300
|
model: {} as any,
|
|
293
301
|
});
|
|
294
302
|
|
|
295
|
-
agent.
|
|
303
|
+
agent.onFeedback(fn);
|
|
296
304
|
|
|
297
305
|
agent.addFeedback({
|
|
298
|
-
|
|
299
|
-
|
|
306
|
+
reward: 1,
|
|
307
|
+
decisionId: 'dec-1',
|
|
308
|
+
comment: 'Good move',
|
|
309
|
+
attributes: { confidence: 'high' },
|
|
300
310
|
});
|
|
301
311
|
|
|
302
|
-
expect(fn).
|
|
312
|
+
expect(fn).toHaveBeenCalledWith(
|
|
313
|
+
expect.objectContaining({
|
|
314
|
+
reward: 1,
|
|
315
|
+
decisionId: 'dec-1',
|
|
316
|
+
comment: 'Good move',
|
|
317
|
+
attributes: { confidence: 'high' },
|
|
318
|
+
episodeId: expect.any(String),
|
|
319
|
+
timestamp: expect.any(Number),
|
|
320
|
+
})
|
|
321
|
+
);
|
|
303
322
|
});
|
|
304
323
|
|
|
305
324
|
test('You can listen for decision events', async () => {
|
|
@@ -438,20 +457,21 @@ test('agent.getDecisions() returns decisions from context', () => {
|
|
|
438
457
|
id: 'test',
|
|
439
458
|
events: {},
|
|
440
459
|
model: {} as any,
|
|
441
|
-
|
|
460
|
+
policy: async (agent) => {
|
|
442
461
|
return {
|
|
443
462
|
id: Date.now().toString(),
|
|
463
|
+
decisionId: null,
|
|
444
464
|
episodeId: agent.episodeId,
|
|
445
|
-
|
|
465
|
+
policy: 'test-policy',
|
|
446
466
|
goal: '',
|
|
447
|
-
goalState:
|
|
467
|
+
goalState: null,
|
|
448
468
|
paths: [
|
|
449
469
|
{
|
|
450
|
-
state:
|
|
470
|
+
state: null,
|
|
451
471
|
steps: [],
|
|
452
472
|
},
|
|
453
473
|
],
|
|
454
|
-
nextEvent:
|
|
474
|
+
nextEvent: null,
|
|
455
475
|
timestamp: Date.now(),
|
|
456
476
|
};
|
|
457
477
|
},
|
|
@@ -561,8 +581,8 @@ test('agent.addFeedback() accepts custom episodeId', () => {
|
|
|
561
581
|
|
|
562
582
|
const customEpisodeId = 'custom-episode-123';
|
|
563
583
|
const feedback = agent.addFeedback({
|
|
564
|
-
|
|
565
|
-
|
|
584
|
+
reward: 1,
|
|
585
|
+
decisionId: 'dec-1',
|
|
566
586
|
episodeId: customEpisodeId,
|
|
567
587
|
});
|
|
568
588
|
|
|
@@ -605,7 +625,7 @@ test('agent.addFeedback() accepts decisionId', () => {
|
|
|
605
625
|
|
|
606
626
|
const decisionId = 'decision-123';
|
|
607
627
|
const feedback = agent.addFeedback({
|
|
608
|
-
|
|
628
|
+
reward: 1,
|
|
609
629
|
decisionId,
|
|
610
630
|
});
|
|
611
631
|
|
|
@@ -616,3 +636,242 @@ test('agent.addFeedback() accepts decisionId', () => {
|
|
|
616
636
|
})
|
|
617
637
|
);
|
|
618
638
|
});
|
|
639
|
+
|
|
640
|
+
test('You can listen for observation events', () => {
|
|
641
|
+
const fn = vi.fn();
|
|
642
|
+
const agent = createAgent({
|
|
643
|
+
id: 'test',
|
|
644
|
+
events: {},
|
|
645
|
+
model: {} as any,
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
agent.onObservation(fn);
|
|
649
|
+
|
|
650
|
+
agent.addObservation({
|
|
651
|
+
state: { value: 'playing' },
|
|
652
|
+
goal: 'Win the game',
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
expect(fn).toHaveBeenCalledWith(
|
|
656
|
+
expect.objectContaining({
|
|
657
|
+
state: { value: 'playing' },
|
|
658
|
+
episodeId: expect.any(String),
|
|
659
|
+
timestamp: expect.any(Number),
|
|
660
|
+
})
|
|
661
|
+
);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test('You can listen for decision events', () => {
|
|
665
|
+
const fn = vi.fn();
|
|
666
|
+
const agent = createAgent({
|
|
667
|
+
id: 'test',
|
|
668
|
+
events: {
|
|
669
|
+
MOVE: z.object({}),
|
|
670
|
+
},
|
|
671
|
+
model: {} as any,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
agent.onDecision(fn);
|
|
675
|
+
|
|
676
|
+
const decision = {
|
|
677
|
+
id: 'decision-1',
|
|
678
|
+
decisionId: null,
|
|
679
|
+
episodeId: agent.episodeId,
|
|
680
|
+
policy: 'test-policy',
|
|
681
|
+
goal: 'Win the game',
|
|
682
|
+
goalState: { value: 'won' },
|
|
683
|
+
paths: [],
|
|
684
|
+
nextEvent: { type: 'MOVE' },
|
|
685
|
+
timestamp: Date.now(),
|
|
686
|
+
} satisfies AgentDecision<typeof agent>;
|
|
687
|
+
|
|
688
|
+
agent.addDecision(decision);
|
|
689
|
+
|
|
690
|
+
expect(fn).toHaveBeenCalledWith(
|
|
691
|
+
expect.objectContaining({
|
|
692
|
+
id: 'decision-1',
|
|
693
|
+
episodeId: agent.episodeId,
|
|
694
|
+
policy: 'test-policy',
|
|
695
|
+
goal: 'Win the game',
|
|
696
|
+
nextEvent: { type: 'MOVE' },
|
|
697
|
+
})
|
|
698
|
+
);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test('Event listeners can be unsubscribed (onObservation)', () => {
|
|
702
|
+
const fn = vi.fn();
|
|
703
|
+
const agent = createAgent({
|
|
704
|
+
id: 'test',
|
|
705
|
+
events: {},
|
|
706
|
+
model: {} as any,
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
const subscription = agent.onObservation(fn);
|
|
710
|
+
|
|
711
|
+
agent.addObservation({
|
|
712
|
+
state: { value: 'playing' },
|
|
713
|
+
goal: 'Win the game',
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
717
|
+
|
|
718
|
+
subscription.unsubscribe();
|
|
719
|
+
|
|
720
|
+
agent.addObservation({
|
|
721
|
+
state: { value: 'playing' },
|
|
722
|
+
goal: 'Win the game again',
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
expect(fn).toHaveBeenCalledTimes(1); // Still only called once
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test('Event listeners can be unsubscribed (onDecision)', () => {
|
|
729
|
+
const fn = vi.fn();
|
|
730
|
+
const agent = createAgent({
|
|
731
|
+
id: 'test',
|
|
732
|
+
events: {
|
|
733
|
+
MOVE: z.object({}),
|
|
734
|
+
},
|
|
735
|
+
model: {} as any,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const subscription = agent.onDecision(fn);
|
|
739
|
+
|
|
740
|
+
const decision = {
|
|
741
|
+
id: 'decision-1',
|
|
742
|
+
decisionId: null,
|
|
743
|
+
episodeId: agent.episodeId,
|
|
744
|
+
policy: 'test-policy',
|
|
745
|
+
goal: 'Win the game',
|
|
746
|
+
goalState: { value: 'won' },
|
|
747
|
+
paths: [],
|
|
748
|
+
nextEvent: { type: 'MOVE' },
|
|
749
|
+
timestamp: Date.now(),
|
|
750
|
+
} satisfies AgentDecision<typeof agent>;
|
|
751
|
+
|
|
752
|
+
agent.addDecision(decision);
|
|
753
|
+
|
|
754
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
755
|
+
|
|
756
|
+
subscription.unsubscribe();
|
|
757
|
+
|
|
758
|
+
agent.addDecision({
|
|
759
|
+
...decision,
|
|
760
|
+
id: 'decision-2',
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
expect(fn).toHaveBeenCalledTimes(1); // Still only called once
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test('Event listeners can be unsubscribed (onFeedback)', () => {
|
|
767
|
+
const fn = vi.fn();
|
|
768
|
+
const agent = createAgent({
|
|
769
|
+
id: 'test',
|
|
770
|
+
events: {},
|
|
771
|
+
model: {} as any,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const subscription = agent.onFeedback(fn);
|
|
775
|
+
|
|
776
|
+
agent.addFeedback({
|
|
777
|
+
reward: 1,
|
|
778
|
+
decisionId: 'dec-1',
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
782
|
+
|
|
783
|
+
subscription.unsubscribe();
|
|
784
|
+
|
|
785
|
+
agent.addFeedback({
|
|
786
|
+
reward: 0,
|
|
787
|
+
decisionId: 'dec-2',
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
expect(fn).toHaveBeenCalledTimes(1); // Still only called once
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
test('Feedback events include optional fields', () => {
|
|
794
|
+
const fn = vi.fn();
|
|
795
|
+
const agent = createAgent({
|
|
796
|
+
id: 'test',
|
|
797
|
+
events: {},
|
|
798
|
+
model: {} as any,
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
agent.onFeedback(fn);
|
|
802
|
+
|
|
803
|
+
// Test with minimal feedback
|
|
804
|
+
agent.addFeedback({
|
|
805
|
+
reward: 1,
|
|
806
|
+
decisionId: 'dec-1',
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
expect(fn).toHaveBeenCalledWith(
|
|
810
|
+
expect.objectContaining({
|
|
811
|
+
reward: 1,
|
|
812
|
+
decisionId: 'dec-1',
|
|
813
|
+
comment: undefined,
|
|
814
|
+
attributes: {},
|
|
815
|
+
episodeId: expect.any(String),
|
|
816
|
+
timestamp: expect.any(Number),
|
|
817
|
+
})
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
// Test with all optional fields
|
|
821
|
+
agent.addFeedback({
|
|
822
|
+
reward: 0,
|
|
823
|
+
decisionId: 'dec-2',
|
|
824
|
+
comment: 'Could be better',
|
|
825
|
+
attributes: { reason: 'suboptimal' },
|
|
826
|
+
} satisfies AgentFeedbackInput);
|
|
827
|
+
|
|
828
|
+
expect(fn).toHaveBeenLastCalledWith(
|
|
829
|
+
expect.objectContaining({
|
|
830
|
+
reward: 0,
|
|
831
|
+
decisionId: 'dec-2',
|
|
832
|
+
comment: 'Could be better',
|
|
833
|
+
attributes: { reason: 'suboptimal' },
|
|
834
|
+
episodeId: expect.any(String),
|
|
835
|
+
timestamp: expect.any(Number),
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
test('Feedback events maintain episodeId consistency', () => {
|
|
841
|
+
const fn = vi.fn();
|
|
842
|
+
const customEpisodeId = 'custom-episode-123';
|
|
843
|
+
|
|
844
|
+
const agent = createAgent({
|
|
845
|
+
id: 'test',
|
|
846
|
+
events: {},
|
|
847
|
+
model: {} as any,
|
|
848
|
+
episodeId: customEpisodeId,
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
agent.onFeedback(fn);
|
|
852
|
+
|
|
853
|
+
agent.addFeedback({
|
|
854
|
+
reward: 1,
|
|
855
|
+
decisionId: 'dec-1',
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
expect(fn).toHaveBeenCalledWith(
|
|
859
|
+
expect.objectContaining({
|
|
860
|
+
episodeId: customEpisodeId,
|
|
861
|
+
})
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
// Test with explicit different episodeId
|
|
865
|
+
const differentEpisodeId = 'different-episode-456';
|
|
866
|
+
agent.addFeedback({
|
|
867
|
+
reward: 0,
|
|
868
|
+
decisionId: 'dec-2',
|
|
869
|
+
episodeId: differentEpisodeId,
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
expect(fn).toHaveBeenLastCalledWith(
|
|
873
|
+
expect.objectContaining({
|
|
874
|
+
episodeId: differentEpisodeId,
|
|
875
|
+
})
|
|
876
|
+
);
|
|
877
|
+
});
|