@statelyai/agent 0.0.6 → 0.0.8

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/examples/joke.ts CHANGED
@@ -1,53 +1,37 @@
1
1
  import OpenAI from 'openai';
2
2
  import { assign, fromCallback, fromPromise, log, setup } from 'xstate';
3
- import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
3
+ import { createAgent, createOpenAIAdapter, defineEvents } from '../src';
4
4
  import { loadingAnimation } from './helpers/loader';
5
+ import { z } from 'zod';
5
6
 
6
7
  const openai = new OpenAI({
7
8
  apiKey: process.env.OPENAI_API_KEY,
8
9
  });
9
10
 
10
- const schemas = createSchemas({
11
- context: {
12
- type: 'object',
13
- properties: {
14
- topic: { type: 'string' },
15
- jokes: {
16
- type: 'array',
17
- items: {
18
- type: 'string',
19
- },
20
- },
21
- desire: { type: ['string', 'null'] },
22
- lastRating: { type: ['string', 'null'] },
23
- },
24
- required: ['topic', 'jokes', 'desire', 'lastRating'],
25
- },
26
- events: {
27
- askForTopic: {
28
- type: 'object',
29
- properties: {
30
- topic: {
31
- type: 'string',
32
- },
33
- },
34
- },
35
- endJokes: {
36
- type: 'object',
37
- properties: {},
38
- },
39
- },
11
+ const events = defineEvents({
12
+ askForTopic: z.object({
13
+ topic: z.string().describe('The topic for the joke'),
14
+ }),
15
+ tellJoke: z.object({
16
+ joke: z.string().describe('The joke text'),
17
+ }),
18
+ endJokes: z.object({}).describe('End the jokes'),
19
+
20
+ rateJoke: z.object({
21
+ rating: z.number().min(1).max(10),
22
+ explanation: z.string(),
23
+ }),
40
24
  });
41
25
 
42
26
  const adapter = createOpenAIAdapter(openai, {
43
27
  model: 'gpt-3.5-turbo-1106',
44
28
  });
45
29
 
46
- const getJokeCompletion = adapter.fromChat(
30
+ const getJokeCompletion = adapter.fromEvent(
47
31
  (topic: string) => `Tell me a joke about ${topic}.`
48
32
  );
49
33
 
50
- const rateJoke = adapter.fromChat(
34
+ const rateJoke = adapter.fromEvent(
51
35
  (joke: string) => `Rate this joke on a scale of 1 to 10: ${joke}`
52
36
  );
53
37
 
@@ -66,7 +50,7 @@ const getTopic = fromPromise(async () => {
66
50
  });
67
51
 
68
52
  const decide = adapter.fromEvent(
69
- (lastRating: string) =>
53
+ (lastRating: number) =>
70
54
  `Choose what to do next, given the previous rating of the joke: ${lastRating}`
71
55
  );
72
56
  export function getRandomFunnyPhrase() {
@@ -109,8 +93,19 @@ const loader = fromCallback(({ input }: { input: string }) => {
109
93
  });
110
94
 
111
95
  const jokeMachine = setup({
112
- schemas,
113
- types: schemas.types,
96
+ schemas: {
97
+ events: events.schemas,
98
+ },
99
+ types: {
100
+ context: {} as {
101
+ topic: string;
102
+ jokes: string[];
103
+ desire: string | null;
104
+ lastRating: number | null;
105
+ loader: string | null;
106
+ },
107
+ events: events.types,
108
+ },
114
109
  actors: {
115
110
  getJokeCompletion,
116
111
  getTopic,
@@ -119,6 +114,7 @@ const jokeMachine = setup({
119
114
  loader,
120
115
  },
121
116
  }).createMachine({
117
+ id: 'joke',
122
118
  context: () => ({
123
119
  topic: '',
124
120
  jokes: [],
@@ -144,54 +140,45 @@ const jokeMachine = setup({
144
140
  {
145
141
  src: 'getJokeCompletion',
146
142
  input: ({ context }) => context.topic,
147
- onDone: {
148
- actions: [
149
- assign({
150
- jokes: ({ context, event }) =>
151
- context.jokes.concat(
152
- event.output.choices[0]!.message.content!
153
- ),
154
- }),
155
- log((x) => `\n` + x.context.jokes.at(-1)),
156
- ],
157
- target: 'rateJoke',
158
- },
159
143
  },
160
144
  {
161
145
  src: 'loader',
162
146
  input: getRandomFunnyPhrase,
163
147
  },
164
148
  ],
149
+ on: {
150
+ tellJoke: {
151
+ actions: assign({
152
+ jokes: ({ context, event }) => [...context.jokes, event.joke],
153
+ }),
154
+ target: 'rateJoke',
155
+ },
156
+ },
165
157
  },
166
158
  rateJoke: {
167
159
  invoke: [
168
160
  {
169
161
  src: 'rateJoke',
170
162
  input: ({ context }) => context.jokes[context.jokes.length - 1]!,
171
- onDone: {
172
- actions: [
173
- assign({
174
- lastRating: ({ event }) =>
175
- event.output.choices[0]!.message.content!,
176
- }),
177
- log(({ context }) => '\n' + context.lastRating),
178
- ],
179
- target: 'decide',
180
- },
181
163
  },
182
164
  {
183
165
  src: 'loader',
184
166
  input: getRandomRatingPhrase,
185
167
  },
186
168
  ],
169
+ on: {
170
+ rateJoke: {
171
+ actions: assign({
172
+ lastRating: ({ event }) => event.rating,
173
+ }),
174
+ target: 'decide',
175
+ },
176
+ },
187
177
  },
188
178
  decide: {
189
179
  invoke: {
190
180
  src: 'decide',
191
181
  input: ({ context }) => context.lastRating!,
192
- onDone: {
193
- actions: log(({ event }) => event),
194
- },
195
182
  },
196
183
  on: {
197
184
  askForTopic: {
@@ -216,5 +203,11 @@ const jokeMachine = setup({
216
203
  },
217
204
  });
218
205
 
219
- const agent = createAgent(jokeMachine);
206
+ const agent = createAgent(jokeMachine, {
207
+ inspect: (ev) => {
208
+ if (ev.type === '@xstate.event') {
209
+ console.log(`\n${ev.actorRef.id}`, ev.event);
210
+ }
211
+ },
212
+ });
220
213
  agent.start();
@@ -1,6 +1,7 @@
1
1
  import OpenAI from 'openai';
2
- import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
2
+ import { createAgent, createOpenAIAdapter, defineEvents } from '../src';
3
3
  import { assign, setup } from 'xstate';
4
+ import { z } from 'zod';
4
5
  const openai = new OpenAI({
5
6
  apiKey: process.env.OPENAI_API_KEY,
6
7
  });
@@ -23,40 +24,10 @@ const guessLogic = adapter.fromEvent(
23
24
  `
24
25
  );
25
26
 
26
- const schemas = createSchemas({
27
- context: {
28
- type: 'object',
29
- properties: {
30
- lastGuess: {
31
- type: ['number', 'null'],
32
- description: 'The last guess',
33
- },
34
- previousGuesses: {
35
- type: 'array',
36
- items: {
37
- type: 'number',
38
- },
39
- description: 'The previous guesses',
40
- },
41
- answer: {
42
- type: 'number',
43
- description: 'The answer',
44
- },
45
- },
46
- },
47
- events: {
48
- guess: {
49
- properties: {
50
- number: {
51
- // integer
52
- type: 'number',
53
- minimum: 1,
54
- maximum: 10,
55
- },
56
- },
57
- required: ['number'],
58
- },
59
- },
27
+ const events = defineEvents({
28
+ guess: z.object({
29
+ number: z.number().min(1).max(10).describe('The number guessed'),
30
+ }),
60
31
  });
61
32
 
62
33
  const machine = setup({
@@ -66,9 +37,11 @@ const machine = setup({
66
37
  answer: number;
67
38
  },
68
39
  input: {} as { answer: number },
69
- events: schemas.types.events,
40
+ events: events.types,
41
+ },
42
+ schemas: {
43
+ events: events.schemas,
70
44
  },
71
- schemas,
72
45
  actors: {
73
46
  guessLogic,
74
47
  },
@@ -1,6 +1,8 @@
1
1
  import { assign, setup, assertEvent } from 'xstate';
2
2
  import OpenAI from 'openai';
3
- import { createOpenAIAdapter, createSchemas, createAgent } from '../src';
3
+ import { z } from 'zod';
4
+ import { zodToJsonSchema } from 'zod-to-json-schema';
5
+ import { createOpenAIAdapter, defineEvents, createAgent } from '../src';
4
6
 
5
7
  const openai = new OpenAI({
6
8
  apiKey: process.env.OPENAI_API_KEY,
@@ -8,70 +10,32 @@ const openai = new OpenAI({
8
10
 
9
11
  type Player = 'x' | 'o';
10
12
 
11
- const schemas = createSchemas({
12
- context: {
13
- type: 'object',
14
- properties: {
15
- board: {
16
- type: 'array',
17
- items: {
18
- type: ['null', 'string'],
19
- enum: [null, 'x', 'o'],
20
- },
21
- minItems: 9,
22
- maxItems: 9,
23
- description: 'The board of the tic-tac-toe game',
24
- },
25
- moves: {
26
- type: 'number',
27
- description: 'The number of moves that have been played',
28
- },
29
- player: {
30
- type: 'string',
31
- enum: ['x', 'o'],
32
- description: 'The player whose turn it is',
33
- },
34
- gameReport: {
35
- type: 'string',
36
- description: 'The game report',
37
- },
38
- events: {
39
- type: 'array',
40
- items: {
41
- type: 'string',
42
- },
43
- },
44
- },
45
- required: ['board', 'moves', 'player', 'gameReport', 'events'],
46
- },
47
- events: {
48
- 'x.play': {
49
- properties: {
50
- index: {
51
- description: 'The index of the cell to play on',
52
- type: 'number',
53
-
54
- minimum: 0,
55
- maximum: 8,
56
- },
57
- },
58
- },
59
- 'o.play': {
60
- properties: {
61
- index: {
62
- description: 'The index of the cell to play on',
63
- type: 'number',
64
- minimum: 0,
65
- maximum: 8,
66
- },
67
- },
68
- },
69
- reset: {
70
- properties: {},
71
- },
72
- },
13
+ const events = defineEvents({
14
+ 'x.play': z.object({
15
+ index: z
16
+ .number()
17
+ .min(0)
18
+ .max(8)
19
+ .describe('The index of the cell to play on'),
20
+ }),
21
+ 'o.play': z.object({
22
+ index: z
23
+ .number()
24
+ .min(0)
25
+ .max(8)
26
+ .describe('The index of the cell to play on'),
27
+ }),
28
+ reset: z.object({}).describe('Reset the game to the initial state'),
73
29
  });
74
30
 
31
+ interface GameContext {
32
+ board: (Player | null)[];
33
+ moves: number;
34
+ player: Player;
35
+ gameReport: string;
36
+ events: string[];
37
+ }
38
+
75
39
  const adapter = createOpenAIAdapter(openai, {
76
40
  model: 'gpt-4-1106-preview',
77
41
  });
@@ -82,10 +46,10 @@ const initialContext = {
82
46
  player: 'x' as Player,
83
47
  gameReport: '',
84
48
  events: [],
85
- } satisfies typeof schemas.types.context;
49
+ } satisfies GameContext;
86
50
 
87
51
  const bot = adapter.fromEvent(
88
- ({ context }: { context: typeof schemas.types.context }) => `
52
+ ({ context }: { context: GameContext }) => `
89
53
  You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty. The value of x means that the cell is occupied by an x. The value of o means that the cell is occupied by an o.
90
54
 
91
55
  ${JSON.stringify(context, null, 2)}
@@ -94,11 +58,7 @@ Execute the single best next move to try to win the game. Do not play on an exis
94
58
  );
95
59
 
96
60
  const gameReporter = adapter.fromChatStream(
97
- ({
98
- context,
99
- }: {
100
- context: typeof schemas.types.context;
101
- }) => `Here is the game board:
61
+ ({ context }: { context: GameContext }) => `Here is the game board:
102
62
 
103
63
  ${JSON.stringify(context.board, null, 2)}
104
64
 
@@ -131,8 +91,13 @@ function getWinner(board: typeof initialContext.board): Player | null {
131
91
  }
132
92
 
133
93
  export const ticTacToeMachine = setup({
134
- schemas,
135
- types: schemas.types,
94
+ schemas: {
95
+ events: events.schemas,
96
+ },
97
+ types: {
98
+ context: {} as GameContext,
99
+ events: events.types,
100
+ },
136
101
  actors: {
137
102
  bot,
138
103
  gameReporter,
@@ -1,7 +1,8 @@
1
1
  import OpenAI from 'openai';
2
- import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
2
+ import { createAgent, createOpenAIAdapter, defineEvents } from '../src';
3
3
  import { assign, fromPromise, log, setup } from 'xstate';
4
4
  import { getFromTerminal } from './helpers/helpers';
5
+ import { z } from 'zod';
5
6
 
6
7
  async function searchTavily(
7
8
  input: string,
@@ -40,28 +41,21 @@ const openai = new OpenAI({
40
41
  apiKey: process.env.OPENAI_API_KEY,
41
42
  });
42
43
 
43
- const schemas = createSchemas({
44
- context: {
45
- location: { type: 'string' },
46
- history: { type: 'array', items: { type: 'string' } },
47
- count: { type: 'number' },
48
- },
49
- events: {
50
- getWeather: {
51
- description: 'Get the weather for a location',
52
- properties: {
53
- location: {
54
- type: 'string',
55
- description: 'The location to get the weather for',
56
- },
57
- },
58
- },
59
- doSomethingElse: {
60
- description:
61
- 'Do something else, because the user did not provide a location',
62
- properties: {},
63
- },
64
- },
44
+ const events = defineEvents({
45
+ getWeather: z.object({
46
+ location: z.string().describe('The location to get the weather for'),
47
+ }),
48
+ reportWeather: z.object({
49
+ location: z
50
+ .string()
51
+ .describe('The location the weather is being reported for'),
52
+ highF: z.number().describe('The high temperature today in Fahrenheit'),
53
+ lowF: z.number().describe('The low temperature today in Fahrenheit'),
54
+ summary: z.string().describe('A summary of the weather conditions'),
55
+ }),
56
+ doSomethingElse: z
57
+ .object({})
58
+ .describe('Do something else, because the user did not provide a location'),
65
59
  });
66
60
 
67
61
  const adapter = createOpenAIAdapter(openai, {
@@ -79,11 +73,23 @@ const getWeather = fromPromise(async ({ input }: { input: string }) => {
79
73
  return results;
80
74
  });
81
75
 
76
+ const reportWeather = adapter.fromEvent(() => 'Report the weather');
77
+
82
78
  const machine = setup({
83
- schemas,
84
- types: schemas.types,
79
+ schemas: {
80
+ events: events.schemas,
81
+ },
82
+ types: {
83
+ context: {} as {
84
+ location: string;
85
+ history: string[];
86
+ count: number;
87
+ },
88
+ events: events.types,
89
+ },
85
90
  actors: {
86
91
  getWeather,
92
+ reportWeather,
87
93
  decide: adapter.fromEvent(
88
94
  (input: string) =>
89
95
  `Decide what to do based on the given input, which may or may not be a location: ${input}`
@@ -140,6 +146,17 @@ const machine = setup({
140
146
  count: ({ context }) => context.count + 1,
141
147
  }),
142
148
  ],
149
+ target: 'reportWeather',
150
+ },
151
+ },
152
+ },
153
+ reportWeather: {
154
+ invoke: {
155
+ src: 'reportWeather',
156
+ },
157
+ on: {
158
+ reportWeather: {
159
+ actions: log(({ event }) => event),
143
160
  target: 'getLocation',
144
161
  },
145
162
  },
@@ -153,8 +170,12 @@ const machine = setup({
153
170
  },
154
171
  });
155
172
 
156
- createAgent(machine, {
173
+ const actor = createAgent(machine, {
157
174
  input: {
158
175
  location: 'New York',
159
176
  },
160
- }).start();
177
+ });
178
+ actor.subscribe((s) => {
179
+ console.log(s.value);
180
+ });
181
+ actor.start();
@@ -0,0 +1,144 @@
1
+ import { assign, log, setup } from 'xstate';
2
+ import { getFromTerminal } from './helpers/helpers';
3
+ import { createAgent, createOpenAIAdapter, defineEvents } from '../src';
4
+ import OpenAI from 'openai';
5
+ import { z } from 'zod';
6
+
7
+ const openAI = new OpenAI({
8
+ apiKey: process.env.OPENAI_API_KEY,
9
+ });
10
+
11
+ const adapter = createOpenAIAdapter(openAI, {
12
+ model: 'gpt-4-1106-preview',
13
+ });
14
+
15
+ const events = defineEvents({
16
+ guessLetter: z.object({
17
+ letter: z.string().min(1).max(1).describe('The letter guessed'),
18
+ }),
19
+
20
+ guessWord: z.object({
21
+ word: z.string().describe('The word guessed'),
22
+ }),
23
+ });
24
+
25
+ const context = {
26
+ word: null as string | null,
27
+ guessedWord: null as string | null,
28
+ letters: [] as string[],
29
+ };
30
+
31
+ const wordGuesserMachine = setup({
32
+ types: {
33
+ context: {} as typeof context,
34
+ events: events.types,
35
+ },
36
+ actors: {
37
+ getFromTerminal,
38
+ guesser: adapter.fromEvent(
39
+ (input: typeof context) => `
40
+ You are trying to guess the word. The word has ${
41
+ input.word!.length
42
+ } letters. You have guessed the following letters so far: ${input.letters.join(
43
+ ', '
44
+ )}. These letters matched: ${input
45
+ .word!.split('')
46
+ .map((letter) =>
47
+ input.letters.includes(letter.toUpperCase())
48
+ ? letter.toUpperCase()
49
+ : '_'
50
+ )
51
+ .join('')}
52
+ Please make your next guess - type a letter or the full word. You can only make 10 total guesses.
53
+ `
54
+ ),
55
+ },
56
+ schemas: {
57
+ events: events.schemas,
58
+ },
59
+ }).createMachine({
60
+ initial: 'providingWord',
61
+ context,
62
+ states: {
63
+ providingWord: {
64
+ invoke: {
65
+ src: 'getFromTerminal',
66
+ input: 'Enter a word',
67
+ onDone: {
68
+ actions: assign({
69
+ word: ({ event }) => event.output,
70
+ }),
71
+ target: 'guessing',
72
+ },
73
+ },
74
+ },
75
+ guessing: {
76
+ always: {
77
+ guard: ({ context }) => context.letters.length > 10,
78
+ target: 'finalGuess',
79
+ },
80
+ invoke: {
81
+ src: 'guesser',
82
+ input: ({ context }) => context,
83
+ },
84
+ on: {
85
+ guessLetter: {
86
+ actions: assign({
87
+ letters: ({ context, event }) => {
88
+ return [...context.letters, event.letter.toUpperCase()];
89
+ },
90
+ }),
91
+ target: 'guessing',
92
+ reenter: true,
93
+ },
94
+ guessWord: {
95
+ actions: assign({
96
+ guessedWord: ({ event }) => event.word,
97
+ }),
98
+ target: 'gameOver',
99
+ },
100
+ },
101
+ },
102
+ finalGuess: {
103
+ invoke: {
104
+ src: 'guesser',
105
+ input: ({ context }) => context,
106
+ },
107
+ on: {
108
+ guessWord: {
109
+ actions: assign({
110
+ guessedWord: ({ event }) => event.word,
111
+ }),
112
+ target: 'gameOver',
113
+ },
114
+ },
115
+ },
116
+ gameOver: {
117
+ entry: log(({ context }) => {
118
+ if (
119
+ context.guessedWord?.toUpperCase() === context.word?.toUpperCase()
120
+ ) {
121
+ return 'You won!';
122
+ } else {
123
+ return 'You lost! The word was ' + context.word;
124
+ }
125
+ }),
126
+ },
127
+ },
128
+ exit: () => process.exit(),
129
+ });
130
+
131
+ const actor = createAgent(wordGuesserMachine, {
132
+ inspect: (ev) => {
133
+ if (ev.type === '@xstate.event') {
134
+ console.log(ev.event);
135
+ }
136
+ },
137
+ });
138
+
139
+ actor.subscribe((s) => {
140
+ console.log(s.value);
141
+ console.log(s.context);
142
+ });
143
+
144
+ actor.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/agent",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -18,7 +18,9 @@
18
18
  "ts-node": "^10.9.2",
19
19
  "tsup": "^8.0.1",
20
20
  "typescript": "^5.3.3",
21
- "vitest": "^1.2.2"
21
+ "vitest": "^1.2.2",
22
+ "zod": "^3.22.4",
23
+ "zod-to-json-schema": "^3.22.4"
22
24
  },
23
25
  "publishConfig": {
24
26
  "access": "public"
@@ -29,7 +31,7 @@
29
31
  "packageManager": "pnpm@8.11.0",
30
32
  "scripts": {
31
33
  "build": "tsup src/index.ts --format cjs,esm --dts",
32
- "lint": "tsc",
34
+ "lint": "tsc --noEmit",
33
35
  "test": "vitest",
34
36
  "example": "ts-node examples/helpers/runner.ts",
35
37
  "changeset": "changeset",