@statelyai/agent 1.0.0-beta.0 → 1.0.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/heavy-moons-bake.md +12 -0
- package/.changeset/silly-berries-drop.md +5 -0
- package/.changeset/wild-bobcats-care.md +26 -0
- package/.env.template +0 -3
- package/.github/workflows/release.yml +3 -3
- package/dist/index.d.mts +152 -47
- package/dist/index.d.ts +152 -47
- package/dist/index.js +165 -110
- package/dist/index.mjs +155 -108
- package/examples/chatbot.ts +9 -17
- package/examples/cot.ts +5 -7
- package/examples/email.ts +2 -2
- package/examples/example.ts +7 -7
- package/examples/goal.ts +1 -1
- package/examples/joke.ts +8 -10
- package/examples/number.ts +1 -1
- package/examples/raffle.ts +1 -1
- package/examples/sandbox.ts +28 -0
- package/examples/support.ts +1 -1
- package/examples/ticTacToe.ts +18 -22
- package/examples/todo.ts +3 -1
- package/examples/tutor.ts +1 -1
- package/examples/verify.ts +1 -1
- package/examples/weather.ts +1 -1
- package/examples/wiki.ts +1 -1
- package/examples/word.ts +6 -3
- package/package.json +18 -10
- package/src/agent.test.ts +176 -4
- package/src/agent.ts +53 -18
- package/src/decision.ts +6 -5
- package/src/index.ts +2 -2
- package/src/planners/shortestPathPlanner.ts +2 -2
- package/src/planners/simplePlanner.ts +23 -12
- package/src/schemas.ts +4 -8
- package/src/strategies/chain-of-note.ts +3 -3
- package/src/text.ts +19 -31
- package/src/types.ts +134 -35
- package/src/utils.ts +59 -9
- package/src/templates/defaultToolCall.ts +0 -10
package/examples/ticTacToe.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { assign, setup, assertEvent, createActor } from 'xstate';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { createAgent, fromDecision, fromTextStream } from '../src';
|
|
4
4
|
import { openai } from '@ai-sdk/openai';
|
|
5
|
-
import { defaultToolCallTemplate } from '../src/templates/defaultToolCall';
|
|
6
5
|
|
|
7
6
|
const agent = createAgent({
|
|
8
7
|
name: 'tic-tac-toe-bot',
|
|
@@ -24,25 +23,30 @@ const agent = createAgent({
|
|
|
24
23
|
}),
|
|
25
24
|
reset: z.object({}).describe('Reset the game to the initial state'),
|
|
26
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
|
|
31
|
+
.number()
|
|
32
|
+
.min(0)
|
|
33
|
+
.max(9)
|
|
34
|
+
.describe('The number of moves made in the game.'),
|
|
35
|
+
player: z
|
|
36
|
+
.union([z.literal('x'), z.literal('o')])
|
|
37
|
+
.describe('The current player (x or o)'),
|
|
38
|
+
gameReport: z.string(),
|
|
39
|
+
},
|
|
27
40
|
});
|
|
28
41
|
|
|
29
42
|
type Player = 'x' | 'o';
|
|
30
43
|
|
|
31
|
-
interface GameContext {
|
|
32
|
-
board: (Player | null)[];
|
|
33
|
-
moves: number;
|
|
34
|
-
player: Player;
|
|
35
|
-
gameReport: string;
|
|
36
|
-
events: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
44
|
const initialContext = {
|
|
40
45
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
41
46
|
moves: 0,
|
|
42
47
|
player: 'x' as Player,
|
|
43
48
|
gameReport: '',
|
|
44
|
-
|
|
45
|
-
} satisfies GameContext;
|
|
49
|
+
} satisfies typeof agent.types.context;
|
|
46
50
|
|
|
47
51
|
function getWinner(board: typeof initialContext.board): Player | null {
|
|
48
52
|
const lines = [
|
|
@@ -65,8 +69,8 @@ function getWinner(board: typeof initialContext.board): Player | null {
|
|
|
65
69
|
|
|
66
70
|
export const ticTacToeMachine = setup({
|
|
67
71
|
types: {
|
|
68
|
-
context:
|
|
69
|
-
events: agent.
|
|
72
|
+
context: agent.types.context,
|
|
73
|
+
events: agent.types.events,
|
|
70
74
|
},
|
|
71
75
|
actors: {
|
|
72
76
|
agent: fromDecision(agent),
|
|
@@ -82,16 +86,8 @@ export const ticTacToeMachine = setup({
|
|
|
82
86
|
},
|
|
83
87
|
moves: ({ context }) => context.moves + 1,
|
|
84
88
|
player: ({ context }) => (context.player === 'x' ? 'o' : 'x'),
|
|
85
|
-
events: ({ context, event }) => {
|
|
86
|
-
return [...context.events, JSON.stringify(event)];
|
|
87
|
-
},
|
|
88
89
|
}),
|
|
89
90
|
resetGame: assign(initialContext),
|
|
90
|
-
recordEvent: assign({
|
|
91
|
-
events: ({ context, event }) => {
|
|
92
|
-
return [...context.events, JSON.stringify(event)];
|
|
93
|
-
},
|
|
94
|
-
}),
|
|
95
91
|
printBoard: ({ context }) => {
|
|
96
92
|
// Print the context.board in a 3 x 3 grid format
|
|
97
93
|
let boardString = '';
|
|
@@ -173,7 +169,7 @@ export const ticTacToeMachine = setup({
|
|
|
173
169
|
src: 'gameReporter',
|
|
174
170
|
input: ({ context }) => ({
|
|
175
171
|
context: {
|
|
176
|
-
events:
|
|
172
|
+
events: agent.getObservations().map((o) => o.event),
|
|
177
173
|
board: context.board,
|
|
178
174
|
},
|
|
179
175
|
prompt: 'Provide a short game report analyzing the game.',
|
package/examples/todo.ts
CHANGED
|
@@ -36,7 +36,9 @@ const machine = setup({
|
|
|
36
36
|
todos: Todo[];
|
|
37
37
|
command: string | null;
|
|
38
38
|
},
|
|
39
|
-
events: {} as
|
|
39
|
+
events: {} as
|
|
40
|
+
| typeof agent.types.events
|
|
41
|
+
| { type: 'assist'; command: string },
|
|
40
42
|
},
|
|
41
43
|
actors: { agent: fromDecision(agent), getFromTerminal },
|
|
42
44
|
}).createMachine({
|
package/examples/tutor.ts
CHANGED
package/examples/verify.ts
CHANGED
package/examples/weather.ts
CHANGED
package/examples/wiki.ts
CHANGED
package/examples/word.ts
CHANGED
|
@@ -12,7 +12,7 @@ const context = {
|
|
|
12
12
|
|
|
13
13
|
const agent = createAgent({
|
|
14
14
|
name: 'word',
|
|
15
|
-
model: openai('gpt-
|
|
15
|
+
model: openai('gpt-4o'),
|
|
16
16
|
events: {
|
|
17
17
|
'agent.guessLetter': z.object({
|
|
18
18
|
letter: z.string().min(1).max(1).describe('The letter guessed'),
|
|
@@ -36,18 +36,21 @@ const agent = createAgent({
|
|
|
36
36
|
const wordGuesserMachine = setup({
|
|
37
37
|
types: {
|
|
38
38
|
context: {} as typeof context,
|
|
39
|
-
events: agent.
|
|
39
|
+
events: agent.types.events,
|
|
40
40
|
},
|
|
41
41
|
actors: {
|
|
42
42
|
agent: fromDecision(agent),
|
|
43
43
|
getFromTerminal,
|
|
44
44
|
},
|
|
45
|
+
actions: {
|
|
46
|
+
resetContext: assign(context),
|
|
47
|
+
},
|
|
45
48
|
}).createMachine({
|
|
46
49
|
initial: 'providingWord',
|
|
47
50
|
context,
|
|
48
51
|
states: {
|
|
49
52
|
providingWord: {
|
|
50
|
-
entry:
|
|
53
|
+
entry: 'resetContext',
|
|
51
54
|
invoke: {
|
|
52
55
|
src: 'getFromTerminal',
|
|
53
56
|
input: 'Enter a word, and an agent will try to guess it.',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "1.0.0
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.0",
|
|
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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -9,27 +9,35 @@
|
|
|
9
9
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
10
10
|
"lint": "tsc --noEmit",
|
|
11
11
|
"test": "vitest",
|
|
12
|
+
"test:ci": "vitest --run",
|
|
12
13
|
"example": "ts-node examples/helpers/runner.ts",
|
|
13
14
|
"prepublishOnly": "tsup src/index.ts --format cjs,esm --dts",
|
|
14
15
|
"changeset": "changeset",
|
|
15
16
|
"release": "changeset publish",
|
|
16
17
|
"version": "changeset version"
|
|
17
18
|
},
|
|
18
|
-
"keywords": [
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ai",
|
|
21
|
+
"state machine",
|
|
22
|
+
"agent",
|
|
23
|
+
"rl",
|
|
24
|
+
"reinforcement learning"
|
|
25
|
+
],
|
|
19
26
|
"author": "",
|
|
20
27
|
"license": "MIT",
|
|
21
28
|
"devDependencies": {
|
|
22
29
|
"@changesets/changelog-github": "^0.5.0",
|
|
23
|
-
"@changesets/cli": "^2.27.
|
|
30
|
+
"@changesets/cli": "^2.27.7",
|
|
24
31
|
"@langchain/community": "^0.0.53",
|
|
25
32
|
"@langchain/core": "^0.1.63",
|
|
26
33
|
"@langchain/openai": "^0.0.28",
|
|
27
|
-
"@types/node": "^20.14.
|
|
34
|
+
"@types/node": "^20.14.10",
|
|
35
|
+
"@types/object-hash": "^3.0.6",
|
|
28
36
|
"dotenv": "^16.4.5",
|
|
29
37
|
"json-schema-to-ts": "^3.1.0",
|
|
30
38
|
"ts-node": "^10.9.2",
|
|
31
39
|
"tsup": "^8.1.0",
|
|
32
|
-
"typescript": "^5.
|
|
40
|
+
"typescript": "^5.5.3",
|
|
33
41
|
"vitest": "^1.6.0",
|
|
34
42
|
"wikipedia": "^2.1.2",
|
|
35
43
|
"zod": "^3.23.8"
|
|
@@ -38,11 +46,11 @@
|
|
|
38
46
|
"access": "public"
|
|
39
47
|
},
|
|
40
48
|
"dependencies": {
|
|
41
|
-
"@ai-sdk/openai": "^0.0.
|
|
49
|
+
"@ai-sdk/openai": "^0.0.31",
|
|
42
50
|
"@xstate/graph": "^2.0.0",
|
|
43
|
-
"ai": "^3.
|
|
44
|
-
"
|
|
45
|
-
"xstate": "^5.
|
|
51
|
+
"ai": "^3.2.22",
|
|
52
|
+
"object-hash": "^3.0.0",
|
|
53
|
+
"xstate": "^5.15.0"
|
|
46
54
|
},
|
|
47
55
|
"packageManager": "pnpm@8.11.0"
|
|
48
56
|
}
|
package/src/agent.test.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { test, expect } from 'vitest';
|
|
1
|
+
import { test, expect, vi } from 'vitest';
|
|
2
2
|
import { createAgent, type AIAdapter } from './';
|
|
3
3
|
import { createActor, createMachine } from 'xstate';
|
|
4
|
+
import { GenerateTextResult } from 'ai';
|
|
5
|
+
import { z } from 'zod';
|
|
4
6
|
|
|
5
7
|
test('an agent has the expected interface', () => {
|
|
6
8
|
const agent = createAgent({
|
|
@@ -13,11 +15,16 @@ test('an agent has the expected interface', () => {
|
|
|
13
15
|
expect(agent.generateText).toBeDefined();
|
|
14
16
|
expect(agent.streamText).toBeDefined();
|
|
15
17
|
|
|
16
|
-
expect(agent.addFeedback).toBeDefined();
|
|
17
18
|
expect(agent.addMessage).toBeDefined();
|
|
18
19
|
expect(agent.addObservation).toBeDefined();
|
|
20
|
+
expect(agent.addFeedback).toBeDefined();
|
|
19
21
|
expect(agent.addPlan).toBeDefined();
|
|
20
22
|
|
|
23
|
+
expect(agent.getMessages).toBeDefined();
|
|
24
|
+
expect(agent.getObservations).toBeDefined();
|
|
25
|
+
expect(agent.getFeedback).toBeDefined();
|
|
26
|
+
expect(agent.getPlans).toBeDefined();
|
|
27
|
+
|
|
21
28
|
expect(agent.interact).toBeDefined();
|
|
22
29
|
});
|
|
23
30
|
|
|
@@ -45,6 +52,11 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
45
52
|
content: 'msg 1',
|
|
46
53
|
})
|
|
47
54
|
);
|
|
55
|
+
expect(agent.getMessages()).toContainEqual(
|
|
56
|
+
expect.objectContaining({
|
|
57
|
+
content: 'msg 1',
|
|
58
|
+
})
|
|
59
|
+
);
|
|
48
60
|
|
|
49
61
|
expect(agent.select((c) => c.messages)).toContainEqual(
|
|
50
62
|
expect.objectContaining({
|
|
@@ -53,6 +65,13 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
53
65
|
timestamp: expect.any(Number),
|
|
54
66
|
})
|
|
55
67
|
);
|
|
68
|
+
expect(agent.getMessages()).toContainEqual(
|
|
69
|
+
expect.objectContaining({
|
|
70
|
+
content: 'response 1',
|
|
71
|
+
sessionId: expect.any(String),
|
|
72
|
+
timestamp: expect.any(Number),
|
|
73
|
+
})
|
|
74
|
+
);
|
|
56
75
|
});
|
|
57
76
|
|
|
58
77
|
test('agent.addFeedback() adds to feedback', () => {
|
|
@@ -83,6 +102,17 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
83
102
|
timestamp: expect.any(Number),
|
|
84
103
|
})
|
|
85
104
|
);
|
|
105
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
attributes: {
|
|
108
|
+
score: -1,
|
|
109
|
+
},
|
|
110
|
+
goal: 'Win the game',
|
|
111
|
+
observationId: 'obs-1',
|
|
112
|
+
sessionId: expect.any(String),
|
|
113
|
+
timestamp: expect.any(Number),
|
|
114
|
+
})
|
|
115
|
+
);
|
|
86
116
|
});
|
|
87
117
|
|
|
88
118
|
test('agent.addObservation() adds to observations', () => {
|
|
@@ -111,6 +141,46 @@ test('agent.addObservation() adds to observations', () => {
|
|
|
111
141
|
);
|
|
112
142
|
});
|
|
113
143
|
|
|
144
|
+
test('agent.addObservation() adds to observations with machine hash', () => {
|
|
145
|
+
const agent = createAgent({
|
|
146
|
+
name: 'test',
|
|
147
|
+
events: {},
|
|
148
|
+
model: {} as any,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const machine = createMachine({
|
|
152
|
+
initial: 'playing',
|
|
153
|
+
states: {
|
|
154
|
+
playing: {
|
|
155
|
+
on: {
|
|
156
|
+
play: 'lost',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
lost: {},
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const observation = agent.addObservation({
|
|
164
|
+
prevState: { value: 'playing', context: {} },
|
|
165
|
+
event: { type: 'play', position: 3 },
|
|
166
|
+
state: { value: 'lost', context: {} },
|
|
167
|
+
machine,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(observation.sessionId).toEqual(agent.sessionId);
|
|
171
|
+
|
|
172
|
+
expect(agent.select((c) => c.observations)).toContainEqual(
|
|
173
|
+
expect.objectContaining({
|
|
174
|
+
prevState: { value: 'playing', context: {} },
|
|
175
|
+
event: { type: 'play', position: 3 },
|
|
176
|
+
state: { value: 'lost', context: {} },
|
|
177
|
+
machineHash: expect.any(String),
|
|
178
|
+
sessionId: expect.any(String),
|
|
179
|
+
timestamp: expect.any(Number),
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
114
184
|
test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
115
185
|
const machine = createMachine({
|
|
116
186
|
initial: 'a',
|
|
@@ -140,6 +210,12 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
140
210
|
state: expect.objectContaining({ value: 'a' }),
|
|
141
211
|
})
|
|
142
212
|
);
|
|
213
|
+
expect(agent.getObservations()).toContainEqual(
|
|
214
|
+
expect.objectContaining({
|
|
215
|
+
prevState: undefined,
|
|
216
|
+
state: expect.objectContaining({ value: 'a' }),
|
|
217
|
+
})
|
|
218
|
+
);
|
|
143
219
|
|
|
144
220
|
actor.send({ type: 'NEXT' });
|
|
145
221
|
|
|
@@ -175,7 +251,8 @@ test('Agents can use a custom adapter', async () => {
|
|
|
175
251
|
expect(res.text).toEqual('Response');
|
|
176
252
|
});
|
|
177
253
|
|
|
178
|
-
test
|
|
254
|
+
test('You can listen for feedback events', () => {
|
|
255
|
+
const fn = vi.fn();
|
|
179
256
|
const agent = createAgent({
|
|
180
257
|
name: 'test',
|
|
181
258
|
events: {},
|
|
@@ -183,5 +260,100 @@ test.skip('You can listen for emitted agent events', () => {
|
|
|
183
260
|
model: {} as any,
|
|
184
261
|
});
|
|
185
262
|
|
|
186
|
-
agent.on('feedback',
|
|
263
|
+
agent.on('feedback', fn);
|
|
264
|
+
|
|
265
|
+
agent.addFeedback({
|
|
266
|
+
attributes: {
|
|
267
|
+
score: -1,
|
|
268
|
+
},
|
|
269
|
+
goal: 'Win the game',
|
|
270
|
+
observationId: 'obs-1',
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(fn).toHaveBeenCalled();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('You can listen for plan events', async () => {
|
|
277
|
+
const fn = vi.fn();
|
|
278
|
+
const agent = createAgent({
|
|
279
|
+
name: 'test',
|
|
280
|
+
model: {} as any,
|
|
281
|
+
events: {
|
|
282
|
+
WIN: z.object({}),
|
|
283
|
+
},
|
|
284
|
+
adapter: {
|
|
285
|
+
generateText: async (arg) => {
|
|
286
|
+
const keys = Object.keys(arg.tools!);
|
|
287
|
+
|
|
288
|
+
if (keys.length !== 1) {
|
|
289
|
+
throw new Error('Expected only 1 choice');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
toolResults: [
|
|
294
|
+
{
|
|
295
|
+
result: {
|
|
296
|
+
type: keys[0],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
} as any as GenerateTextResult<any>;
|
|
301
|
+
},
|
|
302
|
+
streamText: {} as any,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
agent.on('plan', fn);
|
|
307
|
+
|
|
308
|
+
await agent.decide({
|
|
309
|
+
goal: 'Win the game',
|
|
310
|
+
state: {
|
|
311
|
+
value: 'playing',
|
|
312
|
+
context: {},
|
|
313
|
+
},
|
|
314
|
+
machine: createMachine({
|
|
315
|
+
initial: 'playing',
|
|
316
|
+
states: {
|
|
317
|
+
playing: {
|
|
318
|
+
on: {
|
|
319
|
+
WIN: {
|
|
320
|
+
target: 'won',
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
won: {},
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(fn).toHaveBeenCalledWith(
|
|
330
|
+
expect.objectContaining({
|
|
331
|
+
plan: expect.objectContaining({
|
|
332
|
+
nextEvent: {
|
|
333
|
+
type: 'WIN',
|
|
334
|
+
},
|
|
335
|
+
}),
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('agent.types provides context and event types', () => {
|
|
341
|
+
const agent = createAgent({
|
|
342
|
+
model: {} as any,
|
|
343
|
+
events: {
|
|
344
|
+
setScore: z.object({
|
|
345
|
+
score: z.number(),
|
|
346
|
+
}),
|
|
347
|
+
},
|
|
348
|
+
context: {
|
|
349
|
+
score: z.number(),
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
agent.types satisfies { context: any; events: any };
|
|
354
|
+
|
|
355
|
+
agent.types.context satisfies { score: number };
|
|
356
|
+
|
|
357
|
+
// @ts-expect-error
|
|
358
|
+
agent.types.context satisfies { score: string };
|
|
187
359
|
});
|
package/src/agent.ts
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
Observer,
|
|
8
8
|
toObserver,
|
|
9
9
|
} from 'xstate';
|
|
10
|
-
import { ZodEventMapping } from './schemas';
|
|
10
|
+
import { ZodContextMapping, ZodEventMapping } from './schemas';
|
|
11
11
|
import {
|
|
12
12
|
Agent,
|
|
13
13
|
AgentLogic,
|
|
14
|
-
|
|
14
|
+
AgentMessage,
|
|
15
15
|
AgentPlanner,
|
|
16
16
|
EventsFromZodEventMapping,
|
|
17
17
|
GenerateTextOptions,
|
|
@@ -20,12 +20,14 @@ import {
|
|
|
20
20
|
ObservedState,
|
|
21
21
|
AgentObservationInput,
|
|
22
22
|
AgentMemoryContext,
|
|
23
|
+
AgentObservation,
|
|
24
|
+
ContextFromZodContextMapping,
|
|
23
25
|
} from './types';
|
|
24
26
|
import { simplePlanner } from './planners/simplePlanner';
|
|
25
27
|
import { agentGenerateText, agentStreamText } from './text';
|
|
26
28
|
import { agentDecide } from './decision';
|
|
27
29
|
import { vercelAdapter } from './adapters/vercel';
|
|
28
|
-
import {
|
|
30
|
+
import { getMachineHash, randomId } from './utils';
|
|
29
31
|
|
|
30
32
|
export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
|
|
31
33
|
(state, event, { emit }) => {
|
|
@@ -80,24 +82,42 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
|
|
|
80
82
|
);
|
|
81
83
|
|
|
82
84
|
export function createAgent<
|
|
85
|
+
const TContextSchema extends ZodContextMapping,
|
|
83
86
|
const TEventSchemas extends ZodEventMapping,
|
|
84
|
-
TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas
|
|
87
|
+
TEvents extends EventObject = EventsFromZodEventMapping<TEventSchemas>,
|
|
88
|
+
TContext = ContextFromZodContextMapping<TContextSchema>
|
|
85
89
|
>({
|
|
86
90
|
name,
|
|
87
91
|
description,
|
|
88
92
|
model,
|
|
89
93
|
events,
|
|
90
|
-
|
|
94
|
+
context,
|
|
95
|
+
planner = simplePlanner as AgentPlanner<Agent<TContext, TEvents>>,
|
|
91
96
|
stringify = JSON.stringify,
|
|
92
97
|
getMemory,
|
|
93
98
|
logic = agentLogic as AgentLogic<TEvents>,
|
|
94
99
|
adapter = vercelAdapter,
|
|
95
100
|
...generateTextOptions
|
|
96
101
|
}: {
|
|
102
|
+
/**
|
|
103
|
+
* The unique identifier for the agent.
|
|
104
|
+
*
|
|
105
|
+
* This should be the same across all sessions of a specific agent, as it can be
|
|
106
|
+
* used to retrieve memory for this agent.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const agent = createAgent({
|
|
111
|
+
* id: 'recipe-assistant',
|
|
112
|
+
* // ...
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
id?: string;
|
|
97
117
|
/**
|
|
98
118
|
* The name of the agent
|
|
99
119
|
*/
|
|
100
|
-
name
|
|
120
|
+
name?: string;
|
|
101
121
|
/**
|
|
102
122
|
* A description of the role of the agent
|
|
103
123
|
*/
|
|
@@ -107,21 +127,22 @@ export function createAgent<
|
|
|
107
127
|
* that the agent knows about.
|
|
108
128
|
*/
|
|
109
129
|
events: TEventSchemas;
|
|
110
|
-
|
|
130
|
+
context?: TContextSchema;
|
|
131
|
+
planner?: AgentPlanner<Agent<TContext, TEvents>>;
|
|
111
132
|
stringify?: typeof JSON.stringify;
|
|
112
133
|
/**
|
|
113
134
|
* A function that retrieves the agent's long term memory
|
|
114
135
|
*/
|
|
115
|
-
getMemory?: (agent: Agent<
|
|
136
|
+
getMemory?: (agent: Agent<TContext, TEvents>) => AgentLongTermMemory;
|
|
116
137
|
/**
|
|
117
138
|
* Agent logic
|
|
118
139
|
*/
|
|
119
140
|
logic?: AgentLogic<TEvents>;
|
|
120
141
|
adapter?: AIAdapter;
|
|
121
|
-
} & GenerateTextOptions): Agent<TEvents> {
|
|
122
|
-
const messageHistoryListeners: Observer<
|
|
142
|
+
} & GenerateTextOptions): Agent<TContext, TEvents> {
|
|
143
|
+
const messageHistoryListeners: Observer<AgentMessage>[] = [];
|
|
123
144
|
|
|
124
|
-
const agent = createActor(logic) as unknown as Agent<TEvents>;
|
|
145
|
+
const agent = createActor(logic) as unknown as Agent<TContext, TEvents>;
|
|
125
146
|
agent.events = events;
|
|
126
147
|
agent.model = model;
|
|
127
148
|
agent.name = name;
|
|
@@ -144,7 +165,7 @@ export function createAgent<
|
|
|
144
165
|
agent.addMessage = (messageInput) => {
|
|
145
166
|
const message = {
|
|
146
167
|
...messageInput,
|
|
147
|
-
id: messageInput.id ??
|
|
168
|
+
id: messageInput.id ?? randomId(),
|
|
148
169
|
timestamp: messageInput.timestamp ?? Date.now(),
|
|
149
170
|
sessionId: agent.sessionId,
|
|
150
171
|
};
|
|
@@ -155,6 +176,7 @@ export function createAgent<
|
|
|
155
176
|
|
|
156
177
|
return message;
|
|
157
178
|
};
|
|
179
|
+
agent.getMessages = () => agent.getSnapshot().context.messages;
|
|
158
180
|
|
|
159
181
|
agent.generateText = (opts) => agentGenerateText(agent, opts);
|
|
160
182
|
|
|
@@ -172,14 +194,21 @@ export function createAgent<
|
|
|
172
194
|
});
|
|
173
195
|
return feedback;
|
|
174
196
|
};
|
|
197
|
+
agent.getFeedback = () => agent.getSnapshot().context.feedback;
|
|
175
198
|
|
|
176
199
|
agent.addObservation = (observationInput) => {
|
|
200
|
+
const { prevState, event, state } = observationInput;
|
|
177
201
|
const observation = {
|
|
178
|
-
|
|
179
|
-
|
|
202
|
+
prevState,
|
|
203
|
+
event,
|
|
204
|
+
state,
|
|
205
|
+
id: observationInput.id ?? randomId(),
|
|
180
206
|
sessionId: agent.sessionId,
|
|
181
207
|
timestamp: observationInput.timestamp ?? Date.now(),
|
|
182
|
-
|
|
208
|
+
machineHash: observationInput.machine
|
|
209
|
+
? getMachineHash(observationInput.machine)
|
|
210
|
+
: undefined,
|
|
211
|
+
} satisfies AgentObservation<any>;
|
|
183
212
|
|
|
184
213
|
agent.send({
|
|
185
214
|
type: 'agent.observe',
|
|
@@ -188,6 +217,7 @@ export function createAgent<
|
|
|
188
217
|
|
|
189
218
|
return observation;
|
|
190
219
|
};
|
|
220
|
+
agent.getObservations = () => agent.getSnapshot().context.observations;
|
|
191
221
|
|
|
192
222
|
agent.addPlan = (plan) => {
|
|
193
223
|
agent.send({
|
|
@@ -195,8 +225,9 @@ export function createAgent<
|
|
|
195
225
|
plan,
|
|
196
226
|
});
|
|
197
227
|
};
|
|
228
|
+
agent.getPlans = () => agent.getSnapshot().context.plans;
|
|
198
229
|
|
|
199
|
-
agent.interact = (actorRef, getInput) => {
|
|
230
|
+
agent.interact = ((actorRef, getInput) => {
|
|
200
231
|
let prevState: ObservedState | undefined = undefined;
|
|
201
232
|
let subscribed = true;
|
|
202
233
|
|
|
@@ -234,7 +265,8 @@ export function createAgent<
|
|
|
234
265
|
event: inspEvent.event,
|
|
235
266
|
prevState,
|
|
236
267
|
state: inspEvent.snapshot as any,
|
|
237
|
-
|
|
268
|
+
machine: (actorRef as any).src,
|
|
269
|
+
} satisfies AgentObservationInput;
|
|
238
270
|
|
|
239
271
|
await handleObservation(observationInput);
|
|
240
272
|
},
|
|
@@ -246,6 +278,7 @@ export function createAgent<
|
|
|
246
278
|
prevState: undefined,
|
|
247
279
|
event: { type: '' }, // TODO: unknown events?
|
|
248
280
|
state: actorRef.getSnapshot(),
|
|
281
|
+
machine: (actorRef as any).src,
|
|
249
282
|
});
|
|
250
283
|
}
|
|
251
284
|
|
|
@@ -254,7 +287,9 @@ export function createAgent<
|
|
|
254
287
|
subscribed = false;
|
|
255
288
|
}, // TODO: make this actually unsubscribe
|
|
256
289
|
};
|
|
257
|
-
};
|
|
290
|
+
}) as typeof agent.interact;
|
|
291
|
+
|
|
292
|
+
agent.types = {} as any;
|
|
258
293
|
|
|
259
294
|
agent.start();
|
|
260
295
|
|
package/src/decision.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { AnyMachineSnapshot, fromPromise } from 'xstate';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
AnyAgent,
|
|
4
4
|
AgentDecideOptions,
|
|
5
5
|
AgentDecisionLogic,
|
|
6
6
|
AgentDecisionInput,
|
|
7
7
|
AgentPlanner,
|
|
8
|
+
AgentPlan,
|
|
8
9
|
} from './types';
|
|
9
10
|
import { simplePlanner } from './planners/simplePlanner';
|
|
10
11
|
|
|
11
|
-
export async function agentDecide<T extends
|
|
12
|
+
export async function agentDecide<T extends AnyAgent>(
|
|
12
13
|
agent: T,
|
|
13
14
|
options: AgentDecideOptions
|
|
14
|
-
) {
|
|
15
|
+
): Promise<AgentPlan<any> | undefined> {
|
|
15
16
|
const resolvedOptions = {
|
|
16
17
|
...agent.defaultOptions,
|
|
17
18
|
...options,
|
|
@@ -44,9 +45,9 @@ export async function agentDecide<T extends Agent<any>>(
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function fromDecision(
|
|
47
|
-
agent:
|
|
48
|
+
agent: AnyAgent,
|
|
48
49
|
defaultInput?: AgentDecisionInput
|
|
49
|
-
) {
|
|
50
|
+
): AgentDecisionLogic<any> {
|
|
50
51
|
return fromPromise(async ({ input, self }) => {
|
|
51
52
|
const parentRef = self._parent;
|
|
52
53
|
if (!parentRef) {
|