@statelyai/agent 2.0.0-next.0 → 2.0.0-next.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.
Files changed (51) hide show
  1. package/.changeset/cyan-carpets-perform.md +5 -0
  2. package/.changeset/fast-donkeys-argue.md +5 -0
  3. package/.changeset/grumpy-dolphins-think.md +17 -0
  4. package/.changeset/old-jobs-check.md +5 -0
  5. package/.changeset/old-teachers-tap.md +5 -0
  6. package/.changeset/pre.json +7 -1
  7. package/.changeset/smart-yaks-pull.md +23 -0
  8. package/CHANGELOG.md +52 -0
  9. package/dist/index.d.mts +69 -66
  10. package/dist/index.d.ts +69 -66
  11. package/dist/index.js +111 -79
  12. package/dist/index.mjs +114 -82
  13. package/examples/chatbot-alt.ts +1 -1
  14. package/examples/chatbot.ts +3 -3
  15. package/examples/cot.ts +7 -25
  16. package/examples/customer-service-sim.ts +7 -7
  17. package/examples/email.ts +37 -35
  18. package/examples/example.ts +3 -3
  19. package/examples/goal.ts +3 -3
  20. package/examples/joke.ts +3 -3
  21. package/examples/jugs.ts +5 -8
  22. package/examples/learn-from-feedback.ts +100 -0
  23. package/examples/multi.ts +1 -1
  24. package/examples/number.ts +3 -3
  25. package/examples/raffle.ts +3 -3
  26. package/examples/river-crossing.ts +5 -8
  27. package/examples/simple.ts +14 -11
  28. package/examples/summary.ts +3 -6
  29. package/examples/support.ts +43 -39
  30. package/examples/ticTacToe.ts +48 -6
  31. package/examples/todo.ts +3 -3
  32. package/examples/tutor.ts +4 -4
  33. package/examples/verify.ts +3 -3
  34. package/examples/weather-agent.ts +141 -0
  35. package/examples/weather.ts +24 -24
  36. package/examples/wiki.ts +1 -1
  37. package/examples/word.ts +9 -7
  38. package/package.json +6 -3
  39. package/src/agent.test.ts +161 -19
  40. package/src/agent.ts +66 -250
  41. package/src/decide.test.ts +172 -3
  42. package/src/decide.ts +50 -30
  43. package/src/middleware.ts +2 -14
  44. package/src/strategies/chainOfThought.ts +48 -0
  45. package/src/strategies/shortestPath.test.ts +91 -0
  46. package/src/{planners/shortestPathPlanner.ts → strategies/shortestPath.ts} +32 -19
  47. package/src/{planners/simplePlanner.ts → strategies/simple.ts} +28 -24
  48. package/src/types.ts +75 -34
  49. package/src/utils.ts +23 -0
  50. package/vitest.config.ts +9 -3
  51. package/src/strategies/chain-of-note.ts +0 -106
package/src/decide.ts CHANGED
@@ -3,55 +3,72 @@ import {
3
3
  AnyAgent,
4
4
  AgentDecideOptions,
5
5
  AgentDecisionLogic,
6
- AgentDecisionInput,
7
- AgentPlanner,
8
- AgentPlan,
9
- EventsFromZodEventMapping,
10
- AgentPlanInput,
6
+ AgentDecision,
7
+ AgentDecideInput,
11
8
  TransitionData,
9
+ EventsFromAgent,
12
10
  } from './types';
13
- import { simplePlanner } from './planners/simplePlanner';
14
11
  import { getTransitions } from './utils';
15
- import { CoreTool, tool } from 'ai';
12
+ import { CoreMessage, CoreTool, tool } from 'ai';
16
13
 
17
14
  export async function agentDecide<T extends AnyAgent>(
18
15
  agent: T,
19
- options: AgentDecideOptions
20
- ): Promise<AgentPlan<EventsFromZodEventMapping<T['events']>> | undefined> {
16
+ options: AgentDecideOptions<T>
17
+ ): Promise<AgentDecision<EventsFromAgent<T>> | undefined> {
21
18
  const resolvedOptions = {
22
19
  ...agent.defaultOptions,
23
20
  ...options,
24
21
  };
25
22
  const {
26
- planner = simplePlanner as AgentPlanner<any>,
23
+ strategy = agent.strategy,
27
24
  goal,
25
+ allowedEvents,
28
26
  events = agent.events,
29
27
  state,
30
28
  machine,
31
29
  model = agent.model,
32
- ...otherPlanInput
30
+ messages,
31
+ ...otherDecideInput
33
32
  } = resolvedOptions;
34
33
 
35
- const plan = await planner(agent, {
36
- model,
37
- goal,
38
- events,
39
- state,
40
- machine,
41
- ...otherPlanInput,
42
- });
34
+ const filteredEventSchemas = allowedEvents
35
+ ? Object.fromEntries(
36
+ Object.entries(events).filter(([key]) => {
37
+ return allowedEvents.includes(key);
38
+ })
39
+ )
40
+ : events;
41
+
42
+ let attempts = 0;
43
43
 
44
- if (plan?.nextEvent) {
45
- agent.addPlan(plan);
46
- await resolvedOptions.execute?.(plan.nextEvent);
44
+ const maxAttempts = resolvedOptions.maxAttempts ?? 2;
45
+
46
+ let decision: AgentDecision<any> | undefined;
47
+
48
+ while (attempts++ < maxAttempts) {
49
+ decision = await strategy(agent, {
50
+ model,
51
+ goal,
52
+ events: filteredEventSchemas,
53
+ state,
54
+ machine,
55
+ messages: messages as CoreMessage[], // TODO: fix UIMessage thing
56
+ ...otherDecideInput,
57
+ });
58
+
59
+ if (decision?.nextEvent) {
60
+ agent.addDecision(decision);
61
+ await resolvedOptions.execute?.(decision.nextEvent);
62
+ break;
63
+ }
47
64
  }
48
65
 
49
- return plan;
66
+ return decision;
50
67
  }
51
68
 
52
- export function fromDecision(
53
- agent: AnyAgent,
54
- defaultInput?: AgentDecisionInput
69
+ export function fromDecision<T extends AnyAgent>(
70
+ agent: T,
71
+ defaultInput?: AgentDecideInput<EventsFromAgent<T>>
55
72
  ): AgentDecisionLogic<any> {
56
73
  return fromPromise(async ({ input, self }) => {
57
74
  const parentRef = self._parent;
@@ -70,22 +87,25 @@ export function fromDecision(
70
87
  context: resolvedInput.context,
71
88
  };
72
89
 
73
- const plan = await agentDecide(agent, {
90
+ const decision = await agentDecide(agent, {
74
91
  machine: (parentRef as AnyActor).logic,
75
- state,
92
+ state: snapshot,
93
+ context: resolvedInput.context,
76
94
  execute: async (event) => {
77
95
  parentRef.send(event);
78
96
  },
79
97
  ...resolvedInput,
98
+ // @ts-ignore
99
+ messages: resolvedInput.messages,
80
100
  });
81
101
 
82
- return plan;
102
+ return decision;
83
103
  }) as AgentDecisionLogic<any>;
84
104
  }
85
105
 
86
106
  export function getToolMap<T extends AnyAgent>(
87
107
  _agent: T,
88
- input: AgentPlanInput<any>
108
+ input: AgentDecideInput<any>
89
109
  ): Record<string, CoreTool<any, any>> | undefined {
90
110
  // Get all of the possible next transitions
91
111
  const transitions: TransitionData[] = input.machine
package/src/middleware.ts CHANGED
@@ -17,15 +17,11 @@ export function createAgentMiddleware(agent: AnyAgent) {
17
17
  wrapGenerate: async ({ doGenerate, params }) => {
18
18
  const id = randomId();
19
19
 
20
- params.prompt.forEach((p) => {
20
+ params.prompt.forEach((message) => {
21
21
  agent.addMessage({
22
22
  id,
23
- ...p,
23
+ ...message,
24
24
  timestamp: Date.now(),
25
- correlationId: params.providerMetadata
26
- ?.correlationId as unknown as string,
27
- parentCorrelationId: params.providerMetadata
28
- ?.parentCorrelationId as unknown as string,
29
25
  });
30
26
  });
31
27
 
@@ -43,10 +39,6 @@ export function createAgentMiddleware(agent: AnyAgent) {
43
39
  id,
44
40
  ...message,
45
41
  timestamp: Date.now(),
46
- correlationId: params.providerMetadata
47
- ?.correlationId as unknown as string,
48
- parentCorrelationId: params.providerMetadata
49
- ?.parentCorrelationId as unknown as string,
50
42
  });
51
43
  });
52
44
 
@@ -85,10 +77,6 @@ export function createAgentMiddleware(agent: AnyAgent) {
85
77
  role: 'assistant',
86
78
  content,
87
79
  responseId: id,
88
- correlationId: params.providerMetadata
89
- ?.correlationId as unknown as string,
90
- parentCorrelationId: params.providerMetadata
91
- ?.parentCorrelationId as unknown as string,
92
80
  });
93
81
  },
94
82
  });
@@ -0,0 +1,48 @@
1
+ import { generateText } from 'ai';
2
+ import {
3
+ AnyAgent,
4
+ AgentDecideInput,
5
+ AgentDecision,
6
+ PromptTemplate,
7
+ } from '../types';
8
+ import { getMessages } from '../text';
9
+ import { simpleStrategy } from './simple';
10
+ import { convertToXml } from '../utils';
11
+
12
+ const chainOfThoughtPromptTemplate: PromptTemplate<any> = ({
13
+ context,
14
+ goal,
15
+ }) => {
16
+ return `
17
+ ${convertToXml({ context, goal })}
18
+
19
+ How would you achieve the goal? Think step-by-step.
20
+ `.trim();
21
+ };
22
+
23
+ export async function chainOfThoughtStrategy<T extends AnyAgent>(
24
+ agent: T,
25
+ input: AgentDecideInput<any>
26
+ ): Promise<AgentDecision<any> | undefined> {
27
+ const prompt = chainOfThoughtPromptTemplate({
28
+ context: input.state.context,
29
+ goal: input.goal,
30
+ });
31
+
32
+ const messages = await getMessages(agent, prompt, input);
33
+
34
+ const model = input.model ? agent.wrap(input.model) : agent.model;
35
+
36
+ const result = await generateText({
37
+ model,
38
+ system: input.system ?? agent.description,
39
+ messages,
40
+ });
41
+
42
+ const decision = await simpleStrategy(agent, {
43
+ ...input,
44
+ messages: messages.concat(result.response.messages),
45
+ });
46
+
47
+ return decision;
48
+ }
@@ -0,0 +1,91 @@
1
+ import { createAgent, TypesFromAgent } from '..';
2
+ import { assign, createActor, setup } from 'xstate';
3
+ import { z } from 'zod';
4
+ import { experimental_shortestPathStrategy } from './shortestPath';
5
+ import { test, expect } from 'vitest';
6
+ import { dummyResponseValues, MockLanguageModelV1 } from '../mockModel';
7
+
8
+ test.skip('should find shortest path to goal', async () => {
9
+ const agent = createAgent({
10
+ id: 'counter',
11
+ model: new MockLanguageModelV1({
12
+ doGenerate: async () => {
13
+ return {
14
+ ...dummyResponseValues,
15
+ text: JSON.stringify({
16
+ type: 'object',
17
+ properties: {
18
+ count: {
19
+ type: 'number',
20
+ const: 3,
21
+ },
22
+ },
23
+ required: ['count'],
24
+ }),
25
+ };
26
+ },
27
+ }),
28
+ events: {
29
+ increment: z.object({}).describe('Increment the counter by 1'),
30
+ decrement: z.object({}).describe('Decrement the counter by 1'),
31
+ },
32
+ context: {
33
+ count: z.number().int().describe('Current count value'),
34
+ },
35
+ });
36
+
37
+ const counterMachine = setup({
38
+ types: {} as TypesFromAgent<typeof agent>,
39
+ }).createMachine({
40
+ initial: 'counting',
41
+ context: { count: 0 },
42
+ states: {
43
+ counting: {
44
+ always: {
45
+ guard: ({ context }) => context.count === 3,
46
+ target: 'success',
47
+ },
48
+ on: {
49
+ increment: {
50
+ actions: assign({ count: ({ context }) => context.count + 1 }),
51
+ },
52
+ decrement: {
53
+ actions: assign({ count: ({ context }) => context.count - 1 }),
54
+ },
55
+ },
56
+ },
57
+ success: {
58
+ type: 'final',
59
+ },
60
+ },
61
+ });
62
+
63
+ const counterActor = createActor(counterMachine).start();
64
+
65
+ const decision = await agent.decide({
66
+ machine: counterMachine,
67
+ model: new MockLanguageModelV1({
68
+ defaultObjectGenerationMode: 'tool',
69
+ doGenerate: async () => {
70
+ return {
71
+ ...dummyResponseValues,
72
+ text: JSON.stringify({
73
+ type: 'object',
74
+ properties: {
75
+ count: {
76
+ type: 'number',
77
+ const: 3,
78
+ },
79
+ },
80
+ required: ['count'],
81
+ }),
82
+ };
83
+ },
84
+ }),
85
+ goal: 'Get the counter to exactly 3',
86
+ state: counterActor.getSnapshot(),
87
+ strategy: experimental_shortestPathStrategy,
88
+ });
89
+
90
+ expect(decision?.nextEvent?.type).toBe('increment');
91
+ });
@@ -1,8 +1,8 @@
1
1
  import { generateObject } from 'ai';
2
- import { getToolMap } from '../decide';
3
2
  import {
4
- AgentPlan,
5
- AgentPlanInput,
3
+ AgentDecision,
4
+ AgentDecideInput,
5
+ AgentStrategy,
6
6
  AgentStep,
7
7
  AnyAgent,
8
8
  CostFunction,
@@ -12,6 +12,7 @@ import { getShortestPaths } from '@xstate/graph';
12
12
  import { z } from 'zod';
13
13
  import { zodToJsonSchema } from 'zod-to-json-schema';
14
14
  import Ajv from 'ajv';
15
+ import { AnyMachineSnapshot } from 'xstate';
15
16
 
16
17
  const ajv = new Ajv();
17
18
 
@@ -35,30 +36,31 @@ function trimSteps(steps: AgentStep<any>[], currentState: ObservedState) {
35
36
  return steps.slice(index + 1, steps.length);
36
37
  }
37
38
 
38
- export async function shortestPathPlanner<T extends AnyAgent>(
39
+ export async function experimental_shortestPathStrategy<T extends AnyAgent>(
39
40
  agent: T,
40
- input: AgentPlanInput<any>
41
- ): Promise<AgentPlan<any> | undefined> {
41
+ input: AgentDecideInput<any>
42
+ ): Promise<AgentDecision<any> | undefined> {
42
43
  const costFunction: CostFunction<any> =
43
44
  input.costFunction ?? ((path) => path.weight ?? Infinity);
44
- const existingPlan = agent
45
- .getPlans()
46
- .find((p) => p.planner === 'shortestPath' && p.goal === input.goal);
45
+ const existingDecision = agent
46
+ .getDecisions()
47
+ .find((p) => p.strategy === 'shortestPath' && p.goal === input.goal);
47
48
 
48
- let paths = existingPlan?.paths;
49
+ let paths = existingDecision?.paths;
49
50
 
50
- if (existingPlan) {
51
- console.log('Existing plan found');
51
+ if (existingDecision) {
52
+ console.log('Existing decision found');
52
53
  }
53
54
 
54
- if (!input.machine && !existingPlan) {
55
+ if (!input.machine && !existingDecision) {
55
56
  return;
56
57
  }
57
58
 
58
- if (input.machine && !existingPlan) {
59
+ if (input.machine && !existingDecision) {
59
60
  const contextSchema = zodToJsonSchema(z.object(agent.context));
60
61
  const result = await generateObject({
61
62
  model: agent.model,
63
+ system: input.system ?? agent.description,
62
64
  prompt: `
63
65
  <goal>
64
66
  ${input.goal}
@@ -83,6 +85,16 @@ Examples:
83
85
  {
84
86
  "contextSchema": "{"type": "object", "properties": {"score": {"type": "number", "minimum": 100}}, "required": ["score"]}"
85
87
  }
88
+
89
+ 3. For "fruits contain apple, orange, banana":
90
+ {
91
+ "type": "array",
92
+ "allOf": [
93
+ { "contains": { "const": "apple" } },
94
+ { "contains": { "const": "orange" } },
95
+ { "contains": { "const": "banana" } }
96
+ ]
97
+ }
86
98
  `.trim(),
87
99
  schema: z.object({
88
100
  // valueSchema: z
@@ -109,6 +121,10 @@ Examples:
109
121
  console.log(result.object);
110
122
  const validateContext = ajv.compile(result.object.contextSchema);
111
123
 
124
+ const stateFilter = (state: AnyMachineSnapshot) => {
125
+ return validateContext(state.context);
126
+ };
127
+
112
128
  const resolvedState = input.machine.resolveState({
113
129
  ...input.state,
114
130
  context: input.state.context ?? {},
@@ -116,10 +132,7 @@ Examples:
116
132
 
117
133
  paths = getShortestPaths(input.machine, {
118
134
  fromState: resolvedState,
119
- toState: (state) => {
120
- const v = validateContext(state.context);
121
- return v;
122
- },
135
+ toState: stateFilter,
123
136
  });
124
137
  }
125
138
 
@@ -149,7 +162,7 @@ Examples:
149
162
  const nextStep = leastWeightPath?.steps[0];
150
163
 
151
164
  return {
152
- planner: 'shortestPath',
165
+ strategy: 'shortestPath',
153
166
  episodeId: agent.episodeId,
154
167
  goal: input.goal,
155
168
  goalState: paths[0]?.state,
@@ -1,30 +1,27 @@
1
- import { CoreMessage, type CoreTool, generateText, tool } from 'ai';
1
+ import { CoreMessage, generateText } from 'ai';
2
2
  import {
3
- AgentPlan,
4
- AgentPlanInput,
5
- ObservedState,
3
+ AgentDecision,
4
+ AgentDecideInput,
6
5
  PromptTemplate,
7
- TransitionData,
8
6
  AnyAgent,
9
7
  } from '../types';
10
- import { getAllTransitions, randomId } from '../utils';
11
- import { AnyStateMachine, getNextSnapshot } from 'xstate';
12
- import { defaultTextTemplate } from '../templates/defaultText';
8
+ import { convertToXml, randomId } from '../utils';
9
+ import { getNextSnapshot } from 'xstate';
13
10
  import { getMessages } from '../text';
14
11
  import { getToolMap } from '../decide';
15
12
 
16
- const simplePlannerPromptTemplate: PromptTemplate<any> = (data) => {
13
+ const simpleStrategyPromptTemplate: PromptTemplate<any> = (data) => {
17
14
  return `
18
- ${defaultTextTemplate(data)}
15
+ ${convertToXml(data)}
19
16
 
20
17
  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.
21
18
  `.trim();
22
19
  };
23
20
 
24
- export async function simplePlanner<T extends AnyAgent>(
21
+ export async function simpleStrategy<T extends AnyAgent>(
25
22
  agent: T,
26
- input: AgentPlanInput<any>
27
- ): Promise<AgentPlan<any> | undefined> {
23
+ input: AgentDecideInput<any>
24
+ ): Promise<AgentDecision<any> | undefined> {
28
25
  const toolMap = getToolMap(agent, input);
29
26
 
30
27
  if (!toolMap) {
@@ -34,8 +31,8 @@ export async function simplePlanner<T extends AnyAgent>(
34
31
 
35
32
  // Create a prompt with the given context and goal.
36
33
  // The template is used to ensure that a single tool call at most is made.
37
- const prompt = simplePlannerPromptTemplate({
38
- context: input.state.context,
34
+ const prompt = simpleStrategyPromptTemplate({
35
+ context: input.context,
39
36
  goal: input.goal,
40
37
  });
41
38
 
@@ -45,30 +42,33 @@ export async function simplePlanner<T extends AnyAgent>(
45
42
 
46
43
  const {
47
44
  state,
45
+ context,
48
46
  machine,
49
- previousPlan,
47
+ prevDecision,
50
48
  events,
51
49
  goal,
52
50
  model: _,
53
51
  ...rest
54
52
  } = input;
55
53
 
56
- const machineState = input.machine
57
- ? input.machine.resolveState({
58
- ...input.state,
59
- context: input.state.context,
60
- })
61
- : undefined;
54
+ const machineState =
55
+ input.machine && input.state
56
+ ? input.machine.resolveState({
57
+ ...input.state,
58
+ context: input.state.context ?? {},
59
+ })
60
+ : undefined;
62
61
 
63
62
  const result = await generateText({
64
63
  ...rest,
64
+ system: input.system ?? agent.description,
65
65
  model,
66
66
  messages,
67
67
  tools: toolMap as any,
68
68
  toolChoice: input.toolChoice ?? 'required',
69
69
  });
70
70
 
71
- result.responseMessages.forEach((m) => {
71
+ result.response.messages.forEach((m) => {
72
72
  const message: CoreMessage = m;
73
73
 
74
74
  agent.addMessage({
@@ -87,7 +87,7 @@ export async function simplePlanner<T extends AnyAgent>(
87
87
  }
88
88
 
89
89
  return {
90
- planner: 'simple',
90
+ strategy: 'simple',
91
91
  goal: input.goal,
92
92
  goalState: input.state,
93
93
  nextEvent: singleResult.result,
@@ -109,3 +109,7 @@ export async function simplePlanner<T extends AnyAgent>(
109
109
  ],
110
110
  };
111
111
  }
112
+
113
+ export function createSimpleStrategy<T extends AnyAgent>() {
114
+ return simpleStrategy;
115
+ }