@statelyai/agent 2.0.0-next.0 → 2.0.0-next.2
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/cyan-carpets-perform.md +5 -0
- package/.changeset/fast-donkeys-argue.md +5 -0
- package/.changeset/grumpy-dolphins-think.md +17 -0
- package/.changeset/old-jobs-check.md +5 -0
- package/.changeset/old-teachers-tap.md +5 -0
- package/.changeset/pre.json +7 -1
- package/.changeset/smart-yaks-pull.md +23 -0
- package/CHANGELOG.md +52 -0
- package/dist/index.d.mts +69 -66
- package/dist/index.d.ts +69 -66
- package/dist/index.js +111 -79
- package/dist/index.mjs +114 -82
- package/examples/chatbot-alt.ts +1 -1
- package/examples/chatbot.ts +3 -3
- package/examples/cot.ts +7 -25
- package/examples/customer-service-sim.ts +7 -7
- package/examples/email.ts +37 -35
- package/examples/example.ts +3 -3
- package/examples/goal.ts +3 -3
- package/examples/joke.ts +3 -3
- package/examples/jugs.ts +5 -8
- package/examples/learn-from-feedback.ts +100 -0
- package/examples/multi.ts +1 -1
- package/examples/number.ts +3 -3
- package/examples/raffle.ts +3 -3
- package/examples/river-crossing.ts +5 -8
- package/examples/simple.ts +14 -11
- package/examples/summary.ts +3 -6
- package/examples/support.ts +43 -39
- package/examples/ticTacToe.ts +48 -6
- package/examples/todo.ts +3 -3
- package/examples/tutor.ts +4 -4
- package/examples/verify.ts +3 -3
- package/examples/weather-agent.ts +141 -0
- package/examples/weather.ts +24 -24
- package/examples/wiki.ts +1 -1
- package/examples/word.ts +9 -7
- package/package.json +6 -3
- package/src/agent.test.ts +161 -19
- package/src/agent.ts +66 -250
- package/src/decide.test.ts +172 -3
- package/src/decide.ts +50 -30
- package/src/middleware.ts +2 -14
- package/src/strategies/chainOfThought.ts +48 -0
- package/src/strategies/shortestPath.test.ts +91 -0
- package/src/{planners/shortestPathPlanner.ts → strategies/shortestPath.ts} +32 -19
- package/src/{planners/simplePlanner.ts → strategies/simple.ts} +28 -24
- package/src/types.ts +75 -34
- package/src/utils.ts +23 -0
- package/vitest.config.ts +9 -3
- package/src/strategies/chain-of-note.ts +0 -106
package/src/agent.ts
CHANGED
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
Actor,
|
|
3
3
|
ActorRefLike,
|
|
4
4
|
AnyEventObject,
|
|
5
|
-
AnyStateMachine,
|
|
6
5
|
EventObject,
|
|
7
6
|
fromTransition,
|
|
8
7
|
Subscription,
|
|
@@ -11,7 +10,7 @@ import { ZodContextMapping, ZodEventMapping } from './schemas';
|
|
|
11
10
|
import {
|
|
12
11
|
AgentLogic,
|
|
13
12
|
AgentMessage,
|
|
14
|
-
|
|
13
|
+
AgentStrategy,
|
|
15
14
|
EventsFromZodEventMapping,
|
|
16
15
|
GenerateTextOptions,
|
|
17
16
|
AgentLongTermMemory,
|
|
@@ -23,14 +22,17 @@ import {
|
|
|
23
22
|
AgentFeedback,
|
|
24
23
|
AgentMessageInput,
|
|
25
24
|
AgentFeedbackInput,
|
|
26
|
-
|
|
25
|
+
AgentDecision,
|
|
27
26
|
Compute,
|
|
28
27
|
AgentDecisionInput,
|
|
29
28
|
AgentDecideOptions,
|
|
29
|
+
AnyAgent,
|
|
30
|
+
EventsFromAgent,
|
|
31
|
+
AgentInteractInput,
|
|
30
32
|
} from './types';
|
|
31
|
-
import {
|
|
33
|
+
import { simpleStrategy } from './strategies/simple';
|
|
32
34
|
import { agentDecide } from './decide';
|
|
33
|
-
import { getMachineHash, isActorRef, randomId } from './utils';
|
|
35
|
+
import { getMachineHash, isActorRef, isMachineActor, randomId } from './utils';
|
|
34
36
|
import {
|
|
35
37
|
experimental_wrapLanguageModel,
|
|
36
38
|
LanguageModel,
|
|
@@ -68,17 +70,19 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
|
|
|
68
70
|
});
|
|
69
71
|
break;
|
|
70
72
|
}
|
|
71
|
-
case 'agent.
|
|
72
|
-
state.
|
|
73
|
+
case 'agent.decision': {
|
|
74
|
+
state.decisions.push(event.decision);
|
|
73
75
|
emit({
|
|
74
|
-
type: '
|
|
75
|
-
|
|
76
|
-
plan: event.plan,
|
|
76
|
+
type: 'decision',
|
|
77
|
+
decision: event.decision,
|
|
77
78
|
});
|
|
78
79
|
break;
|
|
79
80
|
}
|
|
80
|
-
default:
|
|
81
|
+
default: {
|
|
82
|
+
// unrecognized
|
|
83
|
+
console.warn('Unrecognized event', event);
|
|
81
84
|
break;
|
|
85
|
+
}
|
|
82
86
|
}
|
|
83
87
|
return state;
|
|
84
88
|
},
|
|
@@ -87,7 +91,7 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
|
|
|
87
91
|
feedback: [],
|
|
88
92
|
messages: [],
|
|
89
93
|
observations: [],
|
|
90
|
-
|
|
94
|
+
decisions: [],
|
|
91
95
|
} as AgentMemoryContext)
|
|
92
96
|
);
|
|
93
97
|
|
|
@@ -98,22 +102,19 @@ export function createAgent<
|
|
|
98
102
|
TContext = ContextFromZodContextMapping<TContextSchema>
|
|
99
103
|
>({
|
|
100
104
|
id,
|
|
101
|
-
|
|
102
|
-
description,
|
|
105
|
+
description: description,
|
|
103
106
|
model,
|
|
104
107
|
events,
|
|
105
108
|
context,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
getMemory,
|
|
109
|
+
episodeId,
|
|
110
|
+
strategy = simpleStrategy,
|
|
109
111
|
logic = agentLogic as AgentLogic<TEvents>,
|
|
110
|
-
...generateTextOptions
|
|
111
112
|
}: {
|
|
112
113
|
/**
|
|
113
114
|
* The unique identifier for the agent.
|
|
114
115
|
*
|
|
115
116
|
* This should be the same across all sessions of a specific agent, as it can be
|
|
116
|
-
* used to retrieve memory for this agent.
|
|
117
|
+
* used to retrieve memory for previous episodes of this agent.
|
|
117
118
|
*
|
|
118
119
|
* @example
|
|
119
120
|
* ```ts
|
|
@@ -124,10 +125,6 @@ export function createAgent<
|
|
|
124
125
|
* ```
|
|
125
126
|
*/
|
|
126
127
|
id?: string;
|
|
127
|
-
/**
|
|
128
|
-
* The name of the agent
|
|
129
|
-
*/
|
|
130
|
-
name?: string;
|
|
131
128
|
/**
|
|
132
129
|
* A description of the role of the agent
|
|
133
130
|
*/
|
|
@@ -138,7 +135,7 @@ export function createAgent<
|
|
|
138
135
|
*/
|
|
139
136
|
events: TEventSchemas;
|
|
140
137
|
context?: TContextSchema;
|
|
141
|
-
|
|
138
|
+
strategy?: AgentStrategy<Agent<TContextSchema, TEventSchemas>>;
|
|
142
139
|
stringify?: typeof JSON.stringify;
|
|
143
140
|
/**
|
|
144
141
|
* A function that retrieves the agent's long term memory
|
|
@@ -150,210 +147,19 @@ export function createAgent<
|
|
|
150
147
|
* Agent logic
|
|
151
148
|
*/
|
|
152
149
|
logic?: AgentLogic<TEvents>;
|
|
153
|
-
|
|
150
|
+
model: LanguageModel;
|
|
151
|
+
episodeId?: string;
|
|
152
|
+
}): Agent<TContextSchema, TEventSchemas> {
|
|
154
153
|
return new Agent({
|
|
155
154
|
id,
|
|
156
155
|
context,
|
|
157
156
|
events,
|
|
158
|
-
name,
|
|
159
157
|
description,
|
|
160
|
-
|
|
158
|
+
strategy: strategy,
|
|
161
159
|
model,
|
|
162
160
|
logic,
|
|
161
|
+
episodeId,
|
|
163
162
|
}) as any;
|
|
164
|
-
// const agent = createActor(logic) as unknown as Agent<TContext, TEvents>;
|
|
165
|
-
// agent.events = events;
|
|
166
|
-
// agent.model = model;
|
|
167
|
-
// agent.name = name;
|
|
168
|
-
// agent.description = description;
|
|
169
|
-
// agent.defaultOptions = { ...generateTextOptions, model };
|
|
170
|
-
// agent.memory = getMemory ? getMemory(agent) : undefined;
|
|
171
|
-
|
|
172
|
-
// agent.onMessage = (callback) => {
|
|
173
|
-
// agent.on('message', (ev) => callback(ev.message));
|
|
174
|
-
// };
|
|
175
|
-
|
|
176
|
-
// agent.decide = (opts) => {
|
|
177
|
-
// return agentDecide(agent, opts);
|
|
178
|
-
// };
|
|
179
|
-
|
|
180
|
-
// agent.addMessage = (messageInput) => {
|
|
181
|
-
// const message = {
|
|
182
|
-
// ...messageInput,
|
|
183
|
-
// id: messageInput.id ?? randomId(),
|
|
184
|
-
// timestamp: messageInput.timestamp ?? Date.now(),
|
|
185
|
-
// sessionId: agent.sessionId,
|
|
186
|
-
// } satisfies AgentMessage;
|
|
187
|
-
// agent.send({
|
|
188
|
-
// type: 'agent.message',
|
|
189
|
-
// message,
|
|
190
|
-
// });
|
|
191
|
-
|
|
192
|
-
// return message;
|
|
193
|
-
// };
|
|
194
|
-
// agent.getMessages = () => agent.getSnapshot().context.messages;
|
|
195
|
-
|
|
196
|
-
// agent.addFeedback = (feedbackInput) => {
|
|
197
|
-
// const feedback = {
|
|
198
|
-
// ...feedbackInput,
|
|
199
|
-
// attributes: { ...feedbackInput.attributes },
|
|
200
|
-
// reward: feedbackInput.reward ?? 0,
|
|
201
|
-
// timestamp: feedbackInput.timestamp ?? Date.now(),
|
|
202
|
-
// sessionId: agent.sessionId,
|
|
203
|
-
// } satisfies AgentFeedback;
|
|
204
|
-
// agent.send({
|
|
205
|
-
// type: 'agent.feedback',
|
|
206
|
-
// feedback,
|
|
207
|
-
// });
|
|
208
|
-
// return feedback;
|
|
209
|
-
// };
|
|
210
|
-
// agent.getFeedback = () => agent.getSnapshot().context.feedback;
|
|
211
|
-
|
|
212
|
-
// agent.addObservation = (observationInput) => {
|
|
213
|
-
// const { prevState, event, state } = observationInput;
|
|
214
|
-
// const observedState = { context: state.context, value: state.value };
|
|
215
|
-
// const observedPrevState = prevState
|
|
216
|
-
// ? {
|
|
217
|
-
// context: prevState.context,
|
|
218
|
-
// value: prevState.value,
|
|
219
|
-
// }
|
|
220
|
-
// : undefined;
|
|
221
|
-
// const observation = {
|
|
222
|
-
// prevState: observedPrevState,
|
|
223
|
-
// event,
|
|
224
|
-
// state: observedState,
|
|
225
|
-
// id: observationInput.id ?? randomId(),
|
|
226
|
-
// sessionId: agent.sessionId,
|
|
227
|
-
// timestamp: observationInput.timestamp ?? Date.now(),
|
|
228
|
-
// machineHash: observationInput.machine
|
|
229
|
-
// ? getMachineHash(observationInput.machine)
|
|
230
|
-
// : undefined,
|
|
231
|
-
// } satisfies AgentObservation<any>;
|
|
232
|
-
|
|
233
|
-
// agent.send({
|
|
234
|
-
// type: 'agent.observe',
|
|
235
|
-
// observation,
|
|
236
|
-
// });
|
|
237
|
-
|
|
238
|
-
// return observation;
|
|
239
|
-
// };
|
|
240
|
-
// agent.getObservations = () => agent.getSnapshot().context.observations;
|
|
241
|
-
|
|
242
|
-
// agent.addPlan = (plan) => {
|
|
243
|
-
// agent.send({
|
|
244
|
-
// type: 'agent.plan',
|
|
245
|
-
// plan,
|
|
246
|
-
// });
|
|
247
|
-
// };
|
|
248
|
-
// agent.getPlans = () => agent.getSnapshot().context.plans;
|
|
249
|
-
|
|
250
|
-
// agent.interact = ((actorRef, getInput) => {
|
|
251
|
-
// let prevState: ObservedState | undefined = undefined;
|
|
252
|
-
// let subscribed = true;
|
|
253
|
-
|
|
254
|
-
// async function handleObservation(observationInput: AgentObservationInput) {
|
|
255
|
-
// const observation = agent.addObservation(observationInput);
|
|
256
|
-
|
|
257
|
-
// const input = getInput?.(observation);
|
|
258
|
-
|
|
259
|
-
// if (input) {
|
|
260
|
-
// await agentDecide(agent, {
|
|
261
|
-
// machine: actorRef.src as AnyStateMachine,
|
|
262
|
-
// state: observation.state,
|
|
263
|
-
// execute: async (event) => {
|
|
264
|
-
// actorRef.send(event);
|
|
265
|
-
// },
|
|
266
|
-
// ...input,
|
|
267
|
-
// });
|
|
268
|
-
// }
|
|
269
|
-
|
|
270
|
-
// prevState = observationInput.state;
|
|
271
|
-
// }
|
|
272
|
-
|
|
273
|
-
// // Inspect system, but only observe specified actor
|
|
274
|
-
// const sub = actorRef.system.inspect({
|
|
275
|
-
// next: async (inspEvent) => {
|
|
276
|
-
// if (
|
|
277
|
-
// !subscribed ||
|
|
278
|
-
// inspEvent.actorRef !== actorRef ||
|
|
279
|
-
// inspEvent.type !== '@xstate.snapshot'
|
|
280
|
-
// ) {
|
|
281
|
-
// return;
|
|
282
|
-
// }
|
|
283
|
-
|
|
284
|
-
// const observationInput = {
|
|
285
|
-
// event: inspEvent.event,
|
|
286
|
-
// prevState,
|
|
287
|
-
// state: inspEvent.snapshot as any,
|
|
288
|
-
// machine: (actorRef as any).src,
|
|
289
|
-
// } satisfies AgentObservationInput;
|
|
290
|
-
|
|
291
|
-
// await handleObservation(observationInput);
|
|
292
|
-
// },
|
|
293
|
-
// });
|
|
294
|
-
|
|
295
|
-
// // If actor already started, interact with current state
|
|
296
|
-
// if ((actorRef as any)._processingStatus === 1) {
|
|
297
|
-
// handleObservation({
|
|
298
|
-
// prevState: undefined,
|
|
299
|
-
// event: { type: '' }, // TODO: unknown events?
|
|
300
|
-
// state: actorRef.getSnapshot(),
|
|
301
|
-
// machine: (actorRef as any).src,
|
|
302
|
-
// });
|
|
303
|
-
// }
|
|
304
|
-
|
|
305
|
-
// return {
|
|
306
|
-
// unsubscribe: () => {
|
|
307
|
-
// sub.unsubscribe();
|
|
308
|
-
// subscribed = false;
|
|
309
|
-
// },
|
|
310
|
-
// };
|
|
311
|
-
// }) as typeof agent.interact;
|
|
312
|
-
|
|
313
|
-
// agent.observe = (actorRef) => {
|
|
314
|
-
// let prevState: ObservedState = actorRef.getSnapshot();
|
|
315
|
-
|
|
316
|
-
// const sub = actorRef.system.inspect({
|
|
317
|
-
// next: async (inspEvent) => {
|
|
318
|
-
// if (
|
|
319
|
-
// inspEvent.actorRef !== actorRef ||
|
|
320
|
-
// inspEvent.type !== '@xstate.snapshot'
|
|
321
|
-
// ) {
|
|
322
|
-
// return;
|
|
323
|
-
// }
|
|
324
|
-
|
|
325
|
-
// const observationInput = {
|
|
326
|
-
// event: inspEvent.event,
|
|
327
|
-
// prevState,
|
|
328
|
-
// state: inspEvent.snapshot as any,
|
|
329
|
-
// machine: (actorRef as any).src,
|
|
330
|
-
// } satisfies AgentObservationInput;
|
|
331
|
-
|
|
332
|
-
// prevState = observationInput.state;
|
|
333
|
-
|
|
334
|
-
// agent.addObservation(observationInput);
|
|
335
|
-
// },
|
|
336
|
-
// });
|
|
337
|
-
|
|
338
|
-
// return sub;
|
|
339
|
-
// };
|
|
340
|
-
|
|
341
|
-
// agent.types = {} as any;
|
|
342
|
-
|
|
343
|
-
// agent.wrap = (modelToWrap) =>
|
|
344
|
-
// experimental_wrapLanguageModel({
|
|
345
|
-
// model: modelToWrap,
|
|
346
|
-
// middleware: createAgentMiddleware(agent),
|
|
347
|
-
// });
|
|
348
|
-
|
|
349
|
-
// agent.model = experimental_wrapLanguageModel({
|
|
350
|
-
// model,
|
|
351
|
-
// middleware: createAgentMiddleware(agent),
|
|
352
|
-
// });
|
|
353
|
-
|
|
354
|
-
// agent.start();
|
|
355
|
-
|
|
356
|
-
// return agent;
|
|
357
163
|
}
|
|
358
164
|
|
|
359
165
|
export class Agent<
|
|
@@ -374,14 +180,14 @@ export class Agent<
|
|
|
374
180
|
public description?: string;
|
|
375
181
|
public events: TEventSchemas;
|
|
376
182
|
public context?: TContextSchema;
|
|
377
|
-
public
|
|
378
|
-
public types: {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
};
|
|
183
|
+
public strategy: AgentStrategy<Agent<TContextSchema, TEventSchemas>>;
|
|
184
|
+
// public types: {
|
|
185
|
+
// events: TEvents;
|
|
186
|
+
// context: Compute<TContext>;
|
|
187
|
+
// };
|
|
382
188
|
public model: LanguageModel;
|
|
383
189
|
public memory: AgentLongTermMemory | undefined;
|
|
384
|
-
public defaultOptions:
|
|
190
|
+
public defaultOptions: AgentDecideOptions<AnyAgent> | undefined; // todo
|
|
385
191
|
|
|
386
192
|
constructor({
|
|
387
193
|
logic = agentLogic as AgentLogic<TEvents>,
|
|
@@ -391,7 +197,8 @@ export class Agent<
|
|
|
391
197
|
model,
|
|
392
198
|
events,
|
|
393
199
|
context,
|
|
394
|
-
|
|
200
|
+
episodeId,
|
|
201
|
+
strategy = simpleStrategy,
|
|
395
202
|
}: {
|
|
396
203
|
logic: AgentLogic<TEvents>;
|
|
397
204
|
id?: string;
|
|
@@ -400,17 +207,18 @@ export class Agent<
|
|
|
400
207
|
model: GenerateTextOptions['model'];
|
|
401
208
|
events: TEventSchemas;
|
|
402
209
|
context?: TContextSchema;
|
|
403
|
-
|
|
210
|
+
strategy?: AgentStrategy<Agent<TContextSchema, TEventSchemas>>;
|
|
211
|
+
episodeId?: string;
|
|
404
212
|
}) {
|
|
405
213
|
super(logic);
|
|
406
214
|
this.model = model;
|
|
407
|
-
this.episodeId =
|
|
215
|
+
this.episodeId = episodeId ?? randomId('episode-');
|
|
408
216
|
this.name = name;
|
|
409
217
|
this.description = description;
|
|
410
218
|
this.events = events;
|
|
411
219
|
this.context = context;
|
|
412
|
-
this.
|
|
413
|
-
this.
|
|
220
|
+
this.strategy = strategy;
|
|
221
|
+
this.id = id ?? randomId();
|
|
414
222
|
|
|
415
223
|
this.start();
|
|
416
224
|
}
|
|
@@ -422,6 +230,13 @@ export class Agent<
|
|
|
422
230
|
return this.on('message', (ev) => fn(ev.message));
|
|
423
231
|
}
|
|
424
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Called whenever the agent (LLM assistant) receives some feedback.
|
|
235
|
+
*/
|
|
236
|
+
public onFeedback(fn: (feedback: AgentFeedback) => void) {
|
|
237
|
+
return this.on('feedback', (ev) => fn(ev.feedback));
|
|
238
|
+
}
|
|
239
|
+
|
|
425
240
|
/**
|
|
426
241
|
* Retrieves messages from the agent's short-term (local) memory.
|
|
427
242
|
*/
|
|
@@ -448,7 +263,6 @@ export class Agent<
|
|
|
448
263
|
const feedback = {
|
|
449
264
|
...feedbackInput,
|
|
450
265
|
attributes: { ...feedbackInput.attributes },
|
|
451
|
-
reward: feedbackInput.reward ?? 0,
|
|
452
266
|
timestamp: feedbackInput.timestamp ?? Date.now(),
|
|
453
267
|
episodeId: this.episodeId,
|
|
454
268
|
} satisfies AgentFeedback;
|
|
@@ -497,17 +311,17 @@ export class Agent<
|
|
|
497
311
|
return this.getSnapshot().context.observations;
|
|
498
312
|
}
|
|
499
313
|
|
|
500
|
-
public
|
|
314
|
+
public addDecision(decision: AgentDecision<TEvents>) {
|
|
501
315
|
this.send({
|
|
502
|
-
type: 'agent.
|
|
503
|
-
|
|
316
|
+
type: 'agent.decision',
|
|
317
|
+
decision,
|
|
504
318
|
});
|
|
505
319
|
}
|
|
506
320
|
/**
|
|
507
321
|
* Retrieves strategies from the agent's short-term (local) memory.
|
|
508
322
|
*/
|
|
509
|
-
public
|
|
510
|
-
return this.getSnapshot().context.
|
|
323
|
+
public getDecisions() {
|
|
324
|
+
return this.getSnapshot().context.decisions;
|
|
511
325
|
}
|
|
512
326
|
|
|
513
327
|
/**
|
|
@@ -558,15 +372,16 @@ export class Agent<
|
|
|
558
372
|
actorRef: TActor,
|
|
559
373
|
getInput: (
|
|
560
374
|
observation: AgentObservation<TActor>
|
|
561
|
-
) =>
|
|
375
|
+
) => AgentInteractInput<this> | void
|
|
562
376
|
): Subscription;
|
|
563
377
|
public interact<TActor extends ActorRefLike>(
|
|
564
378
|
actorRef: TActor,
|
|
565
379
|
getInput?: (
|
|
566
380
|
observation: AgentObservation<TActor>
|
|
567
|
-
) =>
|
|
381
|
+
) => AgentInteractInput<this> | void
|
|
568
382
|
): Subscription {
|
|
569
|
-
const actorRefCheck = isActorRef(actorRef);
|
|
383
|
+
const actorRefCheck = isActorRef(actorRef) && actorRef.src;
|
|
384
|
+
const machine = isMachineActor(actorRef) ? actorRef.src : undefined;
|
|
570
385
|
|
|
571
386
|
let prevState: ObservedState | undefined = undefined;
|
|
572
387
|
let subscribed = true;
|
|
@@ -579,16 +394,15 @@ export class Agent<
|
|
|
579
394
|
const input = getInput?.(observation);
|
|
580
395
|
|
|
581
396
|
if (input) {
|
|
582
|
-
await agentDecide(agent, {
|
|
583
|
-
machine
|
|
584
|
-
? (actorRef.src as AnyStateMachine)
|
|
585
|
-
: undefined,
|
|
397
|
+
const res = await agentDecide(agent, {
|
|
398
|
+
machine,
|
|
586
399
|
state: observation.state,
|
|
587
|
-
execute: async (event) => {
|
|
588
|
-
actorRef.send(event);
|
|
589
|
-
},
|
|
590
400
|
...input,
|
|
591
401
|
});
|
|
402
|
+
|
|
403
|
+
if (res?.nextEvent) {
|
|
404
|
+
actorRef.send(res.nextEvent);
|
|
405
|
+
}
|
|
592
406
|
}
|
|
593
407
|
|
|
594
408
|
prevState = observationInput.state;
|
|
@@ -622,7 +436,7 @@ export class Agent<
|
|
|
622
436
|
if ((actorRef as any)._processingStatus === 1) {
|
|
623
437
|
handleObservation({
|
|
624
438
|
prevState: undefined,
|
|
625
|
-
event:
|
|
439
|
+
event: undefined,
|
|
626
440
|
state: actorRef.getSnapshot(),
|
|
627
441
|
machine: (actorRef as any).src,
|
|
628
442
|
});
|
|
@@ -675,14 +489,16 @@ export class Agent<
|
|
|
675
489
|
}
|
|
676
490
|
|
|
677
491
|
/**
|
|
678
|
-
* Resolves with an `
|
|
492
|
+
* Resolves with an `AgentDecision` based on the information provided in the `options`, including:
|
|
679
493
|
*
|
|
680
494
|
* - The `goal` for the agent to achieve
|
|
681
495
|
* - The observed current `state`
|
|
682
496
|
* - The `machine` (e.g. a state machine) that specifies what can happen next
|
|
683
497
|
* - Additional `context`
|
|
684
498
|
*/
|
|
685
|
-
public decide(
|
|
499
|
+
public async decide(
|
|
500
|
+
opts: AgentDecideOptions<this>
|
|
501
|
+
): Promise<AgentDecision<EventsFromAgent<this>> | undefined> {
|
|
686
502
|
return agentDecide(this, opts);
|
|
687
503
|
}
|
|
688
504
|
}
|
package/src/decide.test.ts
CHANGED
|
@@ -28,7 +28,7 @@ test('fromDecision() makes a decision', async () => {
|
|
|
28
28
|
doGenerate,
|
|
29
29
|
});
|
|
30
30
|
const agent = createAgent({
|
|
31
|
-
|
|
31
|
+
id: 'test',
|
|
32
32
|
model,
|
|
33
33
|
events: {
|
|
34
34
|
doFirst: z.object({}),
|
|
@@ -73,7 +73,7 @@ test('interacts with an actor', async () => {
|
|
|
73
73
|
doGenerate,
|
|
74
74
|
});
|
|
75
75
|
const agent = createAgent({
|
|
76
|
-
|
|
76
|
+
id: 'test',
|
|
77
77
|
model,
|
|
78
78
|
events: {
|
|
79
79
|
doFirst: z.object({}),
|
|
@@ -116,7 +116,7 @@ test('interacts with an actor (late interaction)', async () => {
|
|
|
116
116
|
doGenerate,
|
|
117
117
|
});
|
|
118
118
|
const agent = createAgent({
|
|
119
|
-
|
|
119
|
+
id: 'test',
|
|
120
120
|
model,
|
|
121
121
|
events: {
|
|
122
122
|
doFirst: z.object({}),
|
|
@@ -153,3 +153,172 @@ test('interacts with an actor (late interaction)', async () => {
|
|
|
153
153
|
|
|
154
154
|
expect(actor.getSnapshot().value).toBe('third');
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
test('agent.decide() makes a decision based on goal and state (simple strategy)', async () => {
|
|
158
|
+
const model = new MockLanguageModelV1({
|
|
159
|
+
doGenerate,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const agent = createAgent({
|
|
163
|
+
id: 'test',
|
|
164
|
+
model,
|
|
165
|
+
events: {
|
|
166
|
+
MOVE: z.object({}),
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const decision = await agent.decide({
|
|
171
|
+
goal: 'Make the best move',
|
|
172
|
+
state: {
|
|
173
|
+
value: 'playing',
|
|
174
|
+
context: {
|
|
175
|
+
board: [0, 0, 0],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
machine: createMachine({
|
|
179
|
+
initial: 'playing',
|
|
180
|
+
states: {
|
|
181
|
+
playing: {
|
|
182
|
+
on: {
|
|
183
|
+
MOVE: 'next',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
next: {},
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(decision).toBeDefined();
|
|
192
|
+
expect(decision!.nextEvent).toEqual(
|
|
193
|
+
expect.objectContaining({
|
|
194
|
+
type: 'MOVE',
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test.each([
|
|
200
|
+
[undefined, true],
|
|
201
|
+
[undefined, false],
|
|
202
|
+
[3, true],
|
|
203
|
+
[3, false],
|
|
204
|
+
])(
|
|
205
|
+
'agent.decide() retries if a decision is not made (%i attempts, succeed: %s)',
|
|
206
|
+
async (maxAttempts, succeed) => {
|
|
207
|
+
let attempts = 0;
|
|
208
|
+
const doGenerateWithRetry = async (params: LanguageModelV1CallOptions) => {
|
|
209
|
+
const keys =
|
|
210
|
+
params.mode.type === 'regular'
|
|
211
|
+
? params.mode.tools?.map((t) => t.name)
|
|
212
|
+
: [];
|
|
213
|
+
|
|
214
|
+
console.log('try', attempts, 'max', maxAttempts);
|
|
215
|
+
|
|
216
|
+
const toolCalls =
|
|
217
|
+
succeed && attempts++ === (maxAttempts ?? 2) - 1
|
|
218
|
+
? [
|
|
219
|
+
{
|
|
220
|
+
toolCallType: 'function',
|
|
221
|
+
toolCallId: 'call-1',
|
|
222
|
+
toolName: keys![0],
|
|
223
|
+
args: `{ "type": "${keys?.[0]}" }`,
|
|
224
|
+
},
|
|
225
|
+
]
|
|
226
|
+
: [];
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
...dummyResponseValues,
|
|
230
|
+
finishReason: 'tool-calls',
|
|
231
|
+
toolCalls,
|
|
232
|
+
} as any;
|
|
233
|
+
};
|
|
234
|
+
const model = new MockLanguageModelV1({
|
|
235
|
+
doGenerate: doGenerateWithRetry,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const agent = createAgent({
|
|
239
|
+
id: 'test',
|
|
240
|
+
model,
|
|
241
|
+
events: {
|
|
242
|
+
MOVE: z.object({}),
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const decision = await agent.decide({
|
|
247
|
+
goal: 'Make the best move',
|
|
248
|
+
state: {
|
|
249
|
+
value: 'playing',
|
|
250
|
+
},
|
|
251
|
+
machine: createMachine({
|
|
252
|
+
initial: 'playing',
|
|
253
|
+
states: {
|
|
254
|
+
playing: {
|
|
255
|
+
on: {
|
|
256
|
+
MOVE: 'win',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
win: {},
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
maxAttempts,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (!succeed) {
|
|
266
|
+
expect(decision).toBeUndefined();
|
|
267
|
+
} else {
|
|
268
|
+
expect(decision).toBeDefined();
|
|
269
|
+
expect(decision!.nextEvent).toEqual(
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
type: 'MOVE',
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
test.each([['MOVE'], ['FORFEIT']] as const)(
|
|
279
|
+
'agent.decide() respects allowedEvents constraint (event: %s)',
|
|
280
|
+
async (allowedEventType) => {
|
|
281
|
+
const model = new MockLanguageModelV1({
|
|
282
|
+
doGenerate: async (params: LanguageModelV1CallOptions) => {
|
|
283
|
+
const keys =
|
|
284
|
+
params.mode.type === 'regular'
|
|
285
|
+
? params.mode.tools?.map((t) => t.name)
|
|
286
|
+
: [];
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
...dummyResponseValues,
|
|
290
|
+
finishReason: 'tool-calls',
|
|
291
|
+
toolCalls: [
|
|
292
|
+
{
|
|
293
|
+
toolCallType: 'function',
|
|
294
|
+
toolCallId: 'call-1',
|
|
295
|
+
toolName: keys![0],
|
|
296
|
+
args: `{ "type": "${keys?.[0]}" }`,
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
} as any;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const agent = createAgent({
|
|
304
|
+
id: 'test',
|
|
305
|
+
model,
|
|
306
|
+
events: {
|
|
307
|
+
MOVE: z.object({}),
|
|
308
|
+
SKIP: z.object({}),
|
|
309
|
+
FORFEIT: z.object({}),
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const decision = await agent.decide({
|
|
314
|
+
goal: 'Make the best move',
|
|
315
|
+
state: {
|
|
316
|
+
value: 'playing',
|
|
317
|
+
context: {},
|
|
318
|
+
},
|
|
319
|
+
allowedEvents: [allowedEventType],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(decision?.nextEvent?.type).toEqual(allowedEventType);
|
|
323
|
+
}
|
|
324
|
+
);
|