@statelyai/agent 0.0.7 → 0.1.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.
- package/.vscode/launch.json +12 -1
- package/CHANGELOG.md +47 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +282 -71
- package/dist/index.js +4389 -183
- package/dist/index.mjs +7 -0
- package/examples/chatbot.ts +79 -0
- package/examples/cot.ts +91 -0
- package/examples/email.ts +118 -0
- package/examples/example.ts +81 -0
- package/examples/goal.ts +94 -0
- package/examples/joke.ts +117 -110
- package/examples/multi.ts +103 -0
- package/examples/newspaper.ts +324 -0
- package/examples/number.ts +102 -0
- package/examples/raffle.ts +105 -0
- package/examples/simple.ts +39 -0
- package/examples/support.ts +147 -0
- package/examples/ticTacToe.ts +89 -124
- package/examples/todo.ts +132 -0
- package/examples/tutor.ts +100 -0
- package/examples/verify.ts +120 -0
- package/examples/weather.ts +65 -47
- package/examples/wiki.ts +30 -0
- package/examples/word.ts +168 -0
- package/package.json +18 -11
- package/readme.md +9 -38
- package/src/adapters/vercel.ts +7 -0
- package/src/agent-experimental.ts +221 -0
- package/src/agent.test.ts +187 -0
- package/src/agent.ts +260 -6
- package/src/decision.test.ts +179 -0
- package/src/decision.ts +83 -0
- package/src/index.ts +3 -2
- package/src/memory.ts +25 -0
- package/src/planners/shortestPathPlanner.ts +22 -0
- package/src/planners/simplePlanner.ts +126 -0
- package/src/schemas.ts +13 -38
- package/src/strategies/chain-of-note.ts +155 -0
- package/src/templates/defaultText.ts +18 -0
- package/src/templates/defaultToolCall.ts +10 -0
- package/src/text.ts +232 -0
- package/src/types.ts +363 -46
- package/src/utils.ts +13 -50
- package/tsconfig.json +1 -1
- package/examples/multiAgentCollaboration.ts +0 -0
- package/examples/numberGuesser.ts +0 -128
- package/examples/wordGuesser.ts +0 -156
- package/src/adapter.test.ts +0 -217
- package/src/adapters/openai.ts +0 -298
package/examples/joke.ts
CHANGED
|
@@ -1,74 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
1
|
+
import { assign, createActor, fromCallback, log, setup } from 'xstate';
|
|
2
|
+
import { createAgent, fromDecision } from '../src';
|
|
4
3
|
import { loadingAnimation } from './helpers/loader';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { openai } from '@ai-sdk/openai';
|
|
6
|
+
import { getFromTerminal } from './helpers/helpers';
|
|
5
7
|
|
|
6
|
-
const openai = new OpenAI({
|
|
7
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
8
|
-
});
|
|
9
|
-
|
|
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
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const adapter = createOpenAIAdapter(openai, {
|
|
43
|
-
model: 'gpt-3.5-turbo-1106',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const getJokeCompletion = adapter.fromChat(
|
|
47
|
-
(topic: string) => `Tell me a joke about ${topic}.`
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const rateJoke = adapter.fromChat(
|
|
51
|
-
(joke: string) => `Rate this joke on a scale of 1 to 10: ${joke}`
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const getTopic = fromPromise(async () => {
|
|
55
|
-
const topic = await new Promise<string>((res) => {
|
|
56
|
-
console.log('Give me a joke topic:');
|
|
57
|
-
const listener = (data: Buffer) => {
|
|
58
|
-
const result = data.toString().trim();
|
|
59
|
-
process.stdin.off('data', listener);
|
|
60
|
-
res(result);
|
|
61
|
-
};
|
|
62
|
-
process.stdin.on('data', listener);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return topic;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const decide = adapter.fromEvent(
|
|
69
|
-
(lastRating: string) =>
|
|
70
|
-
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
71
|
-
);
|
|
72
8
|
export function getRandomFunnyPhrase() {
|
|
73
9
|
const funnyPhrases = [
|
|
74
10
|
'Concocting chuckles...',
|
|
@@ -108,17 +44,49 @@ const loader = fromCallback(({ input }: { input: string }) => {
|
|
|
108
44
|
};
|
|
109
45
|
});
|
|
110
46
|
|
|
47
|
+
const agent = createAgent({
|
|
48
|
+
name: 'joke-teller',
|
|
49
|
+
model: openai('gpt-4-turbo'),
|
|
50
|
+
events: {
|
|
51
|
+
askForTopic: z.object({
|
|
52
|
+
topic: z.string().describe('The topic for the joke'),
|
|
53
|
+
}),
|
|
54
|
+
'agent.tellJoke': z.object({
|
|
55
|
+
joke: z.string().describe('The joke text'),
|
|
56
|
+
}),
|
|
57
|
+
'agent.endJokes': z.object({}).describe('End the jokes'),
|
|
58
|
+
'agent.rateJoke': z.object({
|
|
59
|
+
rating: z.number().min(1).max(10),
|
|
60
|
+
explanation: z.string(),
|
|
61
|
+
}),
|
|
62
|
+
'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'),
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
111
72
|
const jokeMachine = setup({
|
|
112
|
-
|
|
113
|
-
|
|
73
|
+
types: {
|
|
74
|
+
context: {} as {
|
|
75
|
+
topic: string;
|
|
76
|
+
jokes: string[];
|
|
77
|
+
desire: string | null;
|
|
78
|
+
lastRating: number | null;
|
|
79
|
+
loader: string | null;
|
|
80
|
+
},
|
|
81
|
+
events: agent.eventTypes,
|
|
82
|
+
},
|
|
114
83
|
actors: {
|
|
115
|
-
|
|
116
|
-
getTopic,
|
|
117
|
-
rateJoke,
|
|
118
|
-
decide,
|
|
84
|
+
agent: fromDecision(agent),
|
|
119
85
|
loader,
|
|
86
|
+
getFromTerminal,
|
|
120
87
|
},
|
|
121
88
|
}).createMachine({
|
|
89
|
+
id: 'joke',
|
|
122
90
|
context: () => ({
|
|
123
91
|
topic: '',
|
|
124
92
|
jokes: [],
|
|
@@ -130,7 +98,8 @@ const jokeMachine = setup({
|
|
|
130
98
|
states: {
|
|
131
99
|
waitingForTopic: {
|
|
132
100
|
invoke: {
|
|
133
|
-
src: '
|
|
101
|
+
src: 'getFromTerminal',
|
|
102
|
+
input: 'Give me a joke topic.',
|
|
134
103
|
onDone: {
|
|
135
104
|
actions: assign({
|
|
136
105
|
topic: ({ event }) => event.output,
|
|
@@ -142,56 +111,93 @@ const jokeMachine = setup({
|
|
|
142
111
|
tellingJoke: {
|
|
143
112
|
invoke: [
|
|
144
113
|
{
|
|
145
|
-
src: '
|
|
146
|
-
input: ({ context }) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
event.output.choices[0]!.message.content!
|
|
153
|
-
),
|
|
154
|
-
}),
|
|
155
|
-
log((x) => `\n` + x.context.jokes.at(-1)),
|
|
156
|
-
],
|
|
157
|
-
target: 'rateJoke',
|
|
158
|
-
},
|
|
114
|
+
src: 'agent',
|
|
115
|
+
input: ({ context }) => ({
|
|
116
|
+
context: {
|
|
117
|
+
topic: context.topic,
|
|
118
|
+
},
|
|
119
|
+
goal: `Tell me a joke about the topic. Do not make any joke that is not relevant to the topic.`,
|
|
120
|
+
}),
|
|
159
121
|
},
|
|
160
122
|
{
|
|
161
123
|
src: 'loader',
|
|
162
124
|
input: getRandomFunnyPhrase,
|
|
163
125
|
},
|
|
164
126
|
],
|
|
127
|
+
on: {
|
|
128
|
+
'agent.tellJoke': {
|
|
129
|
+
actions: [
|
|
130
|
+
assign({
|
|
131
|
+
jokes: ({ context, event }) => [...context.jokes, event.joke],
|
|
132
|
+
}),
|
|
133
|
+
log((x) => x.event.joke),
|
|
134
|
+
],
|
|
135
|
+
target: 'relevance',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
relevance: {
|
|
140
|
+
invoke: {
|
|
141
|
+
src: 'agent',
|
|
142
|
+
input: (x) => ({
|
|
143
|
+
context: {
|
|
144
|
+
topic: x.context.topic,
|
|
145
|
+
lastJoke: x.context.jokes[x.context.jokes.length - 1],
|
|
146
|
+
},
|
|
147
|
+
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.',
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
on: {
|
|
151
|
+
'agent.markAsIrrelevant': {
|
|
152
|
+
actions: log((x) => 'Irrelevant joke: ' + x.event.explanation),
|
|
153
|
+
target: 'waitingForTopic',
|
|
154
|
+
description: 'Continue',
|
|
155
|
+
},
|
|
156
|
+
'agent.markAsRelevant': {
|
|
157
|
+
actions: log('Joke was relevant'),
|
|
158
|
+
target: 'rateJoke',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
165
161
|
},
|
|
166
162
|
rateJoke: {
|
|
167
163
|
invoke: [
|
|
168
164
|
{
|
|
169
|
-
src: '
|
|
170
|
-
input: ({ context }) =>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}),
|
|
177
|
-
log(({ context }) => '\n' + context.lastRating),
|
|
178
|
-
],
|
|
179
|
-
target: 'decide',
|
|
180
|
-
},
|
|
165
|
+
src: 'agent',
|
|
166
|
+
input: ({ context }) => ({
|
|
167
|
+
context: {
|
|
168
|
+
jokes: context.jokes,
|
|
169
|
+
},
|
|
170
|
+
goal: `Rate the last joke on a scale of 1 to 10.`,
|
|
171
|
+
}),
|
|
181
172
|
},
|
|
182
173
|
{
|
|
183
174
|
src: 'loader',
|
|
184
175
|
input: getRandomRatingPhrase,
|
|
185
176
|
},
|
|
186
177
|
],
|
|
178
|
+
on: {
|
|
179
|
+
'agent.rateJoke': {
|
|
180
|
+
actions: [
|
|
181
|
+
assign({
|
|
182
|
+
lastRating: ({ event }) => event.rating,
|
|
183
|
+
}),
|
|
184
|
+
log(
|
|
185
|
+
({ event }) => `Rating: ${event.rating}\n\n${event.explanation}`
|
|
186
|
+
),
|
|
187
|
+
],
|
|
188
|
+
target: 'decide',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
187
191
|
},
|
|
188
192
|
decide: {
|
|
189
193
|
invoke: {
|
|
190
|
-
src: '
|
|
191
|
-
input: ({ context }) =>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
src: 'agent',
|
|
195
|
+
input: ({ context }) => ({
|
|
196
|
+
context: {
|
|
197
|
+
lastRating: context.lastRating,
|
|
198
|
+
},
|
|
199
|
+
goal: `Choose what to do next, given the previous rating of the joke.`,
|
|
200
|
+
}),
|
|
195
201
|
},
|
|
196
202
|
on: {
|
|
197
203
|
askForTopic: {
|
|
@@ -200,7 +206,7 @@ const jokeMachine = setup({
|
|
|
200
206
|
description:
|
|
201
207
|
'Ask for a new topic, because the last joke rated 6 or lower',
|
|
202
208
|
},
|
|
203
|
-
endJokes: {
|
|
209
|
+
'agent.endJokes': {
|
|
204
210
|
target: 'end',
|
|
205
211
|
actions: log('That joke was good enough. Goodbye!'),
|
|
206
212
|
description: 'End the jokes, since the last joke rated 7 or higher',
|
|
@@ -216,5 +222,6 @@ const jokeMachine = setup({
|
|
|
216
222
|
},
|
|
217
223
|
});
|
|
218
224
|
|
|
219
|
-
const
|
|
220
|
-
|
|
225
|
+
const actor = createActor(jokeMachine);
|
|
226
|
+
|
|
227
|
+
actor.start();
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createAgent, fromDecision } from '../src';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { assign, createActor, log, setup } from 'xstate';
|
|
4
|
+
import { getFromTerminal } from './helpers/helpers';
|
|
5
|
+
import { openai } from '@ai-sdk/openai';
|
|
6
|
+
|
|
7
|
+
const agent = createAgent({
|
|
8
|
+
name: 'multi',
|
|
9
|
+
model: openai('gpt-4-1106-preview'),
|
|
10
|
+
events: {
|
|
11
|
+
'agent.respond': z.object({
|
|
12
|
+
response: z.string().describe('The response from the agent'),
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const machine = setup({
|
|
18
|
+
types: {
|
|
19
|
+
context: {} as {
|
|
20
|
+
topic: string | null;
|
|
21
|
+
discourse: string[];
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
actors: {
|
|
25
|
+
getFromTerminal,
|
|
26
|
+
agent: fromDecision(agent),
|
|
27
|
+
},
|
|
28
|
+
}).createMachine({
|
|
29
|
+
initial: 'asking',
|
|
30
|
+
context: {
|
|
31
|
+
topic: null,
|
|
32
|
+
discourse: [],
|
|
33
|
+
},
|
|
34
|
+
states: {
|
|
35
|
+
asking: {
|
|
36
|
+
invoke: {
|
|
37
|
+
src: 'getFromTerminal',
|
|
38
|
+
input: 'What is the question?',
|
|
39
|
+
onDone: {
|
|
40
|
+
actions: assign({
|
|
41
|
+
topic: ({ event }) => event.output,
|
|
42
|
+
}),
|
|
43
|
+
target: 'positiveResponse',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
positiveResponse: {
|
|
48
|
+
invoke: {
|
|
49
|
+
src: 'agent',
|
|
50
|
+
input: ({ context }) => ({
|
|
51
|
+
context,
|
|
52
|
+
goal: 'Debate the topic, and take the positive position. Respond directly to the last message of the discourse. Keep it short.',
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
on: {
|
|
56
|
+
'agent.respond': {
|
|
57
|
+
actions: [
|
|
58
|
+
assign({
|
|
59
|
+
discourse: ({ context, event }) =>
|
|
60
|
+
context.discourse.concat(event.response),
|
|
61
|
+
}),
|
|
62
|
+
log(({ event }) => event.response),
|
|
63
|
+
],
|
|
64
|
+
target: 'negativeResponse',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
negativeResponse: {
|
|
69
|
+
invoke: {
|
|
70
|
+
src: 'agent',
|
|
71
|
+
input: ({ context }) => ({
|
|
72
|
+
model: openai('gpt-3.5-turbo-16k-0613'),
|
|
73
|
+
context,
|
|
74
|
+
goal: 'Debate the topic, and take the negative position. Respond directly to the last message of the discourse. Keep it short.',
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
on: {
|
|
78
|
+
'agent.respond': {
|
|
79
|
+
actions: [
|
|
80
|
+
assign({
|
|
81
|
+
discourse: ({ context, event }) =>
|
|
82
|
+
context.discourse.concat(event.response),
|
|
83
|
+
}),
|
|
84
|
+
log(({ event }) => event.response),
|
|
85
|
+
],
|
|
86
|
+
target: 'positiveResponse',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
always: {
|
|
90
|
+
guard: ({ context }) => context.discourse.length >= 5,
|
|
91
|
+
target: 'debateOver',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
debateOver: {
|
|
95
|
+
type: 'final',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
exit: () => {
|
|
99
|
+
process.exit();
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
createActor(machine).start();
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// Based on GPT Newspaper:
|
|
2
|
+
// https://github.com/assafelovic/gpt-newspaper
|
|
3
|
+
// https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c
|
|
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';
|
|
8
|
+
import { assign, createActor, fromPromise, setup } from 'xstate';
|
|
9
|
+
|
|
10
|
+
interface AgentState {
|
|
11
|
+
topic: string;
|
|
12
|
+
searchResults?: string;
|
|
13
|
+
article?: string;
|
|
14
|
+
critique?: string;
|
|
15
|
+
revisionCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
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
|
+
}
|
|
25
|
+
|
|
26
|
+
async function search({ topic }: Pick<AgentState, 'topic'>): Promise<string> {
|
|
27
|
+
const retriever = new TavilySearchAPIRetriever({
|
|
28
|
+
k: 10,
|
|
29
|
+
apiKey: process.env.TAVILY_API_KEY,
|
|
30
|
+
});
|
|
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);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function curate(
|
|
41
|
+
input: Pick<AgentState, 'topic' | 'searchResults'>
|
|
42
|
+
): 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!);
|
|
70
|
+
const newSearchResults = searchResults.filter((result: any) => {
|
|
71
|
+
return urls.includes(result.metadata.source);
|
|
72
|
+
});
|
|
73
|
+
return JSON.stringify(newSearchResults);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function critique(
|
|
77
|
+
input: Pick<AgentState, 'article' | 'critique'>
|
|
78
|
+
): Promise<string | undefined> {
|
|
79
|
+
let feedbackInstructions = '';
|
|
80
|
+
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, ' ');
|
|
86
|
+
}
|
|
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;
|
|
104
|
+
console.log('critique:', content);
|
|
105
|
+
return content.includes('[DONE]') ? undefined : content;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function write(
|
|
109
|
+
input: Pick<AgentState, 'searchResults' | 'topic'>
|
|
110
|
+
): Promise<string> {
|
|
111
|
+
const response = await model().invoke([
|
|
112
|
+
new SystemMessage(
|
|
113
|
+
`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(
|
|
126
|
+
/\s+/g,
|
|
127
|
+
' '
|
|
128
|
+
)
|
|
129
|
+
),
|
|
130
|
+
]);
|
|
131
|
+
const content = response.content as string;
|
|
132
|
+
return content;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function revise(
|
|
136
|
+
input: Pick<AgentState, 'article' | 'critique'>
|
|
137
|
+
): Promise<string> {
|
|
138
|
+
const response = await model().invoke([
|
|
139
|
+
new SystemMessage(
|
|
140
|
+
`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.
|
|
145
|
+
This is the article: ${input.article}
|
|
146
|
+
This is the critique: ${input.critique}
|
|
147
|
+
Please return the edited article based on the critique given.
|
|
148
|
+
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;
|
|
153
|
+
return content;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const machine = setup({
|
|
157
|
+
types: {
|
|
158
|
+
context: {} as AgentState,
|
|
159
|
+
},
|
|
160
|
+
actors: {
|
|
161
|
+
search: fromPromise(({ input }: { input: Pick<AgentState, 'topic'> }) => {
|
|
162
|
+
return search(input);
|
|
163
|
+
}),
|
|
164
|
+
curate: fromPromise(
|
|
165
|
+
({ input }: { input: Pick<AgentState, 'topic' | 'searchResults'> }) => {
|
|
166
|
+
return curate(input);
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
critique: fromPromise(
|
|
170
|
+
({ input }: { input: Pick<AgentState, 'article' | 'critique'> }) => {
|
|
171
|
+
return critique(input);
|
|
172
|
+
}
|
|
173
|
+
),
|
|
174
|
+
write: fromPromise(
|
|
175
|
+
({ input }: { input: Pick<AgentState, 'searchResults' | 'topic'> }) => {
|
|
176
|
+
return write(input);
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
revise: fromPromise(
|
|
180
|
+
({ input }: { input: Pick<AgentState, 'article' | 'critique'> }) => {
|
|
181
|
+
return revise(input);
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
},
|
|
185
|
+
}).createMachine({
|
|
186
|
+
context: {
|
|
187
|
+
topic: 'Orlando',
|
|
188
|
+
revisionCount: 0,
|
|
189
|
+
},
|
|
190
|
+
initial: 'search',
|
|
191
|
+
states: {
|
|
192
|
+
search: {
|
|
193
|
+
invoke: {
|
|
194
|
+
src: 'search',
|
|
195
|
+
input: ({ context }) => ({
|
|
196
|
+
topic: context.topic,
|
|
197
|
+
}),
|
|
198
|
+
onDone: {
|
|
199
|
+
actions: assign({
|
|
200
|
+
searchResults: ({ event }) => event.output,
|
|
201
|
+
}),
|
|
202
|
+
target: 'curate',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
curate: {
|
|
207
|
+
invoke: {
|
|
208
|
+
src: 'curate',
|
|
209
|
+
input: ({ context }) => ({
|
|
210
|
+
topic: context.topic,
|
|
211
|
+
searchResults: context.searchResults!,
|
|
212
|
+
}),
|
|
213
|
+
onDone: {
|
|
214
|
+
actions: assign({
|
|
215
|
+
searchResults: ({ event }) => event.output,
|
|
216
|
+
}),
|
|
217
|
+
target: 'write',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
write: {
|
|
222
|
+
invoke: {
|
|
223
|
+
src: 'write',
|
|
224
|
+
input: ({ context }) => ({
|
|
225
|
+
topic: context.topic,
|
|
226
|
+
searchResults: context.searchResults!,
|
|
227
|
+
}),
|
|
228
|
+
onDone: {
|
|
229
|
+
actions: assign({
|
|
230
|
+
article: ({ event }) => event.output,
|
|
231
|
+
}),
|
|
232
|
+
target: 'critique',
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
critique: {
|
|
237
|
+
invoke: {
|
|
238
|
+
src: 'critique',
|
|
239
|
+
input: ({ context }) => ({
|
|
240
|
+
article: context.article!,
|
|
241
|
+
critique: context.critique,
|
|
242
|
+
}),
|
|
243
|
+
onDone: [
|
|
244
|
+
{
|
|
245
|
+
guard: ({ event }) => event.output === undefined,
|
|
246
|
+
target: 'done',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
actions: assign({
|
|
250
|
+
article: ({ event }) => event.output,
|
|
251
|
+
}),
|
|
252
|
+
target: 'revise',
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
revise: {
|
|
258
|
+
always: {
|
|
259
|
+
guard: ({ context }) => context.revisionCount > 3,
|
|
260
|
+
target: 'done',
|
|
261
|
+
},
|
|
262
|
+
entry: assign({
|
|
263
|
+
revisionCount: ({ context }) => context.revisionCount + 1,
|
|
264
|
+
}),
|
|
265
|
+
invoke: {
|
|
266
|
+
src: 'revise',
|
|
267
|
+
input: ({ context }) => ({
|
|
268
|
+
article: context.article!,
|
|
269
|
+
critique: context.critique,
|
|
270
|
+
}),
|
|
271
|
+
onDone: {
|
|
272
|
+
actions: assign({
|
|
273
|
+
article: ({ event }) => event.output,
|
|
274
|
+
}),
|
|
275
|
+
target: 'revise',
|
|
276
|
+
reenter: true,
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
done: {
|
|
281
|
+
type: 'final',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
output: ({ context }) => context.article,
|
|
285
|
+
});
|
|
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
|
+
});
|
|
294
|
+
|
|
295
|
+
actor.subscribe({
|
|
296
|
+
next: (s) => {
|
|
297
|
+
console.log('State:', s.value);
|
|
298
|
+
console.log(
|
|
299
|
+
'Context:',
|
|
300
|
+
JSON.stringify(
|
|
301
|
+
s.context,
|
|
302
|
+
(k, v) => {
|
|
303
|
+
if (typeof v === 'string') {
|
|
304
|
+
// truncate if longer than 50 chars
|
|
305
|
+
return v.length > 50 ? `${v.slice(0, 50)}...` : v;
|
|
306
|
+
}
|
|
307
|
+
return v;
|
|
308
|
+
},
|
|
309
|
+
2
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
},
|
|
313
|
+
complete: () => {
|
|
314
|
+
console.log(actor.getSnapshot().output);
|
|
315
|
+
},
|
|
316
|
+
error: (err) => {
|
|
317
|
+
console.error(err);
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
actor.start();
|
|
322
|
+
|
|
323
|
+
// keep the process alive by invoking a promise that never resolves
|
|
324
|
+
new Promise(() => {});
|