@statelyai/agent 2.0.0-next.1 → 2.0.0-next.3

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.
Files changed (51) hide show
  1. package/.changeset/grumpy-dolphins-think.md +17 -0
  2. package/.changeset/old-teachers-tap.md +5 -0
  3. package/.changeset/pink-eagles-deliver.md +13 -0
  4. package/.changeset/pre.json +9 -1
  5. package/.changeset/quiet-turtles-do.md +7 -0
  6. package/.changeset/smart-yaks-pull.md +23 -0
  7. package/.changeset/sweet-clouds-mix.md +16 -0
  8. package/.changeset/swift-mangos-rush.md +5 -0
  9. package/.changeset/tough-ways-rhyme.md +5 -0
  10. package/CHANGELOG.md +79 -0
  11. package/dist/index.d.mts +116 -100
  12. package/dist/index.d.ts +116 -100
  13. package/dist/index.js +102 -72
  14. package/dist/index.mjs +105 -75
  15. package/examples/chatbot.ts +2 -2
  16. package/examples/cot.ts +21 -73
  17. package/examples/customer-service-sim.ts +3 -3
  18. package/examples/email.ts +3 -5
  19. package/examples/example.ts +2 -2
  20. package/examples/goal.ts +2 -2
  21. package/examples/joke.ts +12 -12
  22. package/examples/jugs.ts +4 -7
  23. package/examples/learn-from-feedback.ts +123 -0
  24. package/examples/number.ts +2 -2
  25. package/examples/raffle.ts +2 -2
  26. package/examples/river-crossing.ts +4 -7
  27. package/examples/simple.ts +13 -10
  28. package/examples/summary.ts +2 -5
  29. package/examples/support.ts +38 -38
  30. package/examples/ticTacToe.ts +46 -4
  31. package/examples/todo.ts +3 -3
  32. package/examples/tutor.ts +2 -2
  33. package/examples/verify.ts +2 -2
  34. package/examples/weather-agent.ts +139 -0
  35. package/examples/weather.ts +26 -23
  36. package/examples/word.ts +8 -6
  37. package/package.json +2 -1
  38. package/src/agent.test.ts +37 -52
  39. package/src/agent.ts +93 -60
  40. package/src/decide.test.ts +56 -8
  41. package/src/decide.ts +42 -32
  42. package/src/strategies/chainOfThought.ts +50 -0
  43. package/src/{planners → strategies}/shortestPath.test.ts +4 -7
  44. package/src/strategies/shortestPath.ts +178 -0
  45. package/src/{planners → strategies}/simple.ts +25 -26
  46. package/src/templates/defaultText.ts +3 -0
  47. package/src/text.ts +13 -13
  48. package/src/types.ts +124 -83
  49. package/src/utils.ts +13 -1
  50. package/src/planners/shortestPath.ts +0 -177
  51. package/src/strategies/chain-of-note.ts +0 -106
@@ -1,4 +1,4 @@
1
- import { createAgent, fromDecision } from '../src';
1
+ import { createAgent, EventFromAgent, fromDecision } from '../src';
2
2
  import { assign, createActor, fromPromise, log, setup } from 'xstate';
3
3
  import { fromTerminal } from './helpers/helpers';
4
4
  import { z } from 'zod';
@@ -75,10 +75,10 @@ const machine = setup({
75
75
  types: {
76
76
  context: {} as {
77
77
  location: string;
78
- history: string[];
79
78
  count: number;
79
+ result: string | null;
80
80
  },
81
- events: agent.types.events,
81
+ events: {} as EventFromAgent<typeof agent>,
82
82
  },
83
83
  actors: {
84
84
  agent: fromDecision(agent),
@@ -90,7 +90,7 @@ const machine = setup({
90
90
  context: {
91
91
  location: '',
92
92
  count: 0,
93
- history: [],
93
+ result: null,
94
94
  },
95
95
  states: {
96
96
  getLocation: {
@@ -111,15 +111,6 @@ const machine = setup({
111
111
  },
112
112
  decide: {
113
113
  entry: log('Deciding...'),
114
- invoke: {
115
- src: 'agent',
116
- input: ({ context }) => ({
117
- context: {
118
- location: context.location,
119
- },
120
- goal: `Decide what to do based on the given location, which may or may not be a location`,
121
- }),
122
- },
123
114
  on: {
124
115
  'agent.getWeather': {
125
116
  actions: log(({ event }) => event),
@@ -138,6 +129,7 @@ const machine = setup({
138
129
  log(({ event }) => event.output),
139
130
  assign({
140
131
  count: ({ context }) => context.count + 1,
132
+ result: ({ event }) => event.output,
141
133
  }),
142
134
  ],
143
135
  target: 'reportWeather',
@@ -145,13 +137,6 @@ const machine = setup({
145
137
  },
146
138
  },
147
139
  reportWeather: {
148
- invoke: {
149
- src: 'agent',
150
- input: ({ context }) => ({
151
- goal: 'Report the weather', // TODO
152
- context,
153
- }),
154
- },
155
140
  on: {
156
141
  'agent.reportWeather': {
157
142
  actions: log(({ event }) => event),
@@ -169,7 +154,25 @@ const machine = setup({
169
154
  });
170
155
 
171
156
  const actor = createActor(machine);
172
- actor.subscribe((s) => {
173
- console.log(s.value);
174
- });
175
157
  actor.start();
158
+
159
+ agent.interact(actor, ({ state }) => {
160
+ if (state.matches('decide')) {
161
+ return {
162
+ goal: `Decide what to do based on the given location, which may or may not be a location`,
163
+ context: {
164
+ location: state.context.location,
165
+ },
166
+ };
167
+ }
168
+
169
+ if (state.matches('reportWeather')) {
170
+ return {
171
+ goal: `Report the weather for the given location`,
172
+ context: {
173
+ location: state.context.location,
174
+ result: state.context.result,
175
+ },
176
+ };
177
+ }
178
+ });
package/examples/word.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { assign, createActor, log, setup } from 'xstate';
2
2
  import { fromTerminal } from './helpers/helpers';
3
- import { createAgent, fromDecision } from '../src';
3
+ import {
4
+ ContextFromAgent,
5
+ createAgent,
6
+ fromDecision,
7
+ TypesFromAgent,
8
+ } from '../src';
4
9
  import { z } from 'zod';
5
10
  import { openai } from '@ai-sdk/openai';
6
11
 
@@ -36,13 +41,10 @@ const context = {
36
41
  word: null,
37
42
  guessedWord: null,
38
43
  lettersGuessed: [],
39
- } satisfies typeof agent.types.context;
44
+ } satisfies ContextFromAgent<typeof agent>;
40
45
 
41
46
  const wordGuesserMachine = setup({
42
- types: {
43
- context: agent.types.context,
44
- events: agent.types.events,
45
- },
47
+ types: {} as TypesFromAgent<typeof agent>,
46
48
  actors: {
47
49
  agent: fromDecision(agent),
48
50
  getFromTerminal: fromTerminal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/agent",
3
- "version": "2.0.0-next.1",
3
+ "version": "2.0.0-next.3",
4
4
  "description": "Stateful agents that make decisions based on finite-state machine models",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -15,6 +15,7 @@
15
15
  "author": "David Khourshid <david@stately.ai>",
16
16
  "license": "MIT",
17
17
  "devDependencies": {
18
+ "@ai-sdk/anthropic": "^0.0.54",
18
19
  "@ai-sdk/openai": "^0.0.40",
19
20
  "@changesets/changelog-github": "^0.5.0",
20
21
  "@changesets/cli": "^2.27.9",
package/src/agent.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { test, expect, vi } from 'vitest';
2
- import { createAgent } from './';
2
+ import { createAgent, TypesFromAgent } from './';
3
3
  import { createActor, createMachine } from 'xstate';
4
4
  import { LanguageModelV1CallOptions } from 'ai';
5
5
  import { z } from 'zod';
@@ -17,12 +17,12 @@ test('an agent has the expected interface', () => {
17
17
  expect(agent.addMessage).toBeDefined();
18
18
  expect(agent.addObservation).toBeDefined();
19
19
  expect(agent.addFeedback).toBeDefined();
20
- expect(agent.addPlan).toBeDefined();
20
+ expect(agent.addDecision).toBeDefined();
21
21
 
22
22
  expect(agent.getMessages).toBeDefined();
23
23
  expect(agent.getObservations).toBeDefined();
24
24
  expect(agent.getFeedback).toBeDefined();
25
- expect(agent.getPlans).toBeDefined();
25
+ expect(agent.getDecisions).toBeDefined();
26
26
 
27
27
  expect(agent.interact).toBeDefined();
28
28
  });
@@ -70,34 +70,24 @@ test('agent.addFeedback() adds to feedback', () => {
70
70
  model: {} as any,
71
71
  });
72
72
 
73
- const feedback = agent.addFeedback({
74
- attributes: {
75
- score: -1,
76
- },
73
+ const obs = agent.addObservation({
74
+ prevState: { value: 'playing' },
75
+ state: { value: 'lost' },
76
+ event: { type: 'play', position: 3 },
77
77
  goal: 'Win the game',
78
- observationId: 'obs-1',
78
+ });
79
+
80
+ const feedback = agent.addFeedback({
81
+ score: 0,
82
+ observationId: obs.id,
79
83
  });
80
84
 
81
85
  expect(feedback.episodeId).toEqual(agent.episodeId);
82
86
 
83
87
  expect(agent.getFeedback()).toContainEqual(
84
88
  expect.objectContaining({
85
- attributes: {
86
- score: -1,
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',
89
+ score: 0,
90
+ observationId: obs.id,
101
91
  episodeId: expect.any(String),
102
92
  timestamp: expect.any(Number),
103
93
  })
@@ -115,6 +105,7 @@ test('agent.addObservation() adds to observations', () => {
115
105
  prevState: { value: 'playing', context: {} },
116
106
  event: { type: 'play', position: 3 },
117
107
  state: { value: 'lost', context: {} },
108
+ goal: 'Win the game',
118
109
  });
119
110
 
120
111
  expect(observation.episodeId).toEqual(agent.episodeId);
@@ -139,6 +130,7 @@ test('agent.addObservation() adds to observations (initial state)', () => {
139
130
 
140
131
  const observation = agent.addObservation({
141
132
  state: { value: 'lost' },
133
+ goal: 'Win the game',
142
134
  });
143
135
 
144
136
  expect(observation.episodeId).toEqual(agent.episodeId);
@@ -176,6 +168,7 @@ test('agent.addObservation() adds to observations with machine hash', () => {
176
168
  event: { type: 'play', position: 3 },
177
169
  state: { value: 'lost', context: {} },
178
170
  machine,
171
+ goal: 'Win the game',
179
172
  });
180
173
 
181
174
  expect(observation.episodeId).toEqual(agent.episodeId);
@@ -203,13 +196,11 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
203
196
  state: {
204
197
  value: 'playing',
205
198
  },
199
+ goal: 'Win the game',
206
200
  });
207
201
 
208
202
  const feedback = agent.addFeedback({
209
- attributes: {
210
- score: -1,
211
- },
212
- goal: 'Win the game',
203
+ score: 0,
213
204
  observationId: observation.id,
214
205
  });
215
206
 
@@ -217,10 +208,7 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
217
208
 
218
209
  expect(agent.getFeedback()).toContainEqual(
219
210
  expect.objectContaining({
220
- attributes: {
221
- score: -1,
222
- },
223
- goal: 'Win the game',
211
+ score: 0,
224
212
  observationId: observation.id,
225
213
  episodeId: expect.any(String),
226
214
  timestamp: expect.any(Number),
@@ -228,10 +216,7 @@ test('agent.addFeedback() adds to feedback (with observation)', () => {
228
216
  );
229
217
  expect(agent.getFeedback()).toContainEqual(
230
218
  expect.objectContaining({
231
- attributes: {
232
- score: -1,
233
- },
234
- goal: 'Win the game',
219
+ score: 0,
235
220
  observationId: observation.id,
236
221
  episodeId: expect.any(String),
237
222
  timestamp: expect.any(Number),
@@ -297,17 +282,14 @@ test('You can listen for feedback events', () => {
297
282
  agent.on('feedback', fn);
298
283
 
299
284
  agent.addFeedback({
300
- attributes: {
301
- score: -1,
302
- },
303
- goal: 'Win the game',
285
+ score: -1,
304
286
  observationId: 'obs-1',
305
287
  });
306
288
 
307
289
  expect(fn).toHaveBeenCalled();
308
290
  });
309
291
 
310
- test('You can listen for plan events', async () => {
292
+ test('You can listen for decision events', async () => {
311
293
  const fn = vi.fn();
312
294
  const model = new MockLanguageModelV1({
313
295
  doGenerate: async (params: LanguageModelV1CallOptions) => {
@@ -339,7 +321,7 @@ test('You can listen for plan events', async () => {
339
321
  },
340
322
  });
341
323
 
342
- agent.on('plan', fn);
324
+ agent.on('decision', fn);
343
325
 
344
326
  await agent.decide({
345
327
  goal: 'Win the game',
@@ -364,7 +346,7 @@ test('You can listen for plan events', async () => {
364
346
 
365
347
  expect(fn).toHaveBeenCalledWith(
366
348
  expect.objectContaining({
367
- plan: expect.objectContaining({
349
+ decision: expect.objectContaining({
368
350
  nextEvent: {
369
351
  type: 'WIN',
370
352
  },
@@ -386,12 +368,14 @@ test('agent.types provides context and event types', () => {
386
368
  },
387
369
  });
388
370
 
389
- agent.types satisfies { context: any; events: any };
371
+ let types = {} as TypesFromAgent<typeof agent>;
372
+
373
+ types satisfies { context: any; events: any };
390
374
 
391
- agent.types.context satisfies { score: number };
375
+ types.context satisfies { score: number };
392
376
 
393
377
  // @ts-expect-error
394
- agent.types.context satisfies { score: string };
378
+ types.context satisfies { score: string };
395
379
  });
396
380
 
397
381
  test('It allows unrecognized events', () => {
@@ -436,15 +420,16 @@ test('You can listen for message events', () => {
436
420
  );
437
421
  });
438
422
 
439
- test('agent.getPlans() returns plans from context', () => {
423
+ test('agent.getDecisions() returns decisions from context', () => {
440
424
  const agent = createAgent({
441
425
  id: 'test',
442
426
  events: {},
443
427
  model: {} as any,
444
- planner: async (agent) => {
428
+ strategy: async (agent) => {
445
429
  return {
430
+ id: Date.now().toString(),
446
431
  episodeId: agent.episodeId,
447
- planner: 'test-planner',
432
+ strategy: 'test-strategy',
448
433
  goal: '',
449
434
  goalState: undefined,
450
435
  paths: [
@@ -459,10 +444,10 @@ test('agent.getPlans() returns plans from context', () => {
459
444
  },
460
445
  });
461
446
 
462
- const plans = agent.getPlans();
447
+ const decisions = agent.getDecisions();
463
448
 
464
- expect(plans).toBeDefined();
465
- expect(Array.isArray(plans)).toBe(true);
449
+ expect(decisions).toBeDefined();
450
+ expect(Array.isArray(decisions)).toBe(true);
466
451
  });
467
452
 
468
453
  test('Event listeners can be unsubscribed', () => {