@statelyai/agent 1.0.0-beta.1 → 1.1.2

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/types.ts CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  streamText,
23
23
  StreamTextResult,
24
24
  } from 'ai';
25
- import { ZodEventMapping } from './schemas';
25
+ import { ZodContextMapping, ZodEventMapping } from './schemas';
26
26
  import { TypeOf } from 'zod';
27
27
 
28
28
  export type GenerateTextOptions = Parameters<typeof generateText>[0];
@@ -62,10 +62,11 @@ export type AgentPlan<TEvent extends EventObject> = {
62
62
  goal: string;
63
63
  state: ObservedState;
64
64
  content?: string;
65
- steps?: Array<{
66
- event: TEvent;
67
- state?: ObservedState;
68
- }>;
65
+ /**
66
+ * Executes the plan based on the given `state` and resolves with
67
+ * a potential next `event` to trigger to achieve the `goal`.
68
+ */
69
+ execute: (state: ObservedState) => Promise<TEvent | undefined>;
69
70
  nextEvent: TEvent | undefined;
70
71
  sessionId: string;
71
72
  timestamp: number;
@@ -103,14 +104,14 @@ export type PromptTemplate<TEvents extends EventObject> = (data: {
103
104
  */
104
105
  observations?: AgentObservation<any>[]; // TODO
105
106
  feedback?: AgentFeedback[];
106
- messages?: AgentMessageHistory[];
107
+ messages?: AgentMessage[];
107
108
  plans?: AgentPlan<TEvents>[];
108
109
  }) => string;
109
110
 
110
- export type AgentPlanner<T extends Agent<any>> = (
111
- agent: T['eventTypes'],
112
- options: AgentPlanInput<T['eventTypes']>
113
- ) => Promise<AgentPlan<T['eventTypes']> | undefined>;
111
+ export type AgentPlanner<T extends AnyAgent> = (
112
+ agent: T,
113
+ input: AgentPlanInput<T['types']['events']>
114
+ ) => Promise<AgentPlan<T['types']['events']> | undefined>;
114
115
 
115
116
  export type AgentDecideOptions = {
116
117
  goal: string;
@@ -127,21 +128,28 @@ export type AgentDecideOptions = {
127
128
  >;
128
129
 
129
130
  export interface AgentFeedback {
130
- goal: string;
131
- observationId: string;
131
+ goal?: string;
132
+ observationId?: string;
133
+ /**
134
+ * The message correlation that the feedback is relevant for
135
+ */
136
+ correlationId?: string;
132
137
  attributes: Record<string, any>;
138
+ reward: number;
133
139
  timestamp: number;
134
140
  sessionId: string;
135
141
  }
136
142
 
137
143
  export interface AgentFeedbackInput {
138
- goal: string;
139
- observationId: string; // Observation ID;
140
- attributes: Record<string, any>;
144
+ goal?: string;
145
+ observationId?: string;
146
+ correlationId?: string;
147
+ attributes?: Record<string, any>;
141
148
  timestamp?: number;
149
+ reward?: number;
142
150
  }
143
151
 
144
- export type AgentMessageHistory = CoreMessage & {
152
+ export type AgentMessage = CoreMessage & {
145
153
  timestamp: number;
146
154
  id: string;
147
155
  /**
@@ -151,9 +159,11 @@ export type AgentMessageHistory = CoreMessage & {
151
159
  responseId?: string;
152
160
  result?: GenerateTextResult<any>;
153
161
  sessionId: string;
162
+ correlationId: string;
163
+ parentCorrelationId?: string;
154
164
  };
155
165
 
156
- export type AgentMessageHistoryInput = CoreMessage & {
166
+ export type AgentMessageInput = CoreMessage & {
157
167
  timestamp?: number;
158
168
  id?: string;
159
169
  /**
@@ -161,6 +171,8 @@ export type AgentMessageHistoryInput = CoreMessage & {
161
171
  * which message this message is responding to, if any.
162
172
  */
163
173
  responseId?: string;
174
+ correlationId?: string;
175
+ parentCorrelationId?: string;
164
176
  result?: GenerateTextResult<any>;
165
177
  };
166
178
 
@@ -169,6 +181,7 @@ export interface AgentObservation<TActor extends AnyActorRef> {
169
181
  prevState: SnapshotFrom<TActor> | undefined;
170
182
  event: EventFrom<TActor>;
171
183
  state: SnapshotFrom<TActor>;
184
+ machineHash: string | undefined;
172
185
  sessionId: string;
173
186
  timestamp: number;
174
187
  }
@@ -178,6 +191,7 @@ export interface AgentObservationInput {
178
191
  prevState: ObservedState | undefined;
179
192
  event: AnyEventObject;
180
193
  state: ObservedState;
194
+ machine?: AnyStateMachine;
181
195
  timestamp?: number;
182
196
  }
183
197
 
@@ -203,7 +217,7 @@ export type AgentEmitted<TEvents extends EventObject> =
203
217
  }
204
218
  | {
205
219
  type: 'message';
206
- message: AgentMessageHistory;
220
+ message: AgentMessage;
207
221
  }
208
222
  | {
209
223
  type: 'plan';
@@ -222,7 +236,7 @@ export type AgentLogic<TEvents extends EventObject> = ActorLogic<
222
236
  }
223
237
  | {
224
238
  type: 'agent.message';
225
- message: AgentMessageHistory;
239
+ message: AgentMessage;
226
240
  }
227
241
  | {
228
242
  type: 'agent.plan';
@@ -240,21 +254,30 @@ export type EventsFromZodEventMapping<TEventSchemas extends ZodEventMapping> =
240
254
  } & TypeOf<TEventSchemas[K]>;
241
255
  }>;
242
256
 
243
- export type Agent<TEvents extends EventObject> = ActorRefFrom<
257
+ export type ContextFromZodContextMapping<
258
+ TContextSchema extends ZodContextMapping
259
+ > = {
260
+ [K in keyof TContextSchema & string]: TypeOf<TContextSchema[K]>;
261
+ };
262
+
263
+ export type Agent<TContext, TEvents extends EventObject> = ActorRefFrom<
244
264
  AgentLogic<TEvents>
245
265
  > & {
246
266
  /**
247
- * The general name of the agent. All agents with the same name are related and
267
+ * The name of the agent. All agents with the same name are related and
248
268
  * able to share experiences (observations, feedback) with each other.
249
269
  */
250
- name: string;
270
+ name?: string;
251
271
  /**
252
- * The unique id of the agent. This is used to partition message history.
272
+ * The unique identifier for the agent.
253
273
  */
254
274
  id?: string;
255
275
  description?: string;
256
276
  events: ZodEventMapping;
257
- eventTypes: TEvents;
277
+ types: {
278
+ events: TEvents;
279
+ context: Compute<TContext>;
280
+ };
258
281
  model: LanguageModel;
259
282
  defaultOptions: GenerateTextOptions;
260
283
  memory: AgentLongTermMemory | undefined;
@@ -271,7 +294,7 @@ export type Agent<TEvents extends EventObject> = ActorRefFrom<
271
294
  *
272
295
  * - The `goal` for the agent to achieve
273
296
  * - The observed current `state`
274
- * - The `logic` (e.g. a state machine) that specifies what can happen next
297
+ * - The `machine` (e.g. a state machine) that specifies what can happen next
275
298
  * - Additional `context`
276
299
  */
277
300
  decide: (
@@ -281,49 +304,124 @@ export type Agent<TEvents extends EventObject> = ActorRefFrom<
281
304
  // Generate text
282
305
  generateText: (
283
306
  options: AgentGenerateTextOptions
284
- ) => Promise<GenerateTextResult<Record<string, any>>>;
307
+ ) => Promise<AgentGenerateTextResult>;
285
308
 
286
309
  // Stream text
287
310
  streamText: (
288
311
  options: AgentStreamTextOptions
289
- ) => Promise<StreamTextResult<Record<string, CoreTool<any, any>>>>;
312
+ ) => Promise<AgentStreamTextResult>;
290
313
 
291
- addObservation: (observation: AgentObservationInput) => AgentObservation<any>; // TODO
292
- addMessage: (history: AgentMessageHistoryInput) => AgentMessageHistory;
293
- addFeedback: (feedbackItem: AgentFeedbackInput) => AgentFeedback;
314
+ addObservation: (
315
+ observationInput: AgentObservationInput
316
+ ) => AgentObservation<any>; // TODO
317
+ addMessage: (messageInput: AgentMessageInput) => AgentMessage;
318
+ addFeedback: (feedbackInput: AgentFeedbackInput) => AgentFeedback;
294
319
  addPlan: (plan: AgentPlan<TEvents>) => void;
295
320
  /**
296
321
  * Called whenever the agent (LLM assistant) receives or sends a message.
297
322
  */
298
- onMessage: (callback: (message: AgentMessageHistory) => void) => void;
323
+ onMessage: (callback: (message: AgentMessage) => void) => void;
299
324
  /**
300
325
  * Selects agent data from its context.
326
+ *
327
+ * @deprecated Select from `agent.getSnapshot().context` directly or:
328
+ * - `agent.getMessages()`
329
+ * - `agent.getObservations()`
330
+ * - `agent.getFeedback()`
331
+ * - `agent.getPlans()`
301
332
  */
302
333
  select: <T>(selector: (context: AgentMemoryContext) => T) => T;
303
334
 
304
335
  /**
305
- * Inspects state machine actor transitions and automatically observes
306
- * (prevState, event, state) tuples.
336
+ * Retrieves messages from the agent's short-term (local) memory.
337
+ */
338
+ getMessages: () => AgentMessage[];
339
+
340
+ /**
341
+ * Retrieves observations from the agent's short-term (local) memory.
307
342
  */
308
- interact: <TActor extends AnyActorRef>(
343
+ getObservations: () => AgentObservation<Agent<TContext, TEvents>>[];
344
+
345
+ /**
346
+ * Retrieves feedback from the agent's short-term (local) memory.
347
+ */
348
+ getFeedback: () => AgentFeedback[];
349
+
350
+ /**
351
+ * Retrieves strategies from the agent's short-term (local) memory.
352
+ */
353
+ getPlans: () => AgentPlan<TEvents>[];
354
+
355
+ /**
356
+ * Interacts with this state machine actor by inspecting state transitions and storing them as observations.
357
+ *
358
+ * Observations contain the `prevState`, `event`, and current `state` of this
359
+ * actor, as well as other properties that are useful when recalled.
360
+ * These observations are stored in the `agent`'s short-term (local) memory
361
+ * and can be retrieved via `agent.getObservations()`.
362
+ *
363
+ * @example
364
+ * ```ts
365
+ * // Only observes the actor's state transitions
366
+ * agent.interact(actor);
367
+ *
368
+ * actor.start();
369
+ * ```
370
+ */
371
+ interact<TActor extends AnyActorRef>(actorRef: TActor): Subscription;
372
+ /**
373
+ * Interacts with this state machine actor by:
374
+ * 1. Inspecting state transitions and storing them as observations
375
+ * 2. Deciding what to do next (which event to send the actor) based on
376
+ * the agent input returned from `getInput(observation)`, if `getInput(…)` is provided as the 2nd argument.
377
+ *
378
+ * Observations contain the `prevState`, `event`, and current `state` of this
379
+ * actor, as well as other properties that are useful when recalled.
380
+ * These observations are stored in the `agent`'s short-term (local) memory
381
+ * and can be retrieved via `agent.getObservations()`.
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * // Observes the actor's state transitions and
386
+ * // makes a decision if on the "summarize" state
387
+ * agent.interact(actor, observed => {
388
+ * if (observed.state.matches('summarize')) {
389
+ * return {
390
+ * context: observed.state.context,
391
+ * goal: 'Summarize the message'
392
+ * }
393
+ * }
394
+ * });
395
+ *
396
+ * actor.start();
397
+ * ```
398
+ */
399
+ interact<TActor extends AnyActorRef>(
309
400
  actorRef: TActor,
310
- getInput?: (
401
+ getInput: (
311
402
  observation: AgentObservation<TActor>
312
403
  ) => AgentDecisionInput | undefined
313
- ) => Subscription;
404
+ ): Subscription;
314
405
  };
315
406
 
316
- export type AnyAgent = Agent<any>;
407
+ export type AnyAgent = Agent<any, any>;
317
408
 
318
- export type FromAgent<T> = T | ((self: AnyAgent) => T | Promise<T>);
409
+ export type FromAgent<T> = T | ((agent: AnyAgent) => T | Promise<T>);
319
410
 
320
- export interface CommonTextOptions {
411
+ export type CommonTextOptions = {
321
412
  prompt: FromAgent<string>;
322
413
  model?: LanguageModel;
323
414
  context?: Record<string, any>;
324
- messages?: FromAgent<CoreMessage[]> | true;
415
+ messages?: FromAgent<CoreMessage[]>;
325
416
  template?: PromptTemplate<any>;
326
- }
417
+ correlationId?: string;
418
+ parentCorrelationId?: string;
419
+ };
420
+
421
+ export type TextResultMeta = {
422
+ correlationId: string;
423
+ parentCorrelationId?: string;
424
+ };
327
425
 
328
426
  export type AgentGenerateTextOptions = Omit<
329
427
  GenerateTextOptions,
@@ -331,12 +429,16 @@ export type AgentGenerateTextOptions = Omit<
331
429
  > &
332
430
  CommonTextOptions;
333
431
 
432
+ export type AgentGenerateTextResult = GenerateTextResult<any> & TextResultMeta;
433
+
334
434
  export type AgentStreamTextOptions = Omit<
335
435
  StreamTextOptions,
336
436
  'model' | 'prompt' | 'messages'
337
437
  > &
338
438
  CommonTextOptions;
339
439
 
440
+ export type AgentStreamTextResult = StreamTextResult<any> & TextResultMeta;
441
+
340
442
  export interface ObservedState {
341
443
  /**
342
444
  * The current state value of the state machine, e.g.
@@ -356,7 +458,7 @@ export type ObservedStateFrom<TActor extends AnyActorRef> = Pick<
356
458
 
357
459
  export type AgentMemoryContext = {
358
460
  observations: AgentObservation<any>[]; // TODO
359
- messages: AgentMessageHistory[];
461
+ messages: AgentMessage[];
360
462
  plans: AgentPlan<any>[];
361
463
  feedback: AgentFeedback[];
362
464
  };
@@ -393,3 +495,5 @@ export interface AIAdapter {
393
495
  generateText: typeof generateText;
394
496
  streamText: typeof streamText;
395
497
  }
498
+
499
+ export type Compute<A extends any> = { [K in keyof A]: A[K] } & unknown;
package/src/utils.ts CHANGED
@@ -1,18 +1,50 @@
1
- import { AnyMachineSnapshot, AnyStateNode } from 'xstate';
1
+ import { AnyMachineSnapshot, AnyStateMachine, AnyStateNode } from 'xstate';
2
+ import hash from 'object-hash';
2
3
  import { TransitionData } from './types';
3
4
 
4
5
  export function getAllTransitions(state: AnyMachineSnapshot): TransitionData[] {
5
6
  const nodes = state._nodes;
6
7
  const transitions = (nodes as AnyStateNode[])
7
8
  .map((node) => [...(node as AnyStateNode).transitions.values()])
8
- .flat(2)
9
- .map((transition) => ({
10
- ...transition,
11
- guard:
12
- typeof transition.guard === 'string'
13
- ? { type: transition.guard }
14
- : (transition.guard as any), // TODO: fix
15
- }));
9
+ .map((nodeTransitions) => {
10
+ return nodeTransitions.map((nodeEventTransitions) => {
11
+ return nodeEventTransitions.map((transition) => {
12
+ return {
13
+ ...transition,
14
+ guard:
15
+ typeof transition.guard === 'string'
16
+ ? { type: transition.guard }
17
+ : (transition.guard as any), // TODO: fix
18
+ };
19
+ });
20
+ });
21
+ })
22
+ .flat(2);
23
+
24
+ return transitions;
25
+ }
26
+
27
+ export function getAllMachineTransitions(
28
+ stateNode: AnyStateNode
29
+ ): TransitionData[] {
30
+ const transitions: TransitionData[] = [...stateNode.transitions.values()]
31
+ .map((nodeTransitions) => {
32
+ return nodeTransitions.map((transition) => {
33
+ return {
34
+ ...transition,
35
+ guard:
36
+ typeof transition.guard === 'string'
37
+ ? { type: transition.guard }
38
+ : (transition.guard as any), // TODO: fix
39
+ };
40
+ });
41
+ })
42
+ .flat(2);
43
+
44
+ for (const s of Object.values(stateNode.states)) {
45
+ const stateTransitions = getAllMachineTransitions(s);
46
+ transitions.push(...stateTransitions);
47
+ }
16
48
 
17
49
  return transitions;
18
50
  }
@@ -26,3 +58,15 @@ export function randomId() {
26
58
  const random = Math.random().toString(36).substring(2, 9);
27
59
  return timestamp + random;
28
60
  }
61
+
62
+ const machineHashes: WeakMap<AnyStateMachine, string> = new WeakMap();
63
+ /**
64
+ * Returns a string hash representing only the transitions in the state machine.
65
+ */
66
+ export function getMachineHash(machine: AnyStateMachine): string {
67
+ if (machineHashes.has(machine)) return machineHashes.get(machine)!;
68
+ const transitions = getAllMachineTransitions(machine.root);
69
+ const machineHash = hash(transitions);
70
+ machineHashes.set(machine, machineHash);
71
+ return machineHash;
72
+ }
@@ -1,5 +0,0 @@
1
- ---
2
- "@statelyai/agent": patch
3
- ---
4
-
5
- Use nanoid
@@ -1,10 +0,0 @@
1
- import { PromptTemplate } from '../types';
2
- import { defaultTextTemplate } from './defaultText';
3
-
4
- export const defaultToolCallTemplate: PromptTemplate<any> = (data) => {
5
- return `
6
- ${defaultTextTemplate(data)}
7
-
8
- Only make a single tool call to achieve the above goal.
9
- `.trim();
10
- };