@statelyai/agent 0.0.2 → 0.0.4
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/.changeset/config.json +1 -1
- package/.env.template +6 -0
- package/.github/actions/ci-setup/action.yml +24 -0
- package/.github/workflows/release.yml +35 -0
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +38 -44
- package/dist/index.js +64 -39
- package/examples/helpers/helpers.ts +17 -0
- package/examples/helpers/loader.ts +32 -0
- package/examples/helpers/runner.ts +27 -0
- package/examples/joke.ts +121 -56
- package/examples/multiAgentCollaboration.ts +0 -0
- package/examples/ticTacToe.ts +41 -66
- package/examples/weather.ts +160 -0
- package/package.json +14 -6
- package/readme.md +41 -0
- package/src/{openai.ts → adapters/openai.ts} +68 -58
- package/src/agent.ts +8 -0
- package/src/index.ts +3 -6
- package/src/schemas.ts +38 -0
- package/dist/index.d.mts +0 -3
- package/dist/index.mjs +0 -7
package/examples/joke.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { assign,
|
|
3
|
-
import { createAgent } from '../src';
|
|
2
|
+
import { assign, fromCallback, fromPromise, log, setup } from 'xstate';
|
|
3
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
4
|
+
import { loadingAnimation } from './helpers/loader';
|
|
4
5
|
|
|
5
6
|
const openai = new OpenAI({
|
|
6
7
|
apiKey: process.env.OPENAI_API_KEY,
|
|
7
8
|
});
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
model: 'gpt-3.5-turbo-1106',
|
|
10
|
+
const schemas = createSchemas({
|
|
11
11
|
context: {
|
|
12
12
|
topic: { type: 'string' },
|
|
13
13
|
jokes: {
|
|
@@ -15,24 +15,41 @@ const agent = createAgent(openai, {
|
|
|
15
15
|
items: {
|
|
16
16
|
type: 'string',
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
},
|
|
19
|
+
desire: { type: ['string', 'null'] as const },
|
|
20
|
+
lastRating: { type: ['string', 'null'] as const },
|
|
21
|
+
},
|
|
22
|
+
events: {
|
|
23
|
+
askForTopic: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
topic: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
endJokes: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {},
|
|
20
34
|
},
|
|
21
35
|
},
|
|
22
|
-
events: {},
|
|
23
36
|
});
|
|
24
37
|
|
|
25
|
-
const
|
|
38
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
39
|
+
model: 'gpt-3.5-turbo-1106',
|
|
40
|
+
});
|
|
26
41
|
|
|
27
|
-
const getJokeCompletion =
|
|
42
|
+
const getJokeCompletion = adapter.fromChat(
|
|
43
|
+
(topic: string) => `Tell me a joke about ${topic}.`
|
|
44
|
+
);
|
|
28
45
|
|
|
29
|
-
const rateJoke =
|
|
46
|
+
const rateJoke = adapter.fromChat(
|
|
30
47
|
(joke: string) => `Rate this joke on a scale of 1 to 10: ${joke}`
|
|
31
48
|
);
|
|
32
49
|
|
|
33
50
|
const getTopic = fromPromise(async () => {
|
|
34
51
|
const topic = await new Promise<string>((res) => {
|
|
35
|
-
console.log('Give me a topic:
|
|
52
|
+
console.log('Give me a joke topic:');
|
|
36
53
|
const listener = (data: Buffer) => {
|
|
37
54
|
const result = data.toString().trim();
|
|
38
55
|
process.stdin.off('data', listener);
|
|
@@ -44,33 +61,66 @@ const getTopic = fromPromise(async () => {
|
|
|
44
61
|
return topic;
|
|
45
62
|
});
|
|
46
63
|
|
|
47
|
-
const decide =
|
|
64
|
+
const decide = adapter.fromEventChoice(
|
|
48
65
|
(lastRating: string) =>
|
|
49
66
|
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
50
67
|
);
|
|
68
|
+
export function getRandomFunnyPhrase() {
|
|
69
|
+
const funnyPhrases = [
|
|
70
|
+
'Concocting chuckles...',
|
|
71
|
+
'Brewing belly laughs...',
|
|
72
|
+
'Fabricating funnies...',
|
|
73
|
+
'Assembling amusement...',
|
|
74
|
+
'Molding merriment...',
|
|
75
|
+
'Whipping up wisecracks...',
|
|
76
|
+
'Generating guffaws...',
|
|
77
|
+
'Inventing hilarity...',
|
|
78
|
+
'Cultivating chortles...',
|
|
79
|
+
'Hatching howlers...',
|
|
80
|
+
];
|
|
81
|
+
return funnyPhrases[Math.floor(Math.random() * funnyPhrases.length)]!;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getRandomRatingPhrase() {
|
|
85
|
+
const ratingPhrases = [
|
|
86
|
+
'Assessing amusement...',
|
|
87
|
+
'Evaluating hilarity...',
|
|
88
|
+
'Ranking chuckles...',
|
|
89
|
+
'Classifying cackles...',
|
|
90
|
+
'Scoring snickers...',
|
|
91
|
+
'Rating roars...',
|
|
92
|
+
'Judging jollity...',
|
|
93
|
+
'Measuring merriment...',
|
|
94
|
+
'Rating rib-ticklers...',
|
|
95
|
+
];
|
|
96
|
+
return ratingPhrases[Math.floor(Math.random() * ratingPhrases.length)]!;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const loader = fromCallback(({ input }: { input: string }) => {
|
|
100
|
+
const anim = loadingAnimation(input);
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
anim.stop();
|
|
104
|
+
};
|
|
105
|
+
});
|
|
51
106
|
|
|
52
107
|
const jokeMachine = setup({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
topic: string;
|
|
56
|
-
jokes: string[];
|
|
57
|
-
desire: string | null;
|
|
58
|
-
lastRating: string | null;
|
|
59
|
-
},
|
|
60
|
-
input: {} as { topic: string },
|
|
61
|
-
},
|
|
108
|
+
schemas,
|
|
109
|
+
types: schemas.types,
|
|
62
110
|
actors: {
|
|
63
111
|
getJokeCompletion,
|
|
64
112
|
getTopic,
|
|
65
113
|
rateJoke,
|
|
66
114
|
decide,
|
|
115
|
+
loader,
|
|
67
116
|
},
|
|
68
117
|
}).createMachine({
|
|
69
|
-
context: (
|
|
70
|
-
topic:
|
|
118
|
+
context: () => ({
|
|
119
|
+
topic: '',
|
|
71
120
|
jokes: [],
|
|
72
121
|
desire: null,
|
|
73
122
|
lastRating: null,
|
|
123
|
+
loader: null,
|
|
74
124
|
}),
|
|
75
125
|
initial: 'waitingForTopic',
|
|
76
126
|
states: {
|
|
@@ -86,56 +136,69 @@ const jokeMachine = setup({
|
|
|
86
136
|
},
|
|
87
137
|
},
|
|
88
138
|
tellingJoke: {
|
|
89
|
-
invoke:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
139
|
+
invoke: [
|
|
140
|
+
{
|
|
141
|
+
src: 'getJokeCompletion',
|
|
142
|
+
input: ({ context }) => context.topic,
|
|
143
|
+
onDone: {
|
|
144
|
+
actions: [
|
|
145
|
+
assign({
|
|
146
|
+
jokes: ({ context, event }) =>
|
|
147
|
+
context.jokes.concat(
|
|
148
|
+
event.output.choices[0]!.message.content!
|
|
149
|
+
),
|
|
150
|
+
}),
|
|
151
|
+
log((x) => `\n` + x.context.jokes.at(-1)),
|
|
152
|
+
],
|
|
153
|
+
target: 'rateJoke',
|
|
154
|
+
},
|
|
101
155
|
},
|
|
102
|
-
|
|
156
|
+
{
|
|
157
|
+
src: 'loader',
|
|
158
|
+
input: getRandomFunnyPhrase,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
103
161
|
},
|
|
104
162
|
rateJoke: {
|
|
105
|
-
invoke:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
event
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
163
|
+
invoke: [
|
|
164
|
+
{
|
|
165
|
+
src: 'rateJoke',
|
|
166
|
+
input: ({ context }) => context.jokes[context.jokes.length - 1]!,
|
|
167
|
+
onDone: {
|
|
168
|
+
actions: [
|
|
169
|
+
assign({
|
|
170
|
+
lastRating: ({ event }) =>
|
|
171
|
+
event.output.choices[0]!.message.content!,
|
|
172
|
+
}),
|
|
173
|
+
log(({ context }) => '\n' + context.lastRating),
|
|
174
|
+
],
|
|
175
|
+
target: 'decide',
|
|
176
|
+
},
|
|
117
177
|
},
|
|
118
|
-
|
|
178
|
+
{
|
|
179
|
+
src: 'loader',
|
|
180
|
+
input: getRandomRatingPhrase,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
119
183
|
},
|
|
120
184
|
decide: {
|
|
121
185
|
invoke: {
|
|
122
186
|
src: 'decide',
|
|
123
187
|
input: ({ context }) => context.lastRating!,
|
|
124
188
|
onDone: {
|
|
125
|
-
actions:
|
|
126
|
-
log(({ event }) => event),
|
|
127
|
-
raise(({ event }) => event.output![0]!),
|
|
128
|
-
],
|
|
189
|
+
actions: log(({ event }) => event),
|
|
129
190
|
},
|
|
130
191
|
},
|
|
131
192
|
on: {
|
|
132
193
|
askForTopic: {
|
|
133
194
|
target: 'waitingForTopic',
|
|
195
|
+
actions: log("That joke wasn't good enough. Let's try again."),
|
|
134
196
|
description:
|
|
135
197
|
'Ask for a new topic, because the last joke rated 6 or lower',
|
|
136
198
|
},
|
|
137
199
|
endJokes: {
|
|
138
200
|
target: 'end',
|
|
201
|
+
actions: log('That joke was good enough. Goodbye!'),
|
|
139
202
|
description: 'End the jokes, since the last joke rated 7 or higher',
|
|
140
203
|
},
|
|
141
204
|
},
|
|
@@ -144,8 +207,10 @@ const jokeMachine = setup({
|
|
|
144
207
|
type: 'final',
|
|
145
208
|
},
|
|
146
209
|
},
|
|
210
|
+
exit: () => {
|
|
211
|
+
process.exit();
|
|
212
|
+
},
|
|
147
213
|
});
|
|
148
214
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
actor.start();
|
|
215
|
+
const agent = createAgent(jokeMachine);
|
|
216
|
+
agent.start();
|
|
File without changes
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { assign, setup, assertEvent
|
|
1
|
+
import { assign, setup, assertEvent } from 'xstate';
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
|
-
import { createAgent } from '../src
|
|
3
|
+
import { createOpenAIAdapter, createSchemas, createAgent } from '../src';
|
|
4
4
|
|
|
5
5
|
const openai = new OpenAI({
|
|
6
6
|
apiKey: process.env.OPENAI_API_KEY,
|
|
@@ -8,8 +8,7 @@ const openai = new OpenAI({
|
|
|
8
8
|
|
|
9
9
|
type Player = 'x' | 'o';
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
model: 'gpt-3.5-turbo-1106',
|
|
11
|
+
const schemas = createSchemas({
|
|
13
12
|
context: {
|
|
14
13
|
board: {
|
|
15
14
|
type: 'array',
|
|
@@ -30,11 +29,6 @@ const agent = createAgent(openai, {
|
|
|
30
29
|
enum: ['x', 'o'],
|
|
31
30
|
description: 'The player whose turn it is',
|
|
32
31
|
},
|
|
33
|
-
winner: {
|
|
34
|
-
type: ['null', 'string'],
|
|
35
|
-
enum: [null, 'x', 'o'],
|
|
36
|
-
description: 'The player who won the game',
|
|
37
|
-
},
|
|
38
32
|
gameReport: {
|
|
39
33
|
type: 'string',
|
|
40
34
|
description: 'The game report',
|
|
@@ -74,17 +68,20 @@ const agent = createAgent(openai, {
|
|
|
74
68
|
},
|
|
75
69
|
});
|
|
76
70
|
|
|
71
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
72
|
+
model: 'gpt-4-1106-preview',
|
|
73
|
+
});
|
|
74
|
+
|
|
77
75
|
const initialContext = {
|
|
78
76
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
79
77
|
moves: 0,
|
|
80
78
|
player: 'x' as Player,
|
|
81
|
-
winner: null as Player | null,
|
|
82
79
|
gameReport: '',
|
|
83
80
|
events: [],
|
|
84
|
-
} satisfies typeof
|
|
81
|
+
} satisfies typeof schemas.types.context;
|
|
85
82
|
|
|
86
|
-
const bot =
|
|
87
|
-
({ context }: { context: typeof
|
|
83
|
+
const bot = adapter.fromEventChoice(
|
|
84
|
+
({ context }: { context: typeof schemas.types.context }) => `
|
|
88
85
|
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.
|
|
89
86
|
|
|
90
87
|
${JSON.stringify(context, null, 2)}
|
|
@@ -92,14 +89,12 @@ ${JSON.stringify(context, null, 2)}
|
|
|
92
89
|
Execute the single best next move to try to win the game. Do not play on an existing cell.`
|
|
93
90
|
);
|
|
94
91
|
|
|
95
|
-
const gameReporter =
|
|
92
|
+
const gameReporter = adapter.fromChatStream(
|
|
96
93
|
({
|
|
97
94
|
context,
|
|
98
95
|
}: {
|
|
99
|
-
context: typeof
|
|
100
|
-
}) => `
|
|
101
|
-
context.winner ?? 'nobody'
|
|
102
|
-
}. This was the ending board state, represented as a 9-element array:
|
|
96
|
+
context: typeof schemas.types.context;
|
|
97
|
+
}) => `Here is the game board:
|
|
103
98
|
|
|
104
99
|
${JSON.stringify(context.board, null, 2)}
|
|
105
100
|
|
|
@@ -107,11 +102,33 @@ And here are the events that led to this game state:
|
|
|
107
102
|
|
|
108
103
|
${context.events.join('\n')}
|
|
109
104
|
|
|
105
|
+
The winner is ${getWinner(context.board)}.
|
|
106
|
+
|
|
110
107
|
Provide a very short game report analyzing the game.`
|
|
111
108
|
);
|
|
112
109
|
|
|
110
|
+
function getWinner(board: typeof initialContext.board): Player | null {
|
|
111
|
+
const lines = [
|
|
112
|
+
[0, 1, 2],
|
|
113
|
+
[3, 4, 5],
|
|
114
|
+
[6, 7, 8],
|
|
115
|
+
[0, 3, 6],
|
|
116
|
+
[1, 4, 7],
|
|
117
|
+
[2, 5, 8],
|
|
118
|
+
[0, 4, 8],
|
|
119
|
+
[2, 4, 6],
|
|
120
|
+
] as const;
|
|
121
|
+
for (const [a, b, c] of lines) {
|
|
122
|
+
if (board[a] !== null && board[a] === board[b] && board[a] === board[c]) {
|
|
123
|
+
return board[a]!;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
export const ticTacToeMachine = setup({
|
|
114
|
-
|
|
130
|
+
schemas,
|
|
131
|
+
types: schemas.types,
|
|
115
132
|
actors: {
|
|
116
133
|
bot,
|
|
117
134
|
gameReporter,
|
|
@@ -131,9 +148,6 @@ export const ticTacToeMachine = setup({
|
|
|
131
148
|
},
|
|
132
149
|
}),
|
|
133
150
|
resetGame: assign(initialContext),
|
|
134
|
-
setWinner: assign({
|
|
135
|
-
winner: ({ context }) => (context.player === 'x' ? 'o' : 'x'),
|
|
136
|
-
}),
|
|
137
151
|
recordEvent: assign({
|
|
138
152
|
events: ({ context, event }) => {
|
|
139
153
|
return [...context.events, JSON.stringify(event)];
|
|
@@ -142,37 +156,9 @@ export const ticTacToeMachine = setup({
|
|
|
142
156
|
},
|
|
143
157
|
guards: {
|
|
144
158
|
checkWin: ({ context }) => {
|
|
145
|
-
const
|
|
146
|
-
const winningLines = [
|
|
147
|
-
[0, 1, 2],
|
|
148
|
-
[3, 4, 5],
|
|
149
|
-
[6, 7, 8],
|
|
150
|
-
[0, 3, 6],
|
|
151
|
-
[1, 4, 7],
|
|
152
|
-
[2, 5, 8],
|
|
153
|
-
[0, 4, 8],
|
|
154
|
-
[2, 4, 6],
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
for (let line of winningLines) {
|
|
158
|
-
const xWon = line.every((index) => {
|
|
159
|
-
return board[index] === 'x';
|
|
160
|
-
});
|
|
159
|
+
const winner = getWinner(context.board);
|
|
161
160
|
|
|
162
|
-
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const oWon = line.every((index) => {
|
|
167
|
-
return board[index] === 'o';
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
if (oWon) {
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return false;
|
|
161
|
+
return !!winner;
|
|
176
162
|
},
|
|
177
163
|
checkDraw: ({ context }) => {
|
|
178
164
|
return context.moves === 9;
|
|
@@ -202,11 +188,6 @@ export const ticTacToeMachine = setup({
|
|
|
202
188
|
invoke: {
|
|
203
189
|
src: 'bot',
|
|
204
190
|
input: ({ context }) => ({ context }),
|
|
205
|
-
onDone: {
|
|
206
|
-
actions: raise(({ event }) => {
|
|
207
|
-
return event.output![0] as any;
|
|
208
|
-
}),
|
|
209
|
-
},
|
|
210
191
|
},
|
|
211
192
|
on: {
|
|
212
193
|
'x.play': [
|
|
@@ -223,11 +204,6 @@ export const ticTacToeMachine = setup({
|
|
|
223
204
|
invoke: {
|
|
224
205
|
src: 'bot',
|
|
225
206
|
input: ({ context }) => ({ context }),
|
|
226
|
-
onDone: {
|
|
227
|
-
actions: raise(({ event }) => {
|
|
228
|
-
return event.output![0]!;
|
|
229
|
-
}),
|
|
230
|
-
},
|
|
231
207
|
},
|
|
232
208
|
on: {
|
|
233
209
|
'o.play': [
|
|
@@ -261,7 +237,6 @@ export const ticTacToeMachine = setup({
|
|
|
261
237
|
states: {
|
|
262
238
|
winner: {
|
|
263
239
|
tags: 'winner',
|
|
264
|
-
entry: 'setWinner',
|
|
265
240
|
},
|
|
266
241
|
draw: {
|
|
267
242
|
tags: 'draw',
|
|
@@ -277,8 +252,8 @@ export const ticTacToeMachine = setup({
|
|
|
277
252
|
},
|
|
278
253
|
});
|
|
279
254
|
|
|
280
|
-
const
|
|
281
|
-
|
|
255
|
+
const agent = createAgent(ticTacToeMachine);
|
|
256
|
+
agent.subscribe((s) => {
|
|
282
257
|
console.log(s.value, s.context);
|
|
283
258
|
});
|
|
284
|
-
|
|
259
|
+
agent.start();
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
3
|
+
import { assign, fromPromise, log, setup } from 'xstate';
|
|
4
|
+
import { getFromTerminal } from './helpers/helpers';
|
|
5
|
+
|
|
6
|
+
async function searchTavily(
|
|
7
|
+
input: string,
|
|
8
|
+
options: {
|
|
9
|
+
maxResults?: number;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
}
|
|
12
|
+
) {
|
|
13
|
+
const body: Record<string, unknown> = {
|
|
14
|
+
query: input,
|
|
15
|
+
max_results: options.maxResults,
|
|
16
|
+
api_key: options.apiKey,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const response = await fetch('https://api.tavily.com/search', {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'content-type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const json = await response.json();
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Request failed with status code ${response.status}: ${json.error}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (!Array.isArray(json.results)) {
|
|
34
|
+
throw new Error(`Could not parse Tavily results. Please try again.`);
|
|
35
|
+
}
|
|
36
|
+
return JSON.stringify(json.results);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const openai = new OpenAI({
|
|
40
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
41
|
+
});
|
|
42
|
+
|
|
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
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
68
|
+
model: 'gpt-4-1106-preview',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const getWeather = fromPromise(async ({ input }: { input: string }) => {
|
|
72
|
+
const results = await searchTavily(
|
|
73
|
+
`Get the weather for this location: ${input}`,
|
|
74
|
+
{
|
|
75
|
+
maxResults: 5,
|
|
76
|
+
apiKey: process.env.TAVILY_API_KEY!,
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
return results;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const machine = setup({
|
|
83
|
+
schemas,
|
|
84
|
+
types: schemas.types,
|
|
85
|
+
actors: {
|
|
86
|
+
getWeather,
|
|
87
|
+
decide: adapter.fromEventChoice(
|
|
88
|
+
(input: string) =>
|
|
89
|
+
`Decide what to do based on the given input, which may or may not be a location: ${input}`
|
|
90
|
+
),
|
|
91
|
+
getFromTerminal,
|
|
92
|
+
},
|
|
93
|
+
}).createMachine({
|
|
94
|
+
initial: 'getLocation',
|
|
95
|
+
context: {
|
|
96
|
+
location: '',
|
|
97
|
+
count: 0,
|
|
98
|
+
history: [],
|
|
99
|
+
},
|
|
100
|
+
states: {
|
|
101
|
+
getLocation: {
|
|
102
|
+
invoke: {
|
|
103
|
+
src: 'getFromTerminal',
|
|
104
|
+
input: 'Location?',
|
|
105
|
+
onDone: {
|
|
106
|
+
actions: assign({
|
|
107
|
+
location: ({ event }) => event.output,
|
|
108
|
+
}),
|
|
109
|
+
target: 'decide',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
always: {
|
|
113
|
+
guard: ({ context }) => context.count >= 3,
|
|
114
|
+
target: 'stopped',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
decide: {
|
|
118
|
+
entry: log('Deciding...'),
|
|
119
|
+
invoke: {
|
|
120
|
+
src: 'decide',
|
|
121
|
+
input: ({ context }) => context.location,
|
|
122
|
+
},
|
|
123
|
+
on: {
|
|
124
|
+
getWeather: {
|
|
125
|
+
actions: log(({ event }) => event),
|
|
126
|
+
target: 'gettingWeather',
|
|
127
|
+
},
|
|
128
|
+
doSomethingElse: 'getLocation',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
gettingWeather: {
|
|
132
|
+
entry: log('Getting weather...'),
|
|
133
|
+
invoke: {
|
|
134
|
+
src: 'getWeather',
|
|
135
|
+
input: ({ context }) => context.location,
|
|
136
|
+
onDone: {
|
|
137
|
+
actions: [
|
|
138
|
+
log(({ event }) => event.output),
|
|
139
|
+
assign({
|
|
140
|
+
count: ({ context }) => context.count + 1,
|
|
141
|
+
}),
|
|
142
|
+
],
|
|
143
|
+
target: 'getLocation',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
stopped: {
|
|
148
|
+
entry: log('You have used up your search quota. Goodbye!'),
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
exit: () => {
|
|
152
|
+
process.exit();
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
createAgent(machine, {
|
|
157
|
+
input: {
|
|
158
|
+
location: 'New York',
|
|
159
|
+
},
|
|
160
|
+
}).start();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -9,22 +9,30 @@
|
|
|
9
9
|
"author": "",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"devDependencies": {
|
|
12
|
+
"@changesets/changelog-github": "^0.5.0",
|
|
12
13
|
"@changesets/cli": "^2.27.1",
|
|
13
14
|
"@types/node": "^20.10.6",
|
|
15
|
+
"dotenv": "^16.3.1",
|
|
14
16
|
"json-schema-to-ts": "^3.0.0",
|
|
17
|
+
"openai": "^4.24.1",
|
|
18
|
+
"ts-node": "^10.9.2",
|
|
15
19
|
"tsup": "^8.0.1",
|
|
16
20
|
"typescript": "^5.3.3"
|
|
17
21
|
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"openai": "^4.24.1",
|
|
20
|
-
"xstate": "^5.3.1"
|
|
21
|
-
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"xstate": "^5.6.0"
|
|
27
|
+
},
|
|
28
|
+
"packageManager": "pnpm@8.11.0",
|
|
25
29
|
"scripts": {
|
|
26
30
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
27
31
|
"lint": "tsc",
|
|
28
|
-
"test": "vitest run"
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"example": "ts-node examples/helpers/runner.ts",
|
|
34
|
+
"changeset": "changeset",
|
|
35
|
+
"release": "changeset publish",
|
|
36
|
+
"version": "changeset version"
|
|
29
37
|
}
|
|
30
38
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Stately Agent (alpha)
|
|
2
|
+
|
|
3
|
+
🚧 Documentation in progress! Please see [the examples directory](https://github.com/statelyai/agent/tree/main/examples) for working examples.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install `openai`, and `@statelyai/agent`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install openai @statelyai/agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Work in progress. For now, see the examples:
|
|
16
|
+
|
|
17
|
+
- [Joke generator](https://github.com/statelyai/agent/tree/main/examples/joke.ts)
|
|
18
|
+
- Demonstrates `agent.fromChatCompletion(...)` to generate a joke and provide a joke rating
|
|
19
|
+
- Demonstrates `agent.fromEvent(...)` to choose whether to keep generating jokes or stop
|
|
20
|
+
- [Tic-tac-toe](https://github.com/statelyai/agent/tree/main/examples/ticTacToe.ts)
|
|
21
|
+
- Demonstrates `agent.fromEvent(...)` to have an agent play itself in a game of tic-tac-toe with precise events
|
|
22
|
+
- Demonstrates `agent.fromChatCompletionStream(...)` to produce a game report at the end of the game
|
|
23
|
+
- [Weather](https://github.com/statelyai/agent/tree/main/examples/weather.ts)
|
|
24
|
+
- Demonstrates using [Tavily](https://tavily.com/) as an external API
|
|
25
|
+
- Demonstrates `agent.fromEvent(...)` to only use Tavily to get the weather if the user provides a valid location
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
First, clone this repo locally. To run the examples in this repo, create a `.env` file at the root of the repo with the following contents:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
OPENAI_API_KEY="your-openai-api-key"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then, install the dependencies (`npm install`) and run the examples:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run example joke
|
|
39
|
+
# or:
|
|
40
|
+
# npm run example ticTacToe
|
|
41
|
+
```
|