@statelyai/agent 2.0.0-next.2 → 2.0.0-next.4
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/nice-pants-rule.md +10 -0
- package/.changeset/pink-eagles-deliver.md +13 -0
- package/.changeset/pre.json +7 -1
- package/.changeset/quiet-turtles-do.md +7 -0
- package/.changeset/sweet-clouds-mix.md +16 -0
- package/.changeset/swift-mangos-rush.md +5 -0
- package/.changeset/tough-ways-rhyme.md +5 -0
- package/CHANGELOG.md +50 -0
- package/architecture.tldr +175 -0
- package/dist/index.d.mts +116 -102
- package/dist/index.d.ts +116 -102
- package/dist/index.js +51 -74
- package/dist/index.mjs +51 -74
- package/examples/cot.ts +19 -53
- package/examples/customer-service-sim.ts +3 -3
- package/examples/email.ts +2 -10
- package/examples/example.ts +2 -2
- package/examples/goal.ts +2 -2
- package/examples/joke.ts +10 -10
- package/examples/learn-from-feedback.ts +47 -24
- package/examples/number.ts +2 -2
- package/examples/raffle.ts +2 -2
- package/examples/support.ts +7 -11
- package/examples/ticTacToe.ts +2 -2
- package/examples/todo.ts +3 -3
- package/examples/tutor.ts +2 -2
- package/examples/verify.ts +2 -2
- package/examples/weather-agent.ts +3 -5
- package/examples/weather.ts +10 -7
- package/package.json +1 -1
- package/src/agent.test.ts +124 -43
- package/src/agent.ts +61 -42
- package/src/decide.test.ts +22 -0
- package/src/decide.ts +27 -29
- package/src/strategies/chainOfThought.ts +5 -3
- package/src/strategies/shortestPath.ts +7 -2
- package/src/strategies/{simple.ts → simpleStrategy.ts} +5 -16
- package/src/templates/defaultText.ts +3 -0
- package/src/text.ts +13 -14
- package/src/types.ts +103 -104
- package/src/utils.ts +1 -1
package/src/agent.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { test, expect, vi } from 'vitest';
|
|
2
|
-
import { createAgent, TypesFromAgent } from './';
|
|
2
|
+
import { AgentDecision, createAgent, TypesFromAgent } from './';
|
|
3
3
|
import { createActor, createMachine } from 'xstate';
|
|
4
4
|
import { LanguageModelV1CallOptions } from 'ai';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -66,38 +66,43 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
66
66
|
test('agent.addFeedback() adds to feedback', () => {
|
|
67
67
|
const agent = createAgent({
|
|
68
68
|
id: 'test',
|
|
69
|
-
events: {
|
|
69
|
+
events: {
|
|
70
|
+
play: z.object({
|
|
71
|
+
position: z.number(),
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
70
74
|
model: {} as any,
|
|
71
75
|
});
|
|
72
76
|
|
|
73
|
-
const
|
|
74
|
-
attributes: {
|
|
75
|
-
score: -1,
|
|
76
|
-
},
|
|
77
|
+
const decision: AgentDecision<typeof agent> = {
|
|
77
78
|
goal: 'Win the game',
|
|
78
|
-
|
|
79
|
+
episodeId: agent.episodeId,
|
|
80
|
+
goalState: { value: 'won' },
|
|
81
|
+
id: 'decision-1',
|
|
82
|
+
nextEvent: { type: 'play', position: 3 },
|
|
83
|
+
paths: [],
|
|
84
|
+
strategy: 'simple',
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const obs = agent.addObservation({
|
|
89
|
+
decisionId: decision.id,
|
|
90
|
+
prevState: { value: 'playing' },
|
|
91
|
+
event: { type: 'play', position: 3 },
|
|
92
|
+
state: { value: 'lost' },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const feedback = agent.addFeedback({
|
|
96
|
+
score: 0,
|
|
97
|
+
observationId: obs.id,
|
|
79
98
|
});
|
|
80
99
|
|
|
81
100
|
expect(feedback.episodeId).toEqual(agent.episodeId);
|
|
82
101
|
|
|
83
102
|
expect(agent.getFeedback()).toContainEqual(
|
|
84
103
|
expect.objectContaining({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
goal: 'Win the game',
|
|
89
|
-
observationId: 'obs-1',
|
|
90
|
-
episodeId: expect.any(String),
|
|
91
|
-
timestamp: expect.any(Number),
|
|
92
|
-
})
|
|
93
|
-
);
|
|
94
|
-
expect(agent.getFeedback()).toContainEqual(
|
|
95
|
-
expect.objectContaining({
|
|
96
|
-
attributes: {
|
|
97
|
-
score: -1,
|
|
98
|
-
},
|
|
99
|
-
goal: 'Win the game',
|
|
100
|
-
observationId: 'obs-1',
|
|
104
|
+
score: 0,
|
|
105
|
+
observationId: obs.id,
|
|
101
106
|
episodeId: expect.any(String),
|
|
102
107
|
timestamp: expect.any(Number),
|
|
103
108
|
})
|
|
@@ -115,6 +120,7 @@ test('agent.addObservation() adds to observations', () => {
|
|
|
115
120
|
prevState: { value: 'playing', context: {} },
|
|
116
121
|
event: { type: 'play', position: 3 },
|
|
117
122
|
state: { value: 'lost', context: {} },
|
|
123
|
+
goal: 'Win the game',
|
|
118
124
|
});
|
|
119
125
|
|
|
120
126
|
expect(observation.episodeId).toEqual(agent.episodeId);
|
|
@@ -139,6 +145,7 @@ test('agent.addObservation() adds to observations (initial state)', () => {
|
|
|
139
145
|
|
|
140
146
|
const observation = agent.addObservation({
|
|
141
147
|
state: { value: 'lost' },
|
|
148
|
+
goal: 'Win the game',
|
|
142
149
|
});
|
|
143
150
|
|
|
144
151
|
expect(observation.episodeId).toEqual(agent.episodeId);
|
|
@@ -175,7 +182,7 @@ test('agent.addObservation() adds to observations with machine hash', () => {
|
|
|
175
182
|
prevState: { value: 'playing', context: {} },
|
|
176
183
|
event: { type: 'play', position: 3 },
|
|
177
184
|
state: { value: 'lost', context: {} },
|
|
178
|
-
|
|
185
|
+
goal: 'Win the game',
|
|
179
186
|
});
|
|
180
187
|
|
|
181
188
|
expect(observation.episodeId).toEqual(agent.episodeId);
|
|
@@ -185,7 +192,6 @@ test('agent.addObservation() adds to observations with machine hash', () => {
|
|
|
185
192
|
prevState: { value: 'playing', context: {} },
|
|
186
193
|
event: { type: 'play', position: 3 },
|
|
187
194
|
state: { value: 'lost', context: {} },
|
|
188
|
-
machineHash: expect.any(String),
|
|
189
195
|
episodeId: expect.any(String),
|
|
190
196
|
timestamp: expect.any(Number),
|
|
191
197
|
})
|
|
@@ -203,13 +209,11 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
|
203
209
|
state: {
|
|
204
210
|
value: 'playing',
|
|
205
211
|
},
|
|
212
|
+
goal: 'Win the game',
|
|
206
213
|
});
|
|
207
214
|
|
|
208
215
|
const feedback = agent.addFeedback({
|
|
209
|
-
|
|
210
|
-
score: -1,
|
|
211
|
-
},
|
|
212
|
-
goal: 'Win the game',
|
|
216
|
+
score: 0,
|
|
213
217
|
observationId: observation.id,
|
|
214
218
|
});
|
|
215
219
|
|
|
@@ -217,10 +221,7 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
|
217
221
|
|
|
218
222
|
expect(agent.getFeedback()).toContainEqual(
|
|
219
223
|
expect.objectContaining({
|
|
220
|
-
|
|
221
|
-
score: -1,
|
|
222
|
-
},
|
|
223
|
-
goal: 'Win the game',
|
|
224
|
+
score: 0,
|
|
224
225
|
observationId: observation.id,
|
|
225
226
|
episodeId: expect.any(String),
|
|
226
227
|
timestamp: expect.any(Number),
|
|
@@ -228,10 +229,7 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
|
228
229
|
);
|
|
229
230
|
expect(agent.getFeedback()).toContainEqual(
|
|
230
231
|
expect.objectContaining({
|
|
231
|
-
|
|
232
|
-
score: -1,
|
|
233
|
-
},
|
|
234
|
-
goal: 'Win the game',
|
|
232
|
+
score: 0,
|
|
235
233
|
observationId: observation.id,
|
|
236
234
|
episodeId: expect.any(String),
|
|
237
235
|
timestamp: expect.any(Number),
|
|
@@ -297,10 +295,7 @@ test('You can listen for feedback events', () => {
|
|
|
297
295
|
agent.on('feedback', fn);
|
|
298
296
|
|
|
299
297
|
agent.addFeedback({
|
|
300
|
-
|
|
301
|
-
score: -1,
|
|
302
|
-
},
|
|
303
|
-
goal: 'Win the game',
|
|
298
|
+
score: -1,
|
|
304
299
|
observationId: 'obs-1',
|
|
305
300
|
});
|
|
306
301
|
|
|
@@ -445,6 +440,7 @@ test('agent.getDecisions() returns decisions from context', () => {
|
|
|
445
440
|
model: {} as any,
|
|
446
441
|
strategy: async (agent) => {
|
|
447
442
|
return {
|
|
443
|
+
id: Date.now().toString(),
|
|
448
444
|
episodeId: agent.episodeId,
|
|
449
445
|
strategy: 'test-strategy',
|
|
450
446
|
goal: '',
|
|
@@ -520,7 +516,6 @@ test('agent.observe() adds observations from actor snapshots', () => {
|
|
|
520
516
|
expect(agent.getObservations()).toContainEqual(
|
|
521
517
|
expect.objectContaining({
|
|
522
518
|
state: expect.objectContaining({ value: 'idle' }),
|
|
523
|
-
machineHash: expect.any(String),
|
|
524
519
|
})
|
|
525
520
|
);
|
|
526
521
|
|
|
@@ -529,9 +524,95 @@ test('agent.observe() adds observations from actor snapshots', () => {
|
|
|
529
524
|
prevState: expect.objectContaining({ value: 'idle' }),
|
|
530
525
|
event: { type: 'START' },
|
|
531
526
|
state: expect.objectContaining({ value: 'running' }),
|
|
532
|
-
machineHash: expect.any(String),
|
|
533
527
|
})
|
|
534
528
|
);
|
|
535
529
|
|
|
536
530
|
subscription.unsubscribe();
|
|
537
531
|
});
|
|
532
|
+
|
|
533
|
+
test('agent.addObservation() accepts custom episodeId', () => {
|
|
534
|
+
const agent = createAgent({
|
|
535
|
+
id: 'test',
|
|
536
|
+
events: {},
|
|
537
|
+
model: {} as any,
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const customEpisodeId = 'custom-episode-123';
|
|
541
|
+
const observation = agent.addObservation({
|
|
542
|
+
state: { value: 'playing' },
|
|
543
|
+
goal: 'Win the game',
|
|
544
|
+
episodeId: customEpisodeId,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(observation.episodeId).toEqual(customEpisodeId);
|
|
548
|
+
expect(agent.getObservations()).toContainEqual(
|
|
549
|
+
expect.objectContaining({
|
|
550
|
+
episodeId: customEpisodeId,
|
|
551
|
+
})
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test('agent.addFeedback() accepts custom episodeId', () => {
|
|
556
|
+
const agent = createAgent({
|
|
557
|
+
id: 'test',
|
|
558
|
+
events: {},
|
|
559
|
+
model: {} as any,
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const customEpisodeId = 'custom-episode-123';
|
|
563
|
+
const feedback = agent.addFeedback({
|
|
564
|
+
score: 1,
|
|
565
|
+
observationId: 'obs-1',
|
|
566
|
+
episodeId: customEpisodeId,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
expect(feedback.episodeId).toEqual(customEpisodeId);
|
|
570
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
571
|
+
expect.objectContaining({
|
|
572
|
+
episodeId: customEpisodeId,
|
|
573
|
+
})
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test('agent.addObservation() accepts decisionId', () => {
|
|
578
|
+
const agent = createAgent({
|
|
579
|
+
id: 'test',
|
|
580
|
+
events: {},
|
|
581
|
+
model: {} as any,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const decisionId = 'decision-123';
|
|
585
|
+
const observation = agent.addObservation({
|
|
586
|
+
state: { value: 'playing' },
|
|
587
|
+
goal: 'Win the game',
|
|
588
|
+
decisionId,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
expect(observation.decisionId).toEqual(decisionId);
|
|
592
|
+
expect(agent.getObservations()).toContainEqual(
|
|
593
|
+
expect.objectContaining({
|
|
594
|
+
decisionId,
|
|
595
|
+
})
|
|
596
|
+
);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('agent.addFeedback() accepts decisionId', () => {
|
|
600
|
+
const agent = createAgent({
|
|
601
|
+
id: 'test',
|
|
602
|
+
events: {},
|
|
603
|
+
model: {} as any,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const decisionId = 'decision-123';
|
|
607
|
+
const feedback = agent.addFeedback({
|
|
608
|
+
score: 1,
|
|
609
|
+
decisionId,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
expect(feedback.decisionId).toEqual(decisionId);
|
|
613
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
614
|
+
expect.objectContaining({
|
|
615
|
+
decisionId,
|
|
616
|
+
})
|
|
617
|
+
);
|
|
618
|
+
});
|
package/src/agent.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Actor,
|
|
3
3
|
ActorRefLike,
|
|
4
|
-
AnyEventObject,
|
|
5
4
|
EventObject,
|
|
6
5
|
fromTransition,
|
|
7
6
|
Subscription,
|
|
@@ -23,16 +22,13 @@ import {
|
|
|
23
22
|
AgentMessageInput,
|
|
24
23
|
AgentFeedbackInput,
|
|
25
24
|
AgentDecision,
|
|
26
|
-
Compute,
|
|
27
|
-
AgentDecisionInput,
|
|
28
|
-
AgentDecideOptions,
|
|
29
25
|
AnyAgent,
|
|
30
|
-
EventsFromAgent,
|
|
31
26
|
AgentInteractInput,
|
|
27
|
+
AgentDecideInput,
|
|
32
28
|
} from './types';
|
|
33
|
-
import { simpleStrategy } from './strategies/
|
|
29
|
+
import { simpleStrategy } from './strategies/simpleStrategy';
|
|
34
30
|
import { agentDecide } from './decide';
|
|
35
|
-
import {
|
|
31
|
+
import { isActorRef, isMachineActor, randomId } from './utils';
|
|
36
32
|
import {
|
|
37
33
|
experimental_wrapLanguageModel,
|
|
38
34
|
LanguageModel,
|
|
@@ -40,7 +36,7 @@ import {
|
|
|
40
36
|
} from 'ai';
|
|
41
37
|
import { createAgentMiddleware } from './middleware';
|
|
42
38
|
|
|
43
|
-
export const agentLogic: AgentLogic<
|
|
39
|
+
export const agentLogic: AgentLogic<any> = fromTransition(
|
|
44
40
|
(state, event, { emit }) => {
|
|
45
41
|
switch (event.type) {
|
|
46
42
|
case 'agent.feedback': {
|
|
@@ -92,14 +88,15 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
|
|
|
92
88
|
messages: [],
|
|
93
89
|
observations: [],
|
|
94
90
|
decisions: [],
|
|
95
|
-
} as AgentMemoryContext)
|
|
91
|
+
} as AgentMemoryContext<any>)
|
|
96
92
|
);
|
|
97
93
|
|
|
98
94
|
export function createAgent<
|
|
99
95
|
const TContextSchema extends ZodContextMapping,
|
|
100
96
|
const TEventSchemas extends ZodEventMapping,
|
|
101
97
|
TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
|
|
102
|
-
TContext = ContextFromZodContextMapping<TContextSchema
|
|
98
|
+
TContext = ContextFromZodContextMapping<TContextSchema>,
|
|
99
|
+
TAgent extends AnyAgent = Agent<TContextSchema, TEventSchemas>
|
|
103
100
|
>({
|
|
104
101
|
id,
|
|
105
102
|
description: description,
|
|
@@ -108,7 +105,7 @@ export function createAgent<
|
|
|
108
105
|
context,
|
|
109
106
|
episodeId,
|
|
110
107
|
strategy = simpleStrategy,
|
|
111
|
-
logic = agentLogic as AgentLogic<
|
|
108
|
+
logic = agentLogic as AgentLogic<any>,
|
|
112
109
|
}: {
|
|
113
110
|
/**
|
|
114
111
|
* The unique identifier for the agent.
|
|
@@ -142,11 +139,11 @@ export function createAgent<
|
|
|
142
139
|
*/
|
|
143
140
|
getMemory?: (
|
|
144
141
|
agent: Agent<TContextSchema, TEventSchemas>
|
|
145
|
-
) => AgentLongTermMemory
|
|
142
|
+
) => AgentLongTermMemory<TAgent>;
|
|
146
143
|
/**
|
|
147
144
|
* Agent logic
|
|
148
145
|
*/
|
|
149
|
-
logic?: AgentLogic<
|
|
146
|
+
logic?: AgentLogic<TAgent>;
|
|
150
147
|
model: LanguageModel;
|
|
151
148
|
episodeId?: string;
|
|
152
149
|
}): Agent<TContextSchema, TEventSchemas> {
|
|
@@ -167,7 +164,7 @@ export class Agent<
|
|
|
167
164
|
const TEventSchemas extends ZodEventMapping,
|
|
168
165
|
TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
|
|
169
166
|
TContext = ContextFromZodContextMapping<TContextSchema>
|
|
170
|
-
> extends Actor<AgentLogic<
|
|
167
|
+
> extends Actor<AgentLogic<any>> {
|
|
171
168
|
/**
|
|
172
169
|
* The name of the agent. All agents with the same name are related and
|
|
173
170
|
* able to share experiences (observations, feedback) with each other.
|
|
@@ -186,11 +183,10 @@ export class Agent<
|
|
|
186
183
|
// context: Compute<TContext>;
|
|
187
184
|
// };
|
|
188
185
|
public model: LanguageModel;
|
|
189
|
-
public memory: AgentLongTermMemory | undefined;
|
|
190
|
-
public defaultOptions: AgentDecideOptions<AnyAgent> | undefined; // todo
|
|
186
|
+
public memory: AgentLongTermMemory<this> | undefined;
|
|
191
187
|
|
|
192
188
|
constructor({
|
|
193
|
-
logic = agentLogic as AgentLogic<
|
|
189
|
+
logic = agentLogic as AgentLogic<any>,
|
|
194
190
|
id,
|
|
195
191
|
name,
|
|
196
192
|
description,
|
|
@@ -200,7 +196,7 @@ export class Agent<
|
|
|
200
196
|
episodeId,
|
|
201
197
|
strategy = simpleStrategy,
|
|
202
198
|
}: {
|
|
203
|
-
logic: AgentLogic<
|
|
199
|
+
logic: AgentLogic<any>;
|
|
204
200
|
id?: string;
|
|
205
201
|
name?: string;
|
|
206
202
|
description?: string;
|
|
@@ -262,9 +258,10 @@ export class Agent<
|
|
|
262
258
|
public addFeedback(feedbackInput: AgentFeedbackInput) {
|
|
263
259
|
const feedback = {
|
|
264
260
|
...feedbackInput,
|
|
261
|
+
comment: feedbackInput.comment ?? undefined,
|
|
265
262
|
attributes: { ...feedbackInput.attributes },
|
|
266
263
|
timestamp: feedbackInput.timestamp ?? Date.now(),
|
|
267
|
-
episodeId: this.episodeId,
|
|
264
|
+
episodeId: feedbackInput.episodeId ?? this.episodeId,
|
|
268
265
|
} satisfies AgentFeedback;
|
|
269
266
|
this.send({
|
|
270
267
|
type: 'agent.feedback',
|
|
@@ -281,7 +278,7 @@ export class Agent<
|
|
|
281
278
|
}
|
|
282
279
|
|
|
283
280
|
public addObservation(
|
|
284
|
-
observationInput: AgentObservationInput
|
|
281
|
+
observationInput: AgentObservationInput<this>
|
|
285
282
|
): AgentObservation<any> {
|
|
286
283
|
const { prevState, event, state } = observationInput;
|
|
287
284
|
const observation = {
|
|
@@ -289,11 +286,12 @@ export class Agent<
|
|
|
289
286
|
event,
|
|
290
287
|
state,
|
|
291
288
|
id: observationInput.id ?? randomId(),
|
|
292
|
-
episodeId: this.episodeId,
|
|
289
|
+
episodeId: observationInput.episodeId ?? this.episodeId,
|
|
293
290
|
timestamp: observationInput.timestamp ?? Date.now(),
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
decisionId: observationInput.decisionId,
|
|
292
|
+
// machineHash: observationInput.machine
|
|
293
|
+
// ? getMachineHash(observationInput.machine)
|
|
294
|
+
// : undefined,
|
|
297
295
|
} satisfies AgentObservation<any>;
|
|
298
296
|
|
|
299
297
|
this.send({
|
|
@@ -311,7 +309,7 @@ export class Agent<
|
|
|
311
309
|
return this.getSnapshot().context.observations;
|
|
312
310
|
}
|
|
313
311
|
|
|
314
|
-
public addDecision(decision: AgentDecision<
|
|
312
|
+
public addDecision(decision: AgentDecision<this>) {
|
|
315
313
|
this.send({
|
|
316
314
|
type: 'agent.decision',
|
|
317
315
|
decision,
|
|
@@ -383,25 +381,29 @@ export class Agent<
|
|
|
383
381
|
const actorRefCheck = isActorRef(actorRef) && actorRef.src;
|
|
384
382
|
const machine = isMachineActor(actorRef) ? actorRef.src : undefined;
|
|
385
383
|
|
|
386
|
-
let prevState: ObservedState | undefined = undefined;
|
|
384
|
+
let prevState: ObservedState<this> | undefined = undefined;
|
|
387
385
|
let subscribed = true;
|
|
388
386
|
|
|
389
387
|
const agent = this;
|
|
390
388
|
|
|
391
|
-
async function handleObservation(
|
|
389
|
+
async function handleObservation(
|
|
390
|
+
observationInput: AgentObservationInput<any>
|
|
391
|
+
) {
|
|
392
392
|
const observation = agent.addObservation(observationInput);
|
|
393
393
|
|
|
394
|
-
const
|
|
394
|
+
const interactInput = getInput?.(observation);
|
|
395
395
|
|
|
396
|
-
if (
|
|
397
|
-
const
|
|
396
|
+
if (interactInput) {
|
|
397
|
+
const decision = await agentDecide(agent, {
|
|
398
398
|
machine,
|
|
399
399
|
state: observation.state,
|
|
400
|
-
...
|
|
400
|
+
...interactInput,
|
|
401
401
|
});
|
|
402
402
|
|
|
403
|
-
if (
|
|
404
|
-
|
|
403
|
+
if (decision?.nextEvent) {
|
|
404
|
+
// @ts-ignore
|
|
405
|
+
decision.nextEvent['_decision'] = decision.id;
|
|
406
|
+
actorRef.send(decision.nextEvent);
|
|
405
407
|
}
|
|
406
408
|
}
|
|
407
409
|
|
|
@@ -420,12 +422,21 @@ export class Agent<
|
|
|
420
422
|
return;
|
|
421
423
|
}
|
|
422
424
|
|
|
425
|
+
const decisionId = inspEvent.event['_decision'] as
|
|
426
|
+
| string
|
|
427
|
+
| undefined;
|
|
428
|
+
|
|
429
|
+
const decision = decisionId
|
|
430
|
+
? agent.getDecisions().find((d) => d.id === decisionId)
|
|
431
|
+
: undefined;
|
|
432
|
+
|
|
423
433
|
const observationInput = {
|
|
424
434
|
event: inspEvent.event,
|
|
425
435
|
prevState,
|
|
426
436
|
state: inspEvent.snapshot as any,
|
|
427
|
-
|
|
428
|
-
|
|
437
|
+
goal: decision?.goal,
|
|
438
|
+
decisionId,
|
|
439
|
+
} satisfies AgentObservationInput<any>;
|
|
429
440
|
|
|
430
441
|
await handleObservation(observationInput);
|
|
431
442
|
},
|
|
@@ -435,10 +446,10 @@ export class Agent<
|
|
|
435
446
|
// If actor already started, interact with current state
|
|
436
447
|
if ((actorRef as any)._processingStatus === 1) {
|
|
437
448
|
handleObservation({
|
|
449
|
+
decisionId: undefined,
|
|
438
450
|
prevState: undefined,
|
|
439
451
|
event: undefined,
|
|
440
452
|
state: actorRef.getSnapshot(),
|
|
441
|
-
machine: (actorRef as any).src,
|
|
442
453
|
});
|
|
443
454
|
}
|
|
444
455
|
|
|
@@ -451,7 +462,7 @@ export class Agent<
|
|
|
451
462
|
}
|
|
452
463
|
|
|
453
464
|
public observe<TActor extends ActorRefLike>(actorRef: TActor): Subscription {
|
|
454
|
-
let prevState: ObservedState = actorRef.getSnapshot();
|
|
465
|
+
let prevState: ObservedState<this> = actorRef.getSnapshot();
|
|
455
466
|
const actorRefCheck = isActorRef(actorRef);
|
|
456
467
|
|
|
457
468
|
const sub = actorRefCheck
|
|
@@ -464,12 +475,20 @@ export class Agent<
|
|
|
464
475
|
return;
|
|
465
476
|
}
|
|
466
477
|
|
|
478
|
+
const decisionId = inspEvent.event['_decision'] as
|
|
479
|
+
| string
|
|
480
|
+
| undefined;
|
|
481
|
+
const decision = decisionId
|
|
482
|
+
? this.getDecisions().find((d) => d.id === decisionId)
|
|
483
|
+
: undefined;
|
|
484
|
+
|
|
467
485
|
const observationInput = {
|
|
486
|
+
decisionId,
|
|
468
487
|
event: inspEvent.event,
|
|
469
488
|
prevState,
|
|
470
489
|
state: inspEvent.snapshot as any,
|
|
471
|
-
|
|
472
|
-
} satisfies AgentObservationInput
|
|
490
|
+
goal: decision?.goal,
|
|
491
|
+
} satisfies AgentObservationInput<this>;
|
|
473
492
|
|
|
474
493
|
prevState = observationInput.state;
|
|
475
494
|
|
|
@@ -497,8 +516,8 @@ export class Agent<
|
|
|
497
516
|
* - Additional `context`
|
|
498
517
|
*/
|
|
499
518
|
public async decide(
|
|
500
|
-
|
|
501
|
-
): Promise<AgentDecision<
|
|
502
|
-
return agentDecide(this,
|
|
519
|
+
input: AgentDecideInput<this>
|
|
520
|
+
): Promise<AgentDecision<this> | undefined> {
|
|
521
|
+
return agentDecide(this, input);
|
|
503
522
|
}
|
|
504
523
|
}
|
package/src/decide.test.ts
CHANGED
|
@@ -322,3 +322,25 @@ test.each([['MOVE'], ['FORFEIT']] as const)(
|
|
|
322
322
|
expect(decision?.nextEvent?.type).toEqual(allowedEventType);
|
|
323
323
|
}
|
|
324
324
|
);
|
|
325
|
+
|
|
326
|
+
test('agent.decide() accepts custom episodeId', async () => {
|
|
327
|
+
const model = new MockLanguageModelV1({
|
|
328
|
+
doGenerate,
|
|
329
|
+
});
|
|
330
|
+
const agent = createAgent({
|
|
331
|
+
id: 'test',
|
|
332
|
+
events: {
|
|
333
|
+
WIN: z.object({}),
|
|
334
|
+
},
|
|
335
|
+
model,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const customEpisodeId = 'custom-episode-123';
|
|
339
|
+
const decision = await agent.decide({
|
|
340
|
+
goal: 'Win the game',
|
|
341
|
+
state: { value: 'playing' },
|
|
342
|
+
episodeId: customEpisodeId,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(decision?.episodeId).toEqual(customEpisodeId);
|
|
346
|
+
});
|
package/src/decide.ts
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import { AnyActor, AnyMachineSnapshot, fromPromise } from 'xstate';
|
|
2
2
|
import {
|
|
3
3
|
AnyAgent,
|
|
4
|
-
AgentDecideOptions,
|
|
5
4
|
AgentDecisionLogic,
|
|
6
5
|
AgentDecision,
|
|
7
6
|
AgentDecideInput,
|
|
8
7
|
TransitionData,
|
|
9
|
-
|
|
8
|
+
EventFromAgent,
|
|
10
9
|
} from './types';
|
|
11
10
|
import { getTransitions } from './utils';
|
|
12
11
|
import { CoreMessage, CoreTool, tool } from 'ai';
|
|
12
|
+
import { ZodEventMapping } from './schemas';
|
|
13
13
|
|
|
14
|
-
export async function agentDecide<
|
|
15
|
-
agent:
|
|
16
|
-
options:
|
|
17
|
-
): Promise<AgentDecision<
|
|
18
|
-
const resolvedOptions =
|
|
19
|
-
...agent.defaultOptions,
|
|
20
|
-
...options,
|
|
21
|
-
};
|
|
14
|
+
export async function agentDecide<TAgent extends AnyAgent>(
|
|
15
|
+
agent: TAgent,
|
|
16
|
+
options: AgentDecideInput<TAgent>
|
|
17
|
+
): Promise<AgentDecision<TAgent> | undefined> {
|
|
18
|
+
const resolvedOptions = options;
|
|
22
19
|
const {
|
|
23
20
|
strategy = agent.strategy,
|
|
24
21
|
goal,
|
|
@@ -28,6 +25,8 @@ export async function agentDecide<T extends AnyAgent>(
|
|
|
28
25
|
machine,
|
|
29
26
|
model = agent.model,
|
|
30
27
|
messages,
|
|
28
|
+
episodeId = agent.episodeId,
|
|
29
|
+
maxAttempts = 2,
|
|
31
30
|
...otherDecideInput
|
|
32
31
|
} = resolvedOptions;
|
|
33
32
|
|
|
@@ -41,16 +40,20 @@ export async function agentDecide<T extends AnyAgent>(
|
|
|
41
40
|
|
|
42
41
|
let attempts = 0;
|
|
43
42
|
|
|
44
|
-
const maxAttempts = resolvedOptions.maxAttempts ?? 2;
|
|
45
|
-
|
|
46
43
|
let decision: AgentDecision<any> | undefined;
|
|
47
44
|
|
|
45
|
+
const minimalState = {
|
|
46
|
+
value: state.value,
|
|
47
|
+
context: state.context,
|
|
48
|
+
};
|
|
49
|
+
|
|
48
50
|
while (attempts++ < maxAttempts) {
|
|
49
51
|
decision = await strategy(agent, {
|
|
52
|
+
episodeId,
|
|
50
53
|
model,
|
|
51
54
|
goal,
|
|
52
55
|
events: filteredEventSchemas,
|
|
53
|
-
state,
|
|
56
|
+
state: minimalState,
|
|
54
57
|
machine,
|
|
55
58
|
messages: messages as CoreMessage[], // TODO: fix UIMessage thing
|
|
56
59
|
...otherDecideInput,
|
|
@@ -58,7 +61,6 @@ export async function agentDecide<T extends AnyAgent>(
|
|
|
58
61
|
|
|
59
62
|
if (decision?.nextEvent) {
|
|
60
63
|
agent.addDecision(decision);
|
|
61
|
-
await resolvedOptions.execute?.(decision.nextEvent);
|
|
62
64
|
break;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -68,7 +70,7 @@ export async function agentDecide<T extends AnyAgent>(
|
|
|
68
70
|
|
|
69
71
|
export function fromDecision<T extends AnyAgent>(
|
|
70
72
|
agent: T,
|
|
71
|
-
defaultInput?: AgentDecideInput<
|
|
73
|
+
defaultInput?: AgentDecideInput<EventFromAgent<T>>
|
|
72
74
|
): AgentDecisionLogic<any> {
|
|
73
75
|
return fromPromise(async ({ input, self }) => {
|
|
74
76
|
const parentRef = self._parent;
|
|
@@ -82,43 +84,39 @@ export function fromDecision<T extends AnyAgent>(
|
|
|
82
84
|
...defaultInput,
|
|
83
85
|
...inputObject,
|
|
84
86
|
};
|
|
85
|
-
const state = {
|
|
86
|
-
value: snapshot.value,
|
|
87
|
-
context: resolvedInput.context,
|
|
88
|
-
};
|
|
89
87
|
|
|
90
|
-
const decision = await agentDecide(agent, {
|
|
88
|
+
const decision = await agentDecide<typeof agent>(agent, {
|
|
91
89
|
machine: (parentRef as AnyActor).logic,
|
|
92
90
|
state: snapshot,
|
|
93
|
-
context: resolvedInput.context,
|
|
94
|
-
execute: async (event) => {
|
|
95
|
-
parentRef.send(event);
|
|
96
|
-
},
|
|
97
91
|
...resolvedInput,
|
|
98
92
|
// @ts-ignore
|
|
99
93
|
messages: resolvedInput.messages,
|
|
100
94
|
});
|
|
101
95
|
|
|
96
|
+
if (decision?.nextEvent) {
|
|
97
|
+
parentRef.send(decision.nextEvent);
|
|
98
|
+
}
|
|
99
|
+
|
|
102
100
|
return decision;
|
|
103
101
|
}) as AgentDecisionLogic<any>;
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
export function getToolMap<
|
|
107
|
-
|
|
104
|
+
export function getToolMap<TAgent extends AnyAgent>(
|
|
105
|
+
agent: TAgent,
|
|
108
106
|
input: AgentDecideInput<any>
|
|
109
107
|
): Record<string, CoreTool<any, any>> | undefined {
|
|
108
|
+
const events = input.events ?? (agent.events as ZodEventMapping);
|
|
110
109
|
// Get all of the possible next transitions
|
|
111
110
|
const transitions: TransitionData[] = input.machine
|
|
112
111
|
? getTransitions(input.state, input.machine)
|
|
113
|
-
: Object.entries(
|
|
112
|
+
: Object.entries(events).map(([eventType, { description }]) => ({
|
|
114
113
|
eventType,
|
|
115
114
|
description,
|
|
116
115
|
}));
|
|
117
116
|
|
|
118
117
|
// Only keep the transitions that match the event types that are in the event mapping
|
|
119
118
|
// TODO: allow for custom filters
|
|
120
|
-
const filter = (eventType: string) =>
|
|
121
|
-
Object.keys(input.events).includes(eventType);
|
|
119
|
+
const filter = (eventType: string) => Object.keys(events).includes(eventType);
|
|
122
120
|
|
|
123
121
|
// Mapping of each event type (e.g. "mouse.click")
|
|
124
122
|
// to a valid function name (e.g. "mouse_click")
|