@statelyai/agent 1.1.5 → 2.0.0-next.0
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/light-hats-drive.md +9 -0
- package/.changeset/pre.json +10 -0
- package/.vscode/launch.json +6 -0
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +262 -165
- package/dist/index.d.ts +262 -165
- package/dist/index.js +368 -258
- package/dist/index.mjs +371 -256
- package/examples/chatbot-alt.ts +57 -0
- package/examples/chatbot.ts +11 -16
- package/examples/cot.ts +25 -22
- package/examples/customer-service-sim.ts +107 -0
- package/examples/email.ts +14 -14
- package/examples/example.ts +5 -5
- package/examples/executor.ts +66 -0
- package/examples/goal.ts +11 -11
- package/examples/helpers/helpers.ts +26 -14
- package/examples/joke.ts +78 -75
- package/examples/jugs.ts +125 -0
- package/examples/multi.ts +4 -4
- package/examples/newspaper.ts +98 -104
- package/examples/number.ts +5 -4
- package/examples/raffle.ts +10 -11
- package/examples/river-crossing.ts +140 -0
- package/examples/sandbox.ts +1 -1
- package/examples/simple.ts +4 -2
- package/examples/summary.ts +121 -0
- package/examples/support.ts +5 -5
- package/examples/ticTacToe.ts +86 -45
- package/examples/todo.ts +6 -6
- package/examples/tutor.ts +13 -13
- package/examples/verify.ts +2 -2
- package/examples/weather.ts +5 -8
- package/examples/wiki.ts +26 -7
- package/examples/word.ts +15 -10
- package/package.json +15 -12
- package/readme.md +1 -1
- package/src/agent-experimental.ts +1 -1
- package/src/agent.test.ts +117 -228
- package/src/agent.ts +469 -81
- package/src/{decision.test.ts → decide.test.ts} +26 -50
- package/src/decide.ts +153 -0
- package/src/index.ts +1 -1
- package/src/middleware.ts +103 -0
- package/src/mockModel.ts +47 -0
- package/src/planners/shortestPathPlanner.ts +151 -13
- package/src/planners/simplePlanner.ts +57 -85
- package/src/strategies/chain-of-note.ts +6 -55
- package/src/text.ts +51 -139
- package/src/types.ts +172 -204
- package/src/utils.ts +37 -4
- package/src/adapters/vercel.ts +0 -7
- package/src/decision.ts +0 -84
- package/src/memory.ts +0 -25
package/src/agent.test.ts
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import { test, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
AgentGenerateTextResult,
|
|
4
|
-
AgentMessage,
|
|
5
|
-
createAgent,
|
|
6
|
-
type AIAdapter,
|
|
7
|
-
} from './';
|
|
2
|
+
import { createAgent } from './';
|
|
8
3
|
import { createActor, createMachine } from 'xstate';
|
|
9
|
-
import {
|
|
4
|
+
import { LanguageModelV1CallOptions } from 'ai';
|
|
10
5
|
import { z } from 'zod';
|
|
6
|
+
import { dummyResponseValues, MockLanguageModelV1 } from './mockModel';
|
|
11
7
|
|
|
12
8
|
test('an agent has the expected interface', () => {
|
|
13
9
|
const agent = createAgent({
|
|
14
10
|
name: 'test',
|
|
15
11
|
events: {},
|
|
16
|
-
model:
|
|
12
|
+
model: new MockLanguageModelV1(),
|
|
17
13
|
});
|
|
18
14
|
|
|
19
15
|
expect(agent.decide).toBeDefined();
|
|
20
|
-
expect(agent.generateText).toBeDefined();
|
|
21
|
-
expect(agent.streamText).toBeDefined();
|
|
22
16
|
|
|
23
17
|
expect(agent.addMessage).toBeDefined();
|
|
24
18
|
expect(agent.addObservation).toBeDefined();
|
|
@@ -34,46 +28,36 @@ test('an agent has the expected interface', () => {
|
|
|
34
28
|
});
|
|
35
29
|
|
|
36
30
|
test('agent.addMessage() adds to message history', () => {
|
|
31
|
+
const model = new MockLanguageModelV1();
|
|
32
|
+
|
|
37
33
|
const agent = createAgent({
|
|
38
34
|
name: 'test',
|
|
39
35
|
events: {},
|
|
40
|
-
model
|
|
36
|
+
model,
|
|
41
37
|
});
|
|
42
38
|
|
|
43
39
|
agent.addMessage({
|
|
44
|
-
content: 'msg 1',
|
|
45
40
|
role: 'user',
|
|
41
|
+
content: [{ type: 'text', text: 'msg 1' }],
|
|
46
42
|
});
|
|
47
43
|
|
|
48
44
|
const messageHistory = agent.addMessage({
|
|
49
|
-
content: 'response 1',
|
|
50
45
|
role: 'assistant',
|
|
46
|
+
content: [{ type: 'text', text: 'response 1' }],
|
|
51
47
|
});
|
|
52
48
|
|
|
53
|
-
expect(messageHistory.
|
|
49
|
+
expect(messageHistory.episodeId).toEqual(agent.episodeId);
|
|
54
50
|
|
|
55
|
-
expect(agent.select((c) => c.messages)).toContainEqual(
|
|
56
|
-
expect.objectContaining({
|
|
57
|
-
content: 'msg 1',
|
|
58
|
-
})
|
|
59
|
-
);
|
|
60
51
|
expect(agent.getMessages()).toContainEqual(
|
|
61
52
|
expect.objectContaining({
|
|
62
|
-
content: 'msg 1',
|
|
53
|
+
content: [expect.objectContaining({ text: 'msg 1' })],
|
|
63
54
|
})
|
|
64
55
|
);
|
|
65
56
|
|
|
66
|
-
expect(agent.select((c) => c.messages)).toContainEqual(
|
|
67
|
-
expect.objectContaining({
|
|
68
|
-
content: 'response 1',
|
|
69
|
-
sessionId: expect.any(String),
|
|
70
|
-
timestamp: expect.any(Number),
|
|
71
|
-
})
|
|
72
|
-
);
|
|
73
57
|
expect(agent.getMessages()).toContainEqual(
|
|
74
58
|
expect.objectContaining({
|
|
75
|
-
content: 'response 1',
|
|
76
|
-
|
|
59
|
+
content: [expect.objectContaining({ text: 'response 1' })],
|
|
60
|
+
episodeId: expect.any(String),
|
|
77
61
|
timestamp: expect.any(Number),
|
|
78
62
|
})
|
|
79
63
|
);
|
|
@@ -94,16 +78,16 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
94
78
|
observationId: 'obs-1',
|
|
95
79
|
});
|
|
96
80
|
|
|
97
|
-
expect(feedback.
|
|
81
|
+
expect(feedback.episodeId).toEqual(agent.episodeId);
|
|
98
82
|
|
|
99
|
-
expect(agent.
|
|
83
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
100
84
|
expect.objectContaining({
|
|
101
85
|
attributes: {
|
|
102
86
|
score: -1,
|
|
103
87
|
},
|
|
104
88
|
goal: 'Win the game',
|
|
105
89
|
observationId: 'obs-1',
|
|
106
|
-
|
|
90
|
+
episodeId: expect.any(String),
|
|
107
91
|
timestamp: expect.any(Number),
|
|
108
92
|
})
|
|
109
93
|
);
|
|
@@ -114,7 +98,7 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
114
98
|
},
|
|
115
99
|
goal: 'Win the game',
|
|
116
100
|
observationId: 'obs-1',
|
|
117
|
-
|
|
101
|
+
episodeId: expect.any(String),
|
|
118
102
|
timestamp: expect.any(Number),
|
|
119
103
|
})
|
|
120
104
|
);
|
|
@@ -133,14 +117,36 @@ test('agent.addObservation() adds to observations', () => {
|
|
|
133
117
|
state: { value: 'lost', context: {} },
|
|
134
118
|
});
|
|
135
119
|
|
|
136
|
-
expect(observation.
|
|
120
|
+
expect(observation.episodeId).toEqual(agent.episodeId);
|
|
137
121
|
|
|
138
|
-
expect(agent.
|
|
122
|
+
expect(agent.getObservations()).toContainEqual(
|
|
139
123
|
expect.objectContaining({
|
|
140
124
|
prevState: { value: 'playing', context: {} },
|
|
141
125
|
event: { type: 'play', position: 3 },
|
|
142
126
|
state: { value: 'lost', context: {} },
|
|
143
|
-
|
|
127
|
+
episodeId: expect.any(String),
|
|
128
|
+
timestamp: expect.any(Number),
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('agent.addObservation() adds to observations (initial state)', () => {
|
|
134
|
+
const agent = createAgent({
|
|
135
|
+
name: 'test',
|
|
136
|
+
events: {},
|
|
137
|
+
model: {} as any,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const observation = agent.addObservation({
|
|
141
|
+
state: { value: 'lost' },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(observation.episodeId).toEqual(agent.episodeId);
|
|
145
|
+
|
|
146
|
+
expect(agent.getObservations()).toContainEqual(
|
|
147
|
+
expect.objectContaining({
|
|
148
|
+
state: { value: 'lost', context: undefined },
|
|
149
|
+
episodeId: expect.any(String),
|
|
144
150
|
timestamp: expect.any(Number),
|
|
145
151
|
})
|
|
146
152
|
);
|
|
@@ -172,15 +178,62 @@ test('agent.addObservation() adds to observations with machine hash', () => {
|
|
|
172
178
|
machine,
|
|
173
179
|
});
|
|
174
180
|
|
|
175
|
-
expect(observation.
|
|
181
|
+
expect(observation.episodeId).toEqual(agent.episodeId);
|
|
176
182
|
|
|
177
|
-
expect(agent.
|
|
183
|
+
expect(agent.getObservations()).toContainEqual(
|
|
178
184
|
expect.objectContaining({
|
|
179
185
|
prevState: { value: 'playing', context: {} },
|
|
180
186
|
event: { type: 'play', position: 3 },
|
|
181
187
|
state: { value: 'lost', context: {} },
|
|
182
188
|
machineHash: expect.any(String),
|
|
183
|
-
|
|
189
|
+
episodeId: expect.any(String),
|
|
190
|
+
timestamp: expect.any(Number),
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
196
|
+
const agent = createAgent({
|
|
197
|
+
name: 'test',
|
|
198
|
+
events: {},
|
|
199
|
+
model: {} as any,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const observation = agent.addObservation({
|
|
203
|
+
state: {
|
|
204
|
+
value: 'playing',
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const feedback = agent.addFeedback({
|
|
209
|
+
attributes: {
|
|
210
|
+
score: -1,
|
|
211
|
+
},
|
|
212
|
+
goal: 'Win the game',
|
|
213
|
+
observationId: observation.id,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(feedback.episodeId).toEqual(agent.episodeId);
|
|
217
|
+
|
|
218
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
219
|
+
expect.objectContaining({
|
|
220
|
+
attributes: {
|
|
221
|
+
score: -1,
|
|
222
|
+
},
|
|
223
|
+
goal: 'Win the game',
|
|
224
|
+
observationId: observation.id,
|
|
225
|
+
episodeId: expect.any(String),
|
|
226
|
+
timestamp: expect.any(Number),
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
230
|
+
expect.objectContaining({
|
|
231
|
+
attributes: {
|
|
232
|
+
score: -1,
|
|
233
|
+
},
|
|
234
|
+
goal: 'Win the game',
|
|
235
|
+
observationId: observation.id,
|
|
236
|
+
episodeId: expect.any(String),
|
|
184
237
|
timestamp: expect.any(Number),
|
|
185
238
|
})
|
|
186
239
|
);
|
|
@@ -209,7 +262,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
209
262
|
|
|
210
263
|
actor.start();
|
|
211
264
|
|
|
212
|
-
expect(agent.
|
|
265
|
+
expect(agent.getObservations()).toContainEqual(
|
|
213
266
|
expect.objectContaining({
|
|
214
267
|
prevState: undefined,
|
|
215
268
|
state: expect.objectContaining({ value: 'a' }),
|
|
@@ -224,7 +277,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
224
277
|
|
|
225
278
|
actor.send({ type: 'NEXT' });
|
|
226
279
|
|
|
227
|
-
expect(agent.
|
|
280
|
+
expect(agent.getObservations()).toContainEqual(
|
|
228
281
|
expect.objectContaining({
|
|
229
282
|
prevState: expect.objectContaining({ value: 'a' }),
|
|
230
283
|
event: { type: 'NEXT' },
|
|
@@ -233,35 +286,11 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
233
286
|
);
|
|
234
287
|
});
|
|
235
288
|
|
|
236
|
-
test('Agents can use a custom adapter', async () => {
|
|
237
|
-
const adapter = {
|
|
238
|
-
generateText: async () => {
|
|
239
|
-
return {
|
|
240
|
-
text: 'Response',
|
|
241
|
-
} as any;
|
|
242
|
-
},
|
|
243
|
-
} as unknown as AIAdapter;
|
|
244
|
-
|
|
245
|
-
const agent = createAgent({
|
|
246
|
-
name: 'test',
|
|
247
|
-
events: {},
|
|
248
|
-
adapter,
|
|
249
|
-
model: {} as any,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
const res = await agent.generateText({
|
|
253
|
-
prompt: 'Question?',
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
expect(res.text).toEqual('Response');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
289
|
test('You can listen for feedback events', () => {
|
|
260
290
|
const fn = vi.fn();
|
|
261
291
|
const agent = createAgent({
|
|
262
292
|
name: 'test',
|
|
263
293
|
events: {},
|
|
264
|
-
adapter: {} as any,
|
|
265
294
|
model: {} as any,
|
|
266
295
|
});
|
|
267
296
|
|
|
@@ -280,32 +309,34 @@ test('You can listen for feedback events', () => {
|
|
|
280
309
|
|
|
281
310
|
test('You can listen for plan events', async () => {
|
|
282
311
|
const fn = vi.fn();
|
|
312
|
+
const model = new MockLanguageModelV1({
|
|
313
|
+
doGenerate: async (params: LanguageModelV1CallOptions) => {
|
|
314
|
+
const keys =
|
|
315
|
+
params.mode.type === 'regular'
|
|
316
|
+
? params.mode.tools?.map((tool) => tool.name)
|
|
317
|
+
: [];
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
...dummyResponseValues,
|
|
321
|
+
finishReason: 'tool-calls',
|
|
322
|
+
toolCalls: [
|
|
323
|
+
{
|
|
324
|
+
toolCallType: 'function',
|
|
325
|
+
toolCallId: 'call-1',
|
|
326
|
+
toolName: keys![0],
|
|
327
|
+
args: `{ "type": "${keys?.[0]}" }`,
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
} as any;
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
283
334
|
const agent = createAgent({
|
|
284
335
|
name: 'test',
|
|
285
|
-
model
|
|
336
|
+
model,
|
|
286
337
|
events: {
|
|
287
338
|
WIN: z.object({}),
|
|
288
339
|
},
|
|
289
|
-
adapter: {
|
|
290
|
-
generateText: async (arg) => {
|
|
291
|
-
const keys = Object.keys(arg.tools!);
|
|
292
|
-
|
|
293
|
-
if (keys.length !== 1) {
|
|
294
|
-
throw new Error('Expected only 1 choice');
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return {
|
|
298
|
-
toolResults: [
|
|
299
|
-
{
|
|
300
|
-
result: {
|
|
301
|
-
type: keys[0],
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
} as any as AgentGenerateTextResult;
|
|
306
|
-
},
|
|
307
|
-
streamText: {} as any,
|
|
308
|
-
},
|
|
309
340
|
});
|
|
310
341
|
|
|
311
342
|
agent.on('plan', fn);
|
|
@@ -362,145 +393,3 @@ test('agent.types provides context and event types', () => {
|
|
|
362
393
|
// @ts-expect-error
|
|
363
394
|
agent.types.context satisfies { score: string };
|
|
364
395
|
});
|
|
365
|
-
|
|
366
|
-
test.each(['generateText', 'streamText'] as const)(
|
|
367
|
-
'can provide a correlation ID (%s)',
|
|
368
|
-
async (method) => {
|
|
369
|
-
const agent = createAgent({
|
|
370
|
-
model: {} as any,
|
|
371
|
-
events: {},
|
|
372
|
-
adapter: {
|
|
373
|
-
[method]: async (opts: any) => {
|
|
374
|
-
const res = {
|
|
375
|
-
text: 'response',
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
opts.onFinish?.(res);
|
|
379
|
-
|
|
380
|
-
return res as AgentGenerateTextResult;
|
|
381
|
-
},
|
|
382
|
-
} as any as AIAdapter,
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
const promise = new Promise<AgentMessage>((res) => {
|
|
386
|
-
agent.onMessage((msg) => {
|
|
387
|
-
if (msg.role === 'assistant') {
|
|
388
|
-
res(msg);
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
await agent[method]({
|
|
394
|
-
prompt: 'hi',
|
|
395
|
-
correlationId: 'c-1',
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const msg = await promise;
|
|
399
|
-
|
|
400
|
-
expect(msg.correlationId).toBe('c-1');
|
|
401
|
-
expect(msg.parentCorrelationId).toBe(undefined);
|
|
402
|
-
}
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
test.each(['generateText', 'streamText'] as const)(
|
|
406
|
-
'correlation IDs are automatically generated if not provided (%s)',
|
|
407
|
-
async (method) => {
|
|
408
|
-
const agent = createAgent({
|
|
409
|
-
model: {} as any,
|
|
410
|
-
events: {},
|
|
411
|
-
adapter: {
|
|
412
|
-
[method]: async (opts: any) => {
|
|
413
|
-
const res = {
|
|
414
|
-
text: 'response',
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
opts.onFinish?.(res);
|
|
418
|
-
|
|
419
|
-
return res as AgentGenerateTextResult;
|
|
420
|
-
},
|
|
421
|
-
} as any as AIAdapter,
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
await agent[method]({
|
|
425
|
-
prompt: 'hi',
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const messages = agent.getMessages();
|
|
429
|
-
|
|
430
|
-
expect(messages[0]?.correlationId).toEqual(expect.stringMatching(/.+/));
|
|
431
|
-
expect(messages[0]?.role).toBe('user');
|
|
432
|
-
expect(messages[1]?.correlationId).toEqual(expect.stringMatching(/.+/));
|
|
433
|
-
expect(messages[1]?.role).toBe('assistant');
|
|
434
|
-
|
|
435
|
-
expect(messages[0]!.correlationId).toEqual(messages[1]!.correlationId);
|
|
436
|
-
}
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
test.each(['generateText', 'streamText'] as const)(
|
|
440
|
-
'can provide a parent correlation ID (%s)',
|
|
441
|
-
async (method) => {
|
|
442
|
-
const agent = createAgent({
|
|
443
|
-
model: {} as any,
|
|
444
|
-
events: {},
|
|
445
|
-
adapter: {
|
|
446
|
-
[method]: async (opts: any) => {
|
|
447
|
-
const res = {
|
|
448
|
-
text: 'response',
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
opts.onFinish?.(res);
|
|
452
|
-
|
|
453
|
-
return res as AgentGenerateTextResult;
|
|
454
|
-
},
|
|
455
|
-
} as any as AIAdapter,
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
await agent[method]({
|
|
459
|
-
prompt: 'hi',
|
|
460
|
-
correlationId: 'c-1',
|
|
461
|
-
parentCorrelationId: 'c-0',
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
const msg = agent.getMessages().find((msg) => msg.role === 'assistant')!;
|
|
465
|
-
|
|
466
|
-
expect(msg.correlationId).toBe('c-1');
|
|
467
|
-
expect(msg.parentCorrelationId).toBe('c-0');
|
|
468
|
-
}
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
test.each(['generateText', 'streamText'] as const)(
|
|
472
|
-
'can add feedback to a correlation (%s)',
|
|
473
|
-
async (method) => {
|
|
474
|
-
const agent = createAgent({
|
|
475
|
-
name: 'test',
|
|
476
|
-
model: {} as any,
|
|
477
|
-
events: {},
|
|
478
|
-
adapter: {
|
|
479
|
-
[method]: async (opts: any) => {
|
|
480
|
-
const res = {
|
|
481
|
-
text: 'response',
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
opts.onFinish?.(res);
|
|
485
|
-
|
|
486
|
-
return res as AgentGenerateTextResult;
|
|
487
|
-
},
|
|
488
|
-
} as any as AIAdapter,
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const res = await agent[method]({
|
|
492
|
-
prompt: 'test',
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
agent.addFeedback({
|
|
496
|
-
correlationId: res.correlationId,
|
|
497
|
-
reward: -1,
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
const message = agent.getMessages()[0]!;
|
|
501
|
-
const feedback = agent.getFeedback()[0]!;
|
|
502
|
-
|
|
503
|
-
expect(message.correlationId).toBeDefined();
|
|
504
|
-
expect(feedback.correlationId).toEqual(message.correlationId);
|
|
505
|
-
}
|
|
506
|
-
);
|