@statelyai/agent 2.0.0-next.0 → 2.0.0-next.2
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/cyan-carpets-perform.md +5 -0
- package/.changeset/fast-donkeys-argue.md +5 -0
- package/.changeset/grumpy-dolphins-think.md +17 -0
- package/.changeset/old-jobs-check.md +5 -0
- package/.changeset/old-teachers-tap.md +5 -0
- package/.changeset/pre.json +7 -1
- package/.changeset/smart-yaks-pull.md +23 -0
- package/CHANGELOG.md +52 -0
- package/dist/index.d.mts +69 -66
- package/dist/index.d.ts +69 -66
- package/dist/index.js +111 -79
- package/dist/index.mjs +114 -82
- package/examples/chatbot-alt.ts +1 -1
- package/examples/chatbot.ts +3 -3
- package/examples/cot.ts +7 -25
- package/examples/customer-service-sim.ts +7 -7
- package/examples/email.ts +37 -35
- package/examples/example.ts +3 -3
- package/examples/goal.ts +3 -3
- package/examples/joke.ts +3 -3
- package/examples/jugs.ts +5 -8
- package/examples/learn-from-feedback.ts +100 -0
- package/examples/multi.ts +1 -1
- package/examples/number.ts +3 -3
- package/examples/raffle.ts +3 -3
- package/examples/river-crossing.ts +5 -8
- package/examples/simple.ts +14 -11
- package/examples/summary.ts +3 -6
- package/examples/support.ts +43 -39
- package/examples/ticTacToe.ts +48 -6
- package/examples/todo.ts +3 -3
- package/examples/tutor.ts +4 -4
- package/examples/verify.ts +3 -3
- package/examples/weather-agent.ts +141 -0
- package/examples/weather.ts +24 -24
- package/examples/wiki.ts +1 -1
- package/examples/word.ts +9 -7
- package/package.json +6 -3
- package/src/agent.test.ts +161 -19
- package/src/agent.ts +66 -250
- package/src/decide.test.ts +172 -3
- package/src/decide.ts +50 -30
- package/src/middleware.ts +2 -14
- package/src/strategies/chainOfThought.ts +48 -0
- package/src/strategies/shortestPath.test.ts +91 -0
- package/src/{planners/shortestPathPlanner.ts → strategies/shortestPath.ts} +32 -19
- package/src/{planners/simplePlanner.ts → strategies/simple.ts} +28 -24
- package/src/types.ts +75 -34
- package/src/utils.ts +23 -0
- package/vitest.config.ts +9 -3
- package/src/strategies/chain-of-note.ts +0 -106
package/examples/verify.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { assign, createActor, setup, log } from 'xstate';
|
|
2
2
|
import { fromTerminal } from './helpers/helpers';
|
|
3
|
-
import { createAgent, fromDecision } from '../src';
|
|
3
|
+
import { createAgent, EventsFromAgent, 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
|
+
id: 'verifier',
|
|
9
9
|
model: openai('gpt-3.5-turbo-16k-0613'),
|
|
10
10
|
events: {
|
|
11
11
|
'agent.validateAnswer': z.object({
|
|
@@ -35,7 +35,7 @@ const machine = setup({
|
|
|
35
35
|
answer: string | null;
|
|
36
36
|
validation: string | null;
|
|
37
37
|
},
|
|
38
|
-
events: agent
|
|
38
|
+
events: {} as EventsFromAgent<typeof agent>,
|
|
39
39
|
},
|
|
40
40
|
actors: {
|
|
41
41
|
getFromTerminal: fromTerminal,
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createAgent } from '../src';
|
|
3
|
+
import { assign, createActor, fromPromise, setup } from 'xstate';
|
|
4
|
+
// import { anthropic } from '@ai-sdk/anthropic';
|
|
5
|
+
import { fromTerminal } from './helpers/helpers';
|
|
6
|
+
import { openai } from '@ai-sdk/openai';
|
|
7
|
+
|
|
8
|
+
// Create the weather agent
|
|
9
|
+
const agent = createAgent({
|
|
10
|
+
id: 'weather-agent',
|
|
11
|
+
model: openai('gpt-4o'),
|
|
12
|
+
events: {
|
|
13
|
+
'weather.check': z.object({
|
|
14
|
+
location: z.string().describe('The location to check weather for'),
|
|
15
|
+
}),
|
|
16
|
+
'weather.report': z.object({
|
|
17
|
+
temperature: z.string(),
|
|
18
|
+
conditions: z.string(),
|
|
19
|
+
}),
|
|
20
|
+
'agent.respond': z.object({
|
|
21
|
+
response: z.string(),
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Create the weather tool/service
|
|
27
|
+
const getWeather = (location: string) => {
|
|
28
|
+
if (
|
|
29
|
+
location.toLowerCase().includes('sf') ||
|
|
30
|
+
location.toLowerCase().includes('san francisco')
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
temperature: '60 degrees',
|
|
34
|
+
conditions: 'foggy',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
temperature: '90 degrees',
|
|
39
|
+
conditions: 'sunny',
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Create the state machine
|
|
44
|
+
const machine = setup({
|
|
45
|
+
types: {
|
|
46
|
+
context: {} as {
|
|
47
|
+
input: string | null;
|
|
48
|
+
location: string | null;
|
|
49
|
+
weather: any;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
actors: {
|
|
53
|
+
getFromTerminal: fromTerminal,
|
|
54
|
+
getWeather: fromPromise(async ({ input }: { input: string }) => {
|
|
55
|
+
return getWeather(input);
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
}).createMachine({
|
|
59
|
+
context: {
|
|
60
|
+
input: null,
|
|
61
|
+
location: null,
|
|
62
|
+
weather: null,
|
|
63
|
+
},
|
|
64
|
+
initial: 'user',
|
|
65
|
+
states: {
|
|
66
|
+
user: {
|
|
67
|
+
invoke: {
|
|
68
|
+
src: 'getFromTerminal',
|
|
69
|
+
input: 'Ask me about the weather!',
|
|
70
|
+
onDone: {
|
|
71
|
+
actions: assign({
|
|
72
|
+
input: ({ event }) => event.output,
|
|
73
|
+
}),
|
|
74
|
+
target: 'processing',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
processing: {
|
|
79
|
+
entry: () => console.log('Processing...'),
|
|
80
|
+
on: {
|
|
81
|
+
'weather.check': {
|
|
82
|
+
actions: assign({
|
|
83
|
+
input: ({ event }) => event.location,
|
|
84
|
+
}),
|
|
85
|
+
target: 'checking',
|
|
86
|
+
},
|
|
87
|
+
'agent.respond': {
|
|
88
|
+
actions: ({ event }) => console.log(event.response),
|
|
89
|
+
target: 'responded',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
checking: {
|
|
94
|
+
entry: ({ event }) => console.log('Checking weather...', event),
|
|
95
|
+
invoke: {
|
|
96
|
+
src: 'getWeather',
|
|
97
|
+
input: ({ context }) => context.input!,
|
|
98
|
+
onDone: {
|
|
99
|
+
actions: assign({
|
|
100
|
+
weather: ({ event }) => event.output,
|
|
101
|
+
}),
|
|
102
|
+
target: 'responding',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
responding: {
|
|
107
|
+
on: {
|
|
108
|
+
'agent.respond': {
|
|
109
|
+
actions: ({ event }) => console.log(event.response),
|
|
110
|
+
target: 'responded',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
responded: {
|
|
115
|
+
after: {
|
|
116
|
+
1000: { target: 'user' },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Create and start the actor
|
|
123
|
+
const actor = createActor(machine).start();
|
|
124
|
+
|
|
125
|
+
agent.interact(actor, (obs) => {
|
|
126
|
+
if (obs.state.matches('processing')) {
|
|
127
|
+
return {
|
|
128
|
+
goal: 'Determine if user is asking about weather and for which location. If so, get the weather. Otherwise, respond to the user.',
|
|
129
|
+
context: obs.state.context,
|
|
130
|
+
messages: agent.getMessages(),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (obs.state.matches('responding')) {
|
|
135
|
+
return {
|
|
136
|
+
goal: 'Provide a natural response about the weather in ${context.location}',
|
|
137
|
+
context: obs.state.context,
|
|
138
|
+
messages: agent.getMessages(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
package/examples/weather.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAgent, fromDecision } from '../src';
|
|
1
|
+
import { createAgent, EventsFromAgent, fromDecision } from '../src';
|
|
2
2
|
import { assign, createActor, fromPromise, log, setup } from 'xstate';
|
|
3
3
|
import { fromTerminal } from './helpers/helpers';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -49,7 +49,7 @@ const getWeather = fromPromise(async ({ input }: { input: string }) => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const agent = createAgent({
|
|
52
|
-
|
|
52
|
+
id: 'weather',
|
|
53
53
|
model: openai('gpt-4o-mini'),
|
|
54
54
|
events: {
|
|
55
55
|
'agent.getWeather': z.object({
|
|
@@ -75,10 +75,10 @@ const machine = setup({
|
|
|
75
75
|
types: {
|
|
76
76
|
context: {} as {
|
|
77
77
|
location: string;
|
|
78
|
-
history: string[];
|
|
79
78
|
count: number;
|
|
79
|
+
result: string | null;
|
|
80
80
|
},
|
|
81
|
-
events: agent
|
|
81
|
+
events: {} as EventsFromAgent<typeof agent>,
|
|
82
82
|
},
|
|
83
83
|
actors: {
|
|
84
84
|
agent: fromDecision(agent),
|
|
@@ -90,7 +90,7 @@ const machine = setup({
|
|
|
90
90
|
context: {
|
|
91
91
|
location: '',
|
|
92
92
|
count: 0,
|
|
93
|
-
|
|
93
|
+
result: null,
|
|
94
94
|
},
|
|
95
95
|
states: {
|
|
96
96
|
getLocation: {
|
|
@@ -111,15 +111,6 @@ const machine = setup({
|
|
|
111
111
|
},
|
|
112
112
|
decide: {
|
|
113
113
|
entry: log('Deciding...'),
|
|
114
|
-
invoke: {
|
|
115
|
-
src: 'agent',
|
|
116
|
-
input: ({ context }) => ({
|
|
117
|
-
context: {
|
|
118
|
-
location: context.location,
|
|
119
|
-
},
|
|
120
|
-
goal: `Decide what to do based on the given location, which may or may not be a location`,
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
123
114
|
on: {
|
|
124
115
|
'agent.getWeather': {
|
|
125
116
|
actions: log(({ event }) => event),
|
|
@@ -138,6 +129,7 @@ const machine = setup({
|
|
|
138
129
|
log(({ event }) => event.output),
|
|
139
130
|
assign({
|
|
140
131
|
count: ({ context }) => context.count + 1,
|
|
132
|
+
result: ({ event }) => event.output,
|
|
141
133
|
}),
|
|
142
134
|
],
|
|
143
135
|
target: 'reportWeather',
|
|
@@ -145,13 +137,6 @@ const machine = setup({
|
|
|
145
137
|
},
|
|
146
138
|
},
|
|
147
139
|
reportWeather: {
|
|
148
|
-
invoke: {
|
|
149
|
-
src: 'agent',
|
|
150
|
-
input: ({ context }) => ({
|
|
151
|
-
goal: 'Report the weather', // TODO
|
|
152
|
-
context,
|
|
153
|
-
}),
|
|
154
|
-
},
|
|
155
140
|
on: {
|
|
156
141
|
'agent.reportWeather': {
|
|
157
142
|
actions: log(({ event }) => event),
|
|
@@ -169,7 +154,22 @@ const machine = setup({
|
|
|
169
154
|
});
|
|
170
155
|
|
|
171
156
|
const actor = createActor(machine);
|
|
172
|
-
actor.subscribe((s) => {
|
|
173
|
-
console.log(s.value);
|
|
174
|
-
});
|
|
175
157
|
actor.start();
|
|
158
|
+
|
|
159
|
+
agent.interact(actor, (obs) => {
|
|
160
|
+
if (obs.state.matches('decide')) {
|
|
161
|
+
return {
|
|
162
|
+
goal: `Decide what to do based on the given location, which may or may not be a location`,
|
|
163
|
+
context: {
|
|
164
|
+
location: obs.state.context.location,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (obs.state.matches('reportWeather')) {
|
|
170
|
+
return {
|
|
171
|
+
goal: `Report the weather for the given location`,
|
|
172
|
+
context: obs.state.context,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
});
|
package/examples/wiki.ts
CHANGED
package/examples/word.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { assign, createActor, log, setup } from 'xstate';
|
|
2
2
|
import { fromTerminal } from './helpers/helpers';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ContextFromAgent,
|
|
5
|
+
createAgent,
|
|
6
|
+
fromDecision,
|
|
7
|
+
TypesFromAgent,
|
|
8
|
+
} from '../src';
|
|
4
9
|
import { z } from 'zod';
|
|
5
10
|
import { openai } from '@ai-sdk/openai';
|
|
6
11
|
|
|
7
12
|
const agent = createAgent({
|
|
8
|
-
|
|
13
|
+
id: 'word',
|
|
9
14
|
model: openai('gpt-4o-mini'),
|
|
10
15
|
context: {
|
|
11
16
|
word: z.string().nullable().describe('The word to guess'),
|
|
@@ -36,13 +41,10 @@ const context = {
|
|
|
36
41
|
word: null,
|
|
37
42
|
guessedWord: null,
|
|
38
43
|
lettersGuessed: [],
|
|
39
|
-
} satisfies typeof agent
|
|
44
|
+
} satisfies ContextFromAgent<typeof agent>;
|
|
40
45
|
|
|
41
46
|
const wordGuesserMachine = setup({
|
|
42
|
-
types: {
|
|
43
|
-
context: agent.types.context,
|
|
44
|
-
events: agent.types.events,
|
|
45
|
-
},
|
|
47
|
+
types: {} as TypesFromAgent<typeof agent>,
|
|
46
48
|
actors: {
|
|
47
49
|
agent: fromDecision(agent),
|
|
48
50
|
getFromTerminal: fromTerminal,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.2",
|
|
4
4
|
"description": "Stateful agents that make decisions based on finite-state machine models",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"author": "David Khourshid <david@stately.ai>",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"devDependencies": {
|
|
18
|
+
"@ai-sdk/anthropic": "^0.0.54",
|
|
18
19
|
"@ai-sdk/openai": "^0.0.40",
|
|
19
20
|
"@changesets/changelog-github": "^0.5.0",
|
|
20
21
|
"@changesets/cli": "^2.27.9",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"@tavily/core": "^0.0.2",
|
|
25
26
|
"@types/node": "^20.17.1",
|
|
26
27
|
"@types/object-hash": "^3.0.6",
|
|
28
|
+
"@vitest/coverage-v8": "^2.1.4",
|
|
27
29
|
"dotenv": "^16.4.5",
|
|
28
30
|
"json-schema-to-ts": "^3.1.1",
|
|
29
31
|
"ts-node": "^10.9.2",
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
},
|
|
39
41
|
"dependencies": {
|
|
40
42
|
"@xstate/graph": "^2.0.1",
|
|
41
|
-
"ai": "^3.4.
|
|
43
|
+
"ai": "^3.4.31",
|
|
42
44
|
"ajv": "^8.17.1",
|
|
43
45
|
"object-hash": "^3.0.0",
|
|
44
46
|
"xstate": "^5.18.2",
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
"example": "ts-node examples/helpers/runner.ts",
|
|
53
55
|
"changeset": "changeset",
|
|
54
56
|
"release": "changeset publish",
|
|
55
|
-
"version": "changeset version"
|
|
57
|
+
"version": "changeset version",
|
|
58
|
+
"coverage": "vitest run --coverage"
|
|
56
59
|
}
|
|
57
60
|
}
|
package/src/agent.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { test, expect, vi } from 'vitest';
|
|
2
|
-
import { createAgent } from './';
|
|
2
|
+
import { createAgent, TypesFromAgent } from './';
|
|
3
3
|
import { createActor, createMachine } from 'xstate';
|
|
4
4
|
import { LanguageModelV1CallOptions } from 'ai';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -7,7 +7,7 @@ import { dummyResponseValues, MockLanguageModelV1 } from './mockModel';
|
|
|
7
7
|
|
|
8
8
|
test('an agent has the expected interface', () => {
|
|
9
9
|
const agent = createAgent({
|
|
10
|
-
|
|
10
|
+
id: 'test',
|
|
11
11
|
events: {},
|
|
12
12
|
model: new MockLanguageModelV1(),
|
|
13
13
|
});
|
|
@@ -17,12 +17,12 @@ test('an agent has the expected interface', () => {
|
|
|
17
17
|
expect(agent.addMessage).toBeDefined();
|
|
18
18
|
expect(agent.addObservation).toBeDefined();
|
|
19
19
|
expect(agent.addFeedback).toBeDefined();
|
|
20
|
-
expect(agent.
|
|
20
|
+
expect(agent.addDecision).toBeDefined();
|
|
21
21
|
|
|
22
22
|
expect(agent.getMessages).toBeDefined();
|
|
23
23
|
expect(agent.getObservations).toBeDefined();
|
|
24
24
|
expect(agent.getFeedback).toBeDefined();
|
|
25
|
-
expect(agent.
|
|
25
|
+
expect(agent.getDecisions).toBeDefined();
|
|
26
26
|
|
|
27
27
|
expect(agent.interact).toBeDefined();
|
|
28
28
|
});
|
|
@@ -31,7 +31,7 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
31
31
|
const model = new MockLanguageModelV1();
|
|
32
32
|
|
|
33
33
|
const agent = createAgent({
|
|
34
|
-
|
|
34
|
+
id: 'test',
|
|
35
35
|
events: {},
|
|
36
36
|
model,
|
|
37
37
|
});
|
|
@@ -65,7 +65,7 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
65
65
|
|
|
66
66
|
test('agent.addFeedback() adds to feedback', () => {
|
|
67
67
|
const agent = createAgent({
|
|
68
|
-
|
|
68
|
+
id: 'test',
|
|
69
69
|
events: {},
|
|
70
70
|
model: {} as any,
|
|
71
71
|
});
|
|
@@ -106,7 +106,7 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
106
106
|
|
|
107
107
|
test('agent.addObservation() adds to observations', () => {
|
|
108
108
|
const agent = createAgent({
|
|
109
|
-
|
|
109
|
+
id: 'test',
|
|
110
110
|
events: {},
|
|
111
111
|
model: {} as any,
|
|
112
112
|
});
|
|
@@ -132,7 +132,7 @@ test('agent.addObservation() adds to observations', () => {
|
|
|
132
132
|
|
|
133
133
|
test('agent.addObservation() adds to observations (initial state)', () => {
|
|
134
134
|
const agent = createAgent({
|
|
135
|
-
|
|
135
|
+
id: 'test',
|
|
136
136
|
events: {},
|
|
137
137
|
model: {} as any,
|
|
138
138
|
});
|
|
@@ -154,7 +154,7 @@ test('agent.addObservation() adds to observations (initial state)', () => {
|
|
|
154
154
|
|
|
155
155
|
test('agent.addObservation() adds to observations with machine hash', () => {
|
|
156
156
|
const agent = createAgent({
|
|
157
|
-
|
|
157
|
+
id: 'test',
|
|
158
158
|
events: {},
|
|
159
159
|
model: {} as any,
|
|
160
160
|
});
|
|
@@ -194,7 +194,7 @@ test('agent.addObservation() adds to observations with machine hash', () => {
|
|
|
194
194
|
|
|
195
195
|
test('agent.addFeedback() adds to feedback (with observation)', () => {
|
|
196
196
|
const agent = createAgent({
|
|
197
|
-
|
|
197
|
+
id: 'test',
|
|
198
198
|
events: {},
|
|
199
199
|
model: {} as any,
|
|
200
200
|
});
|
|
@@ -251,7 +251,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
const agent = createAgent({
|
|
254
|
-
|
|
254
|
+
id: 'test',
|
|
255
255
|
events: {},
|
|
256
256
|
model: {} as any,
|
|
257
257
|
});
|
|
@@ -289,7 +289,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
289
289
|
test('You can listen for feedback events', () => {
|
|
290
290
|
const fn = vi.fn();
|
|
291
291
|
const agent = createAgent({
|
|
292
|
-
|
|
292
|
+
id: 'test',
|
|
293
293
|
events: {},
|
|
294
294
|
model: {} as any,
|
|
295
295
|
});
|
|
@@ -307,7 +307,7 @@ test('You can listen for feedback events', () => {
|
|
|
307
307
|
expect(fn).toHaveBeenCalled();
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
-
test('You can listen for
|
|
310
|
+
test('You can listen for decision events', async () => {
|
|
311
311
|
const fn = vi.fn();
|
|
312
312
|
const model = new MockLanguageModelV1({
|
|
313
313
|
doGenerate: async (params: LanguageModelV1CallOptions) => {
|
|
@@ -332,14 +332,14 @@ test('You can listen for plan events', async () => {
|
|
|
332
332
|
});
|
|
333
333
|
|
|
334
334
|
const agent = createAgent({
|
|
335
|
-
|
|
335
|
+
id: 'test',
|
|
336
336
|
model,
|
|
337
337
|
events: {
|
|
338
338
|
WIN: z.object({}),
|
|
339
339
|
},
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
-
agent.on('
|
|
342
|
+
agent.on('decision', fn);
|
|
343
343
|
|
|
344
344
|
await agent.decide({
|
|
345
345
|
goal: 'Win the game',
|
|
@@ -364,7 +364,7 @@ test('You can listen for plan events', async () => {
|
|
|
364
364
|
|
|
365
365
|
expect(fn).toHaveBeenCalledWith(
|
|
366
366
|
expect.objectContaining({
|
|
367
|
-
|
|
367
|
+
decision: expect.objectContaining({
|
|
368
368
|
nextEvent: {
|
|
369
369
|
type: 'WIN',
|
|
370
370
|
},
|
|
@@ -386,10 +386,152 @@ test('agent.types provides context and event types', () => {
|
|
|
386
386
|
},
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
-
|
|
389
|
+
let types = {} as TypesFromAgent<typeof agent>;
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
types satisfies { context: any; events: any };
|
|
392
|
+
|
|
393
|
+
types.context satisfies { score: number };
|
|
392
394
|
|
|
393
395
|
// @ts-expect-error
|
|
394
|
-
|
|
396
|
+
types.context satisfies { score: string };
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('It allows unrecognized events', () => {
|
|
400
|
+
const agent = createAgent({
|
|
401
|
+
model: {} as any,
|
|
402
|
+
events: {},
|
|
403
|
+
context: {},
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
expect(() => {
|
|
407
|
+
agent.send({
|
|
408
|
+
// @ts-expect-error
|
|
409
|
+
type: 'unrecognized',
|
|
410
|
+
});
|
|
411
|
+
}).not.toThrow();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test('You can listen for message events', () => {
|
|
415
|
+
const fn = vi.fn();
|
|
416
|
+
const agent = createAgent({
|
|
417
|
+
id: 'test',
|
|
418
|
+
events: {},
|
|
419
|
+
model: {} as any,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
agent.onMessage(fn);
|
|
423
|
+
|
|
424
|
+
const message = {
|
|
425
|
+
role: 'user' as const,
|
|
426
|
+
content: [{ type: 'text' as const, text: 'test message' }],
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
agent.addMessage(message);
|
|
430
|
+
|
|
431
|
+
expect(fn).toHaveBeenCalledWith(
|
|
432
|
+
expect.objectContaining({
|
|
433
|
+
role: 'user',
|
|
434
|
+
content: [{ type: 'text', text: 'test message' }],
|
|
435
|
+
episodeId: expect.any(String),
|
|
436
|
+
timestamp: expect.any(Number),
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('agent.getDecisions() returns decisions from context', () => {
|
|
442
|
+
const agent = createAgent({
|
|
443
|
+
id: 'test',
|
|
444
|
+
events: {},
|
|
445
|
+
model: {} as any,
|
|
446
|
+
strategy: async (agent) => {
|
|
447
|
+
return {
|
|
448
|
+
episodeId: agent.episodeId,
|
|
449
|
+
strategy: 'test-strategy',
|
|
450
|
+
goal: '',
|
|
451
|
+
goalState: undefined,
|
|
452
|
+
paths: [
|
|
453
|
+
{
|
|
454
|
+
state: undefined,
|
|
455
|
+
steps: [],
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
nextEvent: undefined,
|
|
459
|
+
timestamp: Date.now(),
|
|
460
|
+
};
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const decisions = agent.getDecisions();
|
|
465
|
+
|
|
466
|
+
expect(decisions).toBeDefined();
|
|
467
|
+
expect(Array.isArray(decisions)).toBe(true);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('Event listeners can be unsubscribed', () => {
|
|
471
|
+
const fn = vi.fn();
|
|
472
|
+
const agent = createAgent({
|
|
473
|
+
id: 'test',
|
|
474
|
+
events: {},
|
|
475
|
+
model: {} as any,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const subscription = agent.on('message', fn);
|
|
479
|
+
|
|
480
|
+
agent.addMessage({
|
|
481
|
+
role: 'user',
|
|
482
|
+
content: [{ type: 'text', text: 'first message' }],
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
486
|
+
|
|
487
|
+
subscription.unsubscribe();
|
|
488
|
+
|
|
489
|
+
agent.addMessage({
|
|
490
|
+
role: 'user',
|
|
491
|
+
content: [{ type: 'text', text: 'second message' }],
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
expect(fn).toHaveBeenCalledTimes(1); // Still only called once
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('agent.observe() adds observations from actor snapshots', () => {
|
|
498
|
+
const machine = createMachine({
|
|
499
|
+
initial: 'idle',
|
|
500
|
+
states: {
|
|
501
|
+
idle: {
|
|
502
|
+
on: { START: 'running' },
|
|
503
|
+
},
|
|
504
|
+
running: {},
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const agent = createAgent({
|
|
509
|
+
id: 'test',
|
|
510
|
+
events: {},
|
|
511
|
+
model: {} as any,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const actor = createActor(machine);
|
|
515
|
+
const subscription = agent.observe(actor);
|
|
516
|
+
|
|
517
|
+
actor.start();
|
|
518
|
+
actor.send({ type: 'START' });
|
|
519
|
+
|
|
520
|
+
expect(agent.getObservations()).toContainEqual(
|
|
521
|
+
expect.objectContaining({
|
|
522
|
+
state: expect.objectContaining({ value: 'idle' }),
|
|
523
|
+
machineHash: expect.any(String),
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
expect(agent.getObservations()).toContainEqual(
|
|
528
|
+
expect.objectContaining({
|
|
529
|
+
prevState: expect.objectContaining({ value: 'idle' }),
|
|
530
|
+
event: { type: 'START' },
|
|
531
|
+
state: expect.objectContaining({ value: 'running' }),
|
|
532
|
+
machineHash: expect.any(String),
|
|
533
|
+
})
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
subscription.unsubscribe();
|
|
395
537
|
});
|