@statelyai/agent 1.1.6 → 2.0.0-next.0

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 (54) hide show
  1. package/.changeset/light-hats-drive.md +9 -0
  2. package/.changeset/pre.json +10 -0
  3. package/.vscode/launch.json +6 -0
  4. package/CHANGELOG.md +10 -0
  5. package/dist/index.d.mts +262 -165
  6. package/dist/index.d.ts +262 -165
  7. package/dist/index.js +368 -263
  8. package/dist/index.mjs +371 -261
  9. package/examples/chatbot-alt.ts +57 -0
  10. package/examples/chatbot.ts +11 -16
  11. package/examples/cot.ts +25 -22
  12. package/examples/customer-service-sim.ts +107 -0
  13. package/examples/email.ts +14 -14
  14. package/examples/example.ts +5 -5
  15. package/examples/executor.ts +66 -0
  16. package/examples/goal.ts +11 -11
  17. package/examples/helpers/helpers.ts +26 -14
  18. package/examples/joke.ts +78 -75
  19. package/examples/jugs.ts +125 -0
  20. package/examples/multi.ts +4 -4
  21. package/examples/newspaper.ts +98 -104
  22. package/examples/number.ts +5 -4
  23. package/examples/raffle.ts +10 -11
  24. package/examples/river-crossing.ts +140 -0
  25. package/examples/sandbox.ts +1 -1
  26. package/examples/simple.ts +4 -2
  27. package/examples/summary.ts +121 -0
  28. package/examples/support.ts +5 -5
  29. package/examples/ticTacToe.ts +86 -45
  30. package/examples/todo.ts +6 -6
  31. package/examples/tutor.ts +13 -13
  32. package/examples/verify.ts +2 -2
  33. package/examples/weather.ts +5 -8
  34. package/examples/wiki.ts +26 -7
  35. package/examples/word.ts +15 -10
  36. package/package.json +13 -10
  37. package/readme.md +1 -1
  38. package/src/agent-experimental.ts +1 -1
  39. package/src/agent.test.ts +117 -228
  40. package/src/agent.ts +469 -81
  41. package/src/{decision.test.ts → decide.test.ts} +26 -50
  42. package/src/decide.ts +153 -0
  43. package/src/index.ts +1 -1
  44. package/src/middleware.ts +103 -0
  45. package/src/mockModel.ts +47 -0
  46. package/src/planners/shortestPathPlanner.ts +151 -13
  47. package/src/planners/simplePlanner.ts +57 -85
  48. package/src/strategies/chain-of-note.ts +6 -55
  49. package/src/text.ts +51 -144
  50. package/src/types.ts +172 -204
  51. package/src/utils.ts +37 -4
  52. package/src/adapters/vercel.ts +0 -7
  53. package/src/decision.ts +0 -84
  54. package/src/memory.ts +0 -25
package/examples/joke.ts CHANGED
@@ -3,7 +3,7 @@ import { createAgent, fromDecision } from '../src';
3
3
  import { loadingAnimation } from './helpers/loader';
4
4
  import { z } from 'zod';
5
5
  import { openai } from '@ai-sdk/openai';
6
- import { getFromTerminal } from './helpers/helpers';
6
+ import { fromTerminal } from './helpers/helpers';
7
7
 
8
8
  export function getRandomFunnyPhrase() {
9
9
  const funnyPhrases = [
@@ -46,26 +46,30 @@ const loader = fromCallback(({ input }: { input: string }) => {
46
46
 
47
47
  const agent = createAgent({
48
48
  name: 'joke-teller',
49
- model: openai('gpt-4-turbo'),
49
+ model: openai('gpt-4o-mini'),
50
50
  events: {
51
- askForTopic: z.object({
52
- topic: z.string().describe('The topic for the joke'),
53
- }),
51
+ askForTopic: z
52
+ .object({
53
+ topic: z.string().describe('The topic for the joke'),
54
+ })
55
+ .describe('Ask for a new topic, because the last joke rated 6 or lower'),
54
56
  'agent.tellJoke': z.object({
55
57
  joke: z.string().describe('The joke text'),
56
58
  }),
57
- 'agent.endJokes': z.object({}).describe('End the jokes'),
59
+ 'agent.endJokes': z
60
+ .object({})
61
+ .describe('End the jokes, since the last joke rated 7 or higher'),
58
62
  'agent.rateJoke': z.object({
59
63
  rating: z.number().min(1).max(10),
60
64
  explanation: z.string(),
61
65
  }),
62
66
  'agent.continue': z.object({}).describe('Continue'),
63
- 'agent.markAsIrrelevant': z
64
- .object({
65
- explanation: z.string(),
66
- })
67
- .describe('Explains why the joke was irrelevant'),
68
- 'agent.markAsRelevant': z.object({}).describe('The joke was relevant'),
67
+ 'agent.markRelevancy': z.object({
68
+ relevant: z.boolean().describe('Whether the joke was relevant'),
69
+ explanation: z
70
+ .string()
71
+ .describe('The explanation for why the joke was relevant or not'),
72
+ }),
69
73
  },
70
74
  context: {
71
75
  topic: z.string().describe('The topic for the joke'),
@@ -81,7 +85,7 @@ const jokeMachine = setup({
81
85
  actors: {
82
86
  agent: fromDecision(agent),
83
87
  loader,
84
- getFromTerminal,
88
+ getFromTerminal: fromTerminal,
85
89
  },
86
90
  }).createMachine({
87
91
  id: 'joke',
@@ -107,72 +111,44 @@ const jokeMachine = setup({
107
111
  },
108
112
  },
109
113
  tellingJoke: {
110
- invoke: [
111
- {
112
- src: 'agent',
113
- input: ({ context }) => ({
114
- context: {
115
- topic: context.topic,
116
- },
117
- goal: `Tell me a joke about the topic. Do not make any joke that is not relevant to the topic.`,
118
- }),
119
- },
120
- {
121
- src: 'loader',
122
- input: getRandomFunnyPhrase,
123
- },
124
- ],
114
+ invoke: {
115
+ src: 'loader',
116
+ input: getRandomFunnyPhrase,
117
+ },
118
+
125
119
  on: {
126
120
  'agent.tellJoke': {
127
121
  actions: [
128
122
  assign({
129
123
  jokes: ({ context, event }) => [...context.jokes, event.joke],
130
124
  }),
131
- log((x) => x.event.joke),
125
+ log(({ event }) => event.joke),
132
126
  ],
133
127
  target: 'relevance',
134
128
  },
135
129
  },
136
130
  },
137
131
  relevance: {
138
- invoke: {
139
- src: 'agent',
140
- input: (x) => ({
141
- context: {
142
- topic: x.context.topic,
143
- lastJoke: x.context.jokes[x.context.jokes.length - 1],
144
- },
145
- goal: 'An irrelevant joke has no reference to the topic. If the last joke is completely irrelevant to the topic, ask for a new joke topic. Otherwise, continue.',
146
- }),
147
- },
148
132
  on: {
149
- 'agent.markAsIrrelevant': {
150
- actions: log((x) => 'Irrelevant joke: ' + x.event.explanation),
151
- target: 'waitingForTopic',
152
- description: 'Continue',
153
- },
154
- 'agent.markAsRelevant': {
155
- actions: log('Joke was relevant'),
156
- target: 'rateJoke',
157
- },
133
+ 'agent.markRelevancy': [
134
+ {
135
+ guard: ({ event }) => !event.relevant,
136
+ actions: log(
137
+ ({ event }) => 'Irrelevant joke: ' + event.explanation
138
+ ),
139
+ target: 'waitingForTopic',
140
+ description: 'Continue',
141
+ },
142
+ { target: 'rateJoke' },
143
+ ],
158
144
  },
159
145
  },
160
146
  rateJoke: {
161
- invoke: [
162
- {
163
- src: 'agent',
164
- input: ({ context }) => ({
165
- context: {
166
- jokes: context.jokes,
167
- },
168
- goal: `Rate the last joke on a scale of 1 to 10.`,
169
- }),
170
- },
171
- {
172
- src: 'loader',
173
- input: getRandomRatingPhrase,
174
- },
175
- ],
147
+ invoke: {
148
+ src: 'loader',
149
+ input: getRandomRatingPhrase,
150
+ },
151
+
176
152
  on: {
177
153
  'agent.rateJoke': {
178
154
  actions: [
@@ -188,26 +164,14 @@ const jokeMachine = setup({
188
164
  },
189
165
  },
190
166
  decide: {
191
- invoke: {
192
- src: 'agent',
193
- input: ({ context }) => ({
194
- context: {
195
- lastRating: context.lastRating,
196
- },
197
- goal: `Choose what to do next, given the previous rating of the joke.`,
198
- }),
199
- },
200
167
  on: {
201
168
  askForTopic: {
202
169
  target: 'waitingForTopic',
203
170
  actions: log("That joke wasn't good enough. Let's try again."),
204
- description:
205
- 'Ask for a new topic, because the last joke rated 6 or lower',
206
171
  },
207
172
  'agent.endJokes': {
208
173
  target: 'end',
209
174
  actions: log('That joke was good enough. Goodbye!'),
210
- description: 'End the jokes, since the last joke rated 7 or higher',
211
175
  },
212
176
  },
213
177
  },
@@ -222,4 +186,43 @@ const jokeMachine = setup({
222
186
 
223
187
  const actor = createActor(jokeMachine);
224
188
 
189
+ agent.interact(actor, (observed) => {
190
+ if (observed.state.matches('tellingJoke')) {
191
+ return {
192
+ goal: 'Tell me a joke about the topic. Do not make any joke that is not relevant to the topic.',
193
+ context: {
194
+ topic: observed.state.context.topic,
195
+ },
196
+ };
197
+ }
198
+
199
+ if (observed.state.matches('relevance')) {
200
+ return {
201
+ goal: 'An irrelevant joke has no reference to the topic. If the last joke is completely irrelevant to the topic, ask for a new joke topic. Otherwise, continue.',
202
+ context: {
203
+ topic: observed.state.context.topic,
204
+ lastJoke: observed.state.context.jokes.at(-1),
205
+ },
206
+ };
207
+ }
208
+
209
+ if (observed.state.matches('rateJoke')) {
210
+ return {
211
+ goal: 'Rate the last joke on a scale of 1 to 10.',
212
+ context: {
213
+ lastJoke: observed.state.context.jokes.at(-1),
214
+ },
215
+ };
216
+ }
217
+
218
+ if (observed.state.matches('decide')) {
219
+ return {
220
+ goal: 'Choose what to do next, given the previous rating of the joke.',
221
+ context: {
222
+ lastRating: observed.state.context.lastRating,
223
+ },
224
+ };
225
+ }
226
+ });
227
+
225
228
  actor.start();
@@ -0,0 +1,125 @@
1
+ import { createAgent } from '../src';
2
+ import { assign, createActor, setup } from 'xstate';
3
+ import { openai } from '@ai-sdk/openai';
4
+ import { z } from 'zod';
5
+ import { shortestPathPlanner } from '../src/planners/shortestPathPlanner';
6
+
7
+ const agent = createAgent({
8
+ name: 'die-hard-solver',
9
+ model: openai('gpt-4o'),
10
+ events: {
11
+ fill3: z.object({}).describe('Fill the 3-gallon jug'),
12
+ fill5: z.object({}).describe('Fill the 5-gallon jug'),
13
+ empty3: z.object({}).describe('Empty the 3-gallon jug'),
14
+ empty5: z.object({}).describe('Empty the 5-gallon jug'),
15
+ pour3to5: z
16
+ .object({
17
+ reasoning: z
18
+ .string()
19
+ .describe(
20
+ 'Very brief reasoning for pouring 3-gallon jug into 5-gallon jug'
21
+ ),
22
+ })
23
+ .describe('Pour the 3-gallon jug into the 5-gallon jug'),
24
+ pour5to3: z
25
+ .object({
26
+ reasoning: z
27
+ .string()
28
+ .describe(
29
+ 'Very brief reasoning for pouring 3-gallon jug into 5-gallon jug'
30
+ ),
31
+ })
32
+ .describe('Pour the 5-gallon jug into the 3-gallon jug'),
33
+ },
34
+ context: {
35
+ jug3: z.number().int().describe('Gallons of water in the 3-gallon jug'),
36
+ jug5: z.number().int().describe('Gallons of water in the 5-gallon jug'),
37
+ },
38
+ });
39
+
40
+ const waterJugMachine = setup({
41
+ types: {
42
+ context: agent.types.context,
43
+ events: agent.types.events,
44
+ },
45
+ }).createMachine({
46
+ initial: 'solving',
47
+ context: { jug3: 0, jug5: 0 },
48
+ states: {
49
+ solving: {
50
+ always: {
51
+ guard: ({ context }) => context.jug5 === 4,
52
+ target: 'success',
53
+ },
54
+ on: {
55
+ fill3: {
56
+ actions: assign({ jug3: 3 }),
57
+ },
58
+ fill5: {
59
+ actions: assign({ jug5: 5 }),
60
+ },
61
+ empty3: {
62
+ actions: assign({ jug3: 0 }),
63
+ },
64
+ empty5: {
65
+ actions: assign({ jug5: 0 }),
66
+ },
67
+ pour3to5: {
68
+ actions: assign(({ context }) => {
69
+ const total = context.jug3 + context.jug5;
70
+ const newJug5 = Math.min(5, total);
71
+ return {
72
+ jug5: newJug5,
73
+ jug3: total - newJug5,
74
+ };
75
+ }),
76
+ },
77
+ pour5to3: {
78
+ actions: assign(({ context }) => {
79
+ const total = context.jug3 + context.jug5;
80
+ const newJug3 = Math.min(3, total);
81
+ return {
82
+ jug3: newJug3,
83
+ jug5: total - newJug3,
84
+ };
85
+ }),
86
+ },
87
+ },
88
+ },
89
+ success: {
90
+ type: 'final',
91
+ },
92
+ },
93
+ });
94
+
95
+ let maxTries = 0;
96
+ async function main() {
97
+ const waterJugActor = createActor(waterJugMachine).start();
98
+
99
+ while (waterJugActor.getSnapshot().value !== 'success') {
100
+ maxTries++;
101
+ if (maxTries > 20) {
102
+ console.log('Max tries reached');
103
+ throw new Error('Max tries reached');
104
+ }
105
+ const decision = await agent.decide({
106
+ machine: waterJugMachine,
107
+ goal: 'Get exactly 4 gallons of water in the 5-gallon jug',
108
+ state: waterJugActor.getSnapshot(),
109
+ planner: shortestPathPlanner,
110
+ });
111
+
112
+ console.log(decision?.nextEvent);
113
+
114
+ if (decision?.nextEvent) {
115
+ waterJugActor.send(decision.nextEvent);
116
+ console.log(waterJugActor.getSnapshot().context);
117
+ } else {
118
+ console.log('No decision made');
119
+ }
120
+ }
121
+
122
+ console.log('Done');
123
+ }
124
+
125
+ main();
package/examples/multi.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { createAgent, fromDecision } from '../src';
2
2
  import { z } from 'zod';
3
3
  import { assign, createActor, log, setup } from 'xstate';
4
- import { getFromTerminal } from './helpers/helpers';
4
+ import { fromTerminal } from './helpers/helpers';
5
5
  import { openai } from '@ai-sdk/openai';
6
6
 
7
7
  const agent = createAgent({
8
8
  name: 'multi',
9
- model: openai('gpt-4-1106-preview'),
9
+ model: openai('gpt-4o-mini'),
10
10
  events: {
11
11
  'agent.respond': z.object({
12
12
  response: z.string().describe('The response from the agent'),
@@ -22,7 +22,7 @@ const machine = setup({
22
22
  },
23
23
  },
24
24
  actors: {
25
- getFromTerminal,
25
+ getFromTerminal: fromTerminal,
26
26
  agent: fromDecision(agent),
27
27
  },
28
28
  }).createMachine({
@@ -69,7 +69,7 @@ const machine = setup({
69
69
  invoke: {
70
70
  src: 'agent',
71
71
  input: ({ context }) => ({
72
- model: openai('gpt-3.5-turbo-16k-0613'),
72
+ model: openai('gpt-4-turbo'),
73
73
  context,
74
74
  goal: 'Debate the topic, and take the negative position. Respond directly to the last message of the discourse. Keep it short.',
75
75
  }),
@@ -2,10 +2,13 @@
2
2
  // https://github.com/assafelovic/gpt-newspaper
3
3
  // https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c
4
4
 
5
- import { TavilySearchAPIRetriever } from '@langchain/community/retrievers/tavily_search_api';
6
- import { ChatOpenAI } from '@langchain/openai';
7
- import { HumanMessage, SystemMessage } from '@langchain/core/messages';
5
+ import { tavily } from '@tavily/core';
6
+
8
7
  import { assign, createActor, fromPromise, setup } from 'xstate';
8
+ import { createAgent } from '../src';
9
+ import { openai } from '@ai-sdk/openai';
10
+ import { z } from 'zod';
11
+ import { generateObject, generateText } from 'ai';
9
12
 
10
13
  interface AgentState {
11
14
  topic: string;
@@ -15,58 +18,41 @@ interface AgentState {
15
18
  revisionCount: number;
16
19
  }
17
20
 
18
- function model() {
19
- return new ChatOpenAI({
20
- temperature: 0,
21
- modelName: 'gpt-4-1106-preview',
22
- openAIApiKey: process.env.OPENAI_API_KEY,
23
- });
24
- }
21
+ const agent = createAgent({
22
+ model: openai('gpt-4o-mini'),
23
+ events: {},
24
+ });
25
25
 
26
- async function search({ topic }: Pick<AgentState, 'topic'>): Promise<string> {
27
- const retriever = new TavilySearchAPIRetriever({
28
- k: 10,
26
+ async function search({
27
+ topic,
28
+ }: Pick<AgentState, 'topic'>): Promise<string | undefined> {
29
+ const tvly = tavily({
29
30
  apiKey: process.env.TAVILY_API_KEY,
30
31
  });
31
- // let topic = state.agentState.topic;
32
- // must be at least 5 characters long
33
- if (topic.length < 5) {
34
- topic = 'topic: ' + topic;
35
- }
36
- const docs = await retriever.invoke(topic);
37
- return JSON.stringify(docs);
32
+ const response = await tvly.search(topic, {});
33
+
34
+ return response.answer;
38
35
  }
39
36
 
40
37
  async function curate(
41
38
  input: Pick<AgentState, 'topic' | 'searchResults'>
42
39
  ): Promise<string> {
43
- const response = await model().invoke(
44
- [
45
- new SystemMessage(
46
- `You are a personal newspaper editor.
47
- Your sole task is to return a list of URLs of the 5 most relevant articles for the provided topic or query as a JSON list of strings
48
- in this format:
49
- {
50
- urls: ["url1", "url2", "url3", "url4", "url5"]
51
- }
52
- .`.replace(/\s+/g, ' ')
53
- ),
54
- new HumanMessage(
55
- `Today's date is ${new Date().toLocaleDateString('en-GB')}.
56
- Topic or Query: ${input.topic}
57
-
58
- Here is a list of articles:
59
- ${input.searchResults}`.replace(/\s+/g, ' ')
60
- ),
61
- ],
62
- {
63
- response_format: {
64
- type: 'json_object',
65
- },
66
- }
67
- );
68
- const urls = JSON.parse(response.content as string).urls;
69
- const searchResults = JSON.parse(input.searchResults!);
40
+ const response = await generateObject({
41
+ model: agent.model,
42
+ system: `
43
+ You are a personal newspaper editor.
44
+ Your sole task is to return a list of URLs of the 5 most relevant articles for the provided topic or query as a JSON list of strings.`.trim(),
45
+ prompt: `Today's date is ${new Date().toLocaleDateString('en-GB')}
46
+ Topic or Query: ${input.topic}
47
+
48
+ Here is a list of articles:
49
+ ${input.searchResults}`.trim(),
50
+ schema: z.object({
51
+ urls: z.array(z.string()).describe('The URLs of the articles'),
52
+ }),
53
+ });
54
+ const urls = response.object.urls;
55
+ const searchResults = JSON.parse(input.searchResults ?? '[]');
70
56
  const newSearchResults = searchResults.filter((result: any) => {
71
57
  return urls.includes(result.metadata.source);
72
58
  });
@@ -78,29 +64,36 @@ async function critique(
78
64
  ): Promise<string | undefined> {
79
65
  let feedbackInstructions = '';
80
66
  if (input.critique) {
81
- feedbackInstructions =
82
- `The writer has revised the article based on your previous critique: ${input.critique}
83
- The writer might have left feedback for you encoded between <FEEDBACK> tags.
84
- The feedback is only for you to see and will be removed from the final article.
85
- `.replace(/\s+/g, ' ');
67
+ feedbackInstructions = `
68
+ The writer has revised the article based on your previous critique: ${input.critique}
69
+ The writer might have left feedback for you encoded between <FEEDBACK> tags.
70
+ The feedback is only for you to see and will be removed from the final article.
71
+ `.trim();
86
72
  }
87
- const response = await model().invoke([
88
- new SystemMessage(
89
- `You are a personal newspaper writing critique. Your sole purpose is to provide short feedback on a written
90
- article so the writer will know what to fix.
91
- Today's date is ${new Date().toLocaleDateString('en-GB')}
92
- Your task is to provide a really short feedback on the article only if necessary.
93
- if you think the article is good, please return [DONE].
94
- you can provide feedback on the revised article or just
95
- return [DONE] if you think the article is good.
96
- Please return a string of your critique or [DONE].`.replace(/\s+/g, ' ')
97
- ),
98
- new HumanMessage(
99
- `${feedbackInstructions}
100
- This is the article: ${input.article}`
101
- ),
102
- ]);
103
- const content = response.content as string;
73
+
74
+ const response = await generateObject({
75
+ model: agent.model,
76
+ system: `
77
+ You are a personal newspaper writing critique.
78
+ Your sole purpose is to provide short feedback on a written article so the writer will know what to fix.
79
+ Today's date is ${new Date().toLocaleDateString('en-GB')}
80
+ Your task is to provide a really short feedback on the article only if necessary.
81
+ If you think the article is good, please return [DONE].
82
+ You can provide feedback on the revised article or just return [DONE] if you think the article is good.
83
+ Please return a string of your critique or [DONE].`.trim(),
84
+ prompt: `
85
+ ${feedbackInstructions}
86
+ This is the article: ${input.article}`.trim(),
87
+ schema: z.object({
88
+ critique: z
89
+ .string()
90
+ .describe(
91
+ 'The critique of the article or [DONE] if no changes are needed'
92
+ ),
93
+ }),
94
+ });
95
+
96
+ const content = response.object.critique;
104
97
  console.log('critique:', content);
105
98
  return content.includes('[DONE]') ? undefined : content;
106
99
  }
@@ -108,48 +101,55 @@ async function critique(
108
101
  async function write(
109
102
  input: Pick<AgentState, 'searchResults' | 'topic'>
110
103
  ): Promise<string> {
111
- const response = await model().invoke([
112
- new SystemMessage(
104
+ const response = await generateObject({
105
+ model: agent.model,
106
+ system:
113
107
  `You are a personal newspaper writer. Your sole purpose is to write a well-written article about a
114
- topic using a list of articles. Write 5 paragraphs in markdown.`.replace(
115
- /\s+/g,
116
- ' '
117
- )
118
- ),
119
- new HumanMessage(
120
- `Today's date is ${new Date().toLocaleDateString('en-GB')}.
121
- Your task is to write a critically acclaimed article for me about the provided query or
122
- topic based on the sources.
123
- Here is a list of articles: ${input.searchResults}
124
- This is the topic: ${input.topic}
125
- Please return a well-written article based on the provided information.`.replace(
108
+ topic using a list of articles. Write 5 paragraphs in markdown.`.replace(
126
109
  /\s+/g,
127
110
  ' '
128
- )
111
+ ),
112
+ prompt: `Today's date is ${new Date().toLocaleDateString('en-GB')}.
113
+ Your task is to write a critically acclaimed article for me about the provided query or
114
+ topic based on the sources.
115
+ Here is a list of articles: ${input.searchResults}
116
+ This is the topic: ${input.topic}
117
+ Please return a well-written article based on the provided information.`.replace(
118
+ /\s+/g,
119
+ ' '
129
120
  ),
130
- ]);
131
- const content = response.content as string;
121
+ schema: z.object({
122
+ article: z
123
+ .string()
124
+ .describe('The well-written article based on the provided information'),
125
+ }),
126
+ });
127
+
128
+ const content = response.object.article;
132
129
  return content;
133
130
  }
134
-
135
131
  async function revise(
136
132
  input: Pick<AgentState, 'article' | 'critique'>
137
133
  ): Promise<string> {
138
- const response = await model().invoke([
139
- new SystemMessage(
134
+ const response = await generateObject({
135
+ model: agent.model,
136
+ system:
140
137
  `You are a personal newspaper editor. Your sole purpose is to edit a well-written article about a
141
- topic based on given critique.`.replace(/\s+/g, ' ')
142
- ),
143
- new HumanMessage(
144
- `Your task is to edit the article based on the critique given.
138
+ topic based on given critique.`.replace(/\s+/g, ' '),
139
+ prompt: `Your task is to edit the article based on the critique given.
145
140
  This is the article: ${input.article}
146
141
  This is the critique: ${input.critique}
147
142
  Please return the edited article based on the critique given.
148
143
  You may leave feedback about the critique encoded between <FEEDBACK> tags like this:
149
- <FEEDBACK> here goes the feedback ...</FEEDBACK>`.replace(/\s+/g, ' ')
150
- ),
151
- ]);
152
- const content = response.content as string;
144
+ <FEEDBACK> here goes the feedback ...</FEEDBACK>`.replace(/\s+/g, ' '),
145
+ schema: z.object({
146
+ article: z
147
+ .string()
148
+ .describe('The edited article based on the critique given'),
149
+ }),
150
+ });
151
+
152
+ const content = response.object.article;
153
153
  return content;
154
154
  }
155
155
 
@@ -284,13 +284,7 @@ const machine = setup({
284
284
  output: ({ context }) => context.article,
285
285
  });
286
286
 
287
- const actor = createActor(machine, {
288
- // inspect: (inspEv) => {
289
- // if (inspEv.type === '@xstate.event') {
290
- // console.log(JSON.stringify(inspEv.event, null, 2));
291
- // }
292
- // },
293
- });
287
+ const actor = createActor(machine);
294
288
 
295
289
  actor.subscribe({
296
290
  next: (s) => {
@@ -2,13 +2,14 @@ import { createAgent, fromDecision } from '../src';
2
2
  import { assign, createActor, log, setup } from 'xstate';
3
3
  import { z } from 'zod';
4
4
  import { openai } from '@ai-sdk/openai';
5
- import { getFromTerminal } from './helpers/helpers';
5
+ import { fromTerminal } from './helpers/helpers';
6
6
 
7
7
  const agent = createAgent({
8
8
  name: 'number-guesser',
9
9
  model: openai('gpt-3.5-turbo-1106'),
10
10
  events: {
11
11
  'agent.guess': z.object({
12
+ reasoning: z.string().describe('The reasoning for the guess'),
12
13
  number: z.number().min(1).max(10).describe('The number guessed'),
13
14
  }),
14
15
  },
@@ -24,7 +25,7 @@ const machine = setup({
24
25
  },
25
26
  actors: {
26
27
  agent: fromDecision(agent),
27
- getFromTerminal,
28
+ getFromTerminal: fromTerminal,
28
29
  },
29
30
  }).createMachine({
30
31
  context: {
@@ -39,7 +40,7 @@ const machine = setup({
39
40
  input: 'Enter a number between 1 and 10',
40
41
  onDone: {
41
42
  actions: assign({
42
- answer: (x) => +x.event.output,
43
+ answer: ({ event }) => +event.output,
43
44
  }),
44
45
  target: 'guessing',
45
46
  },
@@ -78,7 +79,7 @@ const machine = setup({
78
79
  event.number,
79
80
  ],
80
81
  }),
81
- log((x) => x.event.number),
82
+ log(({ event }) => `${event.number} (${event.reasoning})`),
82
83
  ],
83
84
  target: 'guessing',
84
85
  reenter: true,