@statelyai/agent 2.0.0-next.1 → 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.
Files changed (51) hide show
  1. package/.changeset/grumpy-dolphins-think.md +17 -0
  2. package/.changeset/old-teachers-tap.md +5 -0
  3. package/.changeset/pink-eagles-deliver.md +13 -0
  4. package/.changeset/pre.json +9 -1
  5. package/.changeset/quiet-turtles-do.md +7 -0
  6. package/.changeset/smart-yaks-pull.md +23 -0
  7. package/.changeset/sweet-clouds-mix.md +16 -0
  8. package/.changeset/swift-mangos-rush.md +5 -0
  9. package/.changeset/tough-ways-rhyme.md +5 -0
  10. package/CHANGELOG.md +79 -0
  11. package/dist/index.d.mts +116 -100
  12. package/dist/index.d.ts +116 -100
  13. package/dist/index.js +102 -72
  14. package/dist/index.mjs +105 -75
  15. package/examples/chatbot.ts +2 -2
  16. package/examples/cot.ts +21 -73
  17. package/examples/customer-service-sim.ts +3 -3
  18. package/examples/email.ts +3 -5
  19. package/examples/example.ts +2 -2
  20. package/examples/goal.ts +2 -2
  21. package/examples/joke.ts +12 -12
  22. package/examples/jugs.ts +4 -7
  23. package/examples/learn-from-feedback.ts +123 -0
  24. package/examples/number.ts +2 -2
  25. package/examples/raffle.ts +2 -2
  26. package/examples/river-crossing.ts +4 -7
  27. package/examples/simple.ts +13 -10
  28. package/examples/summary.ts +2 -5
  29. package/examples/support.ts +38 -38
  30. package/examples/ticTacToe.ts +46 -4
  31. package/examples/todo.ts +3 -3
  32. package/examples/tutor.ts +2 -2
  33. package/examples/verify.ts +2 -2
  34. package/examples/weather-agent.ts +139 -0
  35. package/examples/weather.ts +26 -23
  36. package/examples/word.ts +8 -6
  37. package/package.json +2 -1
  38. package/src/agent.test.ts +37 -52
  39. package/src/agent.ts +93 -60
  40. package/src/decide.test.ts +56 -8
  41. package/src/decide.ts +42 -32
  42. package/src/strategies/chainOfThought.ts +50 -0
  43. package/src/{planners → strategies}/shortestPath.test.ts +4 -7
  44. package/src/strategies/shortestPath.ts +178 -0
  45. package/src/{planners → strategies}/simple.ts +25 -26
  46. package/src/templates/defaultText.ts +3 -0
  47. package/src/text.ts +13 -13
  48. package/src/types.ts +124 -83
  49. package/src/utils.ts +13 -1
  50. package/src/planners/shortestPath.ts +0 -177
  51. package/src/strategies/chain-of-note.ts +0 -106
@@ -1,7 +1,7 @@
1
- import { createAgent } from '../';
1
+ import { createAgent, TypesFromAgent } from '..';
2
2
  import { assign, createActor, setup } from 'xstate';
3
3
  import { z } from 'zod';
4
- import { experimental_createShortestPathPlanner } from './shortestPath';
4
+ import { experimental_shortestPathStrategy } from './shortestPath';
5
5
  import { test, expect } from 'vitest';
6
6
  import { dummyResponseValues, MockLanguageModelV1 } from '../mockModel';
7
7
 
@@ -35,10 +35,7 @@ test.skip('should find shortest path to goal', async () => {
35
35
  });
36
36
 
37
37
  const counterMachine = setup({
38
- types: {
39
- context: agent.types.context,
40
- events: agent.types.events,
41
- },
38
+ types: {} as TypesFromAgent<typeof agent>,
42
39
  }).createMachine({
43
40
  initial: 'counting',
44
41
  context: { count: 0 },
@@ -87,7 +84,7 @@ test.skip('should find shortest path to goal', async () => {
87
84
  }),
88
85
  goal: 'Get the counter to exactly 3',
89
86
  state: counterActor.getSnapshot(),
90
- planner: experimental_createShortestPathPlanner(),
87
+ strategy: experimental_shortestPathStrategy,
91
88
  });
92
89
 
93
90
  expect(decision?.nextEvent?.type).toBe('increment');
@@ -0,0 +1,178 @@
1
+ import { generateObject } from 'ai';
2
+ import {
3
+ AgentDecision,
4
+ AgentDecideInput,
5
+ AgentStrategy,
6
+ AgentStep,
7
+ AnyAgent,
8
+ CostFunction,
9
+ ObservedState,
10
+ } from '../types';
11
+ import { getShortestPaths } from '@xstate/graph';
12
+ import { z } from 'zod';
13
+ import { zodToJsonSchema } from 'zod-to-json-schema';
14
+ import Ajv from 'ajv';
15
+ import { AnyMachineSnapshot } from 'xstate';
16
+ import { randomId } from '../utils';
17
+
18
+ const ajv = new Ajv();
19
+
20
+ function observedStatesEqual(
21
+ state1: ObservedState<any>,
22
+ state2: ObservedState<any>
23
+ ) {
24
+ // check state value && state context
25
+ return (
26
+ JSON.stringify(state1.value) === JSON.stringify(state2.value) &&
27
+ JSON.stringify(state1.context) === JSON.stringify(state2.context)
28
+ );
29
+ }
30
+
31
+ function trimSteps(steps: AgentStep<any>[], currentState: ObservedState<any>) {
32
+ const index = steps.findIndex(
33
+ (step) => step.state && observedStatesEqual(step.state, currentState)
34
+ );
35
+
36
+ if (index === -1) {
37
+ return undefined;
38
+ }
39
+
40
+ return steps.slice(index + 1, steps.length);
41
+ }
42
+
43
+ export async function experimental_shortestPathStrategy<T extends AnyAgent>(
44
+ agent: T,
45
+ input: AgentDecideInput<any>
46
+ ): Promise<AgentDecision<any> | undefined> {
47
+ const costFunction: CostFunction<any> =
48
+ input.costFunction ?? ((path) => path.weight ?? Infinity);
49
+ const existingDecision = agent
50
+ .getDecisions()
51
+ .find((p) => p.strategy === 'shortestPath' && p.goal === input.goal);
52
+
53
+ let paths = existingDecision?.paths;
54
+
55
+ if (existingDecision) {
56
+ console.log('Existing decision found');
57
+ }
58
+
59
+ if (!input.machine && !existingDecision) {
60
+ return;
61
+ }
62
+
63
+ if (input.machine && !existingDecision) {
64
+ const contextSchema = zodToJsonSchema(z.object(agent.context));
65
+ const result = await generateObject({
66
+ model: agent.model,
67
+ system: input.system ?? agent.description,
68
+ prompt: `
69
+ <goal>
70
+ ${input.goal}
71
+ </goal>
72
+ <contextSchema>
73
+ ${contextSchema}
74
+ </contextSchema>
75
+
76
+
77
+ Update the context JSON schema so that it validates the context to determine that it reaches the goal. Return the result as a diff.
78
+
79
+ The contextSchema properties must not change. Do not add or remove properties, or modify the name of the properties.
80
+ Use "const" for exact required values and define ranges/types for flexible conditions.
81
+
82
+ Examples:
83
+ 1. For "user is logged in with admin role":
84
+ {
85
+ "contextSchema": "{"type": "object", "properties": {"role": {"const": "admin"}, "lastLogin": {"type": "string"}}, "required": ["role"]}"
86
+ }
87
+
88
+ 2. For "score is above 100":
89
+ {
90
+ "contextSchema": "{"type": "object", "properties": {"score": {"type": "number", "minimum": 100}}, "required": ["score"]}"
91
+ }
92
+
93
+ 3. For "fruits contain apple, orange, banana":
94
+ {
95
+ "type": "array",
96
+ "allOf": [
97
+ { "contains": { "const": "apple" } },
98
+ { "contains": { "const": "orange" } },
99
+ { "contains": { "const": "banana" } }
100
+ ]
101
+ }
102
+ `.trim(),
103
+ schema: z.object({
104
+ // valueSchema: z
105
+ // .string()
106
+ // .describe('The JSON Schema representing the goal state value'),
107
+ contextSchema: z
108
+ .object({
109
+ type: z.literal('object'),
110
+ properties: z.object(
111
+ Object.keys((contextSchema as any).properties).reduce(
112
+ (acc, key) => {
113
+ acc[key] = z.any();
114
+ return acc;
115
+ },
116
+ {} as any
117
+ )
118
+ ),
119
+ required: z.array(z.string()).optional(),
120
+ })
121
+ .describe('The JSON Schema representing the goal state context'),
122
+ }),
123
+ });
124
+
125
+ console.log(result.object);
126
+ const validateContext = ajv.compile(result.object.contextSchema);
127
+
128
+ const stateFilter = (state: AnyMachineSnapshot) => {
129
+ return validateContext(state.context);
130
+ };
131
+
132
+ const resolvedState = input.machine.resolveState({
133
+ ...input.state,
134
+ context: input.state.context ?? {},
135
+ });
136
+
137
+ paths = getShortestPaths(input.machine, {
138
+ fromState: resolvedState,
139
+ toState: stateFilter,
140
+ });
141
+ }
142
+
143
+ if (!paths) {
144
+ return undefined;
145
+ }
146
+
147
+ const trimmedPaths = paths
148
+ .map((path) => {
149
+ const trimmedSteps = trimSteps(path.steps, input.state);
150
+ if (!trimmedSteps) {
151
+ return undefined;
152
+ }
153
+ return {
154
+ ...path,
155
+ steps: trimmedSteps,
156
+ };
157
+ })
158
+ .filter((p): p is NonNullable<typeof p> => p !== undefined);
159
+
160
+ // Sort paths from least weight to most weight
161
+ const sortedPaths = trimmedPaths.sort(
162
+ (a, b) => costFunction(a) - costFunction(b)
163
+ );
164
+
165
+ const leastWeightPath = sortedPaths[0];
166
+ const nextStep = leastWeightPath?.steps[0];
167
+
168
+ return {
169
+ id: randomId(),
170
+ strategy: 'shortestPath',
171
+ episodeId: agent.episodeId,
172
+ goal: input.goal,
173
+ goalState: paths[0]?.state,
174
+ nextEvent: nextStep?.event,
175
+ paths,
176
+ timestamp: Date.now(),
177
+ };
178
+ }
@@ -1,23 +1,27 @@
1
1
  import { CoreMessage, generateText } from 'ai';
2
- import { AgentPlan, AgentPlanInput, PromptTemplate, AnyAgent } from '../types';
3
- import { randomId } from '../utils';
2
+ import {
3
+ AgentDecision,
4
+ AgentDecideInput,
5
+ PromptTemplate,
6
+ AnyAgent,
7
+ } from '../types';
8
+ import { convertToXml, randomId } from '../utils';
4
9
  import { getNextSnapshot } from 'xstate';
5
- import { defaultTextTemplate } from '../templates/defaultText';
6
10
  import { getMessages } from '../text';
7
11
  import { getToolMap } from '../decide';
8
12
 
9
- const simplePlannerPromptTemplate: PromptTemplate<any> = (data) => {
13
+ const simpleStrategyPromptTemplate: PromptTemplate<any> = (data) => {
10
14
  return `
11
- ${defaultTextTemplate(data)}
15
+ ${convertToXml(data)}
12
16
 
13
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.
14
18
  `.trim();
15
19
  };
16
20
 
17
- export async function simplePlanner<T extends AnyAgent>(
21
+ export async function simpleStrategy<T extends AnyAgent>(
18
22
  agent: T,
19
- input: AgentPlanInput<any>
20
- ): Promise<AgentPlan<any> | undefined> {
23
+ input: AgentDecideInput<any>
24
+ ): Promise<AgentDecision<any> | undefined> {
21
25
  const toolMap = getToolMap(agent, input);
22
26
 
23
27
  if (!toolMap) {
@@ -27,8 +31,9 @@ export async function simplePlanner<T extends AnyAgent>(
27
31
 
28
32
  // Create a prompt with the given context and goal.
29
33
  // The template is used to ensure that a single tool call at most is made.
30
- const prompt = simplePlannerPromptTemplate({
31
- context: input.state.context,
34
+ const prompt = simpleStrategyPromptTemplate({
35
+ stateValue: input.state.value,
36
+ context: input.context ?? input.state.context,
32
37
  goal: input.goal,
33
38
  });
34
39
 
@@ -36,22 +41,15 @@ export async function simplePlanner<T extends AnyAgent>(
36
41
 
37
42
  const model = input.model ? agent.wrap(input.model) : agent.model;
38
43
 
39
- const {
40
- state,
41
- machine,
42
- previousPlan,
43
- events,
44
- goal,
45
- model: _,
46
- ...rest
47
- } = input;
44
+ const { state, machine, events, goal, model: _, ...rest } = input;
48
45
 
49
- const machineState = input.machine
50
- ? input.machine.resolveState({
51
- ...input.state,
52
- context: input.state.context,
53
- })
54
- : undefined;
46
+ const machineState =
47
+ input.machine && input.state
48
+ ? input.machine.resolveState({
49
+ ...input.state,
50
+ context: input.state.context ?? {},
51
+ })
52
+ : undefined;
55
53
 
56
54
  const result = await generateText({
57
55
  ...rest,
@@ -81,7 +79,8 @@ export async function simplePlanner<T extends AnyAgent>(
81
79
  }
82
80
 
83
81
  return {
84
- planner: 'simple',
82
+ id: randomId(),
83
+ strategy: 'simple',
85
84
  goal: input.goal,
86
85
  goalState: input.state,
87
86
  nextEvent: singleResult.result,
@@ -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 = {