@statelyai/agent 1.1.6 → 2.0.0-next.1
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/light-hats-drive.md +9 -0
- package/.changeset/old-jobs-check.md +5 -0
- package/.changeset/pre.json +13 -0
- package/.vscode/launch.json +6 -0
- package/CHANGELOG.md +22 -0
- package/dist/index.d.mts +262 -171
- package/dist/index.d.ts +262 -171
- package/dist/index.js +383 -274
- package/dist/index.mjs +386 -272
- package/examples/chatbot-alt.ts +57 -0
- package/examples/chatbot.ts +12 -17
- package/examples/cot.ts +26 -23
- package/examples/customer-service-sim.ts +107 -0
- package/examples/email.ts +37 -41
- package/examples/example.ts +6 -6
- package/examples/executor.ts +66 -0
- package/examples/goal.ts +12 -12
- package/examples/helpers/helpers.ts +26 -14
- package/examples/joke.ts +79 -76
- package/examples/jugs.ts +125 -0
- package/examples/multi.ts +5 -5
- package/examples/newspaper.ts +98 -104
- package/examples/number.ts +6 -5
- package/examples/raffle.ts +11 -12
- package/examples/river-crossing.ts +140 -0
- package/examples/sandbox.ts +1 -1
- package/examples/simple.ts +5 -3
- package/examples/summary.ts +121 -0
- package/examples/support.ts +6 -6
- package/examples/ticTacToe.ts +86 -45
- package/examples/todo.ts +7 -7
- package/examples/tutor.ts +15 -15
- package/examples/verify.ts +3 -3
- package/examples/weather.ts +6 -9
- package/examples/wiki.ts +27 -8
- package/examples/word.ts +16 -11
- package/package.json +16 -11
- package/readme.md +1 -1
- package/src/agent-experimental.ts +1 -1
- package/src/agent.test.ts +243 -214
- package/src/agent.ts +286 -95
- package/src/decide.test.ts +276 -0
- package/src/decide.ts +163 -0
- package/src/index.ts +1 -1
- package/src/middleware.ts +91 -0
- package/src/mockModel.ts +47 -0
- package/src/planners/shortestPath.test.ts +94 -0
- package/src/planners/shortestPath.ts +177 -0
- package/src/planners/simple.ts +105 -0
- package/src/strategies/chain-of-note.ts +6 -55
- package/src/text.ts +51 -144
- package/src/types.ts +187 -212
- package/src/utils.ts +48 -4
- package/vitest.config.ts +9 -3
- package/src/adapters/vercel.ts +0 -7
- package/src/decision.test.ts +0 -179
- package/src/decision.ts +0 -84
- package/src/memory.ts +0 -25
- package/src/planners/shortestPathPlanner.ts +0 -22
- package/src/planners/simplePlanner.ts +0 -139
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { createAgent, fromDecision } from '.';
|
|
3
|
+
import { createActor, createMachine, waitFor } from 'xstate';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { LanguageModelV1CallOptions } from 'ai';
|
|
6
|
+
import { dummyResponseValues, MockLanguageModelV1 } from './mockModel';
|
|
7
|
+
|
|
8
|
+
const doGenerate = async (params: LanguageModelV1CallOptions) => {
|
|
9
|
+
const keys =
|
|
10
|
+
params.mode.type === 'regular' ? params.mode.tools?.map((t) => t.name) : [];
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
...dummyResponseValues,
|
|
14
|
+
finishReason: 'tool-calls',
|
|
15
|
+
toolCalls: [
|
|
16
|
+
{
|
|
17
|
+
toolCallType: 'function',
|
|
18
|
+
toolCallId: 'call-1',
|
|
19
|
+
toolName: keys![0],
|
|
20
|
+
args: `{ "type": "${keys?.[0]}" }`,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
} as any;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
test('fromDecision() makes a decision', async () => {
|
|
27
|
+
const model = new MockLanguageModelV1({
|
|
28
|
+
doGenerate,
|
|
29
|
+
});
|
|
30
|
+
const agent = createAgent({
|
|
31
|
+
id: 'test',
|
|
32
|
+
model,
|
|
33
|
+
events: {
|
|
34
|
+
doFirst: z.object({}),
|
|
35
|
+
doSecond: z.object({}),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const machine = createMachine({
|
|
40
|
+
initial: 'first',
|
|
41
|
+
states: {
|
|
42
|
+
first: {
|
|
43
|
+
invoke: {
|
|
44
|
+
src: fromDecision(agent),
|
|
45
|
+
},
|
|
46
|
+
on: {
|
|
47
|
+
doFirst: 'second',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
second: {
|
|
51
|
+
invoke: {
|
|
52
|
+
src: fromDecision(agent),
|
|
53
|
+
},
|
|
54
|
+
on: {
|
|
55
|
+
doSecond: 'third',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
third: {},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const actor = createActor(machine);
|
|
63
|
+
|
|
64
|
+
actor.start();
|
|
65
|
+
|
|
66
|
+
await waitFor(actor, (s) => s.matches('third'));
|
|
67
|
+
|
|
68
|
+
expect(actor.getSnapshot().value).toBe('third');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('interacts with an actor', async () => {
|
|
72
|
+
const model = new MockLanguageModelV1({
|
|
73
|
+
doGenerate,
|
|
74
|
+
});
|
|
75
|
+
const agent = createAgent({
|
|
76
|
+
id: 'test',
|
|
77
|
+
model,
|
|
78
|
+
events: {
|
|
79
|
+
doFirst: z.object({}),
|
|
80
|
+
doSecond: z.object({}),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const machine = createMachine({
|
|
85
|
+
initial: 'first',
|
|
86
|
+
states: {
|
|
87
|
+
first: {
|
|
88
|
+
on: {
|
|
89
|
+
doFirst: 'second',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
second: {
|
|
93
|
+
on: {
|
|
94
|
+
doSecond: 'third',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
third: {},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const actor = createActor(machine);
|
|
102
|
+
|
|
103
|
+
agent.interact(actor, () => ({
|
|
104
|
+
goal: 'Some goal',
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
actor.start();
|
|
108
|
+
|
|
109
|
+
await waitFor(actor, (s) => s.matches('third'));
|
|
110
|
+
|
|
111
|
+
expect(actor.getSnapshot().value).toBe('third');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('interacts with an actor (late interaction)', async () => {
|
|
115
|
+
const model = new MockLanguageModelV1({
|
|
116
|
+
doGenerate,
|
|
117
|
+
});
|
|
118
|
+
const agent = createAgent({
|
|
119
|
+
id: 'test',
|
|
120
|
+
model,
|
|
121
|
+
events: {
|
|
122
|
+
doFirst: z.object({}),
|
|
123
|
+
doSecond: z.object({}),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const machine = createMachine({
|
|
128
|
+
initial: 'first',
|
|
129
|
+
states: {
|
|
130
|
+
first: {
|
|
131
|
+
on: {
|
|
132
|
+
doFirst: 'second',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
second: {
|
|
136
|
+
on: {
|
|
137
|
+
doSecond: 'third',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
third: {},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const actor = createActor(machine);
|
|
145
|
+
|
|
146
|
+
actor.start();
|
|
147
|
+
|
|
148
|
+
agent.interact(actor, () => ({
|
|
149
|
+
goal: 'Some goal',
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
await waitFor(actor, (s) => s.matches('third'));
|
|
153
|
+
|
|
154
|
+
expect(actor.getSnapshot().value).toBe('third');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('agent.decide() makes a decision based on goal and state (simple planner)', async () => {
|
|
158
|
+
const model = new MockLanguageModelV1({
|
|
159
|
+
doGenerate,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const agent = createAgent({
|
|
163
|
+
id: 'test',
|
|
164
|
+
model,
|
|
165
|
+
events: {
|
|
166
|
+
MOVE: z.object({}),
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const plan = await agent.decide({
|
|
171
|
+
goal: 'Make the best move',
|
|
172
|
+
state: {
|
|
173
|
+
value: 'playing',
|
|
174
|
+
context: {
|
|
175
|
+
board: [0, 0, 0],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
machine: createMachine({
|
|
179
|
+
initial: 'playing',
|
|
180
|
+
states: {
|
|
181
|
+
playing: {
|
|
182
|
+
on: {
|
|
183
|
+
MOVE: 'next',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
next: {},
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(plan).toBeDefined();
|
|
192
|
+
expect(plan!.nextEvent).toEqual(
|
|
193
|
+
expect.objectContaining({
|
|
194
|
+
type: 'MOVE',
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test.each([
|
|
200
|
+
[undefined, true],
|
|
201
|
+
[undefined, false],
|
|
202
|
+
[3, true],
|
|
203
|
+
[3, false],
|
|
204
|
+
])(
|
|
205
|
+
'agent.decide() retries if a decision is not made (%i attempts, succeed: %s)',
|
|
206
|
+
async (maxAttempts, succeed) => {
|
|
207
|
+
let attempts = 0;
|
|
208
|
+
const doGenerateWithRetry = async (params: LanguageModelV1CallOptions) => {
|
|
209
|
+
const keys =
|
|
210
|
+
params.mode.type === 'regular'
|
|
211
|
+
? params.mode.tools?.map((t) => t.name)
|
|
212
|
+
: [];
|
|
213
|
+
|
|
214
|
+
console.log('try', attempts, 'max', maxAttempts);
|
|
215
|
+
|
|
216
|
+
const toolCalls =
|
|
217
|
+
succeed && attempts++ === (maxAttempts ?? 2) - 1
|
|
218
|
+
? [
|
|
219
|
+
{
|
|
220
|
+
toolCallType: 'function',
|
|
221
|
+
toolCallId: 'call-1',
|
|
222
|
+
toolName: keys![0],
|
|
223
|
+
args: `{ "type": "${keys?.[0]}" }`,
|
|
224
|
+
},
|
|
225
|
+
]
|
|
226
|
+
: [];
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
...dummyResponseValues,
|
|
230
|
+
finishReason: 'tool-calls',
|
|
231
|
+
toolCalls,
|
|
232
|
+
} as any;
|
|
233
|
+
};
|
|
234
|
+
const model = new MockLanguageModelV1({
|
|
235
|
+
doGenerate: doGenerateWithRetry,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const agent = createAgent({
|
|
239
|
+
id: 'test',
|
|
240
|
+
model,
|
|
241
|
+
events: {
|
|
242
|
+
MOVE: z.object({}),
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const plan = await agent.decide({
|
|
247
|
+
goal: 'Make the best move',
|
|
248
|
+
state: {
|
|
249
|
+
value: 'playing',
|
|
250
|
+
},
|
|
251
|
+
machine: createMachine({
|
|
252
|
+
initial: 'playing',
|
|
253
|
+
states: {
|
|
254
|
+
playing: {
|
|
255
|
+
on: {
|
|
256
|
+
MOVE: 'win',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
win: {},
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
maxAttempts,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (!succeed) {
|
|
266
|
+
expect(plan).toBeUndefined();
|
|
267
|
+
} else {
|
|
268
|
+
expect(plan).toBeDefined();
|
|
269
|
+
expect(plan!.nextEvent).toEqual(
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
type: 'MOVE',
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
);
|
package/src/decide.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { AnyActor, AnyMachineSnapshot, fromPromise } from 'xstate';
|
|
2
|
+
import {
|
|
3
|
+
AnyAgent,
|
|
4
|
+
AgentDecideOptions,
|
|
5
|
+
AgentDecisionLogic,
|
|
6
|
+
AgentDecisionInput,
|
|
7
|
+
AgentPlanner,
|
|
8
|
+
AgentPlan,
|
|
9
|
+
EventsFromZodEventMapping,
|
|
10
|
+
AgentPlanInput,
|
|
11
|
+
TransitionData,
|
|
12
|
+
} from './types';
|
|
13
|
+
import { simplePlanner } from './planners/simple';
|
|
14
|
+
import { getTransitions } from './utils';
|
|
15
|
+
import { CoreMessage, CoreTool, tool } from 'ai';
|
|
16
|
+
|
|
17
|
+
export async function agentDecide<T extends AnyAgent>(
|
|
18
|
+
agent: T,
|
|
19
|
+
options: AgentDecideOptions<T>
|
|
20
|
+
): Promise<AgentPlan<EventsFromZodEventMapping<T['events']>> | undefined> {
|
|
21
|
+
const resolvedOptions = {
|
|
22
|
+
...agent.defaultOptions,
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
const {
|
|
26
|
+
planner = simplePlanner as AgentPlanner<any>,
|
|
27
|
+
goal,
|
|
28
|
+
events = agent.events,
|
|
29
|
+
state,
|
|
30
|
+
machine,
|
|
31
|
+
model = agent.model,
|
|
32
|
+
messages,
|
|
33
|
+
...otherPlanInput
|
|
34
|
+
} = resolvedOptions;
|
|
35
|
+
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
|
|
38
|
+
const maxAttempts = resolvedOptions.maxAttempts ?? 2;
|
|
39
|
+
|
|
40
|
+
let plan;
|
|
41
|
+
|
|
42
|
+
while (attempts++ < maxAttempts) {
|
|
43
|
+
plan = await planner(agent, {
|
|
44
|
+
model,
|
|
45
|
+
goal,
|
|
46
|
+
events,
|
|
47
|
+
state,
|
|
48
|
+
machine,
|
|
49
|
+
messages: messages as CoreMessage[], // TODO: fix UIMessage thing
|
|
50
|
+
...otherPlanInput,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (plan?.nextEvent) {
|
|
54
|
+
agent.addPlan(plan);
|
|
55
|
+
await resolvedOptions.execute?.(plan.nextEvent);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return plan;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function fromDecision(
|
|
63
|
+
agent: AnyAgent,
|
|
64
|
+
defaultInput?: AgentDecisionInput
|
|
65
|
+
): AgentDecisionLogic<any> {
|
|
66
|
+
return fromPromise(async ({ input, self }) => {
|
|
67
|
+
const parentRef = self._parent;
|
|
68
|
+
if (!parentRef) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const snapshot = parentRef.getSnapshot() as AnyMachineSnapshot;
|
|
73
|
+
const inputObject = typeof input === 'string' ? { goal: input } : input;
|
|
74
|
+
const resolvedInput = {
|
|
75
|
+
...defaultInput,
|
|
76
|
+
...inputObject,
|
|
77
|
+
};
|
|
78
|
+
const state = {
|
|
79
|
+
value: snapshot.value,
|
|
80
|
+
context: resolvedInput.context,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const plan = await agentDecide(agent, {
|
|
84
|
+
machine: (parentRef as AnyActor).logic,
|
|
85
|
+
state,
|
|
86
|
+
execute: async (event) => {
|
|
87
|
+
parentRef.send(event);
|
|
88
|
+
},
|
|
89
|
+
...resolvedInput,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return plan;
|
|
93
|
+
}) as AgentDecisionLogic<any>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getToolMap<T extends AnyAgent>(
|
|
97
|
+
_agent: T,
|
|
98
|
+
input: AgentPlanInput<any>
|
|
99
|
+
): Record<string, CoreTool<any, any>> | undefined {
|
|
100
|
+
// Get all of the possible next transitions
|
|
101
|
+
const transitions: TransitionData[] = input.machine
|
|
102
|
+
? getTransitions(input.state, input.machine)
|
|
103
|
+
: Object.entries(input.events).map(([eventType, { description }]) => ({
|
|
104
|
+
eventType,
|
|
105
|
+
description,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
// Only keep the transitions that match the event types that are in the event mapping
|
|
109
|
+
// TODO: allow for custom filters
|
|
110
|
+
const filter = (eventType: string) =>
|
|
111
|
+
Object.keys(input.events).includes(eventType);
|
|
112
|
+
|
|
113
|
+
// Mapping of each event type (e.g. "mouse.click")
|
|
114
|
+
// to a valid function name (e.g. "mouse_click")
|
|
115
|
+
const functionNameMapping: Record<string, string> = {};
|
|
116
|
+
|
|
117
|
+
const toolTransitions = transitions
|
|
118
|
+
.filter((t) => {
|
|
119
|
+
return filter(t.eventType);
|
|
120
|
+
})
|
|
121
|
+
.map((t) => {
|
|
122
|
+
const name = t.eventType.replace(/\./g, '_');
|
|
123
|
+
functionNameMapping[name] = t.eventType;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
type: 'function',
|
|
127
|
+
eventType: t.eventType,
|
|
128
|
+
description: t.description,
|
|
129
|
+
name,
|
|
130
|
+
} as const;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Convert the transition data to a tool map that the
|
|
134
|
+
// Vercel AI SDK can use
|
|
135
|
+
const toolMap: Record<string, CoreTool<any, any>> = {};
|
|
136
|
+
for (const toolTransitionData of toolTransitions) {
|
|
137
|
+
const toolZodType = input.events?.[toolTransitionData.eventType];
|
|
138
|
+
|
|
139
|
+
if (!toolZodType) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
toolMap[toolTransitionData.name] = tool({
|
|
144
|
+
description: toolZodType?.description ?? toolTransitionData.description,
|
|
145
|
+
parameters: toolZodType,
|
|
146
|
+
execute: async (params: Record<string, any>) => {
|
|
147
|
+
const event = {
|
|
148
|
+
type: toolTransitionData.eventType,
|
|
149
|
+
...params,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return event;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!Object.keys(toolMap).length) {
|
|
158
|
+
// No valid transitions for the specified tools
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return toolMap;
|
|
163
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Experimental_LanguageModelV1Middleware as LanguageModelV1Middleware,
|
|
3
|
+
LanguageModelV1StreamPart,
|
|
4
|
+
} from 'ai';
|
|
5
|
+
import {
|
|
6
|
+
AnyAgent,
|
|
7
|
+
LanguageModelV1TextPart,
|
|
8
|
+
LanguageModelV1ToolCallPart,
|
|
9
|
+
} from './types';
|
|
10
|
+
import { randomId } from './utils';
|
|
11
|
+
|
|
12
|
+
export function createAgentMiddleware(agent: AnyAgent) {
|
|
13
|
+
const middleware: LanguageModelV1Middleware = {
|
|
14
|
+
transformParams: async ({ params }) => {
|
|
15
|
+
return params;
|
|
16
|
+
},
|
|
17
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
18
|
+
const id = randomId();
|
|
19
|
+
|
|
20
|
+
params.prompt.forEach((message) => {
|
|
21
|
+
agent.addMessage({
|
|
22
|
+
id,
|
|
23
|
+
...message,
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const result = await doGenerate();
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
wrapStream: async ({ doStream, params }) => {
|
|
34
|
+
const id = randomId();
|
|
35
|
+
|
|
36
|
+
params.prompt.forEach((message) => {
|
|
37
|
+
message.content;
|
|
38
|
+
agent.addMessage({
|
|
39
|
+
id,
|
|
40
|
+
...message,
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const { stream, ...rest } = await doStream();
|
|
46
|
+
|
|
47
|
+
let generatedText = '';
|
|
48
|
+
|
|
49
|
+
const transformStream = new TransformStream<
|
|
50
|
+
LanguageModelV1StreamPart,
|
|
51
|
+
LanguageModelV1StreamPart
|
|
52
|
+
>({
|
|
53
|
+
transform(chunk, controller) {
|
|
54
|
+
if (chunk.type === 'text-delta') {
|
|
55
|
+
generatedText += chunk.textDelta;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
controller.enqueue(chunk);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
flush() {
|
|
62
|
+
const content: (
|
|
63
|
+
| LanguageModelV1TextPart
|
|
64
|
+
| LanguageModelV1ToolCallPart
|
|
65
|
+
)[] = [];
|
|
66
|
+
|
|
67
|
+
if (generatedText) {
|
|
68
|
+
content.push({
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: generatedText,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
agent.addMessage({
|
|
75
|
+
id: randomId(),
|
|
76
|
+
timestamp: Date.now(),
|
|
77
|
+
role: 'assistant',
|
|
78
|
+
content,
|
|
79
|
+
responseId: id,
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
stream: stream.pipeThrough(transformStream),
|
|
86
|
+
...rest,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
return middleware;
|
|
91
|
+
}
|
package/src/mockModel.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { LanguageModelV1 } from 'ai';
|
|
2
|
+
|
|
3
|
+
export class MockLanguageModelV1 implements LanguageModelV1 {
|
|
4
|
+
readonly specificationVersion = 'v1';
|
|
5
|
+
|
|
6
|
+
readonly provider: LanguageModelV1['provider'];
|
|
7
|
+
readonly modelId: LanguageModelV1['modelId'];
|
|
8
|
+
|
|
9
|
+
doGenerate: LanguageModelV1['doGenerate'];
|
|
10
|
+
doStream: LanguageModelV1['doStream'];
|
|
11
|
+
|
|
12
|
+
readonly defaultObjectGenerationMode: LanguageModelV1['defaultObjectGenerationMode'];
|
|
13
|
+
readonly supportsStructuredOutputs: LanguageModelV1['supportsStructuredOutputs'];
|
|
14
|
+
constructor({
|
|
15
|
+
provider = 'mock-provider',
|
|
16
|
+
modelId = 'mock-model-id',
|
|
17
|
+
doGenerate = notImplemented,
|
|
18
|
+
doStream = notImplemented,
|
|
19
|
+
defaultObjectGenerationMode = undefined,
|
|
20
|
+
supportsStructuredOutputs = undefined,
|
|
21
|
+
}: {
|
|
22
|
+
provider?: LanguageModelV1['provider'];
|
|
23
|
+
modelId?: LanguageModelV1['modelId'];
|
|
24
|
+
doGenerate?: LanguageModelV1['doGenerate'];
|
|
25
|
+
doStream?: LanguageModelV1['doStream'];
|
|
26
|
+
defaultObjectGenerationMode?: LanguageModelV1['defaultObjectGenerationMode'];
|
|
27
|
+
supportsStructuredOutputs?: LanguageModelV1['supportsStructuredOutputs'];
|
|
28
|
+
} = {}) {
|
|
29
|
+
this.provider = provider;
|
|
30
|
+
this.modelId = modelId;
|
|
31
|
+
this.doGenerate = doGenerate;
|
|
32
|
+
this.doStream = doStream;
|
|
33
|
+
|
|
34
|
+
this.defaultObjectGenerationMode = defaultObjectGenerationMode;
|
|
35
|
+
this.supportsStructuredOutputs = supportsStructuredOutputs;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function notImplemented(): never {
|
|
40
|
+
throw new Error('Not implemented');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const dummyResponseValues = {
|
|
44
|
+
rawCall: { rawPrompt: 'prompt', rawSettings: {} },
|
|
45
|
+
finishReason: 'stop' as const,
|
|
46
|
+
usage: { promptTokens: 10, completionTokens: 20 },
|
|
47
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createAgent } from '../';
|
|
2
|
+
import { assign, createActor, setup } from 'xstate';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { experimental_createShortestPathPlanner } from './shortestPath';
|
|
5
|
+
import { test, expect } from 'vitest';
|
|
6
|
+
import { dummyResponseValues, MockLanguageModelV1 } from '../mockModel';
|
|
7
|
+
|
|
8
|
+
test.skip('should find shortest path to goal', async () => {
|
|
9
|
+
const agent = createAgent({
|
|
10
|
+
id: 'counter',
|
|
11
|
+
model: new MockLanguageModelV1({
|
|
12
|
+
doGenerate: async () => {
|
|
13
|
+
return {
|
|
14
|
+
...dummyResponseValues,
|
|
15
|
+
text: JSON.stringify({
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
count: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
const: 3,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ['count'],
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
events: {
|
|
29
|
+
increment: z.object({}).describe('Increment the counter by 1'),
|
|
30
|
+
decrement: z.object({}).describe('Decrement the counter by 1'),
|
|
31
|
+
},
|
|
32
|
+
context: {
|
|
33
|
+
count: z.number().int().describe('Current count value'),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const counterMachine = setup({
|
|
38
|
+
types: {
|
|
39
|
+
context: agent.types.context,
|
|
40
|
+
events: agent.types.events,
|
|
41
|
+
},
|
|
42
|
+
}).createMachine({
|
|
43
|
+
initial: 'counting',
|
|
44
|
+
context: { count: 0 },
|
|
45
|
+
states: {
|
|
46
|
+
counting: {
|
|
47
|
+
always: {
|
|
48
|
+
guard: ({ context }) => context.count === 3,
|
|
49
|
+
target: 'success',
|
|
50
|
+
},
|
|
51
|
+
on: {
|
|
52
|
+
increment: {
|
|
53
|
+
actions: assign({ count: ({ context }) => context.count + 1 }),
|
|
54
|
+
},
|
|
55
|
+
decrement: {
|
|
56
|
+
actions: assign({ count: ({ context }) => context.count - 1 }),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
success: {
|
|
61
|
+
type: 'final',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const counterActor = createActor(counterMachine).start();
|
|
67
|
+
|
|
68
|
+
const decision = await agent.decide({
|
|
69
|
+
machine: counterMachine,
|
|
70
|
+
model: new MockLanguageModelV1({
|
|
71
|
+
defaultObjectGenerationMode: 'tool',
|
|
72
|
+
doGenerate: async () => {
|
|
73
|
+
return {
|
|
74
|
+
...dummyResponseValues,
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
count: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
const: 3,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ['count'],
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
goal: 'Get the counter to exactly 3',
|
|
89
|
+
state: counterActor.getSnapshot(),
|
|
90
|
+
planner: experimental_createShortestPathPlanner(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(decision?.nextEvent?.type).toBe('increment');
|
|
94
|
+
});
|