@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/agent.ts CHANGED
@@ -7,11 +7,11 @@ import {
7
7
  Observer,
8
8
  toObserver,
9
9
  } from 'xstate';
10
- import { ZodEventMapping } from './schemas';
10
+ import { ZodContextMapping, ZodEventMapping } from './schemas';
11
11
  import {
12
12
  Agent,
13
13
  AgentLogic,
14
- AgentMessageHistory,
14
+ AgentMessage,
15
15
  AgentPlanner,
16
16
  EventsFromZodEventMapping,
17
17
  GenerateTextOptions,
@@ -20,12 +20,15 @@ import {
20
20
  ObservedState,
21
21
  AgentObservationInput,
22
22
  AgentMemoryContext,
23
+ AgentObservation,
24
+ ContextFromZodContextMapping,
25
+ AgentFeedback,
23
26
  } from './types';
24
27
  import { simplePlanner } from './planners/simplePlanner';
25
28
  import { agentGenerateText, agentStreamText } from './text';
26
29
  import { agentDecide } from './decision';
27
30
  import { vercelAdapter } from './adapters/vercel';
28
- import { randomId } from './utils';
31
+ import { getMachineHash, randomId } from './utils';
29
32
 
30
33
  export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
31
34
  (state, event, { emit }) => {
@@ -71,33 +74,52 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
71
74
  }
72
75
  return state;
73
76
  },
74
- {
75
- feedback: [],
76
- messages: [],
77
- observations: [],
78
- plans: [],
79
- } as AgentMemoryContext
77
+ () =>
78
+ ({
79
+ feedback: [],
80
+ messages: [],
81
+ observations: [],
82
+ plans: [],
83
+ } as AgentMemoryContext)
80
84
  );
81
85
 
82
86
  export function createAgent<
87
+ const TContextSchema extends ZodContextMapping,
83
88
  const TEventSchemas extends ZodEventMapping,
84
- TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>
89
+ TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
90
+ TContext = ContextFromZodContextMapping<TContextSchema>
85
91
  >({
86
92
  name,
87
93
  description,
88
94
  model,
89
95
  events,
90
- planner = simplePlanner as AgentPlanner<Agent<TEvents>>,
96
+ context,
97
+ planner = simplePlanner as AgentPlanner<Agent<TContext, TEvents>>,
91
98
  stringify = JSON.stringify,
92
99
  getMemory,
93
100
  logic = agentLogic as AgentLogic<TEvents>,
94
101
  adapter = vercelAdapter,
95
102
  ...generateTextOptions
96
103
  }: {
104
+ /**
105
+ * The unique identifier for the agent.
106
+ *
107
+ * This should be the same across all sessions of a specific agent, as it can be
108
+ * used to retrieve memory for this agent.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const agent = createAgent({
113
+ * id: 'recipe-assistant',
114
+ * // ...
115
+ * });
116
+ * ```
117
+ */
118
+ id?: string;
97
119
  /**
98
120
  * The name of the agent
99
121
  */
100
- name: string;
122
+ name?: string;
101
123
  /**
102
124
  * A description of the role of the agent
103
125
  */
@@ -107,21 +129,20 @@ export function createAgent<
107
129
  * that the agent knows about.
108
130
  */
109
131
  events: TEventSchemas;
110
- planner?: AgentPlanner<Agent<TEvents>>;
132
+ context?: TContextSchema;
133
+ planner?: AgentPlanner<Agent<TContext, TEvents>>;
111
134
  stringify?: typeof JSON.stringify;
112
135
  /**
113
136
  * A function that retrieves the agent's long term memory
114
137
  */
115
- getMemory?: (agent: Agent<any>) => AgentLongTermMemory;
138
+ getMemory?: (agent: Agent<TContext, TEvents>) => AgentLongTermMemory;
116
139
  /**
117
140
  * Agent logic
118
141
  */
119
142
  logic?: AgentLogic<TEvents>;
120
143
  adapter?: AIAdapter;
121
- } & GenerateTextOptions): Agent<TEvents> {
122
- const messageHistoryListeners: Observer<AgentMessageHistory>[] = [];
123
-
124
- const agent = createActor(logic) as unknown as Agent<TEvents>;
144
+ } & GenerateTextOptions): Agent<TContext, TEvents> {
145
+ const agent = createActor(logic) as unknown as Agent<TContext, TEvents>;
125
146
  agent.events = events;
126
147
  agent.model = model;
127
148
  agent.name = name;
@@ -134,7 +155,7 @@ export function createAgent<
134
155
  agent.memory = getMemory ? getMemory(agent) : undefined;
135
156
 
136
157
  agent.onMessage = (callback) => {
137
- messageHistoryListeners.push(toObserver(callback));
158
+ agent.on('message', (ev) => callback(ev.message));
138
159
  };
139
160
 
140
161
  agent.decide = (opts) => {
@@ -147,7 +168,8 @@ export function createAgent<
147
168
  id: messageInput.id ?? randomId(),
148
169
  timestamp: messageInput.timestamp ?? Date.now(),
149
170
  sessionId: agent.sessionId,
150
- };
171
+ correlationId: messageInput.correlationId ?? randomId(),
172
+ } satisfies AgentMessage;
151
173
  agent.send({
152
174
  type: 'agent.message',
153
175
  message,
@@ -155,6 +177,7 @@ export function createAgent<
155
177
 
156
178
  return message;
157
179
  };
180
+ agent.getMessages = () => agent.getSnapshot().context.messages;
158
181
 
159
182
  agent.generateText = (opts) => agentGenerateText(agent, opts);
160
183
 
@@ -163,23 +186,32 @@ export function createAgent<
163
186
  agent.addFeedback = (feedbackInput) => {
164
187
  const feedback = {
165
188
  ...feedbackInput,
189
+ attributes: { ...feedbackInput.attributes },
190
+ reward: feedbackInput.reward ?? 0,
166
191
  timestamp: feedbackInput.timestamp ?? Date.now(),
167
192
  sessionId: agent.sessionId,
168
- };
193
+ } satisfies AgentFeedback;
169
194
  agent.send({
170
195
  type: 'agent.feedback',
171
196
  feedback,
172
197
  });
173
198
  return feedback;
174
199
  };
200
+ agent.getFeedback = () => agent.getSnapshot().context.feedback;
175
201
 
176
202
  agent.addObservation = (observationInput) => {
203
+ const { prevState, event, state } = observationInput;
177
204
  const observation = {
178
- ...observationInput,
205
+ prevState,
206
+ event,
207
+ state,
179
208
  id: observationInput.id ?? randomId(),
180
209
  sessionId: agent.sessionId,
181
210
  timestamp: observationInput.timestamp ?? Date.now(),
182
- };
211
+ machineHash: observationInput.machine
212
+ ? getMachineHash(observationInput.machine)
213
+ : undefined,
214
+ } satisfies AgentObservation<any>;
183
215
 
184
216
  agent.send({
185
217
  type: 'agent.observe',
@@ -188,6 +220,7 @@ export function createAgent<
188
220
 
189
221
  return observation;
190
222
  };
223
+ agent.getObservations = () => agent.getSnapshot().context.observations;
191
224
 
192
225
  agent.addPlan = (plan) => {
193
226
  agent.send({
@@ -195,8 +228,9 @@ export function createAgent<
195
228
  plan,
196
229
  });
197
230
  };
231
+ agent.getPlans = () => agent.getSnapshot().context.plans;
198
232
 
199
- agent.interact = (actorRef, getInput) => {
233
+ agent.interact = ((actorRef, getInput) => {
200
234
  let prevState: ObservedState | undefined = undefined;
201
235
  let subscribed = true;
202
236
 
@@ -234,7 +268,8 @@ export function createAgent<
234
268
  event: inspEvent.event,
235
269
  prevState,
236
270
  state: inspEvent.snapshot as any,
237
- };
271
+ machine: (actorRef as any).src,
272
+ } satisfies AgentObservationInput;
238
273
 
239
274
  await handleObservation(observationInput);
240
275
  },
@@ -246,6 +281,7 @@ export function createAgent<
246
281
  prevState: undefined,
247
282
  event: { type: '' }, // TODO: unknown events?
248
283
  state: actorRef.getSnapshot(),
284
+ machine: (actorRef as any).src,
249
285
  });
250
286
  }
251
287
 
@@ -254,7 +290,9 @@ export function createAgent<
254
290
  subscribed = false;
255
291
  }, // TODO: make this actually unsubscribe
256
292
  };
257
- };
293
+ }) as typeof agent.interact;
294
+
295
+ agent.types = {} as any;
258
296
 
259
297
  agent.start();
260
298
 
package/src/decision.ts CHANGED
@@ -1,17 +1,18 @@
1
1
  import { AnyMachineSnapshot, fromPromise } from 'xstate';
2
2
  import {
3
- Agent,
3
+ AnyAgent,
4
4
  AgentDecideOptions,
5
5
  AgentDecisionLogic,
6
6
  AgentDecisionInput,
7
7
  AgentPlanner,
8
+ AgentPlan,
8
9
  } from './types';
9
10
  import { simplePlanner } from './planners/simplePlanner';
10
11
 
11
- export async function agentDecide<T extends Agent<any>>(
12
+ export async function agentDecide<T extends AnyAgent>(
12
13
  agent: T,
13
14
  options: AgentDecideOptions
14
- ) {
15
+ ): Promise<AgentPlan<any> | undefined> {
15
16
  const resolvedOptions = {
16
17
  ...agent.defaultOptions,
17
18
  ...options,
@@ -44,9 +45,9 @@ export async function agentDecide<T extends Agent<any>>(
44
45
  }
45
46
 
46
47
  export function fromDecision(
47
- agent: Agent<any>,
48
+ agent: AnyAgent,
48
49
  defaultInput?: AgentDecisionInput
49
- ) {
50
+ ): AgentDecisionLogic<any> {
50
51
  return fromPromise(async ({ input, self }) => {
51
52
  const parentRef = self._parent;
52
53
  if (!parentRef) {
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { createAgent } from './agent';
2
- export { fromText, fromTextStream, agentGenerateText } from './text';
3
- export { fromDecision, agentDecide } from './decision';
2
+ export { fromText, fromTextStream } from './text';
3
+ export { fromDecision } from './decision';
4
4
  export * from './types';
@@ -1,7 +1,7 @@
1
- import { Agent, AgentPlan, AgentPlanInput } from '../types';
1
+ import { Agent, AgentPlan, AgentPlanInput, AnyAgent } from '../types';
2
2
  import { getShortestPaths } from '@xstate/graph';
3
3
 
4
- export async function simplePlanner<T extends Agent<any>>(
4
+ export async function simplePlanner<T extends AnyAgent>(
5
5
  agent: T,
6
6
  input: AgentPlanInput<any>
7
7
  ): Promise<AgentPlan<any> | undefined> {
@@ -1,15 +1,16 @@
1
- import { CoreTool, tool } from 'ai';
1
+ import { type CoreTool, tool } from 'ai';
2
2
  import {
3
- Agent,
4
3
  AgentPlan,
5
4
  AgentPlanInput,
6
5
  ObservedState,
7
6
  PromptTemplate,
8
7
  TransitionData,
8
+ AnyAgent,
9
9
  } from '../types';
10
10
  import { getAllTransitions } from '../utils';
11
11
  import { AnyStateMachine } from 'xstate';
12
12
  import { defaultTextTemplate } from '../templates/defaultText';
13
+ import { getMessages } from '../text';
13
14
 
14
15
  function getTransitions(
15
16
  state: ObservedState,
@@ -27,11 +28,11 @@ const simplePlannerPromptTemplate: PromptTemplate<any> = (data) => {
27
28
  return `
28
29
  ${defaultTextTemplate(data)}
29
30
 
30
- Only make a single tool call to achieve the above goal.
31
+ Make at most one tool call to achieve the above goal. If the goal cannot be achieved with any tool calls, do not make any tool call.
31
32
  `.trim();
32
33
  };
33
34
 
34
- export async function simplePlanner<T extends Agent<any>>(
35
+ export async function simplePlanner<T extends AnyAgent>(
35
36
  agent: T,
36
37
  input: AgentPlanInput<any>
37
38
  ): Promise<AgentPlan<any> | undefined> {
@@ -81,7 +82,7 @@ export async function simplePlanner<T extends Agent<any>>(
81
82
  toolMap[toolTransitionData.name] = tool({
82
83
  description: toolZodType?.description ?? toolTransitionData.description,
83
84
  parameters: toolZodType,
84
- execute: async (params) => {
85
+ execute: async (params: Record<string, any>) => {
85
86
  const event = {
86
87
  type: toolTransitionData.eventType,
87
88
  ...params,
@@ -92,18 +93,26 @@ export async function simplePlanner<T extends Agent<any>>(
92
93
  });
93
94
  }
94
95
 
96
+ if (!Object.keys(toolMap).length) {
97
+ // No valid transitions for the specified tools
98
+ return undefined;
99
+ }
100
+
95
101
  // Create a prompt with the given context and goal.
96
- // The template is used to ensure that a single tool call is made.
102
+ // The template is used to ensure that a single tool call at most is made.
97
103
  const prompt = simplePlannerPromptTemplate({
98
104
  context: input.state.context,
99
105
  goal: input.goal,
100
106
  });
101
107
 
108
+ const messages = await getMessages(agent, prompt, input);
109
+
102
110
  const result = await agent.generateText({
111
+ toolChoice: 'required',
103
112
  ...input,
104
113
  prompt,
114
+ messages,
105
115
  tools: toolMap,
106
- toolChoice: 'required',
107
116
  });
108
117
 
109
118
  const singleResult = result.toolResults[0];
@@ -117,11 +126,12 @@ export async function simplePlanner<T extends Agent<any>>(
117
126
  return {
118
127
  goal: input.goal,
119
128
  state: input.state,
120
- steps: [
121
- {
122
- event: singleResult.result,
123
- },
124
- ],
129
+ execute: async (state) => {
130
+ if (JSON.stringify(state) === JSON.stringify(input.state)) {
131
+ return singleResult.result;
132
+ }
133
+ return undefined;
134
+ },
125
135
  nextEvent: singleResult.result,
126
136
  sessionId: agent.sessionId,
127
137
  timestamp: Date.now(),
package/src/schemas.ts CHANGED
@@ -1,15 +1,11 @@
1
- import type { SomeZodObject } from 'zod';
2
- import { AnyEventObject } from 'xstate';
3
- import { ObservedState } from './types';
1
+ import { ZodType, type SomeZodObject } from 'zod';
4
2
 
5
3
  export type ZodEventMapping = {
6
4
  // map event types to Zod types
7
5
  [eventType: string]: SomeZodObject;
8
6
  };
9
7
 
10
- export type ZodActionMapping = {
11
- [eventType: string]: {
12
- schema: SomeZodObject;
13
- action: (state: ObservedState, event: AnyEventObject) => Promise<void>;
14
- };
8
+ export type ZodContextMapping = {
9
+ // map context keys to Zod types
10
+ [contextKey: string]: ZodType;
15
11
  };
@@ -1,7 +1,7 @@
1
1
  import { GenerateTextResult, LanguageModel } from 'ai';
2
2
  import wiki, { wikiSearchResult, wikiSummary } from 'wikipedia';
3
3
  import { assign, fromPromise, setup } from 'xstate';
4
- import { Agent } from '../types';
4
+ import { AnyAgent } from '../types';
5
5
 
6
6
  const searchWiki = fromPromise(
7
7
  async ({
@@ -44,7 +44,7 @@ export const chainOfNote = setup({
44
44
  types: {
45
45
  input: {} as {
46
46
  model: LanguageModel;
47
- agent: Agent<any>;
47
+ agent: AnyAgent;
48
48
  prompt: string;
49
49
  },
50
50
  context: {} as {
@@ -56,7 +56,7 @@ export const chainOfNote = setup({
56
56
  }[]
57
57
  | null;
58
58
  model: LanguageModel;
59
- agent: Agent<any>;
59
+ agent: AnyAgent;
60
60
  prompt: string;
61
61
  },
62
62
  output: {} as GenerateTextResult<any>,
package/src/text.ts CHANGED
@@ -1,17 +1,13 @@
1
- import type {
2
- CoreMessage,
3
- CoreTool,
4
- GenerateTextResult,
5
- StreamTextResult,
6
- } from 'ai';
1
+ import type { CoreMessage, CoreTool, GenerateTextResult } from 'ai';
7
2
  import {
8
- Agent,
9
3
  AgentGenerateTextOptions,
4
+ AgentGenerateTextResult,
10
5
  AgentStreamTextOptions,
6
+ AgentStreamTextResult,
7
+ AnyAgent,
11
8
  } from './types';
12
9
  import { defaultTextTemplate } from './templates/defaultText';
13
10
  import {
14
- AnyMachineSnapshot,
15
11
  ObservableActorLogic,
16
12
  Observer,
17
13
  PromiseActorLogic,
@@ -29,15 +25,13 @@ import { randomId } from './utils';
29
25
  * @param options
30
26
  * @returns
31
27
  */
32
- async function getMessages(
33
- agent: Agent<any>,
28
+ export async function getMessages(
29
+ agent: AnyAgent,
34
30
  prompt: string,
35
- options: AgentStreamTextOptions
31
+ options: Omit<AgentGenerateTextOptions, 'prompt'>
36
32
  ): Promise<CoreMessage[]> {
37
33
  let messages: CoreMessage[] = [];
38
- if (options.messages === true) {
39
- messages = agent.select((s) => s.messages);
40
- } else if (typeof options.messages === 'function') {
34
+ if (typeof options.messages === 'function') {
41
35
  messages = await options.messages(agent);
42
36
  } else if (options.messages) {
43
37
  messages = options.messages;
@@ -51,14 +45,16 @@ async function getMessages(
51
45
  return messages;
52
46
  }
53
47
 
54
- export async function agentGenerateText<T extends Agent<any>>(
48
+ export async function agentGenerateText<T extends AnyAgent>(
55
49
  agent: T,
56
50
  options: AgentGenerateTextOptions
57
- ) {
51
+ ): Promise<AgentGenerateTextResult> {
58
52
  const resolvedOptions = {
59
53
  ...agent.defaultOptions,
60
54
  ...options,
55
+ correlationId: options.correlationId ?? randomId(),
61
56
  };
57
+ // Generate a correlation ID if one is not provided
62
58
  const template = resolvedOptions.template ?? defaultTextTemplate;
63
59
  // TODO: check if messages was provided instead
64
60
  const id = randomId();
@@ -79,6 +75,8 @@ export async function agentGenerateText<T extends Agent<any>>(
79
75
  role: 'user',
80
76
  content: promptWithContext,
81
77
  timestamp: Date.now(),
78
+ correlationId: resolvedOptions.correlationId,
79
+ parentCorrelationId: resolvedOptions.parentCorrelationId,
82
80
  });
83
81
 
84
82
  const result = await agent.adapter.generateText({
@@ -94,18 +92,25 @@ export async function agentGenerateText<T extends Agent<any>>(
94
92
  timestamp: Date.now(),
95
93
  responseId: id,
96
94
  result,
95
+ correlationId: resolvedOptions.correlationId,
96
+ parentCorrelationId: resolvedOptions.parentCorrelationId,
97
97
  });
98
98
 
99
- return result;
99
+ return {
100
+ ...result,
101
+ parentCorrelationId: resolvedOptions.parentCorrelationId,
102
+ correlationId: resolvedOptions.correlationId,
103
+ };
100
104
  }
101
105
 
102
106
  export async function agentStreamText(
103
- agent: Agent<any>,
107
+ agent: AnyAgent,
104
108
  options: AgentStreamTextOptions
105
- ): Promise<StreamTextResult<any>> {
109
+ ): Promise<AgentStreamTextResult> {
106
110
  const resolvedOptions = {
107
111
  ...agent.defaultOptions,
108
112
  ...options,
113
+ correlationId: options.correlationId ?? randomId(),
109
114
  };
110
115
  const template = resolvedOptions.template ?? defaultTextTemplate;
111
116
 
@@ -127,6 +132,8 @@ export async function agentStreamText(
127
132
  content: promptWithContext,
128
133
  id,
129
134
  timestamp: Date.now(),
135
+ correlationId: resolvedOptions.correlationId,
136
+ parentCorrelationId: resolvedOptions.parentCorrelationId,
130
137
  });
131
138
 
132
139
  const result = await agent.adapter.streamText({
@@ -146,33 +153,35 @@ export async function agentStreamText(
146
153
  usage: res.usage,
147
154
  warnings: res.warnings,
148
155
  rawResponse: res.rawResponse,
156
+ roundtrips: [], // TODO: how do we get this information?
149
157
  },
150
158
  content: res.text,
151
159
  id: randomId(),
152
160
  timestamp: Date.now(),
153
161
  responseId: id,
162
+ correlationId: resolvedOptions.correlationId,
163
+ parentCorrelationId: resolvedOptions.parentCorrelationId,
154
164
  });
155
165
  },
156
166
  });
157
167
 
158
- return result;
168
+ return {
169
+ ...result,
170
+ parentCorrelationId: resolvedOptions.parentCorrelationId,
171
+ correlationId: resolvedOptions.correlationId,
172
+ } as unknown as AgentStreamTextResult; // TODO: fix
159
173
  }
160
174
 
161
- export function fromTextStream<T extends Agent<any>>(
175
+ export function fromTextStream<T extends AnyAgent>(
162
176
  agent: T,
163
177
  defaultOptions?: AgentStreamTextOptions
164
178
  ): ObservableActorLogic<
165
179
  { textDelta: string },
166
180
  Omit<AgentStreamTextOptions, 'context'> & {
167
- context?: AgentStreamTextOptions['context'] | boolean;
181
+ context?: AgentStreamTextOptions['context'];
168
182
  }
169
183
  > {
170
- return fromObservable(({ input, self }) => {
171
- const context =
172
- input.context === true
173
- ? (self._parent?.getSnapshot() as AnyMachineSnapshot).context
174
- : input.context;
175
-
184
+ return fromObservable(({ input }) => {
176
185
  const observers = new Set<Observer<{ textDelta: string }>>();
177
186
 
178
187
  // TODO: check if messages was provided instead
@@ -181,7 +190,7 @@ export function fromTextStream<T extends Agent<any>>(
181
190
  const result = await agentStreamText(agent, {
182
191
  ...defaultOptions,
183
192
  ...input,
184
- context,
193
+ context: input.context,
185
194
  });
186
195
 
187
196
  for await (const part of result.fullStream) {
@@ -208,24 +217,20 @@ export function fromTextStream<T extends Agent<any>>(
208
217
  });
209
218
  }
210
219
 
211
- export function fromText<T extends Agent<any>>(
220
+ export function fromText<T extends AnyAgent>(
212
221
  agent: T,
213
222
  defaultOptions?: AgentGenerateTextOptions
214
223
  ): PromiseActorLogic<
215
224
  GenerateTextResult<Record<string, CoreTool<any, any>>>,
216
225
  Omit<AgentGenerateTextOptions, 'context'> & {
217
- context?: AgentGenerateTextOptions['context'] | boolean;
226
+ context?: AgentGenerateTextOptions['context'];
218
227
  }
219
228
  > {
220
- return fromPromise(async ({ input, self }) => {
221
- const context =
222
- input.context === true
223
- ? (self._parent?.getSnapshot() as AnyMachineSnapshot).context
224
- : input.context;
229
+ return fromPromise(async ({ input }) => {
225
230
  return await agentGenerateText(agent, {
226
231
  ...input,
227
232
  ...defaultOptions,
228
- context,
233
+ context: input.context,
229
234
  });
230
235
  });
231
236
  }