@statelyai/agent 2.0.0-next.2 → 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.
package/src/agent.test.ts CHANGED
@@ -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,10 +282,7 @@ 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
 
@@ -445,6 +427,7 @@ test('agent.getDecisions() returns decisions from context', () => {
445
427
  model: {} as any,
446
428
  strategy: async (agent) => {
447
429
  return {
430
+ id: Date.now().toString(),
448
431
  episodeId: agent.episodeId,
449
432
  strategy: 'test-strategy',
450
433
  goal: '',
package/src/agent.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  Actor,
3
3
  ActorRefLike,
4
- AnyEventObject,
5
4
  EventObject,
6
5
  fromTransition,
7
6
  Subscription,
@@ -23,11 +22,8 @@ import {
23
22
  AgentMessageInput,
24
23
  AgentFeedbackInput,
25
24
  AgentDecision,
26
- Compute,
27
- AgentDecisionInput,
28
25
  AgentDecideOptions,
29
26
  AnyAgent,
30
- EventsFromAgent,
31
27
  AgentInteractInput,
32
28
  } from './types';
33
29
  import { simpleStrategy } from './strategies/simple';
@@ -40,7 +36,7 @@ import {
40
36
  } from 'ai';
41
37
  import { createAgentMiddleware } from './middleware';
42
38
 
43
- export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
39
+ export const agentLogic: AgentLogic<any> = fromTransition(
44
40
  (state, event, { emit }) => {
45
41
  switch (event.type) {
46
42
  case 'agent.feedback': {
@@ -92,14 +88,15 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
92
88
  messages: [],
93
89
  observations: [],
94
90
  decisions: [],
95
- } as AgentMemoryContext)
91
+ } as AgentMemoryContext<any>)
96
92
  );
97
93
 
98
94
  export function createAgent<
99
95
  const TContextSchema extends ZodContextMapping,
100
96
  const TEventSchemas extends ZodEventMapping,
101
97
  TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
102
- TContext = ContextFromZodContextMapping<TContextSchema>
98
+ TContext = ContextFromZodContextMapping<TContextSchema>,
99
+ TAgent extends AnyAgent = Agent<TContextSchema, TEventSchemas>
103
100
  >({
104
101
  id,
105
102
  description: description,
@@ -108,7 +105,7 @@ export function createAgent<
108
105
  context,
109
106
  episodeId,
110
107
  strategy = simpleStrategy,
111
- logic = agentLogic as AgentLogic<TEvents>,
108
+ logic = agentLogic as AgentLogic<any>,
112
109
  }: {
113
110
  /**
114
111
  * The unique identifier for the agent.
@@ -142,11 +139,11 @@ export function createAgent<
142
139
  */
143
140
  getMemory?: (
144
141
  agent: Agent<TContextSchema, TEventSchemas>
145
- ) => AgentLongTermMemory;
142
+ ) => AgentLongTermMemory<TAgent>;
146
143
  /**
147
144
  * Agent logic
148
145
  */
149
- logic?: AgentLogic<TEvents>;
146
+ logic?: AgentLogic<TAgent>;
150
147
  model: LanguageModel;
151
148
  episodeId?: string;
152
149
  }): Agent<TContextSchema, TEventSchemas> {
@@ -167,7 +164,7 @@ export class Agent<
167
164
  const TEventSchemas extends ZodEventMapping,
168
165
  TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
169
166
  TContext = ContextFromZodContextMapping<TContextSchema>
170
- > extends Actor<AgentLogic<TEvents>> {
167
+ > extends Actor<AgentLogic<any>> {
171
168
  /**
172
169
  * The name of the agent. All agents with the same name are related and
173
170
  * able to share experiences (observations, feedback) with each other.
@@ -186,11 +183,11 @@ export class Agent<
186
183
  // context: Compute<TContext>;
187
184
  // };
188
185
  public model: LanguageModel;
189
- public memory: AgentLongTermMemory | undefined;
186
+ public memory: AgentLongTermMemory<this> | undefined;
190
187
  public defaultOptions: AgentDecideOptions<AnyAgent> | undefined; // todo
191
188
 
192
189
  constructor({
193
- logic = agentLogic as AgentLogic<TEvents>,
190
+ logic = agentLogic as AgentLogic<any>,
194
191
  id,
195
192
  name,
196
193
  description,
@@ -200,7 +197,7 @@ export class Agent<
200
197
  episodeId,
201
198
  strategy = simpleStrategy,
202
199
  }: {
203
- logic: AgentLogic<TEvents>;
200
+ logic: AgentLogic<any>;
204
201
  id?: string;
205
202
  name?: string;
206
203
  description?: string;
@@ -262,6 +259,7 @@ export class Agent<
262
259
  public addFeedback(feedbackInput: AgentFeedbackInput) {
263
260
  const feedback = {
264
261
  ...feedbackInput,
262
+ comment: feedbackInput.comment ?? undefined,
265
263
  attributes: { ...feedbackInput.attributes },
266
264
  timestamp: feedbackInput.timestamp ?? Date.now(),
267
265
  episodeId: this.episodeId,
@@ -281,7 +279,7 @@ export class Agent<
281
279
  }
282
280
 
283
281
  public addObservation(
284
- observationInput: AgentObservationInput
282
+ observationInput: AgentObservationInput<this>
285
283
  ): AgentObservation<any> {
286
284
  const { prevState, event, state } = observationInput;
287
285
  const observation = {
@@ -311,7 +309,7 @@ export class Agent<
311
309
  return this.getSnapshot().context.observations;
312
310
  }
313
311
 
314
- public addDecision(decision: AgentDecision<TEvents>) {
312
+ public addDecision(decision: AgentDecision<this>) {
315
313
  this.send({
316
314
  type: 'agent.decision',
317
315
  decision,
@@ -383,25 +381,29 @@ export class Agent<
383
381
  const actorRefCheck = isActorRef(actorRef) && actorRef.src;
384
382
  const machine = isMachineActor(actorRef) ? actorRef.src : undefined;
385
383
 
386
- let prevState: ObservedState | undefined = undefined;
384
+ let prevState: ObservedState<this> | undefined = undefined;
387
385
  let subscribed = true;
388
386
 
389
387
  const agent = this;
390
388
 
391
- async function handleObservation(observationInput: AgentObservationInput) {
389
+ async function handleObservation(
390
+ observationInput: AgentObservationInput<any>
391
+ ) {
392
392
  const observation = agent.addObservation(observationInput);
393
393
 
394
- const input = getInput?.(observation);
394
+ const interactInput = getInput?.(observation);
395
395
 
396
- if (input) {
397
- const res = await agentDecide(agent, {
396
+ if (interactInput) {
397
+ const decision = await agentDecide(agent, {
398
398
  machine,
399
399
  state: observation.state,
400
- ...input,
400
+ ...interactInput,
401
401
  });
402
402
 
403
- if (res?.nextEvent) {
404
- actorRef.send(res.nextEvent);
403
+ if (decision?.nextEvent) {
404
+ // @ts-ignore
405
+ decision.nextEvent['_decision'] = decision.id;
406
+ actorRef.send(decision.nextEvent);
405
407
  }
406
408
  }
407
409
 
@@ -420,12 +422,21 @@ export class Agent<
420
422
  return;
421
423
  }
422
424
 
425
+ const decisionId = inspEvent.event['_decision'] as
426
+ | string
427
+ | undefined;
428
+
429
+ const decision = decisionId
430
+ ? agent.getDecisions().find((d) => d.id === decisionId)
431
+ : undefined;
432
+
423
433
  const observationInput = {
424
434
  event: inspEvent.event,
425
435
  prevState,
426
436
  state: inspEvent.snapshot as any,
427
437
  machine: (actorRef as any).src,
428
- } satisfies AgentObservationInput;
438
+ goal: decision?.goal,
439
+ } satisfies AgentObservationInput<any>;
429
440
 
430
441
  await handleObservation(observationInput);
431
442
  },
@@ -439,6 +450,7 @@ export class Agent<
439
450
  event: undefined,
440
451
  state: actorRef.getSnapshot(),
441
452
  machine: (actorRef as any).src,
453
+ goal: undefined,
442
454
  });
443
455
  }
444
456
 
@@ -451,7 +463,7 @@ export class Agent<
451
463
  }
452
464
 
453
465
  public observe<TActor extends ActorRefLike>(actorRef: TActor): Subscription {
454
- let prevState: ObservedState = actorRef.getSnapshot();
466
+ let prevState: ObservedState<this> = actorRef.getSnapshot();
455
467
  const actorRefCheck = isActorRef(actorRef);
456
468
 
457
469
  const sub = actorRefCheck
@@ -464,12 +476,20 @@ export class Agent<
464
476
  return;
465
477
  }
466
478
 
479
+ const decisionId = inspEvent.event['_decision'] as
480
+ | string
481
+ | undefined;
482
+ const decision = decisionId
483
+ ? this.getDecisions().find((d) => d.id === decisionId)
484
+ : undefined;
485
+
467
486
  const observationInput = {
468
487
  event: inspEvent.event,
469
488
  prevState,
470
489
  state: inspEvent.snapshot as any,
471
490
  machine: (actorRef as any).src,
472
- } satisfies AgentObservationInput;
491
+ goal: decision?.goal,
492
+ } satisfies AgentObservationInput<this>;
473
493
 
474
494
  prevState = observationInput.state;
475
495
 
@@ -498,7 +518,7 @@ export class Agent<
498
518
  */
499
519
  public async decide(
500
520
  opts: AgentDecideOptions<this>
501
- ): Promise<AgentDecision<EventsFromAgent<this>> | undefined> {
521
+ ): Promise<AgentDecision<this> | undefined> {
502
522
  return agentDecide(this, opts);
503
523
  }
504
524
  }
package/src/decide.ts CHANGED
@@ -6,15 +6,15 @@ import {
6
6
  AgentDecision,
7
7
  AgentDecideInput,
8
8
  TransitionData,
9
- EventsFromAgent,
9
+ EventFromAgent,
10
10
  } from './types';
11
11
  import { getTransitions } from './utils';
12
12
  import { CoreMessage, CoreTool, tool } from 'ai';
13
13
 
14
- export async function agentDecide<T extends AnyAgent>(
15
- agent: T,
16
- options: AgentDecideOptions<T>
17
- ): Promise<AgentDecision<EventsFromAgent<T>> | undefined> {
14
+ export async function agentDecide<TAgent extends AnyAgent>(
15
+ agent: TAgent,
16
+ options: AgentDecideOptions<TAgent>
17
+ ): Promise<AgentDecision<TAgent> | undefined> {
18
18
  const resolvedOptions = {
19
19
  ...agent.defaultOptions,
20
20
  ...options,
@@ -45,12 +45,17 @@ export async function agentDecide<T extends AnyAgent>(
45
45
 
46
46
  let decision: AgentDecision<any> | undefined;
47
47
 
48
+ const minimalState = {
49
+ value: state.value,
50
+ context: state.context,
51
+ };
52
+
48
53
  while (attempts++ < maxAttempts) {
49
54
  decision = await strategy(agent, {
50
55
  model,
51
56
  goal,
52
57
  events: filteredEventSchemas,
53
- state,
58
+ state: minimalState,
54
59
  machine,
55
60
  messages: messages as CoreMessage[], // TODO: fix UIMessage thing
56
61
  ...otherDecideInput,
@@ -68,7 +73,7 @@ export async function agentDecide<T extends AnyAgent>(
68
73
 
69
74
  export function fromDecision<T extends AnyAgent>(
70
75
  agent: T,
71
- defaultInput?: AgentDecideInput<EventsFromAgent<T>>
76
+ defaultInput?: AgentDecideInput<EventFromAgent<T>>
72
77
  ): AgentDecisionLogic<any> {
73
78
  return fromPromise(async ({ input, self }) => {
74
79
  const parentRef = self._parent;
@@ -82,15 +87,10 @@ export function fromDecision<T extends AnyAgent>(
82
87
  ...defaultInput,
83
88
  ...inputObject,
84
89
  };
85
- const state = {
86
- value: snapshot.value,
87
- context: resolvedInput.context,
88
- };
89
90
 
90
91
  const decision = await agentDecide(agent, {
91
92
  machine: (parentRef as AnyActor).logic,
92
93
  state: snapshot,
93
- context: resolvedInput.context,
94
94
  execute: async (event) => {
95
95
  parentRef.send(event);
96
96
  },
@@ -10,11 +10,12 @@ import { simpleStrategy } from './simple';
10
10
  import { convertToXml } from '../utils';
11
11
 
12
12
  const chainOfThoughtPromptTemplate: PromptTemplate<any> = ({
13
+ stateValue,
13
14
  context,
14
15
  goal,
15
16
  }) => {
16
17
  return `
17
- ${convertToXml({ context, goal })}
18
+ ${convertToXml({ stateValue, context, goal })}
18
19
 
19
20
  How would you achieve the goal? Think step-by-step.
20
21
  `.trim();
@@ -25,7 +26,8 @@ export async function chainOfThoughtStrategy<T extends AnyAgent>(
25
26
  input: AgentDecideInput<any>
26
27
  ): Promise<AgentDecision<any> | undefined> {
27
28
  const prompt = chainOfThoughtPromptTemplate({
28
- context: input.state.context,
29
+ stateValue: input.state.value,
30
+ context: input.context ?? input.state.context,
29
31
  goal: input.goal,
30
32
  });
31
33
 
@@ -13,10 +13,14 @@ import { z } from 'zod';
13
13
  import { zodToJsonSchema } from 'zod-to-json-schema';
14
14
  import Ajv from 'ajv';
15
15
  import { AnyMachineSnapshot } from 'xstate';
16
+ import { randomId } from '../utils';
16
17
 
17
18
  const ajv = new Ajv();
18
19
 
19
- function observedStatesEqual(state1: ObservedState, state2: ObservedState) {
20
+ function observedStatesEqual(
21
+ state1: ObservedState<any>,
22
+ state2: ObservedState<any>
23
+ ) {
20
24
  // check state value && state context
21
25
  return (
22
26
  JSON.stringify(state1.value) === JSON.stringify(state2.value) &&
@@ -24,7 +28,7 @@ function observedStatesEqual(state1: ObservedState, state2: ObservedState) {
24
28
  );
25
29
  }
26
30
 
27
- function trimSteps(steps: AgentStep<any>[], currentState: ObservedState) {
31
+ function trimSteps(steps: AgentStep<any>[], currentState: ObservedState<any>) {
28
32
  const index = steps.findIndex(
29
33
  (step) => step.state && observedStatesEqual(step.state, currentState)
30
34
  );
@@ -162,6 +166,7 @@ Examples:
162
166
  const nextStep = leastWeightPath?.steps[0];
163
167
 
164
168
  return {
169
+ id: randomId(),
165
170
  strategy: 'shortestPath',
166
171
  episodeId: agent.episodeId,
167
172
  goal: input.goal,
@@ -32,7 +32,8 @@ export async function simpleStrategy<T extends AnyAgent>(
32
32
  // Create a prompt with the given context and goal.
33
33
  // The template is used to ensure that a single tool call at most is made.
34
34
  const prompt = simpleStrategyPromptTemplate({
35
- context: input.context,
35
+ stateValue: input.state.value,
36
+ context: input.context ?? input.state.context,
36
37
  goal: input.goal,
37
38
  });
38
39
 
@@ -40,16 +41,7 @@ export async function simpleStrategy<T extends AnyAgent>(
40
41
 
41
42
  const model = input.model ? agent.wrap(input.model) : agent.model;
42
43
 
43
- const {
44
- state,
45
- context,
46
- machine,
47
- prevDecision,
48
- events,
49
- goal,
50
- model: _,
51
- ...rest
52
- } = input;
44
+ const { state, machine, events, goal, model: _, ...rest } = input;
53
45
 
54
46
  const machineState =
55
47
  input.machine && input.state
@@ -87,6 +79,7 @@ export async function simpleStrategy<T extends AnyAgent>(
87
79
  }
88
80
 
89
81
  return {
82
+ id: randomId(),
90
83
  strategy: 'simple',
91
84
  goal: input.goal,
92
85
  goalState: input.state,
@@ -109,7 +102,3 @@ export async function simpleStrategy<T extends AnyAgent>(
109
102
  ],
110
103
  };
111
104
  }
112
-
113
- export function createSimpleStrategy<T extends AnyAgent>() {
114
- return simpleStrategy;
115
- }
@@ -3,6 +3,9 @@ import { wrapInXml } from '../utils';
3
3
 
4
4
  export const defaultTextTemplate: PromptTemplate<any> = (data) => {
5
5
  const preamble = [
6
+ data.stateValue
7
+ ? wrapInXml('stateValue', JSON.stringify(data.stateValue))
8
+ : undefined,
6
9
  data.context
7
10
  ? wrapInXml('context', JSON.stringify(data.context))
8
11
  : undefined,
package/src/text.ts CHANGED
@@ -28,10 +28,10 @@ import {
28
28
  * @param options
29
29
  * @returns
30
30
  */
31
- export async function getMessages(
32
- agent: AnyAgent,
31
+ export async function getMessages<TAgent extends AnyAgent>(
32
+ agent: TAgent,
33
33
  prompt: string,
34
- options: Omit<AgentGenerateTextOptions, 'prompt'>
34
+ options: Omit<AgentGenerateTextOptions<TAgent>, 'prompt'>
35
35
  ): Promise<CoreMessage[]> {
36
36
  let messages: CoreMessage[] = [];
37
37
  if (typeof options.messages === 'function') {
@@ -48,13 +48,13 @@ export async function getMessages(
48
48
  return messages;
49
49
  }
50
50
 
51
- export function fromTextStream<T extends AnyAgent>(
52
- agent: T,
53
- options?: AgentStreamTextOptions
51
+ export function fromTextStream<TAgent extends AnyAgent>(
52
+ agent: TAgent,
53
+ options?: AgentStreamTextOptions<TAgent>
54
54
  ): ObservableActorLogic<
55
55
  { textDelta: string },
56
- Omit<AgentStreamTextOptions, 'context'> & {
57
- context?: AgentStreamTextOptions['context'];
56
+ Omit<AgentStreamTextOptions<TAgent>, 'context'> & {
57
+ context?: Record<string, any>;
58
58
  }
59
59
  > {
60
60
  const template = options?.template ?? defaultTextTemplate;
@@ -106,13 +106,13 @@ export function fromTextStream<T extends AnyAgent>(
106
106
  });
107
107
  }
108
108
 
109
- export function fromText<T extends AnyAgent>(
110
- agent: T,
111
- options?: AgentGenerateTextOptions
109
+ export function fromText<TAgent extends AnyAgent>(
110
+ agent: TAgent,
111
+ options?: AgentGenerateTextOptions<TAgent>
112
112
  ): PromiseActorLogic<
113
113
  GenerateTextResult<Record<string, CoreTool<any, any>>>,
114
- Omit<AgentGenerateTextOptions, 'context'> & {
115
- context?: AgentGenerateTextOptions['context'];
114
+ Omit<AgentGenerateTextOptions<TAgent>, 'context'> & {
115
+ context?: Record<string, any>;
116
116
  }
117
117
  > {
118
118
  const resolvedOptions = {