@statelyai/agent 1.1.6 → 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.
Files changed (62) hide show
  1. package/.changeset/cyan-carpets-perform.md +5 -0
  2. package/.changeset/fast-donkeys-argue.md +5 -0
  3. package/.changeset/light-hats-drive.md +9 -0
  4. package/.changeset/old-jobs-check.md +5 -0
  5. package/.changeset/pre.json +13 -0
  6. package/.vscode/launch.json +6 -0
  7. package/CHANGELOG.md +22 -0
  8. package/dist/index.d.mts +262 -171
  9. package/dist/index.d.ts +262 -171
  10. package/dist/index.js +383 -274
  11. package/dist/index.mjs +386 -272
  12. package/examples/chatbot-alt.ts +57 -0
  13. package/examples/chatbot.ts +12 -17
  14. package/examples/cot.ts +26 -23
  15. package/examples/customer-service-sim.ts +107 -0
  16. package/examples/email.ts +37 -41
  17. package/examples/example.ts +6 -6
  18. package/examples/executor.ts +66 -0
  19. package/examples/goal.ts +12 -12
  20. package/examples/helpers/helpers.ts +26 -14
  21. package/examples/joke.ts +79 -76
  22. package/examples/jugs.ts +125 -0
  23. package/examples/multi.ts +5 -5
  24. package/examples/newspaper.ts +98 -104
  25. package/examples/number.ts +6 -5
  26. package/examples/raffle.ts +11 -12
  27. package/examples/river-crossing.ts +140 -0
  28. package/examples/sandbox.ts +1 -1
  29. package/examples/simple.ts +5 -3
  30. package/examples/summary.ts +121 -0
  31. package/examples/support.ts +6 -6
  32. package/examples/ticTacToe.ts +86 -45
  33. package/examples/todo.ts +7 -7
  34. package/examples/tutor.ts +15 -15
  35. package/examples/verify.ts +3 -3
  36. package/examples/weather.ts +6 -9
  37. package/examples/wiki.ts +27 -8
  38. package/examples/word.ts +16 -11
  39. package/package.json +16 -11
  40. package/readme.md +1 -1
  41. package/src/agent-experimental.ts +1 -1
  42. package/src/agent.test.ts +243 -214
  43. package/src/agent.ts +286 -95
  44. package/src/decide.test.ts +276 -0
  45. package/src/decide.ts +163 -0
  46. package/src/index.ts +1 -1
  47. package/src/middleware.ts +91 -0
  48. package/src/mockModel.ts +47 -0
  49. package/src/planners/shortestPath.test.ts +94 -0
  50. package/src/planners/shortestPath.ts +177 -0
  51. package/src/planners/simple.ts +105 -0
  52. package/src/strategies/chain-of-note.ts +6 -55
  53. package/src/text.ts +51 -144
  54. package/src/types.ts +187 -212
  55. package/src/utils.ts +48 -4
  56. package/vitest.config.ts +9 -3
  57. package/src/adapters/vercel.ts +0 -7
  58. package/src/decision.test.ts +0 -179
  59. package/src/decision.ts +0 -84
  60. package/src/memory.ts +0 -25
  61. package/src/planners/shortestPathPlanner.ts +0 -22
  62. package/src/planners/simplePlanner.ts +0 -139
package/src/agent.ts CHANGED
@@ -1,34 +1,43 @@
1
1
  import {
2
+ Actor,
3
+ ActorRefLike,
2
4
  AnyEventObject,
3
5
  AnyStateMachine,
4
- createActor,
5
6
  EventObject,
6
7
  fromTransition,
7
- Observer,
8
- toObserver,
8
+ Subscription,
9
9
  } from 'xstate';
10
10
  import { ZodContextMapping, ZodEventMapping } from './schemas';
11
11
  import {
12
- Agent,
13
12
  AgentLogic,
14
13
  AgentMessage,
15
14
  AgentPlanner,
16
15
  EventsFromZodEventMapping,
17
16
  GenerateTextOptions,
18
17
  AgentLongTermMemory,
19
- AIAdapter,
20
18
  ObservedState,
21
19
  AgentObservationInput,
22
20
  AgentMemoryContext,
23
21
  AgentObservation,
24
22
  ContextFromZodContextMapping,
25
23
  AgentFeedback,
24
+ AgentMessageInput,
25
+ AgentFeedbackInput,
26
+ AgentPlan,
27
+ Compute,
28
+ AgentDecisionInput,
29
+ AgentDecideOptions,
30
+ AnyAgent,
26
31
  } from './types';
27
- import { simplePlanner } from './planners/simplePlanner';
28
- import { agentGenerateText, agentStreamText } from './text';
29
- import { agentDecide } from './decision';
30
- import { vercelAdapter } from './adapters/vercel';
31
- import { getMachineHash, randomId } from './utils';
32
+ import { simplePlanner } from './planners/simple';
33
+ import { agentDecide } from './decide';
34
+ import { getMachineHash, isActorRef, isMachineActor, randomId } from './utils';
35
+ import {
36
+ experimental_wrapLanguageModel,
37
+ LanguageModel,
38
+ LanguageModelV1,
39
+ } from 'ai';
40
+ import { createAgentMiddleware } from './middleware';
32
41
 
33
42
  export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
34
43
  (state, event, { emit }) => {
@@ -69,8 +78,11 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
69
78
  });
70
79
  break;
71
80
  }
72
- default:
81
+ default: {
82
+ // unrecognized
83
+ console.warn('Unrecognized event', event);
73
84
  break;
85
+ }
74
86
  }
75
87
  return state;
76
88
  },
@@ -89,23 +101,19 @@ export function createAgent<
89
101
  TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
90
102
  TContext = ContextFromZodContextMapping<TContextSchema>
91
103
  >({
92
- name,
93
- description,
104
+ id,
105
+ description: description,
94
106
  model,
95
107
  events,
96
108
  context,
97
- planner = simplePlanner as AgentPlanner<Agent<TContext, TEvents>>,
98
- stringify = JSON.stringify,
99
- getMemory,
109
+ planner = simplePlanner as AgentPlanner<Agent<TContextSchema, TEventSchemas>>,
100
110
  logic = agentLogic as AgentLogic<TEvents>,
101
- adapter = vercelAdapter,
102
- ...generateTextOptions
103
111
  }: {
104
112
  /**
105
113
  * The unique identifier for the agent.
106
114
  *
107
115
  * This should be the same across all sessions of a specific agent, as it can be
108
- * used to retrieve memory for this agent.
116
+ * used to retrieve memory for previous episodes of this agent.
109
117
  *
110
118
  * @example
111
119
  * ```ts
@@ -116,10 +124,6 @@ export function createAgent<
116
124
  * ```
117
125
  */
118
126
  id?: string;
119
- /**
120
- * The name of the agent
121
- */
122
- name?: string;
123
127
  /**
124
128
  * A description of the role of the agent
125
129
  */
@@ -130,150 +134,292 @@ export function createAgent<
130
134
  */
131
135
  events: TEventSchemas;
132
136
  context?: TContextSchema;
133
- planner?: AgentPlanner<Agent<TContext, TEvents>>;
137
+ planner?: AgentPlanner<Agent<TContextSchema, TEventSchemas>>;
134
138
  stringify?: typeof JSON.stringify;
135
139
  /**
136
140
  * A function that retrieves the agent's long term memory
137
141
  */
138
- getMemory?: (agent: Agent<TContext, TEvents>) => AgentLongTermMemory;
142
+ getMemory?: (
143
+ agent: Agent<TContextSchema, TEventSchemas>
144
+ ) => AgentLongTermMemory;
139
145
  /**
140
146
  * Agent logic
141
147
  */
142
148
  logic?: AgentLogic<TEvents>;
143
- adapter?: AIAdapter;
144
- } & GenerateTextOptions): Agent<TContext, TEvents> {
145
- const agent = createActor(logic) as unknown as Agent<TContext, TEvents>;
146
- agent.events = events;
147
- agent.model = model;
148
- agent.name = name;
149
- agent.description = description;
150
- agent.adapter = adapter;
151
- agent.defaultOptions = { ...generateTextOptions, model };
152
- agent.select = (selector) => {
153
- return selector(agent.getSnapshot().context);
154
- };
155
- agent.memory = getMemory ? getMemory(agent) : undefined;
149
+ model: LanguageModel;
150
+ } & GenerateTextOptions): Agent<TContextSchema, TEventSchemas> {
151
+ return new Agent({
152
+ id,
153
+ context,
154
+ events,
155
+ description,
156
+ planner,
157
+ model,
158
+ logic,
159
+ }) as any;
160
+ }
156
161
 
157
- agent.onMessage = (callback) => {
158
- agent.on('message', (ev) => callback(ev.message));
162
+ export class Agent<
163
+ const TContextSchema extends ZodContextMapping,
164
+ const TEventSchemas extends ZodEventMapping,
165
+ TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
166
+ TContext = ContextFromZodContextMapping<TContextSchema>
167
+ > extends Actor<AgentLogic<TEvents>> {
168
+ /**
169
+ * The name of the agent. All agents with the same name are related and
170
+ * able to share experiences (observations, feedback) with each other.
171
+ */
172
+ public name?: string;
173
+ /**
174
+ * The unique identifier for the agent.
175
+ */
176
+ public episodeId: string;
177
+ public description?: string;
178
+ public events: TEventSchemas;
179
+ public context?: TContextSchema;
180
+ public planner?: AgentPlanner<Agent<TContextSchema, TEventSchemas>>;
181
+ public types: {
182
+ events: TEvents;
183
+ context: Compute<TContext>;
159
184
  };
185
+ public model: LanguageModel;
186
+ public memory: AgentLongTermMemory | undefined;
187
+ public defaultOptions: AgentDecideOptions<AnyAgent> | undefined; // todo
160
188
 
161
- agent.decide = (opts) => {
162
- return agentDecide(agent, opts);
163
- };
189
+ constructor({
190
+ logic = agentLogic as AgentLogic<TEvents>,
191
+ id,
192
+ name,
193
+ description,
194
+ model,
195
+ events,
196
+ context,
197
+ planner = simplePlanner,
198
+ }: {
199
+ logic: AgentLogic<TEvents>;
200
+ id?: string;
201
+ name?: string;
202
+ description?: string;
203
+ model: GenerateTextOptions['model'];
204
+ events: TEventSchemas;
205
+ context?: TContextSchema;
206
+ planner?: AgentPlanner<Agent<TContextSchema, TEventSchemas>>;
207
+ }) {
208
+ super(logic);
209
+ this.model = model;
210
+ this.episodeId = id ?? randomId();
211
+ this.name = name;
212
+ this.description = description;
213
+ this.events = events;
214
+ this.context = context;
215
+ this.planner = planner;
216
+ this.types = {} as any;
217
+
218
+ this.start();
219
+ }
220
+
221
+ /**
222
+ * Called whenever the agent (LLM assistant) receives or sends a message.
223
+ */
224
+ public onMessage(fn: (message: AgentMessage) => void) {
225
+ return this.on('message', (ev) => fn(ev.message));
226
+ }
164
227
 
165
- agent.addMessage = (messageInput) => {
228
+ /**
229
+ * Retrieves messages from the agent's short-term (local) memory.
230
+ */
231
+ public addMessage(messageInput: AgentMessageInput) {
166
232
  const message = {
167
233
  ...messageInput,
168
234
  id: messageInput.id ?? randomId(),
169
235
  timestamp: messageInput.timestamp ?? Date.now(),
170
- sessionId: agent.sessionId,
171
- correlationId: messageInput.correlationId ?? randomId(),
236
+ episodeId: this.episodeId,
172
237
  } satisfies AgentMessage;
173
- agent.send({
238
+ this.send({
174
239
  type: 'agent.message',
175
240
  message,
176
241
  });
177
242
 
178
243
  return message;
179
- };
180
- agent.getMessages = () => agent.getSnapshot().context.messages;
244
+ }
181
245
 
182
- agent.generateText = (opts) => agentGenerateText(agent, opts);
246
+ public getMessages() {
247
+ return this.getSnapshot().context.messages;
248
+ }
183
249
 
184
- agent.streamText = (opts) => agentStreamText(agent, opts);
185
-
186
- agent.addFeedback = (feedbackInput) => {
250
+ public addFeedback(feedbackInput: AgentFeedbackInput) {
187
251
  const feedback = {
188
252
  ...feedbackInput,
189
253
  attributes: { ...feedbackInput.attributes },
190
254
  reward: feedbackInput.reward ?? 0,
191
255
  timestamp: feedbackInput.timestamp ?? Date.now(),
192
- sessionId: agent.sessionId,
256
+ episodeId: this.episodeId,
193
257
  } satisfies AgentFeedback;
194
- agent.send({
258
+ this.send({
195
259
  type: 'agent.feedback',
196
260
  feedback,
197
261
  });
198
262
  return feedback;
199
- };
200
- agent.getFeedback = () => agent.getSnapshot().context.feedback;
263
+ }
264
+
265
+ /**
266
+ * Retrieves feedback from the agent's short-term (local) memory.
267
+ */
268
+ public getFeedback() {
269
+ return this.getSnapshot().context.feedback;
270
+ }
201
271
 
202
- agent.addObservation = (observationInput) => {
272
+ public addObservation(
273
+ observationInput: AgentObservationInput
274
+ ): AgentObservation<any> {
203
275
  const { prevState, event, state } = observationInput;
204
276
  const observation = {
205
277
  prevState,
206
278
  event,
207
279
  state,
208
280
  id: observationInput.id ?? randomId(),
209
- sessionId: agent.sessionId,
281
+ episodeId: this.episodeId,
210
282
  timestamp: observationInput.timestamp ?? Date.now(),
211
283
  machineHash: observationInput.machine
212
284
  ? getMachineHash(observationInput.machine)
213
285
  : undefined,
214
286
  } satisfies AgentObservation<any>;
215
287
 
216
- agent.send({
288
+ this.send({
217
289
  type: 'agent.observe',
218
290
  observation,
219
291
  });
220
292
 
221
293
  return observation;
222
- };
223
- agent.getObservations = () => agent.getSnapshot().context.observations;
294
+ }
295
+
296
+ /**
297
+ * Retrieves observations from the agent's short-term (local) memory.
298
+ */
299
+ public getObservations() {
300
+ return this.getSnapshot().context.observations;
301
+ }
224
302
 
225
- agent.addPlan = (plan) => {
226
- agent.send({
303
+ public addPlan(plan: AgentPlan<TEvents>) {
304
+ this.send({
227
305
  type: 'agent.plan',
228
306
  plan,
229
307
  });
230
- };
231
- agent.getPlans = () => agent.getSnapshot().context.plans;
308
+ }
309
+ /**
310
+ * Retrieves strategies from the agent's short-term (local) memory.
311
+ */
312
+ public getPlans() {
313
+ return this.getSnapshot().context.plans;
314
+ }
315
+
316
+ /**
317
+ * Interacts with this state machine actor by inspecting state transitions and storing them as observations.
318
+ *
319
+ * Observations contain the `prevState`, `event`, and current `state` of this
320
+ * actor, as well as other properties that are useful when recalled.
321
+ * These observations are stored in the `agent`'s short-term (local) memory
322
+ * and can be retrieved via `agent.getObservations()`.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * // Only observes the actor's state transitions
327
+ * agent.interact(actor);
328
+ *
329
+ * actor.start();
330
+ * ```
331
+ */
332
+ public interact<TActor extends ActorRefLike>(actorRef: TActor): Subscription;
333
+ /**
334
+ * Interacts with this state machine actor by:
335
+ * 1. Inspecting state transitions and storing them as observations
336
+ * 2. Deciding what to do next (which event to send the actor) based on
337
+ * the agent input returned from `getInput(observation)`, if `getInput(…)` is provided as the 2nd argument.
338
+ *
339
+ * Observations contain the `prevState`, `event`, and current `state` of this
340
+ * actor, as well as other properties that are useful when recalled.
341
+ * These observations are stored in the `agent`'s short-term (local) memory
342
+ * and can be retrieved via `agent.getObservations()`.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * // Observes the actor's state transitions and
347
+ * // makes a decision if on the "summarize" state
348
+ * agent.interact(actor, observed => {
349
+ * if (observed.state.matches('summarize')) {
350
+ * return {
351
+ * context: observed.state.context,
352
+ * goal: 'Summarize the message'
353
+ * }
354
+ * }
355
+ * });
356
+ *
357
+ * actor.start();
358
+ * ```
359
+ */
360
+ public interact<TActor extends ActorRefLike>(
361
+ actorRef: TActor,
362
+ getInput: (
363
+ observation: AgentObservation<TActor>
364
+ ) => AgentDecisionInput | undefined
365
+ ): Subscription;
366
+ public interact<TActor extends ActorRefLike>(
367
+ actorRef: TActor,
368
+ getInput?: (
369
+ observation: AgentObservation<TActor>
370
+ ) => AgentDecisionInput | undefined
371
+ ): Subscription {
372
+ const actorRefCheck = isActorRef(actorRef) && actorRef.src;
373
+ const machine = isMachineActor(actorRef) ? actorRef.src : undefined;
232
374
 
233
- agent.interact = ((actorRef, getInput) => {
234
375
  let prevState: ObservedState | undefined = undefined;
235
376
  let subscribed = true;
236
377
 
378
+ const agent = this;
379
+
237
380
  async function handleObservation(observationInput: AgentObservationInput) {
238
381
  const observation = agent.addObservation(observationInput);
239
382
 
240
383
  const input = getInput?.(observation);
241
384
 
242
385
  if (input) {
243
- await agentDecide(agent, {
244
- machine: actorRef.src as AnyStateMachine,
386
+ const res = await agentDecide(agent, {
387
+ machine,
245
388
  state: observation.state,
246
- execute: async (event) => {
247
- actorRef.send(event);
248
- },
249
389
  ...input,
250
390
  });
391
+
392
+ if (res?.nextEvent) {
393
+ actorRef.send(res.nextEvent);
394
+ }
251
395
  }
252
396
 
253
397
  prevState = observationInput.state;
254
398
  }
255
399
 
256
400
  // Inspect system, but only observe specified actor
257
- actorRef.system.inspect({
258
- next: async (inspEvent) => {
259
- if (
260
- !subscribed ||
261
- inspEvent.actorRef !== actorRef ||
262
- inspEvent.type !== '@xstate.snapshot'
263
- ) {
264
- return;
265
- }
401
+ const sub = actorRefCheck
402
+ ? actorRef.system.inspect({
403
+ next: async (inspEvent) => {
404
+ if (
405
+ !subscribed ||
406
+ inspEvent.actorRef !== actorRef ||
407
+ inspEvent.type !== '@xstate.snapshot'
408
+ ) {
409
+ return;
410
+ }
266
411
 
267
- const observationInput = {
268
- event: inspEvent.event,
269
- prevState,
270
- state: inspEvent.snapshot as any,
271
- machine: (actorRef as any).src,
272
- } satisfies AgentObservationInput;
412
+ const observationInput = {
413
+ event: inspEvent.event,
414
+ prevState,
415
+ state: inspEvent.snapshot as any,
416
+ machine: (actorRef as any).src,
417
+ } satisfies AgentObservationInput;
273
418
 
274
- await handleObservation(observationInput);
275
- },
276
- });
419
+ await handleObservation(observationInput);
420
+ },
421
+ })
422
+ : undefined;
277
423
 
278
424
  // If actor already started, interact with current state
279
425
  if ((actorRef as any)._processingStatus === 1) {
@@ -287,14 +433,59 @@ export function createAgent<
287
433
 
288
434
  return {
289
435
  unsubscribe: () => {
436
+ sub?.unsubscribe();
290
437
  subscribed = false;
291
- }, // TODO: make this actually unsubscribe
438
+ },
292
439
  };
293
- }) as typeof agent.interact;
440
+ }
441
+
442
+ public observe<TActor extends ActorRefLike>(actorRef: TActor): Subscription {
443
+ let prevState: ObservedState = actorRef.getSnapshot();
444
+ const actorRefCheck = isActorRef(actorRef);
294
445
 
295
- agent.types = {} as any;
446
+ const sub = actorRefCheck
447
+ ? actorRef.system.inspect({
448
+ next: async (inspEvent) => {
449
+ if (
450
+ inspEvent.actorRef !== actorRef ||
451
+ inspEvent.type !== '@xstate.snapshot'
452
+ ) {
453
+ return;
454
+ }
296
455
 
297
- agent.start();
456
+ const observationInput = {
457
+ event: inspEvent.event,
458
+ prevState,
459
+ state: inspEvent.snapshot as any,
460
+ machine: (actorRef as any).src,
461
+ } satisfies AgentObservationInput;
298
462
 
299
- return agent;
463
+ prevState = observationInput.state;
464
+
465
+ this.addObservation(observationInput);
466
+ },
467
+ })
468
+ : undefined;
469
+
470
+ return sub ?? { unsubscribe: () => {} };
471
+ }
472
+
473
+ public wrap(modelToWrap: LanguageModelV1) {
474
+ return experimental_wrapLanguageModel({
475
+ model: modelToWrap,
476
+ middleware: createAgentMiddleware(this),
477
+ });
478
+ }
479
+
480
+ /**
481
+ * Resolves with an `AgentPlan` based on the information provided in the `options`, including:
482
+ *
483
+ * - The `goal` for the agent to achieve
484
+ * - The observed current `state`
485
+ * - The `machine` (e.g. a state machine) that specifies what can happen next
486
+ * - Additional `context`
487
+ */
488
+ public decide(opts: AgentDecideOptions<this>) {
489
+ return agentDecide(this, opts);
490
+ }
300
491
  }