@statelyai/agent 1.1.6 → 2.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/light-hats-drive.md +9 -0
- package/.changeset/pre.json +10 -0
- package/.vscode/launch.json +6 -0
- package/CHANGELOG.md +10 -0
- package/dist/index.d.mts +262 -165
- package/dist/index.d.ts +262 -165
- package/dist/index.js +368 -263
- package/dist/index.mjs +371 -261
- package/examples/chatbot-alt.ts +57 -0
- package/examples/chatbot.ts +11 -16
- package/examples/cot.ts +25 -22
- package/examples/customer-service-sim.ts +107 -0
- package/examples/email.ts +14 -14
- package/examples/example.ts +5 -5
- package/examples/executor.ts +66 -0
- package/examples/goal.ts +11 -11
- package/examples/helpers/helpers.ts +26 -14
- package/examples/joke.ts +78 -75
- package/examples/jugs.ts +125 -0
- package/examples/multi.ts +4 -4
- package/examples/newspaper.ts +98 -104
- package/examples/number.ts +5 -4
- package/examples/raffle.ts +10 -11
- package/examples/river-crossing.ts +140 -0
- package/examples/sandbox.ts +1 -1
- package/examples/simple.ts +4 -2
- package/examples/summary.ts +121 -0
- package/examples/support.ts +5 -5
- package/examples/ticTacToe.ts +86 -45
- package/examples/todo.ts +6 -6
- package/examples/tutor.ts +13 -13
- package/examples/verify.ts +2 -2
- package/examples/weather.ts +5 -8
- package/examples/wiki.ts +26 -7
- package/examples/word.ts +15 -10
- package/package.json +13 -10
- package/readme.md +1 -1
- package/src/agent-experimental.ts +1 -1
- package/src/agent.test.ts +117 -228
- package/src/agent.ts +469 -81
- package/src/{decision.test.ts → decide.test.ts} +26 -50
- package/src/decide.ts +153 -0
- package/src/index.ts +1 -1
- package/src/middleware.ts +103 -0
- package/src/mockModel.ts +47 -0
- package/src/planners/shortestPathPlanner.ts +151 -13
- package/src/planners/simplePlanner.ts +57 -85
- package/src/strategies/chain-of-note.ts +6 -55
- package/src/text.ts +51 -144
- package/src/types.ts +172 -204
- package/src/utils.ts +37 -4
- package/src/adapters/vercel.ts +0 -7
- package/src/decision.ts +0 -84
- package/src/memory.ts +0 -25
package/examples/raffle.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { z } from 'zod';
|
|
|
2
2
|
import { createAgent, fromDecision } from '../src';
|
|
3
3
|
import { openai } from '@ai-sdk/openai';
|
|
4
4
|
import { assign, createActor, log, setup } from 'xstate';
|
|
5
|
-
import {
|
|
5
|
+
import { fromTerminal } from './helpers/helpers';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
name: 'raffle-chooser',
|
|
9
|
-
model: openai('gpt-
|
|
9
|
+
model: openai('gpt-4o-mini'),
|
|
10
10
|
events: {
|
|
11
11
|
'agent.collectEntries': z.object({}).describe('Collect more entries'),
|
|
12
12
|
'agent.draw': z.object({}).describe('Draw a winner'),
|
|
@@ -27,11 +27,10 @@ const machine = setup({
|
|
|
27
27
|
lastInput: string | null;
|
|
28
28
|
entries: string[];
|
|
29
29
|
},
|
|
30
|
-
events:
|
|
30
|
+
events: agent.types.events,
|
|
31
31
|
},
|
|
32
|
-
actors: { agent: fromDecision(agent), getFromTerminal },
|
|
32
|
+
actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal },
|
|
33
33
|
}).createMachine({
|
|
34
|
-
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOjHwBcwAnAqAYggHtCSCA3JgazBLSzyFS5KrXxQEHJpnQVcLANoAGALrKViUAAcmsXHJaaQAD0QAmAOwBmEmYCsADgCMZgJxmALE4ceHSuwA0IACe5gBsZiR29q5KPnaOrmEOAL4pQfw4BMQkEGCiqAR09OgwlCSYTAA2VWCYFACilLRw6kY6egb4RqYIZg52JB52Ya5jSq5+To4eQaEILhYkA0qjVlYeSlsOYWFpGRhZQrn5NIX4xaUiudToAO5tSCAd+vLdT727SiRKHsl2FjCwxcrlmIUQTl2ticHk26ycMPsqXSIEyghyEFud0uZQoJGoYB01AoAHUCIRqI9tLpXoYPohXBYnCQnP4HK4nFYLF5vK45hCPDYmVtdk5uYzuWkUfgmHl4E80dkiO0aV0eogALRhfkIDWDMZjJn9MxhKwjSb7VGHdHCSg0OgqzpvdUIDxmHWc5l2caeKxhVYDFyWxXHPIFIriR2096gXoeVw2VmgiIOBzWJRiqwe2FRdzcpRWE0JPPB61Km73B1PF5q+kIPweEhWMWjCz+BJKJketwkQEWCyuOywv0WaJ2UsCcvY-AUqO12MQls-Ue+Px2KzspweoHLXxsjO-eNmMxSlJAA */
|
|
35
34
|
context: {
|
|
36
35
|
lastInput: null,
|
|
37
36
|
entries: [],
|
|
@@ -56,10 +55,10 @@ const machine = setup({
|
|
|
56
55
|
determining: {
|
|
57
56
|
invoke: {
|
|
58
57
|
src: 'agent',
|
|
59
|
-
input: {
|
|
60
|
-
context
|
|
58
|
+
input: ({ context }) => ({
|
|
59
|
+
context,
|
|
61
60
|
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
|
-
},
|
|
61
|
+
}),
|
|
63
62
|
},
|
|
64
63
|
on: {
|
|
65
64
|
'agent.collectEntries': {
|
|
@@ -76,10 +75,10 @@ const machine = setup({
|
|
|
76
75
|
entry: log('And the winner is...'),
|
|
77
76
|
invoke: {
|
|
78
77
|
src: 'agent',
|
|
79
|
-
input: {
|
|
80
|
-
context
|
|
78
|
+
input: ({ context }) => ({
|
|
79
|
+
context,
|
|
81
80
|
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
|
-
},
|
|
81
|
+
}),
|
|
83
82
|
},
|
|
84
83
|
on: {
|
|
85
84
|
'agent.reportWinner': {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createAgent } from '../src';
|
|
2
|
+
import { assign, createActor, setup } from 'xstate';
|
|
3
|
+
import { openai } from '@ai-sdk/openai';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { shortestPathPlanner } from '../src/planners/shortestPathPlanner';
|
|
6
|
+
|
|
7
|
+
const agent = createAgent({
|
|
8
|
+
name: 'river-crossing-solver',
|
|
9
|
+
model: openai('gpt-4'),
|
|
10
|
+
events: {
|
|
11
|
+
takeWolf: z
|
|
12
|
+
.object({
|
|
13
|
+
reasoning: z.string().describe('Reasoning for taking the wolf across'),
|
|
14
|
+
})
|
|
15
|
+
.describe('Take wolf across the river'),
|
|
16
|
+
takeGoat: z
|
|
17
|
+
.object({
|
|
18
|
+
reasoning: z.string().describe('Reasoning for taking the goat across'),
|
|
19
|
+
})
|
|
20
|
+
.describe('Take goat across the river'),
|
|
21
|
+
takeCabbage: z
|
|
22
|
+
.object({
|
|
23
|
+
reasoning: z
|
|
24
|
+
.string()
|
|
25
|
+
.describe('Reasoning for taking the cabbage across'),
|
|
26
|
+
})
|
|
27
|
+
.describe('Take cabbage across the river'),
|
|
28
|
+
returnEmpty: z
|
|
29
|
+
.object({
|
|
30
|
+
reasoning: z.string().describe('Reasoning for returning alone'),
|
|
31
|
+
})
|
|
32
|
+
.describe('Return across river alone'),
|
|
33
|
+
},
|
|
34
|
+
context: {
|
|
35
|
+
leftBank: z
|
|
36
|
+
.array(z.enum(['wolf', 'goat', 'cabbage']))
|
|
37
|
+
.describe('Items on the left bank'),
|
|
38
|
+
rightBank: z
|
|
39
|
+
.array(z.enum(['wolf', 'goat', 'cabbage']))
|
|
40
|
+
.describe('Items on the right bank'),
|
|
41
|
+
farmerPosition: z
|
|
42
|
+
.enum(['left', 'right'])
|
|
43
|
+
.describe('Which bank the farmer is on'),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const riverCrossingMachine = setup({
|
|
48
|
+
types: {
|
|
49
|
+
context: agent.types.context,
|
|
50
|
+
events: agent.types.events,
|
|
51
|
+
},
|
|
52
|
+
}).createMachine({
|
|
53
|
+
initial: 'solving',
|
|
54
|
+
context: {
|
|
55
|
+
leftBank: ['wolf', 'goat', 'cabbage'],
|
|
56
|
+
rightBank: [],
|
|
57
|
+
farmerPosition: 'left',
|
|
58
|
+
},
|
|
59
|
+
states: {
|
|
60
|
+
solving: {
|
|
61
|
+
always: {
|
|
62
|
+
guard: ({ context }) => context.rightBank.length === 3,
|
|
63
|
+
target: 'success',
|
|
64
|
+
},
|
|
65
|
+
on: {
|
|
66
|
+
takeWolf: {
|
|
67
|
+
guard: ({ context }) =>
|
|
68
|
+
context.leftBank.includes('wolf') &&
|
|
69
|
+
context.farmerPosition === 'left',
|
|
70
|
+
actions: assign(({ context }) => ({
|
|
71
|
+
leftBank: context.leftBank.filter((item) => item !== 'wolf'),
|
|
72
|
+
rightBank: [...context.rightBank, 'wolf'],
|
|
73
|
+
farmerPosition: 'right',
|
|
74
|
+
})),
|
|
75
|
+
},
|
|
76
|
+
takeGoat: {
|
|
77
|
+
guard: ({ context }) =>
|
|
78
|
+
context.leftBank.includes('goat') &&
|
|
79
|
+
context.farmerPosition === 'left',
|
|
80
|
+
actions: assign(({ context }) => ({
|
|
81
|
+
leftBank: context.leftBank.filter((item) => item !== 'goat'),
|
|
82
|
+
rightBank: [...context.rightBank, 'goat'],
|
|
83
|
+
farmerPosition: 'right',
|
|
84
|
+
})),
|
|
85
|
+
},
|
|
86
|
+
takeCabbage: {
|
|
87
|
+
guard: ({ context }) =>
|
|
88
|
+
context.leftBank.includes('cabbage') &&
|
|
89
|
+
context.farmerPosition === 'left',
|
|
90
|
+
actions: assign(({ context }) => ({
|
|
91
|
+
leftBank: context.leftBank.filter((item) => item !== 'cabbage'),
|
|
92
|
+
rightBank: [...context.rightBank, 'cabbage'],
|
|
93
|
+
farmerPosition: 'right',
|
|
94
|
+
})),
|
|
95
|
+
},
|
|
96
|
+
returnEmpty: {
|
|
97
|
+
actions: assign(({ context }) => ({
|
|
98
|
+
farmerPosition:
|
|
99
|
+
context.farmerPosition === 'left' ? 'right' : 'left',
|
|
100
|
+
})),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
success: {
|
|
105
|
+
type: 'final',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let maxTries = 0;
|
|
111
|
+
async function main() {
|
|
112
|
+
const riverActor = createActor(riverCrossingMachine).start();
|
|
113
|
+
|
|
114
|
+
while (riverActor.getSnapshot().value !== 'success') {
|
|
115
|
+
maxTries++;
|
|
116
|
+
if (maxTries > 20) {
|
|
117
|
+
console.log('Max tries reached');
|
|
118
|
+
throw new Error('Max tries reached');
|
|
119
|
+
}
|
|
120
|
+
const decision = await agent.decide({
|
|
121
|
+
machine: riverCrossingMachine,
|
|
122
|
+
goal: 'Get all items safely across the river. Remember: Cannot leave wolf with goat or goat with cabbage unattended.',
|
|
123
|
+
state: riverActor.getSnapshot(),
|
|
124
|
+
planner: shortestPathPlanner,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
console.log(decision?.nextEvent);
|
|
128
|
+
|
|
129
|
+
if (decision?.nextEvent) {
|
|
130
|
+
riverActor.send(decision.nextEvent);
|
|
131
|
+
console.log(riverActor.getSnapshot().context);
|
|
132
|
+
} else {
|
|
133
|
+
console.log('No decision made');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('Successfully crossed the river!');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|
package/examples/sandbox.ts
CHANGED
package/examples/simple.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { openai } from '@ai-sdk/openai';
|
|
|
5
5
|
|
|
6
6
|
const agent = createAgent({
|
|
7
7
|
name: 'simple',
|
|
8
|
-
model: openai('gpt-
|
|
8
|
+
model: openai('gpt-4o-mini'),
|
|
9
9
|
events: {
|
|
10
10
|
'agent.thought': z.object({
|
|
11
11
|
text: z.string().describe('The text of the thought'),
|
|
@@ -21,7 +21,9 @@ const machine = setup({
|
|
|
21
21
|
thinking: {
|
|
22
22
|
invoke: {
|
|
23
23
|
src: 'agent',
|
|
24
|
-
input:
|
|
24
|
+
input: {
|
|
25
|
+
goal: 'Think about a random topic, and then share that thought.',
|
|
26
|
+
},
|
|
25
27
|
},
|
|
26
28
|
on: {
|
|
27
29
|
'agent.thought': {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createAgent, fromDecision } from '../src';
|
|
2
|
+
import { assign, createActor, setup } from 'xstate';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { openai } from '@ai-sdk/openai';
|
|
5
|
+
import { fromTerminal } from './helpers/helpers';
|
|
6
|
+
|
|
7
|
+
const agent = createAgent({
|
|
8
|
+
name: 'summarizing-chat',
|
|
9
|
+
model: openai('gpt-4o'),
|
|
10
|
+
events: {
|
|
11
|
+
'agent.respond': z.object({
|
|
12
|
+
response: z.string().describe('The response from the agent'),
|
|
13
|
+
}),
|
|
14
|
+
'agent.summarize': z.object({
|
|
15
|
+
summary: z.string().describe('Summary of the conversation history'),
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
context: {
|
|
19
|
+
messages: z.array(
|
|
20
|
+
z.object({
|
|
21
|
+
role: z.enum(['user', 'assistant']),
|
|
22
|
+
content: z.string(),
|
|
23
|
+
})
|
|
24
|
+
),
|
|
25
|
+
summary: z.string().nullable(),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const machine = setup({
|
|
30
|
+
types: {
|
|
31
|
+
context: agent.types.context,
|
|
32
|
+
events: agent.types.events,
|
|
33
|
+
},
|
|
34
|
+
actors: {
|
|
35
|
+
agent: fromDecision(agent),
|
|
36
|
+
fromTerminal,
|
|
37
|
+
},
|
|
38
|
+
}).createMachine({
|
|
39
|
+
initial: 'user',
|
|
40
|
+
context: {
|
|
41
|
+
messages: [],
|
|
42
|
+
summary: null,
|
|
43
|
+
},
|
|
44
|
+
states: {
|
|
45
|
+
user: {
|
|
46
|
+
invoke: {
|
|
47
|
+
src: 'fromTerminal',
|
|
48
|
+
input: 'Enter a message:',
|
|
49
|
+
onDone: {
|
|
50
|
+
actions: assign({
|
|
51
|
+
messages: ({ context, event }) => [
|
|
52
|
+
...context.messages,
|
|
53
|
+
{ role: 'user', content: event.output },
|
|
54
|
+
],
|
|
55
|
+
}),
|
|
56
|
+
target: 'chatting',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
chatting: {
|
|
61
|
+
always: {
|
|
62
|
+
guard: ({ context }) => context.messages.length > 10,
|
|
63
|
+
target: 'summarizing',
|
|
64
|
+
},
|
|
65
|
+
invoke: {
|
|
66
|
+
src: 'agent',
|
|
67
|
+
input: ({ context }) => ({
|
|
68
|
+
goal: 'Respond to the user message',
|
|
69
|
+
context: {
|
|
70
|
+
messages: context.messages,
|
|
71
|
+
summary: context.summary,
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
on: {
|
|
76
|
+
'agent.respond': {
|
|
77
|
+
actions: assign({
|
|
78
|
+
messages: ({ context, event }) => [
|
|
79
|
+
...context.messages,
|
|
80
|
+
{ role: 'assistant', content: event.response },
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
target: 'user',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
summarizing: {
|
|
88
|
+
invoke: {
|
|
89
|
+
src: 'agent',
|
|
90
|
+
input: ({ context }) => ({
|
|
91
|
+
goal: 'Create a concise summary of the conversation history',
|
|
92
|
+
context: {
|
|
93
|
+
messages: context.messages,
|
|
94
|
+
previousSummary: context.summary,
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
on: {
|
|
99
|
+
'agent.summarize': {
|
|
100
|
+
actions: assign({
|
|
101
|
+
summary: ({ event }) => event.summary,
|
|
102
|
+
messages: ({ context }) => context.messages.slice(-3), // Keep last 3 messages
|
|
103
|
+
}),
|
|
104
|
+
target: 'chatting',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const actor = createActor(machine);
|
|
112
|
+
actor.subscribe((state) => {
|
|
113
|
+
console.log('Current state:', state.value);
|
|
114
|
+
console.log(
|
|
115
|
+
'Messages:',
|
|
116
|
+
state.context.messages.map((msg) => `${msg.role}: ${msg.content}`)
|
|
117
|
+
);
|
|
118
|
+
console.log('Summary:', state.context.summary);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
actor.start();
|
package/examples/support.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { createActor, log, setup } from 'xstate';
|
|
|
5
5
|
|
|
6
6
|
const agent = createAgent({
|
|
7
7
|
name: 'support-agent',
|
|
8
|
-
model: openai('gpt-
|
|
8
|
+
model: openai('gpt-4o-mini'),
|
|
9
9
|
events: {
|
|
10
10
|
'agent.respond': z.object({
|
|
11
11
|
response: z.string().describe('The response from the agent'),
|
|
@@ -101,11 +101,11 @@ const machine = setup({
|
|
|
101
101
|
technical: {
|
|
102
102
|
invoke: {
|
|
103
103
|
src: 'agent',
|
|
104
|
-
input: {
|
|
105
|
-
context
|
|
104
|
+
input: ({ context }) => ({
|
|
105
|
+
context,
|
|
106
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
107
|
goal: 'Solve the customer issue.',
|
|
108
|
-
},
|
|
108
|
+
}),
|
|
109
109
|
},
|
|
110
110
|
on: {
|
|
111
111
|
'agent.technical.solve': {
|
|
@@ -123,7 +123,7 @@ const machine = setup({
|
|
|
123
123
|
},
|
|
124
124
|
on: {
|
|
125
125
|
'agent.endConversation': {
|
|
126
|
-
actions: log((
|
|
126
|
+
actions: log(({ event }) => event),
|
|
127
127
|
target: 'end',
|
|
128
128
|
},
|
|
129
129
|
},
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -3,40 +3,53 @@ import { z } from 'zod';
|
|
|
3
3
|
import { createAgent, fromDecision, fromTextStream } from '../src';
|
|
4
4
|
import { openai } from '@ai-sdk/openai';
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'agent.x.play': z.object({
|
|
11
|
-
index: z
|
|
12
|
-
.number()
|
|
13
|
-
.min(0)
|
|
14
|
-
.max(8)
|
|
15
|
-
.describe('The index of the cell to play on'),
|
|
16
|
-
}),
|
|
17
|
-
'agent.o.play': z.object({
|
|
18
|
-
index: z
|
|
19
|
-
.number()
|
|
20
|
-
.min(0)
|
|
21
|
-
.max(8)
|
|
22
|
-
.describe('The index of the cell to play on'),
|
|
23
|
-
}),
|
|
24
|
-
reset: z.object({}).describe('Reset the game to the initial state'),
|
|
25
|
-
},
|
|
26
|
-
context: {
|
|
27
|
-
board: z
|
|
28
|
-
.array(z.union([z.literal(null), z.literal('x'), z.literal('o')]))
|
|
29
|
-
.describe('The 3x3 board represented as a 9-element array.'),
|
|
30
|
-
moves: z
|
|
6
|
+
const events = {
|
|
7
|
+
'agent.x.play': z.object({
|
|
8
|
+
reasoning: z.string().describe('The reasoning for the move'),
|
|
9
|
+
index: z
|
|
31
10
|
.number()
|
|
32
11
|
.min(0)
|
|
33
|
-
.max(
|
|
34
|
-
.describe('The
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
12
|
+
.max(8)
|
|
13
|
+
.describe('The index of the cell for xto play on'),
|
|
14
|
+
}),
|
|
15
|
+
'agent.o.play': z.object({
|
|
16
|
+
reasoning: z.string().describe('The reasoning for the move'),
|
|
17
|
+
index: z
|
|
18
|
+
.number()
|
|
19
|
+
.min(0)
|
|
20
|
+
.max(8)
|
|
21
|
+
.describe('The index of the cell for o to play on'),
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const context = {
|
|
26
|
+
board: z
|
|
27
|
+
.array(z.union([z.literal(null), z.literal('x'), z.literal('o')]))
|
|
28
|
+
.describe('The 3x3 board represented as a 9-element array.'),
|
|
29
|
+
moves: z
|
|
30
|
+
.number()
|
|
31
|
+
.min(0)
|
|
32
|
+
.max(9)
|
|
33
|
+
.describe('The number of moves made in the game.'),
|
|
34
|
+
player: z
|
|
35
|
+
.union([z.literal('x'), z.literal('o')])
|
|
36
|
+
.describe('The current player (x or o)'),
|
|
37
|
+
gameReport: z.string(),
|
|
38
|
+
lastReason: z.string(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const xAgent = createAgent({
|
|
42
|
+
name: 'tic-tac-toe-learner',
|
|
43
|
+
model: openai('gpt-4o-mini'),
|
|
44
|
+
events,
|
|
45
|
+
context,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const oAgent = createAgent({
|
|
49
|
+
name: 'tic-tac-toe-noob',
|
|
50
|
+
model: openai('gpt-4o-mini'),
|
|
51
|
+
events,
|
|
52
|
+
context,
|
|
40
53
|
});
|
|
41
54
|
|
|
42
55
|
type Player = 'x' | 'o';
|
|
@@ -46,7 +59,8 @@ const initialContext = {
|
|
|
46
59
|
moves: 0,
|
|
47
60
|
player: 'x' as Player,
|
|
48
61
|
gameReport: '',
|
|
49
|
-
|
|
62
|
+
lastReason: '',
|
|
63
|
+
} satisfies typeof xAgent.types.context;
|
|
50
64
|
|
|
51
65
|
function getWinner(board: typeof initialContext.board): Player | null {
|
|
52
66
|
const lines = [
|
|
@@ -69,12 +83,15 @@ function getWinner(board: typeof initialContext.board): Player | null {
|
|
|
69
83
|
|
|
70
84
|
export const ticTacToeMachine = setup({
|
|
71
85
|
types: {
|
|
72
|
-
context:
|
|
73
|
-
events:
|
|
86
|
+
context: xAgent.types.context,
|
|
87
|
+
events: {} as
|
|
88
|
+
| typeof xAgent.types.events
|
|
89
|
+
| {
|
|
90
|
+
type: 'reset';
|
|
91
|
+
},
|
|
74
92
|
},
|
|
75
93
|
actors: {
|
|
76
|
-
|
|
77
|
-
gameReporter: fromTextStream(agent),
|
|
94
|
+
gameReporter: fromTextStream(xAgent),
|
|
78
95
|
},
|
|
79
96
|
actions: {
|
|
80
97
|
updateBoard: assign({
|
|
@@ -90,14 +107,14 @@ export const ticTacToeMachine = setup({
|
|
|
90
107
|
resetGame: assign(initialContext),
|
|
91
108
|
printBoard: ({ context }) => {
|
|
92
109
|
// Print the context.board in a 3 x 3 grid format
|
|
93
|
-
let boardString =
|
|
110
|
+
let boardString = `${context.lastReason}\n`;
|
|
94
111
|
for (let i = 0; i < context.board.length; i++) {
|
|
95
112
|
if ([0, 3, 6].includes(i)) {
|
|
96
113
|
boardString += context.board[i] ?? ' ';
|
|
97
114
|
} else {
|
|
98
115
|
boardString += ' | ' + (context.board[i] ?? ' ');
|
|
99
116
|
if ([2, 5].includes(i)) {
|
|
100
|
-
boardString +=
|
|
117
|
+
boardString += `\n--+---+--\n`;
|
|
101
118
|
}
|
|
102
119
|
}
|
|
103
120
|
}
|
|
@@ -142,7 +159,12 @@ export const ticTacToeMachine = setup({
|
|
|
142
159
|
{
|
|
143
160
|
target: 'o',
|
|
144
161
|
guard: 'isValidMove',
|
|
145
|
-
actions:
|
|
162
|
+
actions: [
|
|
163
|
+
assign({
|
|
164
|
+
lastReason: ({ event }) => event.reasoning,
|
|
165
|
+
}),
|
|
166
|
+
'updateBoard',
|
|
167
|
+
],
|
|
146
168
|
},
|
|
147
169
|
{ target: 'x', reenter: true },
|
|
148
170
|
],
|
|
@@ -155,7 +177,12 @@ export const ticTacToeMachine = setup({
|
|
|
155
177
|
{
|
|
156
178
|
target: 'x',
|
|
157
179
|
guard: 'isValidMove',
|
|
158
|
-
actions:
|
|
180
|
+
actions: [
|
|
181
|
+
assign({
|
|
182
|
+
lastReason: ({ event }) => event.reasoning,
|
|
183
|
+
}),
|
|
184
|
+
'updateBoard',
|
|
185
|
+
],
|
|
159
186
|
},
|
|
160
187
|
{ target: 'o', reenter: true },
|
|
161
188
|
],
|
|
@@ -169,7 +196,7 @@ export const ticTacToeMachine = setup({
|
|
|
169
196
|
src: 'gameReporter',
|
|
170
197
|
input: ({ context }) => ({
|
|
171
198
|
context: {
|
|
172
|
-
events:
|
|
199
|
+
events: xAgent.getObservations().map((o) => o.event),
|
|
173
200
|
board: context.board,
|
|
174
201
|
},
|
|
175
202
|
prompt: 'Provide a short game report analyzing the game.',
|
|
@@ -207,10 +234,24 @@ export const ticTacToeMachine = setup({
|
|
|
207
234
|
|
|
208
235
|
const actor = createActor(ticTacToeMachine);
|
|
209
236
|
|
|
210
|
-
|
|
211
|
-
if (observed.state.matches(
|
|
237
|
+
xAgent.interact(actor, (observed) => {
|
|
238
|
+
if (observed.state.matches({ playing: 'x' })) {
|
|
239
|
+
return {
|
|
240
|
+
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.
|
|
241
|
+
|
|
242
|
+
${JSON.stringify(observed.state.context, null, 2)}
|
|
243
|
+
|
|
244
|
+
Execute the single best next move to try to win the game. Do not play on an existing cell.`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
oAgent.interact(actor, (observed) => {
|
|
252
|
+
if (observed.state.matches({ playing: 'o' })) {
|
|
212
253
|
return {
|
|
213
|
-
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.
|
|
254
|
+
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.
|
|
214
255
|
|
|
215
256
|
${JSON.stringify(observed.state.context, null, 2)}
|
|
216
257
|
|
package/examples/todo.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { assign, setup, assertEvent, createActor, createMachine } from 'xstate';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { createAgent, fromDecision } from '../src';
|
|
4
4
|
import { openai } from '@ai-sdk/openai';
|
|
5
|
-
import {
|
|
5
|
+
import { fromTerminal } from './helpers/helpers';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
name: 'todo',
|
|
9
|
-
model: openai('gpt-4o'),
|
|
9
|
+
model: openai('gpt-4o-mini'),
|
|
10
10
|
events: {
|
|
11
11
|
addTodo: z.object({
|
|
12
12
|
title: z.string().min(1).max(100).describe('The title of the todo'),
|
|
@@ -40,7 +40,7 @@ const machine = setup({
|
|
|
40
40
|
| typeof agent.types.events
|
|
41
41
|
| { type: 'assist'; command: string },
|
|
42
42
|
},
|
|
43
|
-
actors: { agent: fromDecision(agent), getFromTerminal },
|
|
43
|
+
actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal },
|
|
44
44
|
}).createMachine({
|
|
45
45
|
context: {
|
|
46
46
|
command: null,
|
|
@@ -118,10 +118,10 @@ const machine = setup({
|
|
|
118
118
|
assisting: {
|
|
119
119
|
invoke: {
|
|
120
120
|
src: 'agent',
|
|
121
|
-
input: (
|
|
121
|
+
input: ({ context }) => ({
|
|
122
122
|
context: {
|
|
123
|
-
command:
|
|
124
|
-
todos:
|
|
123
|
+
command: context.command,
|
|
124
|
+
todos: context.todos,
|
|
125
125
|
},
|
|
126
126
|
goal: 'Interpret the command as an action for this todo list; for example, "I need donuts" would add a todo item with the message "Get donuts".',
|
|
127
127
|
}),
|
package/examples/tutor.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { assign, createActor, log, setup } from 'xstate';
|
|
2
|
-
import {
|
|
2
|
+
import { fromTerminal } from './helpers/helpers';
|
|
3
3
|
import { createAgent, fromDecision } from '../src';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { openai } from '@ai-sdk/openai';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
name: 'tutor',
|
|
9
|
-
model: openai('gpt-
|
|
9
|
+
model: openai('gpt-4o-mini'),
|
|
10
10
|
events: {
|
|
11
11
|
teach: z.object({
|
|
12
12
|
instruction: z
|
|
@@ -30,7 +30,7 @@ const machine = setup({
|
|
|
30
30
|
},
|
|
31
31
|
events: agent.types.events,
|
|
32
32
|
},
|
|
33
|
-
actors: { agent: fromDecision(agent), getFromTerminal },
|
|
33
|
+
actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal },
|
|
34
34
|
}).createMachine({
|
|
35
35
|
initial: 'human',
|
|
36
36
|
context: {
|
|
@@ -43,8 +43,8 @@ const machine = setup({
|
|
|
43
43
|
input: 'Say something in Spanish:',
|
|
44
44
|
onDone: {
|
|
45
45
|
actions: assign({
|
|
46
|
-
conversation: (
|
|
47
|
-
|
|
46
|
+
conversation: ({ context, event }) =>
|
|
47
|
+
context.conversation.concat(`User: ` + event.output),
|
|
48
48
|
}),
|
|
49
49
|
target: 'ai',
|
|
50
50
|
},
|
|
@@ -56,15 +56,15 @@ const machine = setup({
|
|
|
56
56
|
teaching: {
|
|
57
57
|
invoke: {
|
|
58
58
|
src: 'agent',
|
|
59
|
-
input: (
|
|
60
|
-
context
|
|
59
|
+
input: ({ context }) => ({
|
|
60
|
+
context,
|
|
61
61
|
goal: 'Give brief feedback to the human based on the most recent response of the conversation',
|
|
62
62
|
maxTokens: 100,
|
|
63
63
|
}),
|
|
64
64
|
},
|
|
65
65
|
on: {
|
|
66
66
|
teach: {
|
|
67
|
-
actions: (
|
|
67
|
+
actions: ({ event }) => console.log(event.instruction),
|
|
68
68
|
target: 'responding',
|
|
69
69
|
},
|
|
70
70
|
},
|
|
@@ -72,8 +72,8 @@ const machine = setup({
|
|
|
72
72
|
responding: {
|
|
73
73
|
invoke: {
|
|
74
74
|
src: 'agent',
|
|
75
|
-
input: (
|
|
76
|
-
context
|
|
75
|
+
input: ({ context }) => ({
|
|
76
|
+
context,
|
|
77
77
|
goal: 'Respond to the last message of the conversation in Spanish',
|
|
78
78
|
}),
|
|
79
79
|
},
|
|
@@ -81,10 +81,10 @@ const machine = setup({
|
|
|
81
81
|
respond: {
|
|
82
82
|
actions: [
|
|
83
83
|
assign({
|
|
84
|
-
conversation: (
|
|
85
|
-
|
|
84
|
+
conversation: ({ context, event }) =>
|
|
85
|
+
context.conversation.concat(`Agent: ` + event.response),
|
|
86
86
|
}),
|
|
87
|
-
log((
|
|
87
|
+
log(({ event }) => event.response),
|
|
88
88
|
],
|
|
89
89
|
target: 'done',
|
|
90
90
|
},
|