@statelyai/agent 2.0.0-next.0 → 2.0.0-next.1
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/old-jobs-check.md +5 -0
- package/.changeset/pre.json +4 -1
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +19 -25
- package/dist/index.d.ts +19 -25
- package/dist/index.js +43 -39
- package/dist/index.mjs +43 -39
- package/examples/chatbot-alt.ts +1 -1
- package/examples/chatbot.ts +1 -1
- package/examples/cot.ts +1 -1
- package/examples/customer-service-sim.ts +4 -4
- package/examples/email.ts +29 -33
- package/examples/example.ts +1 -1
- package/examples/goal.ts +1 -1
- package/examples/joke.ts +1 -1
- package/examples/jugs.ts +3 -3
- package/examples/multi.ts +1 -1
- package/examples/number.ts +1 -1
- package/examples/raffle.ts +1 -1
- package/examples/river-crossing.ts +3 -3
- package/examples/simple.ts +1 -1
- package/examples/summary.ts +1 -1
- package/examples/support.ts +1 -1
- package/examples/ticTacToe.ts +2 -2
- package/examples/todo.ts +1 -1
- package/examples/tutor.ts +2 -2
- package/examples/verify.ts +1 -1
- package/examples/weather.ts +1 -1
- package/examples/wiki.ts +1 -1
- package/examples/word.ts +1 -1
- package/package.json +5 -3
- package/src/agent.test.ts +150 -10
- package/src/agent.ts +20 -217
- package/src/decide.test.ts +124 -3
- package/src/decide.ts +24 -14
- package/src/middleware.ts +2 -14
- package/src/planners/shortestPath.test.ts +94 -0
- package/src/planners/shortestPath.ts +177 -0
- package/src/planners/{simplePlanner.ts → simple.ts} +6 -12
- package/src/types.ts +15 -8
- package/src/utils.ts +11 -0
- package/vitest.config.ts +9 -3
- package/src/planners/shortestPathPlanner.ts +0 -160
package/src/agent.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { dummyResponseValues, MockLanguageModelV1 } from './mockModel';
|
|
|
7
7
|
|
|
8
8
|
test('an agent has the expected interface', () => {
|
|
9
9
|
const agent = createAgent({
|
|
10
|
-
|
|
10
|
+
id: 'test',
|
|
11
11
|
events: {},
|
|
12
12
|
model: new MockLanguageModelV1(),
|
|
13
13
|
});
|
|
@@ -31,7 +31,7 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
31
31
|
const model = new MockLanguageModelV1();
|
|
32
32
|
|
|
33
33
|
const agent = createAgent({
|
|
34
|
-
|
|
34
|
+
id: 'test',
|
|
35
35
|
events: {},
|
|
36
36
|
model,
|
|
37
37
|
});
|
|
@@ -65,7 +65,7 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
65
65
|
|
|
66
66
|
test('agent.addFeedback() adds to feedback', () => {
|
|
67
67
|
const agent = createAgent({
|
|
68
|
-
|
|
68
|
+
id: 'test',
|
|
69
69
|
events: {},
|
|
70
70
|
model: {} as any,
|
|
71
71
|
});
|
|
@@ -106,7 +106,7 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
106
106
|
|
|
107
107
|
test('agent.addObservation() adds to observations', () => {
|
|
108
108
|
const agent = createAgent({
|
|
109
|
-
|
|
109
|
+
id: 'test',
|
|
110
110
|
events: {},
|
|
111
111
|
model: {} as any,
|
|
112
112
|
});
|
|
@@ -132,7 +132,7 @@ test('agent.addObservation() adds to observations', () => {
|
|
|
132
132
|
|
|
133
133
|
test('agent.addObservation() adds to observations (initial state)', () => {
|
|
134
134
|
const agent = createAgent({
|
|
135
|
-
|
|
135
|
+
id: 'test',
|
|
136
136
|
events: {},
|
|
137
137
|
model: {} as any,
|
|
138
138
|
});
|
|
@@ -154,7 +154,7 @@ test('agent.addObservation() adds to observations (initial state)', () => {
|
|
|
154
154
|
|
|
155
155
|
test('agent.addObservation() adds to observations with machine hash', () => {
|
|
156
156
|
const agent = createAgent({
|
|
157
|
-
|
|
157
|
+
id: 'test',
|
|
158
158
|
events: {},
|
|
159
159
|
model: {} as any,
|
|
160
160
|
});
|
|
@@ -194,7 +194,7 @@ test('agent.addObservation() adds to observations with machine hash', () => {
|
|
|
194
194
|
|
|
195
195
|
test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
196
196
|
const agent = createAgent({
|
|
197
|
-
|
|
197
|
+
id: 'test',
|
|
198
198
|
events: {},
|
|
199
199
|
model: {} as any,
|
|
200
200
|
});
|
|
@@ -251,7 +251,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
const agent = createAgent({
|
|
254
|
-
|
|
254
|
+
id: 'test',
|
|
255
255
|
events: {},
|
|
256
256
|
model: {} as any,
|
|
257
257
|
});
|
|
@@ -289,7 +289,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
289
289
|
test('You can listen for feedback events', () => {
|
|
290
290
|
const fn = vi.fn();
|
|
291
291
|
const agent = createAgent({
|
|
292
|
-
|
|
292
|
+
id: 'test',
|
|
293
293
|
events: {},
|
|
294
294
|
model: {} as any,
|
|
295
295
|
});
|
|
@@ -332,7 +332,7 @@ test('You can listen for plan events', async () => {
|
|
|
332
332
|
});
|
|
333
333
|
|
|
334
334
|
const agent = createAgent({
|
|
335
|
-
|
|
335
|
+
id: 'test',
|
|
336
336
|
model,
|
|
337
337
|
events: {
|
|
338
338
|
WIN: z.object({}),
|
|
@@ -393,3 +393,143 @@ test('agent.types provides context and event types', () => {
|
|
|
393
393
|
// @ts-expect-error
|
|
394
394
|
agent.types.context satisfies { score: string };
|
|
395
395
|
});
|
|
396
|
+
|
|
397
|
+
test('It allows unrecognized events', () => {
|
|
398
|
+
const agent = createAgent({
|
|
399
|
+
model: {} as any,
|
|
400
|
+
events: {},
|
|
401
|
+
context: {},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(() => {
|
|
405
|
+
agent.send({
|
|
406
|
+
// @ts-expect-error
|
|
407
|
+
type: 'unrecognized',
|
|
408
|
+
});
|
|
409
|
+
}).not.toThrow();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('You can listen for message events', () => {
|
|
413
|
+
const fn = vi.fn();
|
|
414
|
+
const agent = createAgent({
|
|
415
|
+
id: 'test',
|
|
416
|
+
events: {},
|
|
417
|
+
model: {} as any,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
agent.onMessage(fn);
|
|
421
|
+
|
|
422
|
+
const message = {
|
|
423
|
+
role: 'user' as const,
|
|
424
|
+
content: [{ type: 'text' as const, text: 'test message' }],
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
agent.addMessage(message);
|
|
428
|
+
|
|
429
|
+
expect(fn).toHaveBeenCalledWith(
|
|
430
|
+
expect.objectContaining({
|
|
431
|
+
role: 'user',
|
|
432
|
+
content: [{ type: 'text', text: 'test message' }],
|
|
433
|
+
episodeId: expect.any(String),
|
|
434
|
+
timestamp: expect.any(Number),
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('agent.getPlans() returns plans from context', () => {
|
|
440
|
+
const agent = createAgent({
|
|
441
|
+
id: 'test',
|
|
442
|
+
events: {},
|
|
443
|
+
model: {} as any,
|
|
444
|
+
planner: async (agent) => {
|
|
445
|
+
return {
|
|
446
|
+
episodeId: agent.episodeId,
|
|
447
|
+
planner: 'test-planner',
|
|
448
|
+
goal: '',
|
|
449
|
+
goalState: undefined,
|
|
450
|
+
paths: [
|
|
451
|
+
{
|
|
452
|
+
state: undefined,
|
|
453
|
+
steps: [],
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
nextEvent: undefined,
|
|
457
|
+
timestamp: Date.now(),
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const plans = agent.getPlans();
|
|
463
|
+
|
|
464
|
+
expect(plans).toBeDefined();
|
|
465
|
+
expect(Array.isArray(plans)).toBe(true);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test('Event listeners can be unsubscribed', () => {
|
|
469
|
+
const fn = vi.fn();
|
|
470
|
+
const agent = createAgent({
|
|
471
|
+
id: 'test',
|
|
472
|
+
events: {},
|
|
473
|
+
model: {} as any,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const subscription = agent.on('message', fn);
|
|
477
|
+
|
|
478
|
+
agent.addMessage({
|
|
479
|
+
role: 'user',
|
|
480
|
+
content: [{ type: 'text', text: 'first message' }],
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
484
|
+
|
|
485
|
+
subscription.unsubscribe();
|
|
486
|
+
|
|
487
|
+
agent.addMessage({
|
|
488
|
+
role: 'user',
|
|
489
|
+
content: [{ type: 'text', text: 'second message' }],
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
expect(fn).toHaveBeenCalledTimes(1); // Still only called once
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test('agent.observe() adds observations from actor snapshots', () => {
|
|
496
|
+
const machine = createMachine({
|
|
497
|
+
initial: 'idle',
|
|
498
|
+
states: {
|
|
499
|
+
idle: {
|
|
500
|
+
on: { START: 'running' },
|
|
501
|
+
},
|
|
502
|
+
running: {},
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const agent = createAgent({
|
|
507
|
+
id: 'test',
|
|
508
|
+
events: {},
|
|
509
|
+
model: {} as any,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const actor = createActor(machine);
|
|
513
|
+
const subscription = agent.observe(actor);
|
|
514
|
+
|
|
515
|
+
actor.start();
|
|
516
|
+
actor.send({ type: 'START' });
|
|
517
|
+
|
|
518
|
+
expect(agent.getObservations()).toContainEqual(
|
|
519
|
+
expect.objectContaining({
|
|
520
|
+
state: expect.objectContaining({ value: 'idle' }),
|
|
521
|
+
machineHash: expect.any(String),
|
|
522
|
+
})
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
expect(agent.getObservations()).toContainEqual(
|
|
526
|
+
expect.objectContaining({
|
|
527
|
+
prevState: expect.objectContaining({ value: 'idle' }),
|
|
528
|
+
event: { type: 'START' },
|
|
529
|
+
state: expect.objectContaining({ value: 'running' }),
|
|
530
|
+
machineHash: expect.any(String),
|
|
531
|
+
})
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
subscription.unsubscribe();
|
|
535
|
+
});
|
package/src/agent.ts
CHANGED
|
@@ -27,10 +27,11 @@ import {
|
|
|
27
27
|
Compute,
|
|
28
28
|
AgentDecisionInput,
|
|
29
29
|
AgentDecideOptions,
|
|
30
|
+
AnyAgent,
|
|
30
31
|
} from './types';
|
|
31
|
-
import { simplePlanner } from './planners/
|
|
32
|
+
import { simplePlanner } from './planners/simple';
|
|
32
33
|
import { agentDecide } from './decide';
|
|
33
|
-
import { getMachineHash, isActorRef, randomId } from './utils';
|
|
34
|
+
import { getMachineHash, isActorRef, isMachineActor, randomId } from './utils';
|
|
34
35
|
import {
|
|
35
36
|
experimental_wrapLanguageModel,
|
|
36
37
|
LanguageModel,
|
|
@@ -77,8 +78,11 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
|
|
|
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
|
},
|
|
@@ -98,22 +102,18 @@ 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
109
|
planner = simplePlanner as AgentPlanner<Agent<TContextSchema, TEventSchemas>>,
|
|
107
|
-
stringify = JSON.stringify,
|
|
108
|
-
getMemory,
|
|
109
110
|
logic = agentLogic as AgentLogic<TEvents>,
|
|
110
|
-
...generateTextOptions
|
|
111
111
|
}: {
|
|
112
112
|
/**
|
|
113
113
|
* The unique identifier for the agent.
|
|
114
114
|
*
|
|
115
115
|
* This should be the same across all sessions of a specific agent, as it can be
|
|
116
|
-
* used to retrieve memory for this agent.
|
|
116
|
+
* used to retrieve memory for previous episodes of this agent.
|
|
117
117
|
*
|
|
118
118
|
* @example
|
|
119
119
|
* ```ts
|
|
@@ -124,10 +124,6 @@ export function createAgent<
|
|
|
124
124
|
* ```
|
|
125
125
|
*/
|
|
126
126
|
id?: string;
|
|
127
|
-
/**
|
|
128
|
-
* The name of the agent
|
|
129
|
-
*/
|
|
130
|
-
name?: string;
|
|
131
127
|
/**
|
|
132
128
|
* A description of the role of the agent
|
|
133
129
|
*/
|
|
@@ -150,210 +146,17 @@ export function createAgent<
|
|
|
150
146
|
* Agent logic
|
|
151
147
|
*/
|
|
152
148
|
logic?: AgentLogic<TEvents>;
|
|
149
|
+
model: LanguageModel;
|
|
153
150
|
} & GenerateTextOptions): Agent<TContextSchema, TEventSchemas> {
|
|
154
151
|
return new Agent({
|
|
155
152
|
id,
|
|
156
153
|
context,
|
|
157
154
|
events,
|
|
158
|
-
name,
|
|
159
155
|
description,
|
|
160
156
|
planner,
|
|
161
157
|
model,
|
|
162
158
|
logic,
|
|
163
159
|
}) 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
160
|
}
|
|
358
161
|
|
|
359
162
|
export class Agent<
|
|
@@ -381,7 +184,7 @@ export class Agent<
|
|
|
381
184
|
};
|
|
382
185
|
public model: LanguageModel;
|
|
383
186
|
public memory: AgentLongTermMemory | undefined;
|
|
384
|
-
public defaultOptions:
|
|
187
|
+
public defaultOptions: AgentDecideOptions<AnyAgent> | undefined; // todo
|
|
385
188
|
|
|
386
189
|
constructor({
|
|
387
190
|
logic = agentLogic as AgentLogic<TEvents>,
|
|
@@ -566,7 +369,8 @@ export class Agent<
|
|
|
566
369
|
observation: AgentObservation<TActor>
|
|
567
370
|
) => AgentDecisionInput | undefined
|
|
568
371
|
): Subscription {
|
|
569
|
-
const actorRefCheck = isActorRef(actorRef);
|
|
372
|
+
const actorRefCheck = isActorRef(actorRef) && actorRef.src;
|
|
373
|
+
const machine = isMachineActor(actorRef) ? actorRef.src : undefined;
|
|
570
374
|
|
|
571
375
|
let prevState: ObservedState | undefined = undefined;
|
|
572
376
|
let subscribed = true;
|
|
@@ -579,16 +383,15 @@ export class Agent<
|
|
|
579
383
|
const input = getInput?.(observation);
|
|
580
384
|
|
|
581
385
|
if (input) {
|
|
582
|
-
await agentDecide(agent, {
|
|
583
|
-
machine
|
|
584
|
-
? (actorRef.src as AnyStateMachine)
|
|
585
|
-
: undefined,
|
|
386
|
+
const res = await agentDecide(agent, {
|
|
387
|
+
machine,
|
|
586
388
|
state: observation.state,
|
|
587
|
-
execute: async (event) => {
|
|
588
|
-
actorRef.send(event);
|
|
589
|
-
},
|
|
590
389
|
...input,
|
|
591
390
|
});
|
|
391
|
+
|
|
392
|
+
if (res?.nextEvent) {
|
|
393
|
+
actorRef.send(res.nextEvent);
|
|
394
|
+
}
|
|
592
395
|
}
|
|
593
396
|
|
|
594
397
|
prevState = observationInput.state;
|
|
@@ -682,7 +485,7 @@ export class Agent<
|
|
|
682
485
|
* - The `machine` (e.g. a state machine) that specifies what can happen next
|
|
683
486
|
* - Additional `context`
|
|
684
487
|
*/
|
|
685
|
-
public decide(opts: AgentDecideOptions) {
|
|
488
|
+
public decide(opts: AgentDecideOptions<this>) {
|
|
686
489
|
return agentDecide(this, opts);
|
|
687
490
|
}
|
|
688
491
|
}
|
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,124 @@ 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 planner)', 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 plan = 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(plan).toBeDefined();
|
|
192
|
+
expect(plan!.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 plan = 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(plan).toBeUndefined();
|
|
267
|
+
} else {
|
|
268
|
+
expect(plan).toBeDefined();
|
|
269
|
+
expect(plan!.nextEvent).toEqual(
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
type: 'MOVE',
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
);
|