@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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createAgent, fromDecision } from '../src';
|
|
2
|
+
import { assign, createActor, log, setup } from 'xstate';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { openai } from '@ai-sdk/openai';
|
|
5
|
+
import { getFromTerminal } from './helpers/helpers';
|
|
6
|
+
|
|
7
|
+
const agent = createAgent({
|
|
8
|
+
name: 'number-guesser',
|
|
9
|
+
model: openai('gpt-3.5-turbo-1106'),
|
|
10
|
+
events: {
|
|
11
|
+
'agent.guess': z.object({
|
|
12
|
+
number: z.number().min(1).max(10).describe('The number guessed'),
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const machine = setup({
|
|
18
|
+
types: {
|
|
19
|
+
context: {} as {
|
|
20
|
+
previousGuesses: number[];
|
|
21
|
+
answer: number | null;
|
|
22
|
+
},
|
|
23
|
+
events: agent.eventTypes,
|
|
24
|
+
},
|
|
25
|
+
actors: {
|
|
26
|
+
agent: fromDecision(agent),
|
|
27
|
+
getFromTerminal,
|
|
28
|
+
},
|
|
29
|
+
}).createMachine({
|
|
30
|
+
context: {
|
|
31
|
+
answer: null,
|
|
32
|
+
previousGuesses: [],
|
|
33
|
+
},
|
|
34
|
+
initial: 'providing',
|
|
35
|
+
states: {
|
|
36
|
+
providing: {
|
|
37
|
+
invoke: {
|
|
38
|
+
src: 'getFromTerminal',
|
|
39
|
+
input: 'Enter a number between 1 and 10',
|
|
40
|
+
onDone: {
|
|
41
|
+
actions: assign({
|
|
42
|
+
answer: (x) => +x.event.output,
|
|
43
|
+
}),
|
|
44
|
+
target: 'guessing',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
guessing: {
|
|
49
|
+
always: {
|
|
50
|
+
guard: ({ context }) =>
|
|
51
|
+
context.answer === context.previousGuesses.at(-1),
|
|
52
|
+
target: 'winner',
|
|
53
|
+
},
|
|
54
|
+
invoke: {
|
|
55
|
+
src: 'agent',
|
|
56
|
+
input: ({ context }) => ({
|
|
57
|
+
goal: `
|
|
58
|
+
Guess the number between 1 and 10. The previous guesses were ${
|
|
59
|
+
context.previousGuesses.length
|
|
60
|
+
? context.previousGuesses.join(', ')
|
|
61
|
+
: 'not made yet'
|
|
62
|
+
} and the last result was ${
|
|
63
|
+
context.previousGuesses.length === 0
|
|
64
|
+
? 'not given yet'
|
|
65
|
+
: context.previousGuesses.at(-1)! - context.answer! > 0
|
|
66
|
+
? 'too high'
|
|
67
|
+
: 'too low'
|
|
68
|
+
}.
|
|
69
|
+
`,
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
on: {
|
|
73
|
+
'agent.guess': {
|
|
74
|
+
actions: [
|
|
75
|
+
assign({
|
|
76
|
+
previousGuesses: ({ context, event }) => [
|
|
77
|
+
...context.previousGuesses,
|
|
78
|
+
event.number,
|
|
79
|
+
],
|
|
80
|
+
}),
|
|
81
|
+
log((x) => x.event.number),
|
|
82
|
+
],
|
|
83
|
+
target: 'guessing',
|
|
84
|
+
reenter: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
winner: {
|
|
89
|
+
entry: log('You guessed the correct number!'),
|
|
90
|
+
type: 'final',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
exit: () => {
|
|
94
|
+
process.exit();
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const actor = createActor(machine, {
|
|
99
|
+
input: { answer: 4 },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
actor.start();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createAgent, fromDecision } from '../src';
|
|
3
|
+
import { openai } from '@ai-sdk/openai';
|
|
4
|
+
import { assign, createActor, log, setup } from 'xstate';
|
|
5
|
+
import { getFromTerminal } from './helpers/helpers';
|
|
6
|
+
|
|
7
|
+
const agent = createAgent({
|
|
8
|
+
name: 'raffle-chooser',
|
|
9
|
+
model: openai('gpt-4-turbo'),
|
|
10
|
+
events: {
|
|
11
|
+
'agent.collectEntries': z.object({}).describe('Collect more entries'),
|
|
12
|
+
'agent.draw': z.object({}).describe('Draw a winner'),
|
|
13
|
+
'agent.reportWinner': z.object({
|
|
14
|
+
winningEntry: z.string().describe('The winning entry'),
|
|
15
|
+
firstRunnerUp: z.string().describe('The first runner up entry'),
|
|
16
|
+
secondRunnerUp: z.string().describe('The second runner up entry'),
|
|
17
|
+
explanation: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe('Explanation for why you chose the winning entry'),
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const machine = setup({
|
|
25
|
+
types: {
|
|
26
|
+
context: {} as {
|
|
27
|
+
lastInput: string | null;
|
|
28
|
+
entries: string[];
|
|
29
|
+
},
|
|
30
|
+
events: {} as typeof agent.eventTypes | { type: 'draw' },
|
|
31
|
+
},
|
|
32
|
+
actors: { agent: fromDecision(agent), getFromTerminal },
|
|
33
|
+
}).createMachine({
|
|
34
|
+
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOjHwBcwAnAqAYggHtCSCA3JgazBLSzyFS5KrXxQEHJpnQVcLANoAGALrKViUAAcmsXHJaaQAD0QAmAOwBmEmYCsADgCMZgJxmALE4ceHSuwA0IACe5gBsZiR29q5KPnaOrmEOAL4pQfw4BMQkEGCiqAR09OgwlCSYTAA2VWCYFACilLRw6kY6egb4RqYIZg52JB52Ya5jSq5+To4eQaEILhYkA0qjVlYeSlsOYWFpGRhZQrn5NIX4xaUiudToAO5tSCAd+vLdT727SiRKHsl2FjCwxcrlmIUQTl2ticHk26ycMPsqXSIEyghyEFud0uZQoJGoYB01AoAHUCIRqI9tLpXoYPohXBYnCQnP4HK4nFYLF5vK45hCPDYmVtdk5uYzuWkUfgmHl4E80dkiO0aV0eogALRhfkIDWDMZjJn9MxhKwjSb7VGHdHCSg0OgqzpvdUIDxmHWc5l2caeKxhVYDFyWxXHPIFIriR2096gXoeVw2VmgiIOBzWJRiqwe2FRdzcpRWE0JPPB61Km73B1PF5q+kIPweEhWMWjCz+BJKJketwkQEWCyuOywv0WaJ2UsCcvY-AUqO12MQls-Ue+Px2KzspweoHLXxsjO-eNmMxSlJAA */
|
|
35
|
+
context: {
|
|
36
|
+
lastInput: null,
|
|
37
|
+
entries: [],
|
|
38
|
+
},
|
|
39
|
+
initial: 'entering',
|
|
40
|
+
states: {
|
|
41
|
+
entering: {
|
|
42
|
+
entry: log(({ context }) => context.entries),
|
|
43
|
+
invoke: {
|
|
44
|
+
src: 'getFromTerminal',
|
|
45
|
+
input: 'What technology are you most interested in right now?',
|
|
46
|
+
onDone: [
|
|
47
|
+
{
|
|
48
|
+
actions: assign({
|
|
49
|
+
lastInput: ({ event }) => event.output,
|
|
50
|
+
}),
|
|
51
|
+
target: 'determining',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
determining: {
|
|
57
|
+
invoke: {
|
|
58
|
+
src: 'agent',
|
|
59
|
+
input: {
|
|
60
|
+
context: true,
|
|
61
|
+
goal: 'If the last input explicitly says to end the drawing and/or choose a winner, start the drawing process. Otherwise, get more entries.',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
on: {
|
|
65
|
+
'agent.collectEntries': {
|
|
66
|
+
target: 'entering',
|
|
67
|
+
actions: assign({
|
|
68
|
+
entries: ({ context }) => [...context.entries, context.lastInput!],
|
|
69
|
+
lastInput: null,
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
'agent.draw': 'drawing',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
drawing: {
|
|
76
|
+
entry: log('And the winner is...'),
|
|
77
|
+
invoke: {
|
|
78
|
+
src: 'agent',
|
|
79
|
+
input: {
|
|
80
|
+
context: true,
|
|
81
|
+
goal: 'Choose the technology that sounds most exciting to you from the entries. Be as unbiased as possible in your choice. Explain why you chose the winning entry.',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
on: {
|
|
85
|
+
'agent.reportWinner': {
|
|
86
|
+
actions: log(
|
|
87
|
+
({ event }) =>
|
|
88
|
+
`\n🎉🎉🎉 ${event.winningEntry} 🎉🎉🎉\n\n${event.explanation}`
|
|
89
|
+
),
|
|
90
|
+
target: 'winner',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
winner: {
|
|
95
|
+
type: 'final',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
exit: () => {
|
|
99
|
+
process.exit(0);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const actor = createActor(machine);
|
|
104
|
+
|
|
105
|
+
actor.start();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createAgent, fromDecision } from '../src';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { setup, createActor } from 'xstate';
|
|
4
|
+
import { openai } from '@ai-sdk/openai';
|
|
5
|
+
|
|
6
|
+
const agent = createAgent({
|
|
7
|
+
name: 'simple',
|
|
8
|
+
model: openai('gpt-3.5-turbo-16k-0613'),
|
|
9
|
+
events: {
|
|
10
|
+
'agent.thought': z.object({
|
|
11
|
+
text: z.string().describe('The text of the thought'),
|
|
12
|
+
}),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const machine = setup({
|
|
17
|
+
actors: { agent: fromDecision(agent) },
|
|
18
|
+
}).createMachine({
|
|
19
|
+
initial: 'thinking',
|
|
20
|
+
states: {
|
|
21
|
+
thinking: {
|
|
22
|
+
invoke: {
|
|
23
|
+
src: 'agent',
|
|
24
|
+
input: 'Think about a random topic, and then share that thought.',
|
|
25
|
+
},
|
|
26
|
+
on: {
|
|
27
|
+
'agent.thought': {
|
|
28
|
+
actions: ({ event }) => console.log(event.text),
|
|
29
|
+
target: 'thought',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
thought: {
|
|
34
|
+
type: 'final',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const actor = createActor(machine).start();
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { openai } from '@ai-sdk/openai';
|
|
2
|
+
import { createAgent, fromDecision } from '../src';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createActor, log, setup } from 'xstate';
|
|
5
|
+
|
|
6
|
+
const agent = createAgent({
|
|
7
|
+
name: 'support-agent',
|
|
8
|
+
model: openai('gpt-4-1106-preview'),
|
|
9
|
+
events: {
|
|
10
|
+
'agent.respond': z.object({
|
|
11
|
+
response: z.string().describe('The response from the agent'),
|
|
12
|
+
}),
|
|
13
|
+
'agent.frontline.classify': z.object({
|
|
14
|
+
category: z
|
|
15
|
+
.enum(['billing', 'technical', 'other'])
|
|
16
|
+
.describe('The category of the customer issue'),
|
|
17
|
+
}),
|
|
18
|
+
'agent.refund': z
|
|
19
|
+
.object({
|
|
20
|
+
response: z.string().describe('The response from the agent'),
|
|
21
|
+
})
|
|
22
|
+
.describe('The agent wants to refund the user'),
|
|
23
|
+
'agent.technical.solve': z.object({
|
|
24
|
+
solution: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe('The solution provided by the technical agent'),
|
|
27
|
+
}),
|
|
28
|
+
'agent.endConversation': z
|
|
29
|
+
.object({
|
|
30
|
+
response: z.string().describe('The response from the agent'),
|
|
31
|
+
})
|
|
32
|
+
.describe('The agent ends the conversation'),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const machine = setup({
|
|
37
|
+
types: {
|
|
38
|
+
events: agent.eventTypes,
|
|
39
|
+
input: {} as string,
|
|
40
|
+
context: {} as {
|
|
41
|
+
customerIssue: string;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
actors: { agent: fromDecision(agent) },
|
|
45
|
+
}).createMachine({
|
|
46
|
+
initial: 'frontline',
|
|
47
|
+
context: ({ input }) => ({
|
|
48
|
+
customerIssue: input,
|
|
49
|
+
}),
|
|
50
|
+
states: {
|
|
51
|
+
frontline: {
|
|
52
|
+
invoke: {
|
|
53
|
+
src: 'agent',
|
|
54
|
+
input: ({ context }) => ({
|
|
55
|
+
context,
|
|
56
|
+
system: `You are frontline support staff for LangCorp, a company that sells computers.
|
|
57
|
+
Be concise in your responses.
|
|
58
|
+
You can chat with customers and help them with basic questions, but if the customer is having a billing or technical problem,
|
|
59
|
+
do not try to answer the question directly or gather information.
|
|
60
|
+
Instead, immediately transfer them to the billing or technical team by asking the user to hold for a moment.
|
|
61
|
+
Otherwise, just respond conversationally.`,
|
|
62
|
+
goal: `The previous conversation is an interaction between a customer support representative and a user.
|
|
63
|
+
Classify whether the representative is routing the user to a billing or technical team, or whether they are just responding conversationally.`,
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
on: {
|
|
67
|
+
'agent.frontline.classify': [
|
|
68
|
+
{
|
|
69
|
+
actions: log(({ event }) => event),
|
|
70
|
+
guard: ({ event }) => event.category === 'billing',
|
|
71
|
+
target: 'billing',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
actions: log(({ event }) => event),
|
|
75
|
+
guard: ({ event }) => event.category === 'technical',
|
|
76
|
+
target: 'technical',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
actions: log(({ event }) => event),
|
|
80
|
+
target: 'conversational',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
billing: {
|
|
86
|
+
invoke: {
|
|
87
|
+
src: 'agent',
|
|
88
|
+
input: {
|
|
89
|
+
system:
|
|
90
|
+
'Your job is to detect whether a billing support representative wants to refund the user.',
|
|
91
|
+
goal: `The following text is a response from a customer support representative. Extract whether they want to refund the user or not.`,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
on: {
|
|
95
|
+
'agent.refund': {
|
|
96
|
+
actions: log(({ event }) => event),
|
|
97
|
+
target: 'refund',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
technical: {
|
|
102
|
+
invoke: {
|
|
103
|
+
src: 'agent',
|
|
104
|
+
input: {
|
|
105
|
+
context: true,
|
|
106
|
+
system: `You are an expert at diagnosing technical computer issues. You work for a company called LangCorp that sells computers. Help the user to the best of your ability, but be concise in your responses.`,
|
|
107
|
+
goal: 'Solve the customer issue.',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
on: {
|
|
111
|
+
'agent.technical.solve': {
|
|
112
|
+
actions: log(({ event }) => event),
|
|
113
|
+
target: 'conversational',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
conversational: {
|
|
118
|
+
invoke: {
|
|
119
|
+
src: 'agent',
|
|
120
|
+
input: {
|
|
121
|
+
goal: 'You are a customer support agent that is ending the conversation with the customer. Respond politely and thank them for their time.',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
on: {
|
|
125
|
+
'agent.endConversation': {
|
|
126
|
+
actions: log((x) => x.event),
|
|
127
|
+
target: 'end',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
refund: {
|
|
132
|
+
entry: () => console.log('Refunding...'),
|
|
133
|
+
after: {
|
|
134
|
+
1000: { target: 'conversational' },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
end: {
|
|
138
|
+
type: 'final',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const actor = createActor(machine, {
|
|
144
|
+
input: `I've changed my mind and I want a refund for order #182818!`,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
actor.start();
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -1,80 +1,40 @@
|
|
|
1
|
-
import { assign, setup, assertEvent } from 'xstate';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
},
|
|
1
|
+
import { assign, setup, assertEvent, createActor } from 'xstate';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createAgent, fromDecision, fromTextStream } from '../src';
|
|
4
|
+
import { openai } from '@ai-sdk/openai';
|
|
5
|
+
import { defaultToolCallTemplate } from '../src/templates/defaultToolCall';
|
|
6
|
+
|
|
7
|
+
const agent = createAgent({
|
|
8
|
+
name: 'tic-tac-toe-bot',
|
|
9
|
+
model: openai('gpt-4-0125-preview'),
|
|
47
10
|
events: {
|
|
48
|
-
'x.play': {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
type: 'number',
|
|
64
|
-
minimum: 0,
|
|
65
|
-
maximum: 8,
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
reset: {
|
|
70
|
-
properties: {},
|
|
71
|
-
},
|
|
11
|
+
'agent.x.play': z.object({
|
|
12
|
+
index: z
|
|
13
|
+
.number()
|
|
14
|
+
.min(0)
|
|
15
|
+
.max(8)
|
|
16
|
+
.describe('The index of the cell to play on'),
|
|
17
|
+
}),
|
|
18
|
+
'agent.o.play': z.object({
|
|
19
|
+
index: z
|
|
20
|
+
.number()
|
|
21
|
+
.min(0)
|
|
22
|
+
.max(8)
|
|
23
|
+
.describe('The index of the cell to play on'),
|
|
24
|
+
}),
|
|
25
|
+
reset: z.object({}).describe('Reset the game to the initial state'),
|
|
72
26
|
},
|
|
73
27
|
});
|
|
74
28
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
29
|
+
type Player = 'x' | 'o';
|
|
30
|
+
|
|
31
|
+
interface GameContext {
|
|
32
|
+
board: (Player | null)[];
|
|
33
|
+
moves: number;
|
|
34
|
+
player: Player;
|
|
35
|
+
gameReport: string;
|
|
36
|
+
events: string[];
|
|
37
|
+
}
|
|
78
38
|
|
|
79
39
|
const initialContext = {
|
|
80
40
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
@@ -82,34 +42,7 @@ const initialContext = {
|
|
|
82
42
|
player: 'x' as Player,
|
|
83
43
|
gameReport: '',
|
|
84
44
|
events: [],
|
|
85
|
-
} satisfies
|
|
86
|
-
|
|
87
|
-
const bot = adapter.fromEvent(
|
|
88
|
-
({ context }: { context: typeof schemas.types.context }) => `
|
|
89
|
-
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
|
-
|
|
91
|
-
${JSON.stringify(context, null, 2)}
|
|
92
|
-
|
|
93
|
-
Execute the single best next move to try to win the game. Do not play on an existing cell.`
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const gameReporter = adapter.fromChatStream(
|
|
97
|
-
({
|
|
98
|
-
context,
|
|
99
|
-
}: {
|
|
100
|
-
context: typeof schemas.types.context;
|
|
101
|
-
}) => `Here is the game board:
|
|
102
|
-
|
|
103
|
-
${JSON.stringify(context.board, null, 2)}
|
|
104
|
-
|
|
105
|
-
And here are the events that led to this game state:
|
|
106
|
-
|
|
107
|
-
${context.events.join('\n')}
|
|
108
|
-
|
|
109
|
-
The winner is ${getWinner(context.board)}.
|
|
110
|
-
|
|
111
|
-
Provide a very short game report analyzing the game.`
|
|
112
|
-
);
|
|
45
|
+
} satisfies GameContext;
|
|
113
46
|
|
|
114
47
|
function getWinner(board: typeof initialContext.board): Player | null {
|
|
115
48
|
const lines = [
|
|
@@ -131,16 +64,18 @@ function getWinner(board: typeof initialContext.board): Player | null {
|
|
|
131
64
|
}
|
|
132
65
|
|
|
133
66
|
export const ticTacToeMachine = setup({
|
|
134
|
-
|
|
135
|
-
|
|
67
|
+
types: {
|
|
68
|
+
context: {} as GameContext,
|
|
69
|
+
events: agent.eventTypes,
|
|
70
|
+
},
|
|
136
71
|
actors: {
|
|
137
|
-
|
|
138
|
-
gameReporter,
|
|
72
|
+
agent: fromDecision(agent),
|
|
73
|
+
gameReporter: fromTextStream(agent),
|
|
139
74
|
},
|
|
140
75
|
actions: {
|
|
141
76
|
updateBoard: assign({
|
|
142
77
|
board: ({ context, event }) => {
|
|
143
|
-
assertEvent(event, ['x.play', 'o.play']);
|
|
78
|
+
assertEvent(event, ['agent.x.play', 'agent.o.play']);
|
|
144
79
|
const updatedBoard = [...context.board];
|
|
145
80
|
updatedBoard[event.index] = context.player;
|
|
146
81
|
return updatedBoard;
|
|
@@ -157,6 +92,22 @@ export const ticTacToeMachine = setup({
|
|
|
157
92
|
return [...context.events, JSON.stringify(event)];
|
|
158
93
|
},
|
|
159
94
|
}),
|
|
95
|
+
printBoard: ({ context }) => {
|
|
96
|
+
// Print the context.board in a 3 x 3 grid format
|
|
97
|
+
let boardString = '';
|
|
98
|
+
for (let i = 0; i < context.board.length; i++) {
|
|
99
|
+
if ([0, 3, 6].includes(i)) {
|
|
100
|
+
boardString += context.board[i] ?? ' ';
|
|
101
|
+
} else {
|
|
102
|
+
boardString += ' | ' + (context.board[i] ?? ' ');
|
|
103
|
+
if ([2, 5].includes(i)) {
|
|
104
|
+
boardString += '\n--+---+--\n';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(boardString);
|
|
110
|
+
},
|
|
160
111
|
},
|
|
161
112
|
guards: {
|
|
162
113
|
checkWin: ({ context }) => {
|
|
@@ -169,7 +120,7 @@ export const ticTacToeMachine = setup({
|
|
|
169
120
|
},
|
|
170
121
|
isValidMove: ({ context, event }) => {
|
|
171
122
|
try {
|
|
172
|
-
assertEvent(event, ['o.play', 'x.play']);
|
|
123
|
+
assertEvent(event, ['agent.o.play', 'agent.x.play']);
|
|
173
124
|
} catch {
|
|
174
125
|
return false;
|
|
175
126
|
}
|
|
@@ -189,12 +140,9 @@ export const ticTacToeMachine = setup({
|
|
|
189
140
|
initial: 'x',
|
|
190
141
|
states: {
|
|
191
142
|
x: {
|
|
192
|
-
|
|
193
|
-
src: 'bot',
|
|
194
|
-
input: ({ context }) => ({ context }),
|
|
195
|
-
},
|
|
143
|
+
entry: 'printBoard',
|
|
196
144
|
on: {
|
|
197
|
-
'x.play': [
|
|
145
|
+
'agent.x.play': [
|
|
198
146
|
{
|
|
199
147
|
target: 'o',
|
|
200
148
|
guard: 'isValidMove',
|
|
@@ -205,12 +153,9 @@ export const ticTacToeMachine = setup({
|
|
|
205
153
|
},
|
|
206
154
|
},
|
|
207
155
|
o: {
|
|
208
|
-
|
|
209
|
-
src: 'bot',
|
|
210
|
-
input: ({ context }) => ({ context }),
|
|
211
|
-
},
|
|
156
|
+
entry: 'printBoard',
|
|
212
157
|
on: {
|
|
213
|
-
'o.play': [
|
|
158
|
+
'agent.o.play': [
|
|
214
159
|
{
|
|
215
160
|
target: 'x',
|
|
216
161
|
guard: 'isValidMove',
|
|
@@ -226,13 +171,21 @@ export const ticTacToeMachine = setup({
|
|
|
226
171
|
initial: 'winner',
|
|
227
172
|
invoke: {
|
|
228
173
|
src: 'gameReporter',
|
|
229
|
-
input: ({ context }) => ({
|
|
174
|
+
input: ({ context }) => ({
|
|
175
|
+
context: {
|
|
176
|
+
events: context.events,
|
|
177
|
+
board: context.board,
|
|
178
|
+
},
|
|
179
|
+
prompt: 'Provide a short game report analyzing the game.',
|
|
180
|
+
}),
|
|
230
181
|
onSnapshot: {
|
|
231
182
|
actions: assign({
|
|
232
183
|
gameReport: ({ context, event }) => {
|
|
184
|
+
console.log(
|
|
185
|
+
context.gameReport + (event.snapshot.context?.textDelta ?? '')
|
|
186
|
+
);
|
|
233
187
|
return (
|
|
234
|
-
context.gameReport +
|
|
235
|
-
(event.snapshot.context?.choices[0]?.delta.content ?? '')
|
|
188
|
+
context.gameReport + (event.snapshot.context?.textDelta ?? '')
|
|
236
189
|
);
|
|
237
190
|
},
|
|
238
191
|
}),
|
|
@@ -256,8 +209,20 @@ export const ticTacToeMachine = setup({
|
|
|
256
209
|
},
|
|
257
210
|
});
|
|
258
211
|
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
212
|
+
const actor = createActor(ticTacToeMachine);
|
|
213
|
+
|
|
214
|
+
agent.interact(actor, (observed) => {
|
|
215
|
+
if (observed.state.matches('playing')) {
|
|
216
|
+
return {
|
|
217
|
+
goal: `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.
|
|
218
|
+
|
|
219
|
+
${JSON.stringify(observed.state.context, null, 2)}
|
|
220
|
+
|
|
221
|
+
Execute the single best next move to try to win the game. Do not play on an existing cell.`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return;
|
|
262
226
|
});
|
|
263
|
-
|
|
227
|
+
|
|
228
|
+
actor.start();
|