@statelyai/agent 1.0.0-beta.1 → 1.1.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/.env.template +0 -3
- package/.github/actions/ci-setup/action.yml +11 -11
- package/.github/workflows/release.yml +19 -7
- package/CHANGELOG.md +85 -0
- package/dist/index.d.mts +164 -54
- package/dist/index.d.ts +164 -54
- package/dist/index.js +190 -117
- package/dist/index.mjs +181 -117
- 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 -21
- 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 +1 -1
- package/package.json +22 -22
- package/readme.md +1 -4
- package/src/agent.test.ts +324 -5
- package/src/agent.ts +64 -26
- package/src/decision.ts +6 -5
- package/src/index.ts +2 -2
- package/src/planners/shortestPathPlanner.ts +2 -2
- package/src/planners/simplePlanner.ts +22 -12
- package/src/schemas.ts +4 -8
- package/src/strategies/chain-of-note.ts +3 -3
- package/src/text.ts +42 -37
- package/src/types.ts +146 -42
- package/src/utils.ts +53 -9
- package/.changeset/shaggy-buttons-itch.md +0 -5
- package/src/templates/defaultToolCall.ts +0 -10
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createAgent } from '../src';
|
|
3
|
+
import { openai } from '@ai-sdk/openai';
|
|
4
|
+
import { createMachine } from 'xstate';
|
|
5
|
+
|
|
6
|
+
const agent = createAgent({
|
|
7
|
+
model: openai('gpt-4o'),
|
|
8
|
+
events: {
|
|
9
|
+
doSomething: z.object({}).describe('Do something'),
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const machine = createMachine({
|
|
15
|
+
on: {
|
|
16
|
+
doSomething: {},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
const result = await agent.decide({
|
|
20
|
+
goal: 'Do not do anything',
|
|
21
|
+
state: { value: {}, context: {} },
|
|
22
|
+
machine,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log(result);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main();
|
package/examples/support.ts
CHANGED
package/examples/ticTacToe.ts
CHANGED
|
@@ -23,25 +23,30 @@ const agent = createAgent({
|
|
|
23
23
|
}),
|
|
24
24
|
reset: z.object({}).describe('Reset the game to the initial state'),
|
|
25
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
|
+
},
|
|
26
40
|
});
|
|
27
41
|
|
|
28
42
|
type Player = 'x' | 'o';
|
|
29
43
|
|
|
30
|
-
interface GameContext {
|
|
31
|
-
board: (Player | null)[];
|
|
32
|
-
moves: number;
|
|
33
|
-
player: Player;
|
|
34
|
-
gameReport: string;
|
|
35
|
-
events: string[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
44
|
const initialContext = {
|
|
39
45
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
40
46
|
moves: 0,
|
|
41
47
|
player: 'x' as Player,
|
|
42
48
|
gameReport: '',
|
|
43
|
-
|
|
44
|
-
} satisfies GameContext;
|
|
49
|
+
} satisfies typeof agent.types.context;
|
|
45
50
|
|
|
46
51
|
function getWinner(board: typeof initialContext.board): Player | null {
|
|
47
52
|
const lines = [
|
|
@@ -64,8 +69,8 @@ function getWinner(board: typeof initialContext.board): Player | null {
|
|
|
64
69
|
|
|
65
70
|
export const ticTacToeMachine = setup({
|
|
66
71
|
types: {
|
|
67
|
-
context:
|
|
68
|
-
events: agent.
|
|
72
|
+
context: agent.types.context,
|
|
73
|
+
events: agent.types.events,
|
|
69
74
|
},
|
|
70
75
|
actors: {
|
|
71
76
|
agent: fromDecision(agent),
|
|
@@ -81,16 +86,8 @@ export const ticTacToeMachine = setup({
|
|
|
81
86
|
},
|
|
82
87
|
moves: ({ context }) => context.moves + 1,
|
|
83
88
|
player: ({ context }) => (context.player === 'x' ? 'o' : 'x'),
|
|
84
|
-
events: ({ context, event }) => {
|
|
85
|
-
return [...context.events, JSON.stringify(event)];
|
|
86
|
-
},
|
|
87
89
|
}),
|
|
88
90
|
resetGame: assign(initialContext),
|
|
89
|
-
recordEvent: assign({
|
|
90
|
-
events: ({ context, event }) => {
|
|
91
|
-
return [...context.events, JSON.stringify(event)];
|
|
92
|
-
},
|
|
93
|
-
}),
|
|
94
91
|
printBoard: ({ context }) => {
|
|
95
92
|
// Print the context.board in a 3 x 3 grid format
|
|
96
93
|
let boardString = '';
|
|
@@ -172,7 +169,7 @@ export const ticTacToeMachine = setup({
|
|
|
172
169
|
src: 'gameReporter',
|
|
173
170
|
input: ({ context }) => ({
|
|
174
171
|
context: {
|
|
175
|
-
events:
|
|
172
|
+
events: agent.getObservations().map((o) => o.event),
|
|
176
173
|
board: context.board,
|
|
177
174
|
},
|
|
178
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
package/package.json
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
10
|
-
"lint": "tsc --noEmit",
|
|
11
|
-
"test": "vitest",
|
|
12
|
-
"test:ci": "vitest --run",
|
|
13
|
-
"example": "ts-node examples/helpers/runner.ts",
|
|
14
|
-
"prepublishOnly": "tsup src/index.ts --format cjs,esm --dts",
|
|
15
|
-
"changeset": "changeset",
|
|
16
|
-
"release": "changeset publish",
|
|
17
|
-
"version": "changeset version"
|
|
18
|
-
},
|
|
19
8
|
"keywords": [
|
|
20
9
|
"ai",
|
|
21
10
|
"state machine",
|
|
@@ -27,17 +16,18 @@
|
|
|
27
16
|
"license": "MIT",
|
|
28
17
|
"devDependencies": {
|
|
29
18
|
"@changesets/changelog-github": "^0.5.0",
|
|
30
|
-
"@changesets/cli": "^2.27.
|
|
19
|
+
"@changesets/cli": "^2.27.7",
|
|
31
20
|
"@langchain/community": "^0.0.53",
|
|
32
21
|
"@langchain/core": "^0.1.63",
|
|
33
22
|
"@langchain/openai": "^0.0.28",
|
|
34
|
-
"@types/node": "^20.14.
|
|
23
|
+
"@types/node": "^20.14.13",
|
|
24
|
+
"@types/object-hash": "^3.0.6",
|
|
35
25
|
"dotenv": "^16.4.5",
|
|
36
26
|
"json-schema-to-ts": "^3.1.0",
|
|
37
27
|
"ts-node": "^10.9.2",
|
|
38
|
-
"tsup": "^8.
|
|
39
|
-
"typescript": "^5.4
|
|
40
|
-
"vitest": "^
|
|
28
|
+
"tsup": "^8.2.3",
|
|
29
|
+
"typescript": "^5.5.4",
|
|
30
|
+
"vitest": "^2.0.4",
|
|
41
31
|
"wikipedia": "^2.1.2",
|
|
42
32
|
"zod": "^3.23.8"
|
|
43
33
|
},
|
|
@@ -45,10 +35,20 @@
|
|
|
45
35
|
"access": "public"
|
|
46
36
|
},
|
|
47
37
|
"dependencies": {
|
|
48
|
-
"@ai-sdk/openai": "^0.0.
|
|
38
|
+
"@ai-sdk/openai": "^0.0.40",
|
|
49
39
|
"@xstate/graph": "^2.0.0",
|
|
50
|
-
"ai": "^3.2.
|
|
51
|
-
"
|
|
40
|
+
"ai": "^3.2.40",
|
|
41
|
+
"object-hash": "^3.0.0",
|
|
42
|
+
"xstate": "^5.16.0"
|
|
52
43
|
},
|
|
53
|
-
"
|
|
54
|
-
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
46
|
+
"lint": "tsc --noEmit",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:ci": "vitest --run",
|
|
49
|
+
"example": "ts-node examples/helpers/runner.ts",
|
|
50
|
+
"changeset": "changeset",
|
|
51
|
+
"release": "changeset publish",
|
|
52
|
+
"version": "changeset version"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/readme.md
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# Stately Agent
|
|
2
2
|
|
|
3
|
-
> [!WARNING]
|
|
4
|
-
> Alpha software! Not ready for production yet.
|
|
5
|
-
|
|
6
3
|
Stately Agent is a flexible framework for building AI agents using state machines. Stately agents go beyond normal LLM-based AI agents by:
|
|
7
4
|
|
|
8
5
|
- Using state machines to guide the agent's behavior, powered by [XState](https://stately.ai/docs/xstate)
|
|
@@ -10,4 +7,4 @@ Stately Agent is a flexible framework for building AI agents using state machine
|
|
|
10
7
|
- Enabling custom **planning** abilities for agents to achieve specific goals based on state machine logic, observations, and feedback
|
|
11
8
|
- Wrapping the [Vercel AI SDK](https://sdk.vercel.ai/) to easily support multiple model providers, such as OpenAI, Anthropic, Google, Mistral, Groq, Perplexity, and more
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
**Read the documentation: [stately.ai/docs/agents](https://stately.ai/docs/agents)**
|
package/src/agent.test.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { test, expect } from 'vitest';
|
|
2
|
-
import {
|
|
1
|
+
import { test, expect, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
AgentGenerateTextResult,
|
|
4
|
+
AgentMessage,
|
|
5
|
+
createAgent,
|
|
6
|
+
type AIAdapter,
|
|
7
|
+
} from './';
|
|
3
8
|
import { createActor, createMachine } from 'xstate';
|
|
9
|
+
import { GenerateTextResult } from 'ai';
|
|
10
|
+
import { z } from 'zod';
|
|
4
11
|
|
|
5
12
|
test('an agent has the expected interface', () => {
|
|
6
13
|
const agent = createAgent({
|
|
@@ -13,11 +20,16 @@ test('an agent has the expected interface', () => {
|
|
|
13
20
|
expect(agent.generateText).toBeDefined();
|
|
14
21
|
expect(agent.streamText).toBeDefined();
|
|
15
22
|
|
|
16
|
-
expect(agent.addFeedback).toBeDefined();
|
|
17
23
|
expect(agent.addMessage).toBeDefined();
|
|
18
24
|
expect(agent.addObservation).toBeDefined();
|
|
25
|
+
expect(agent.addFeedback).toBeDefined();
|
|
19
26
|
expect(agent.addPlan).toBeDefined();
|
|
20
27
|
|
|
28
|
+
expect(agent.getMessages).toBeDefined();
|
|
29
|
+
expect(agent.getObservations).toBeDefined();
|
|
30
|
+
expect(agent.getFeedback).toBeDefined();
|
|
31
|
+
expect(agent.getPlans).toBeDefined();
|
|
32
|
+
|
|
21
33
|
expect(agent.interact).toBeDefined();
|
|
22
34
|
});
|
|
23
35
|
|
|
@@ -45,6 +57,11 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
45
57
|
content: 'msg 1',
|
|
46
58
|
})
|
|
47
59
|
);
|
|
60
|
+
expect(agent.getMessages()).toContainEqual(
|
|
61
|
+
expect.objectContaining({
|
|
62
|
+
content: 'msg 1',
|
|
63
|
+
})
|
|
64
|
+
);
|
|
48
65
|
|
|
49
66
|
expect(agent.select((c) => c.messages)).toContainEqual(
|
|
50
67
|
expect.objectContaining({
|
|
@@ -53,6 +70,13 @@ test('agent.addMessage() adds to message history', () => {
|
|
|
53
70
|
timestamp: expect.any(Number),
|
|
54
71
|
})
|
|
55
72
|
);
|
|
73
|
+
expect(agent.getMessages()).toContainEqual(
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
content: 'response 1',
|
|
76
|
+
sessionId: expect.any(String),
|
|
77
|
+
timestamp: expect.any(Number),
|
|
78
|
+
})
|
|
79
|
+
);
|
|
56
80
|
});
|
|
57
81
|
|
|
58
82
|
test('agent.addFeedback() adds to feedback', () => {
|
|
@@ -83,6 +107,17 @@ test('agent.addFeedback() adds to feedback', () => {
|
|
|
83
107
|
timestamp: expect.any(Number),
|
|
84
108
|
})
|
|
85
109
|
);
|
|
110
|
+
expect(agent.getFeedback()).toContainEqual(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
attributes: {
|
|
113
|
+
score: -1,
|
|
114
|
+
},
|
|
115
|
+
goal: 'Win the game',
|
|
116
|
+
observationId: 'obs-1',
|
|
117
|
+
sessionId: expect.any(String),
|
|
118
|
+
timestamp: expect.any(Number),
|
|
119
|
+
})
|
|
120
|
+
);
|
|
86
121
|
});
|
|
87
122
|
|
|
88
123
|
test('agent.addObservation() adds to observations', () => {
|
|
@@ -111,6 +146,46 @@ test('agent.addObservation() adds to observations', () => {
|
|
|
111
146
|
);
|
|
112
147
|
});
|
|
113
148
|
|
|
149
|
+
test('agent.addObservation() adds to observations with machine hash', () => {
|
|
150
|
+
const agent = createAgent({
|
|
151
|
+
name: 'test',
|
|
152
|
+
events: {},
|
|
153
|
+
model: {} as any,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const machine = createMachine({
|
|
157
|
+
initial: 'playing',
|
|
158
|
+
states: {
|
|
159
|
+
playing: {
|
|
160
|
+
on: {
|
|
161
|
+
play: 'lost',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
lost: {},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const observation = agent.addObservation({
|
|
169
|
+
prevState: { value: 'playing', context: {} },
|
|
170
|
+
event: { type: 'play', position: 3 },
|
|
171
|
+
state: { value: 'lost', context: {} },
|
|
172
|
+
machine,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(observation.sessionId).toEqual(agent.sessionId);
|
|
176
|
+
|
|
177
|
+
expect(agent.select((c) => c.observations)).toContainEqual(
|
|
178
|
+
expect.objectContaining({
|
|
179
|
+
prevState: { value: 'playing', context: {} },
|
|
180
|
+
event: { type: 'play', position: 3 },
|
|
181
|
+
state: { value: 'lost', context: {} },
|
|
182
|
+
machineHash: expect.any(String),
|
|
183
|
+
sessionId: expect.any(String),
|
|
184
|
+
timestamp: expect.any(Number),
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
114
189
|
test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
115
190
|
const machine = createMachine({
|
|
116
191
|
initial: 'a',
|
|
@@ -140,6 +215,12 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
|
|
|
140
215
|
state: expect.objectContaining({ value: 'a' }),
|
|
141
216
|
})
|
|
142
217
|
);
|
|
218
|
+
expect(agent.getObservations()).toContainEqual(
|
|
219
|
+
expect.objectContaining({
|
|
220
|
+
prevState: undefined,
|
|
221
|
+
state: expect.objectContaining({ value: 'a' }),
|
|
222
|
+
})
|
|
223
|
+
);
|
|
143
224
|
|
|
144
225
|
actor.send({ type: 'NEXT' });
|
|
145
226
|
|
|
@@ -175,7 +256,8 @@ test('Agents can use a custom adapter', async () => {
|
|
|
175
256
|
expect(res.text).toEqual('Response');
|
|
176
257
|
});
|
|
177
258
|
|
|
178
|
-
test
|
|
259
|
+
test('You can listen for feedback events', () => {
|
|
260
|
+
const fn = vi.fn();
|
|
179
261
|
const agent = createAgent({
|
|
180
262
|
name: 'test',
|
|
181
263
|
events: {},
|
|
@@ -183,5 +265,242 @@ test.skip('You can listen for emitted agent events', () => {
|
|
|
183
265
|
model: {} as any,
|
|
184
266
|
});
|
|
185
267
|
|
|
186
|
-
agent.on('feedback',
|
|
268
|
+
agent.on('feedback', fn);
|
|
269
|
+
|
|
270
|
+
agent.addFeedback({
|
|
271
|
+
attributes: {
|
|
272
|
+
score: -1,
|
|
273
|
+
},
|
|
274
|
+
goal: 'Win the game',
|
|
275
|
+
observationId: 'obs-1',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(fn).toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('You can listen for plan events', async () => {
|
|
282
|
+
const fn = vi.fn();
|
|
283
|
+
const agent = createAgent({
|
|
284
|
+
name: 'test',
|
|
285
|
+
model: {} as any,
|
|
286
|
+
events: {
|
|
287
|
+
WIN: z.object({}),
|
|
288
|
+
},
|
|
289
|
+
adapter: {
|
|
290
|
+
generateText: async (arg) => {
|
|
291
|
+
const keys = Object.keys(arg.tools!);
|
|
292
|
+
|
|
293
|
+
if (keys.length !== 1) {
|
|
294
|
+
throw new Error('Expected only 1 choice');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
toolResults: [
|
|
299
|
+
{
|
|
300
|
+
result: {
|
|
301
|
+
type: keys[0],
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
} as any as AgentGenerateTextResult;
|
|
306
|
+
},
|
|
307
|
+
streamText: {} as any,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
agent.on('plan', fn);
|
|
312
|
+
|
|
313
|
+
await agent.decide({
|
|
314
|
+
goal: 'Win the game',
|
|
315
|
+
state: {
|
|
316
|
+
value: 'playing',
|
|
317
|
+
context: {},
|
|
318
|
+
},
|
|
319
|
+
machine: createMachine({
|
|
320
|
+
initial: 'playing',
|
|
321
|
+
states: {
|
|
322
|
+
playing: {
|
|
323
|
+
on: {
|
|
324
|
+
WIN: {
|
|
325
|
+
target: 'won',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
won: {},
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(fn).toHaveBeenCalledWith(
|
|
335
|
+
expect.objectContaining({
|
|
336
|
+
plan: expect.objectContaining({
|
|
337
|
+
nextEvent: {
|
|
338
|
+
type: 'WIN',
|
|
339
|
+
},
|
|
340
|
+
}),
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('agent.types provides context and event types', () => {
|
|
346
|
+
const agent = createAgent({
|
|
347
|
+
model: {} as any,
|
|
348
|
+
events: {
|
|
349
|
+
setScore: z.object({
|
|
350
|
+
score: z.number(),
|
|
351
|
+
}),
|
|
352
|
+
},
|
|
353
|
+
context: {
|
|
354
|
+
score: z.number(),
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
agent.types satisfies { context: any; events: any };
|
|
359
|
+
|
|
360
|
+
agent.types.context satisfies { score: number };
|
|
361
|
+
|
|
362
|
+
// @ts-expect-error
|
|
363
|
+
agent.types.context satisfies { score: string };
|
|
187
364
|
});
|
|
365
|
+
|
|
366
|
+
test.each(['generateText', 'streamText'] as const)(
|
|
367
|
+
'can provide a correlation ID (%s)',
|
|
368
|
+
async (method) => {
|
|
369
|
+
const agent = createAgent({
|
|
370
|
+
model: {} as any,
|
|
371
|
+
events: {},
|
|
372
|
+
adapter: {
|
|
373
|
+
[method]: async (opts: any) => {
|
|
374
|
+
const res = {
|
|
375
|
+
text: 'response',
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
opts.onFinish?.(res);
|
|
379
|
+
|
|
380
|
+
return res as AgentGenerateTextResult;
|
|
381
|
+
},
|
|
382
|
+
} as any as AIAdapter,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const promise = new Promise<AgentMessage>((res) => {
|
|
386
|
+
agent.onMessage((msg) => {
|
|
387
|
+
if (msg.role === 'assistant') {
|
|
388
|
+
res(msg);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
await agent[method]({
|
|
394
|
+
prompt: 'hi',
|
|
395
|
+
correlationId: 'c-1',
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const msg = await promise;
|
|
399
|
+
|
|
400
|
+
expect(msg.correlationId).toBe('c-1');
|
|
401
|
+
expect(msg.parentCorrelationId).toBe(undefined);
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
test.each(['generateText', 'streamText'] as const)(
|
|
406
|
+
'correlation IDs are automatically generated if not provided (%s)',
|
|
407
|
+
async (method) => {
|
|
408
|
+
const agent = createAgent({
|
|
409
|
+
model: {} as any,
|
|
410
|
+
events: {},
|
|
411
|
+
adapter: {
|
|
412
|
+
[method]: async (opts: any) => {
|
|
413
|
+
const res = {
|
|
414
|
+
text: 'response',
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
opts.onFinish?.(res);
|
|
418
|
+
|
|
419
|
+
return res as AgentGenerateTextResult;
|
|
420
|
+
},
|
|
421
|
+
} as any as AIAdapter,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
await agent[method]({
|
|
425
|
+
prompt: 'hi',
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const messages = agent.getMessages();
|
|
429
|
+
|
|
430
|
+
expect(messages[0]?.correlationId).toEqual(expect.stringMatching(/.+/));
|
|
431
|
+
expect(messages[0]?.role).toBe('user');
|
|
432
|
+
expect(messages[1]?.correlationId).toEqual(expect.stringMatching(/.+/));
|
|
433
|
+
expect(messages[1]?.role).toBe('assistant');
|
|
434
|
+
|
|
435
|
+
expect(messages[0]!.correlationId).toEqual(messages[1]!.correlationId);
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
test.each(['generateText', 'streamText'] as const)(
|
|
440
|
+
'can provide a parent correlation ID (%s)',
|
|
441
|
+
async (method) => {
|
|
442
|
+
const agent = createAgent({
|
|
443
|
+
model: {} as any,
|
|
444
|
+
events: {},
|
|
445
|
+
adapter: {
|
|
446
|
+
[method]: async (opts: any) => {
|
|
447
|
+
const res = {
|
|
448
|
+
text: 'response',
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
opts.onFinish?.(res);
|
|
452
|
+
|
|
453
|
+
return res as AgentGenerateTextResult;
|
|
454
|
+
},
|
|
455
|
+
} as any as AIAdapter,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
await agent[method]({
|
|
459
|
+
prompt: 'hi',
|
|
460
|
+
correlationId: 'c-1',
|
|
461
|
+
parentCorrelationId: 'c-0',
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const msg = agent.getMessages().find((msg) => msg.role === 'assistant')!;
|
|
465
|
+
|
|
466
|
+
expect(msg.correlationId).toBe('c-1');
|
|
467
|
+
expect(msg.parentCorrelationId).toBe('c-0');
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
test.each(['generateText', 'streamText'] as const)(
|
|
472
|
+
'can add feedback to a correlation (%s)',
|
|
473
|
+
async (method) => {
|
|
474
|
+
const agent = createAgent({
|
|
475
|
+
name: 'test',
|
|
476
|
+
model: {} as any,
|
|
477
|
+
events: {},
|
|
478
|
+
adapter: {
|
|
479
|
+
[method]: async (opts: any) => {
|
|
480
|
+
const res = {
|
|
481
|
+
text: 'response',
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
opts.onFinish?.(res);
|
|
485
|
+
|
|
486
|
+
return res as AgentGenerateTextResult;
|
|
487
|
+
},
|
|
488
|
+
} as any as AIAdapter,
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const res = await agent[method]({
|
|
492
|
+
prompt: 'test',
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
agent.addFeedback({
|
|
496
|
+
correlationId: res.correlationId,
|
|
497
|
+
reward: -1,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const message = agent.getMessages()[0]!;
|
|
501
|
+
const feedback = agent.getFeedback()[0]!;
|
|
502
|
+
|
|
503
|
+
expect(message.correlationId).toBeDefined();
|
|
504
|
+
expect(feedback.correlationId).toEqual(message.correlationId);
|
|
505
|
+
}
|
|
506
|
+
);
|