@statelyai/agent 2.0.0-next.0 → 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/old-jobs-check.md +5 -0
- package/.changeset/pre.json +4 -1
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +19 -25
- package/dist/index.d.ts +19 -25
- package/dist/index.js +43 -39
- package/dist/index.mjs +43 -39
- package/examples/chatbot-alt.ts +1 -1
- package/examples/chatbot.ts +1 -1
- package/examples/cot.ts +1 -1
- package/examples/customer-service-sim.ts +4 -4
- package/examples/email.ts +29 -33
- package/examples/example.ts +1 -1
- package/examples/goal.ts +1 -1
- package/examples/joke.ts +1 -1
- package/examples/jugs.ts +3 -3
- package/examples/multi.ts +1 -1
- package/examples/number.ts +1 -1
- package/examples/raffle.ts +1 -1
- package/examples/river-crossing.ts +3 -3
- package/examples/simple.ts +1 -1
- package/examples/summary.ts +1 -1
- package/examples/support.ts +1 -1
- package/examples/ticTacToe.ts +2 -2
- package/examples/todo.ts +1 -1
- package/examples/tutor.ts +2 -2
- 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 +5 -3
- package/src/agent.test.ts +150 -10
- package/src/agent.ts +20 -217
- package/src/decide.test.ts +124 -3
- package/src/decide.ts +24 -14
- package/src/middleware.ts +2 -14
- package/src/planners/shortestPath.test.ts +94 -0
- package/src/planners/shortestPath.ts +177 -0
- package/src/planners/{simplePlanner.ts → simple.ts} +6 -12
- package/src/types.ts +15 -8
- package/src/utils.ts +11 -0
- package/vitest.config.ts +9 -3
- package/src/planners/shortestPathPlanner.ts +0 -160
package/src/decide.ts
CHANGED
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
AgentPlanInput,
|
|
11
11
|
TransitionData,
|
|
12
12
|
} from './types';
|
|
13
|
-
import { simplePlanner } from './planners/
|
|
13
|
+
import { simplePlanner } from './planners/simple';
|
|
14
14
|
import { getTransitions } from './utils';
|
|
15
|
-
import { CoreTool, tool } from 'ai';
|
|
15
|
+
import { CoreMessage, CoreTool, tool } from 'ai';
|
|
16
16
|
|
|
17
17
|
export async function agentDecide<T extends AnyAgent>(
|
|
18
18
|
agent: T,
|
|
19
|
-
options: AgentDecideOptions
|
|
19
|
+
options: AgentDecideOptions<T>
|
|
20
20
|
): Promise<AgentPlan<EventsFromZodEventMapping<T['events']>> | undefined> {
|
|
21
21
|
const resolvedOptions = {
|
|
22
22
|
...agent.defaultOptions,
|
|
@@ -29,21 +29,31 @@ export async function agentDecide<T extends AnyAgent>(
|
|
|
29
29
|
state,
|
|
30
30
|
machine,
|
|
31
31
|
model = agent.model,
|
|
32
|
+
messages,
|
|
32
33
|
...otherPlanInput
|
|
33
34
|
} = resolvedOptions;
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
machine,
|
|
41
|
-
...otherPlanInput,
|
|
42
|
-
});
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
|
|
38
|
+
const maxAttempts = resolvedOptions.maxAttempts ?? 2;
|
|
39
|
+
|
|
40
|
+
let plan;
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
agent
|
|
46
|
-
|
|
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
|
+
}
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
return plan;
|
package/src/middleware.ts
CHANGED
|
@@ -17,15 +17,11 @@ export function createAgentMiddleware(agent: AnyAgent) {
|
|
|
17
17
|
wrapGenerate: async ({ doGenerate, params }) => {
|
|
18
18
|
const id = randomId();
|
|
19
19
|
|
|
20
|
-
params.prompt.forEach((
|
|
20
|
+
params.prompt.forEach((message) => {
|
|
21
21
|
agent.addMessage({
|
|
22
22
|
id,
|
|
23
|
-
...
|
|
23
|
+
...message,
|
|
24
24
|
timestamp: Date.now(),
|
|
25
|
-
correlationId: params.providerMetadata
|
|
26
|
-
?.correlationId as unknown as string,
|
|
27
|
-
parentCorrelationId: params.providerMetadata
|
|
28
|
-
?.parentCorrelationId as unknown as string,
|
|
29
25
|
});
|
|
30
26
|
});
|
|
31
27
|
|
|
@@ -43,10 +39,6 @@ export function createAgentMiddleware(agent: AnyAgent) {
|
|
|
43
39
|
id,
|
|
44
40
|
...message,
|
|
45
41
|
timestamp: Date.now(),
|
|
46
|
-
correlationId: params.providerMetadata
|
|
47
|
-
?.correlationId as unknown as string,
|
|
48
|
-
parentCorrelationId: params.providerMetadata
|
|
49
|
-
?.parentCorrelationId as unknown as string,
|
|
50
42
|
});
|
|
51
43
|
});
|
|
52
44
|
|
|
@@ -85,10 +77,6 @@ export function createAgentMiddleware(agent: AnyAgent) {
|
|
|
85
77
|
role: 'assistant',
|
|
86
78
|
content,
|
|
87
79
|
responseId: id,
|
|
88
|
-
correlationId: params.providerMetadata
|
|
89
|
-
?.correlationId as unknown as string,
|
|
90
|
-
parentCorrelationId: params.providerMetadata
|
|
91
|
-
?.parentCorrelationId as unknown as string,
|
|
92
80
|
});
|
|
93
81
|
},
|
|
94
82
|
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { generateObject } from 'ai';
|
|
2
|
+
import {
|
|
3
|
+
AgentPlan,
|
|
4
|
+
AgentPlanInput,
|
|
5
|
+
AgentPlanner,
|
|
6
|
+
AgentStep,
|
|
7
|
+
AnyAgent,
|
|
8
|
+
CostFunction,
|
|
9
|
+
ObservedState,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { getShortestPaths } from '@xstate/graph';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
14
|
+
import Ajv from 'ajv';
|
|
15
|
+
import { AnyMachineSnapshot } from 'xstate';
|
|
16
|
+
|
|
17
|
+
const ajv = new Ajv();
|
|
18
|
+
|
|
19
|
+
function observedStatesEqual(state1: ObservedState, state2: ObservedState) {
|
|
20
|
+
// check state value && state context
|
|
21
|
+
return (
|
|
22
|
+
JSON.stringify(state1.value) === JSON.stringify(state2.value) &&
|
|
23
|
+
JSON.stringify(state1.context) === JSON.stringify(state2.context)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function trimSteps(steps: AgentStep<any>[], currentState: ObservedState) {
|
|
28
|
+
const index = steps.findIndex(
|
|
29
|
+
(step) => step.state && observedStatesEqual(step.state, currentState)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (index === -1) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return steps.slice(index + 1, steps.length);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function experimental_createShortestPathPlanner<
|
|
40
|
+
T extends AnyAgent
|
|
41
|
+
>(): AgentPlanner<T> {
|
|
42
|
+
return async function shortestPathPlanner<T extends AnyAgent>(
|
|
43
|
+
agent: T,
|
|
44
|
+
input: AgentPlanInput<any>
|
|
45
|
+
): Promise<AgentPlan<any> | undefined> {
|
|
46
|
+
const costFunction: CostFunction<any> =
|
|
47
|
+
input.costFunction ?? ((path) => path.weight ?? Infinity);
|
|
48
|
+
const existingPlan = agent
|
|
49
|
+
.getPlans()
|
|
50
|
+
.find((p) => p.planner === 'shortestPath' && p.goal === input.goal);
|
|
51
|
+
|
|
52
|
+
let paths = existingPlan?.paths;
|
|
53
|
+
|
|
54
|
+
if (existingPlan) {
|
|
55
|
+
console.log('Existing plan found');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!input.machine && !existingPlan) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (input.machine && !existingPlan) {
|
|
63
|
+
const contextSchema = zodToJsonSchema(z.object(agent.context));
|
|
64
|
+
const result = await generateObject({
|
|
65
|
+
model: agent.model,
|
|
66
|
+
system: input.system ?? agent.description,
|
|
67
|
+
prompt: `
|
|
68
|
+
<goal>
|
|
69
|
+
${input.goal}
|
|
70
|
+
</goal>
|
|
71
|
+
<contextSchema>
|
|
72
|
+
${contextSchema}
|
|
73
|
+
</contextSchema>
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
Update the context JSON schema so that it validates the context to determine that it reaches the goal. Return the result as a diff.
|
|
77
|
+
|
|
78
|
+
The contextSchema properties must not change. Do not add or remove properties, or modify the name of the properties.
|
|
79
|
+
Use "const" for exact required values and define ranges/types for flexible conditions.
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
1. For "user is logged in with admin role":
|
|
83
|
+
{
|
|
84
|
+
"contextSchema": "{"type": "object", "properties": {"role": {"const": "admin"}, "lastLogin": {"type": "string"}}, "required": ["role"]}"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
2. For "score is above 100":
|
|
88
|
+
{
|
|
89
|
+
"contextSchema": "{"type": "object", "properties": {"score": {"type": "number", "minimum": 100}}, "required": ["score"]}"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
3. For "fruits contain apple, orange, banana":
|
|
93
|
+
{
|
|
94
|
+
"type": "array",
|
|
95
|
+
"allOf": [
|
|
96
|
+
{ "contains": { "const": "apple" } },
|
|
97
|
+
{ "contains": { "const": "orange" } },
|
|
98
|
+
{ "contains": { "const": "banana" } }
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
`.trim(),
|
|
102
|
+
schema: z.object({
|
|
103
|
+
// valueSchema: z
|
|
104
|
+
// .string()
|
|
105
|
+
// .describe('The JSON Schema representing the goal state value'),
|
|
106
|
+
contextSchema: z
|
|
107
|
+
.object({
|
|
108
|
+
type: z.literal('object'),
|
|
109
|
+
properties: z.object(
|
|
110
|
+
Object.keys((contextSchema as any).properties).reduce(
|
|
111
|
+
(acc, key) => {
|
|
112
|
+
acc[key] = z.any();
|
|
113
|
+
return acc;
|
|
114
|
+
},
|
|
115
|
+
{} as any
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
required: z.array(z.string()).optional(),
|
|
119
|
+
})
|
|
120
|
+
.describe('The JSON Schema representing the goal state context'),
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
console.log(result.object);
|
|
125
|
+
const validateContext = ajv.compile(result.object.contextSchema);
|
|
126
|
+
|
|
127
|
+
const stateFilter = (state: AnyMachineSnapshot) => {
|
|
128
|
+
return validateContext(state.context);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const resolvedState = input.machine.resolveState({
|
|
132
|
+
...input.state,
|
|
133
|
+
context: input.state.context ?? {},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
paths = getShortestPaths(input.machine, {
|
|
137
|
+
fromState: resolvedState,
|
|
138
|
+
toState: stateFilter,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!paths) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const trimmedPaths = paths
|
|
147
|
+
.map((path) => {
|
|
148
|
+
const trimmedSteps = trimSteps(path.steps, input.state);
|
|
149
|
+
if (!trimmedSteps) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
...path,
|
|
154
|
+
steps: trimmedSteps,
|
|
155
|
+
};
|
|
156
|
+
})
|
|
157
|
+
.filter((p): p is NonNullable<typeof p> => p !== undefined);
|
|
158
|
+
|
|
159
|
+
// Sort paths from least weight to most weight
|
|
160
|
+
const sortedPaths = trimmedPaths.sort(
|
|
161
|
+
(a, b) => costFunction(a) - costFunction(b)
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const leastWeightPath = sortedPaths[0];
|
|
165
|
+
const nextStep = leastWeightPath?.steps[0];
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
planner: 'shortestPath',
|
|
169
|
+
episodeId: agent.episodeId,
|
|
170
|
+
goal: input.goal,
|
|
171
|
+
goalState: paths[0]?.state,
|
|
172
|
+
nextEvent: nextStep?.event,
|
|
173
|
+
paths,
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import { CoreMessage,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
ObservedState,
|
|
6
|
-
PromptTemplate,
|
|
7
|
-
TransitionData,
|
|
8
|
-
AnyAgent,
|
|
9
|
-
} from '../types';
|
|
10
|
-
import { getAllTransitions, randomId } from '../utils';
|
|
11
|
-
import { AnyStateMachine, getNextSnapshot } from 'xstate';
|
|
1
|
+
import { CoreMessage, generateText } from 'ai';
|
|
2
|
+
import { AgentPlan, AgentPlanInput, PromptTemplate, AnyAgent } from '../types';
|
|
3
|
+
import { randomId } from '../utils';
|
|
4
|
+
import { getNextSnapshot } from 'xstate';
|
|
12
5
|
import { defaultTextTemplate } from '../templates/defaultText';
|
|
13
6
|
import { getMessages } from '../text';
|
|
14
7
|
import { getToolMap } from '../decide';
|
|
@@ -62,13 +55,14 @@ export async function simplePlanner<T extends AnyAgent>(
|
|
|
62
55
|
|
|
63
56
|
const result = await generateText({
|
|
64
57
|
...rest,
|
|
58
|
+
system: input.system ?? agent.description,
|
|
65
59
|
model,
|
|
66
60
|
messages,
|
|
67
61
|
tools: toolMap as any,
|
|
68
62
|
toolChoice: input.toolChoice ?? 'required',
|
|
69
63
|
});
|
|
70
64
|
|
|
71
|
-
result.
|
|
65
|
+
result.response.messages.forEach((m) => {
|
|
72
66
|
const message: CoreMessage = m;
|
|
73
67
|
|
|
74
68
|
agent.addMessage({
|
package/src/types.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type CostFunction<TEvent extends EventObject> = (
|
|
|
32
32
|
) => number;
|
|
33
33
|
|
|
34
34
|
export type AgentPlanInput<TEvent extends EventObject> = Omit<
|
|
35
|
-
|
|
35
|
+
AgentGenerateTextOptions,
|
|
36
36
|
'prompt' | 'tools'
|
|
37
37
|
> & {
|
|
38
38
|
/**
|
|
@@ -63,6 +63,12 @@ export type AgentPlanInput<TEvent extends EventObject> = Omit<
|
|
|
63
63
|
* The total cost of the path to the goal state.
|
|
64
64
|
*/
|
|
65
65
|
costFunction?: CostFunction<TEvent>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The maximum number of attempts to generate a plan.
|
|
69
|
+
* Defaults to 2.
|
|
70
|
+
*/
|
|
71
|
+
maxAttempts?: number;
|
|
66
72
|
};
|
|
67
73
|
|
|
68
74
|
export type AgentStep<TEvent extends EventObject> = {
|
|
@@ -146,14 +152,19 @@ export type AgentPlanner<T extends AnyAgent> = (
|
|
|
146
152
|
input: AgentPlanInput<T['types']['events']>
|
|
147
153
|
) => Promise<AgentPlan<T['types']['events']> | undefined>;
|
|
148
154
|
|
|
149
|
-
export type AgentDecideOptions = {
|
|
155
|
+
export type AgentDecideOptions<T extends AnyAgent> = {
|
|
150
156
|
goal: string;
|
|
151
157
|
model?: LanguageModel;
|
|
152
158
|
state: ObservedState;
|
|
153
159
|
machine?: AnyStateMachine;
|
|
154
160
|
execute?: (event: AnyEventObject) => Promise<void>;
|
|
155
|
-
planner?: AgentPlanner<
|
|
161
|
+
planner?: AgentPlanner<T>;
|
|
156
162
|
events?: ZodEventMapping;
|
|
163
|
+
/**
|
|
164
|
+
* The maximum number of times the agent will attempt to make a decision.
|
|
165
|
+
* Defaults to 2.
|
|
166
|
+
*/
|
|
167
|
+
maxAttempts?: number;
|
|
157
168
|
} & Omit<Parameters<typeof generateText>[0], 'model' | 'tools' | 'prompt'>;
|
|
158
169
|
|
|
159
170
|
export interface AgentFeedback {
|
|
@@ -162,7 +173,6 @@ export interface AgentFeedback {
|
|
|
162
173
|
/**
|
|
163
174
|
* The message correlation that the feedback is relevant for
|
|
164
175
|
*/
|
|
165
|
-
correlationId?: string;
|
|
166
176
|
attributes: Record<string, any>;
|
|
167
177
|
reward: number;
|
|
168
178
|
timestamp: number;
|
|
@@ -172,7 +182,6 @@ export interface AgentFeedback {
|
|
|
172
182
|
export interface AgentFeedbackInput {
|
|
173
183
|
goal?: string;
|
|
174
184
|
observationId?: string;
|
|
175
|
-
correlationId?: string;
|
|
176
185
|
attributes?: Record<string, any>;
|
|
177
186
|
timestamp?: number;
|
|
178
187
|
reward?: number;
|
|
@@ -313,8 +322,6 @@ export type AgentMessageInput = CoreMessage & {
|
|
|
313
322
|
* which message this message is responding to, if any.
|
|
314
323
|
*/
|
|
315
324
|
responseId?: string;
|
|
316
|
-
correlationId?: string;
|
|
317
|
-
parentCorrelationId?: string;
|
|
318
325
|
result?: GenerateTextResult<any>;
|
|
319
326
|
};
|
|
320
327
|
|
|
@@ -402,7 +409,7 @@ export type ContextFromZodContextMapping<
|
|
|
402
409
|
[K in keyof TContextSchema & string]: TypeOf<TContextSchema[K]>;
|
|
403
410
|
};
|
|
404
411
|
|
|
405
|
-
export type AnyAgent = Agent<any, any>;
|
|
412
|
+
export type AnyAgent = Agent<any, any, any, any>;
|
|
406
413
|
|
|
407
414
|
export type FromAgent<T> = T | ((agent: AnyAgent) => T | Promise<T>);
|
|
408
415
|
|
package/src/utils.ts
CHANGED
|
@@ -103,3 +103,14 @@ export function getTransitions(
|
|
|
103
103
|
});
|
|
104
104
|
return getAllTransitions(resolvedState);
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
export function isMachineActor(
|
|
108
|
+
actor: ActorRefLike
|
|
109
|
+
): actor is typeof actor & { src: AnyStateMachine } {
|
|
110
|
+
return (
|
|
111
|
+
'src' in actor &&
|
|
112
|
+
typeof actor.src === 'object' &&
|
|
113
|
+
actor.src !== null &&
|
|
114
|
+
'definition' in actor.src
|
|
115
|
+
);
|
|
116
|
+
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
3
|
dotenv.config();
|
|
4
4
|
|
|
5
|
-
export default {
|
|
5
|
+
export default defineConfig({
|
|
6
6
|
test: {
|
|
7
7
|
testTimeout: 10000, // Global timeout of 10000ms for all tests
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html'],
|
|
11
|
+
exclude: ['**.test.ts'],
|
|
12
|
+
include: ['src'],
|
|
13
|
+
},
|
|
8
14
|
},
|
|
9
|
-
};
|
|
15
|
+
});
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { generateObject } from 'ai';
|
|
2
|
-
import { getToolMap } from '../decide';
|
|
3
|
-
import {
|
|
4
|
-
AgentPlan,
|
|
5
|
-
AgentPlanInput,
|
|
6
|
-
AgentStep,
|
|
7
|
-
AnyAgent,
|
|
8
|
-
CostFunction,
|
|
9
|
-
ObservedState,
|
|
10
|
-
} from '../types';
|
|
11
|
-
import { getShortestPaths } from '@xstate/graph';
|
|
12
|
-
import { z } from 'zod';
|
|
13
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
14
|
-
import Ajv from 'ajv';
|
|
15
|
-
|
|
16
|
-
const ajv = new Ajv();
|
|
17
|
-
|
|
18
|
-
function observedStatesEqual(state1: ObservedState, state2: ObservedState) {
|
|
19
|
-
// check state value && state context
|
|
20
|
-
return (
|
|
21
|
-
JSON.stringify(state1.value) === JSON.stringify(state2.value) &&
|
|
22
|
-
JSON.stringify(state1.context) === JSON.stringify(state2.context)
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function trimSteps(steps: AgentStep<any>[], currentState: ObservedState) {
|
|
27
|
-
const index = steps.findIndex(
|
|
28
|
-
(step) => step.state && observedStatesEqual(step.state, currentState)
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
if (index === -1) {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return steps.slice(index + 1, steps.length);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function shortestPathPlanner<T extends AnyAgent>(
|
|
39
|
-
agent: T,
|
|
40
|
-
input: AgentPlanInput<any>
|
|
41
|
-
): Promise<AgentPlan<any> | undefined> {
|
|
42
|
-
const costFunction: CostFunction<any> =
|
|
43
|
-
input.costFunction ?? ((path) => path.weight ?? Infinity);
|
|
44
|
-
const existingPlan = agent
|
|
45
|
-
.getPlans()
|
|
46
|
-
.find((p) => p.planner === 'shortestPath' && p.goal === input.goal);
|
|
47
|
-
|
|
48
|
-
let paths = existingPlan?.paths;
|
|
49
|
-
|
|
50
|
-
if (existingPlan) {
|
|
51
|
-
console.log('Existing plan found');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!input.machine && !existingPlan) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (input.machine && !existingPlan) {
|
|
59
|
-
const contextSchema = zodToJsonSchema(z.object(agent.context));
|
|
60
|
-
const result = await generateObject({
|
|
61
|
-
model: agent.model,
|
|
62
|
-
prompt: `
|
|
63
|
-
<goal>
|
|
64
|
-
${input.goal}
|
|
65
|
-
</goal>
|
|
66
|
-
<contextSchema>
|
|
67
|
-
${contextSchema}
|
|
68
|
-
</contextSchema>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Update the context JSON schema so that it validates the context to determine that it reaches the goal. Return the result as a diff.
|
|
72
|
-
|
|
73
|
-
The contextSchema properties must not change. Do not add or remove properties, or modify the name of the properties.
|
|
74
|
-
Use "const" for exact required values and define ranges/types for flexible conditions.
|
|
75
|
-
|
|
76
|
-
Examples:
|
|
77
|
-
1. For "user is logged in with admin role":
|
|
78
|
-
{
|
|
79
|
-
"contextSchema": "{"type": "object", "properties": {"role": {"const": "admin"}, "lastLogin": {"type": "string"}}, "required": ["role"]}"
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
2. For "score is above 100":
|
|
83
|
-
{
|
|
84
|
-
"contextSchema": "{"type": "object", "properties": {"score": {"type": "number", "minimum": 100}}, "required": ["score"]}"
|
|
85
|
-
}
|
|
86
|
-
`.trim(),
|
|
87
|
-
schema: z.object({
|
|
88
|
-
// valueSchema: z
|
|
89
|
-
// .string()
|
|
90
|
-
// .describe('The JSON Schema representing the goal state value'),
|
|
91
|
-
contextSchema: z
|
|
92
|
-
.object({
|
|
93
|
-
type: z.literal('object'),
|
|
94
|
-
properties: z.object(
|
|
95
|
-
Object.keys((contextSchema as any).properties).reduce(
|
|
96
|
-
(acc, key) => {
|
|
97
|
-
acc[key] = z.any();
|
|
98
|
-
return acc;
|
|
99
|
-
},
|
|
100
|
-
{} as any
|
|
101
|
-
)
|
|
102
|
-
),
|
|
103
|
-
required: z.array(z.string()).optional(),
|
|
104
|
-
})
|
|
105
|
-
.describe('The JSON Schema representing the goal state context'),
|
|
106
|
-
}),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
console.log(result.object);
|
|
110
|
-
const validateContext = ajv.compile(result.object.contextSchema);
|
|
111
|
-
|
|
112
|
-
const resolvedState = input.machine.resolveState({
|
|
113
|
-
...input.state,
|
|
114
|
-
context: input.state.context ?? {},
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
paths = getShortestPaths(input.machine, {
|
|
118
|
-
fromState: resolvedState,
|
|
119
|
-
toState: (state) => {
|
|
120
|
-
const v = validateContext(state.context);
|
|
121
|
-
return v;
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!paths) {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const trimmedPaths = paths
|
|
131
|
-
.map((path) => {
|
|
132
|
-
const trimmedSteps = trimSteps(path.steps, input.state);
|
|
133
|
-
if (!trimmedSteps) {
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
...path,
|
|
138
|
-
steps: trimmedSteps,
|
|
139
|
-
};
|
|
140
|
-
})
|
|
141
|
-
.filter((p): p is NonNullable<typeof p> => p !== undefined);
|
|
142
|
-
|
|
143
|
-
// Sort paths from least weight to most weight
|
|
144
|
-
const sortedPaths = trimmedPaths.sort(
|
|
145
|
-
(a, b) => costFunction(a) - costFunction(b)
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const leastWeightPath = sortedPaths[0];
|
|
149
|
-
const nextStep = leastWeightPath?.steps[0];
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
planner: 'shortestPath',
|
|
153
|
-
episodeId: agent.episodeId,
|
|
154
|
-
goal: input.goal,
|
|
155
|
-
goalState: paths[0]?.state,
|
|
156
|
-
nextEvent: nextStep?.event,
|
|
157
|
-
paths,
|
|
158
|
-
timestamp: Date.now(),
|
|
159
|
-
};
|
|
160
|
-
}
|