@statelyai/agent 2.0.0-next.1 → 2.0.0-next.3
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/grumpy-dolphins-think.md +17 -0
- package/.changeset/old-teachers-tap.md +5 -0
- package/.changeset/pink-eagles-deliver.md +13 -0
- package/.changeset/pre.json +9 -1
- package/.changeset/quiet-turtles-do.md +7 -0
- package/.changeset/smart-yaks-pull.md +23 -0
- package/.changeset/sweet-clouds-mix.md +16 -0
- package/.changeset/swift-mangos-rush.md +5 -0
- package/.changeset/tough-ways-rhyme.md +5 -0
- package/CHANGELOG.md +79 -0
- package/dist/index.d.mts +116 -100
- package/dist/index.d.ts +116 -100
- package/dist/index.js +102 -72
- package/dist/index.mjs +105 -75
- package/examples/chatbot.ts +2 -2
- package/examples/cot.ts +21 -73
- package/examples/customer-service-sim.ts +3 -3
- package/examples/email.ts +3 -5
- package/examples/example.ts +2 -2
- package/examples/goal.ts +2 -2
- package/examples/joke.ts +12 -12
- package/examples/jugs.ts +4 -7
- package/examples/learn-from-feedback.ts +123 -0
- package/examples/number.ts +2 -2
- package/examples/raffle.ts +2 -2
- package/examples/river-crossing.ts +4 -7
- package/examples/simple.ts +13 -10
- package/examples/summary.ts +2 -5
- package/examples/support.ts +38 -38
- package/examples/ticTacToe.ts +46 -4
- package/examples/todo.ts +3 -3
- package/examples/tutor.ts +2 -2
- package/examples/verify.ts +2 -2
- package/examples/weather-agent.ts +139 -0
- package/examples/weather.ts +26 -23
- package/examples/word.ts +8 -6
- package/package.json +2 -1
- package/src/agent.test.ts +37 -52
- package/src/agent.ts +93 -60
- package/src/decide.test.ts +56 -8
- package/src/decide.ts +42 -32
- package/src/strategies/chainOfThought.ts +50 -0
- package/src/{planners → strategies}/shortestPath.test.ts +4 -7
- package/src/strategies/shortestPath.ts +178 -0
- package/src/{planners → strategies}/simple.ts +25 -26
- package/src/templates/defaultText.ts +3 -0
- package/src/text.ts +13 -13
- package/src/types.ts +124 -83
- package/src/utils.ts +13 -1
- package/src/planners/shortestPath.ts +0 -177
- package/src/strategies/chain-of-note.ts +0 -106
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createAgent } from '
|
|
1
|
+
import { createAgent, TypesFromAgent } from '..';
|
|
2
2
|
import { assign, createActor, setup } from 'xstate';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import {
|
|
4
|
+
import { experimental_shortestPathStrategy } from './shortestPath';
|
|
5
5
|
import { test, expect } from 'vitest';
|
|
6
6
|
import { dummyResponseValues, MockLanguageModelV1 } from '../mockModel';
|
|
7
7
|
|
|
@@ -35,10 +35,7 @@ test.skip('should find shortest path to goal', async () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
const counterMachine = setup({
|
|
38
|
-
types: {
|
|
39
|
-
context: agent.types.context,
|
|
40
|
-
events: agent.types.events,
|
|
41
|
-
},
|
|
38
|
+
types: {} as TypesFromAgent<typeof agent>,
|
|
42
39
|
}).createMachine({
|
|
43
40
|
initial: 'counting',
|
|
44
41
|
context: { count: 0 },
|
|
@@ -87,7 +84,7 @@ test.skip('should find shortest path to goal', async () => {
|
|
|
87
84
|
}),
|
|
88
85
|
goal: 'Get the counter to exactly 3',
|
|
89
86
|
state: counterActor.getSnapshot(),
|
|
90
|
-
|
|
87
|
+
strategy: experimental_shortestPathStrategy,
|
|
91
88
|
});
|
|
92
89
|
|
|
93
90
|
expect(decision?.nextEvent?.type).toBe('increment');
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { generateObject } from 'ai';
|
|
2
|
+
import {
|
|
3
|
+
AgentDecision,
|
|
4
|
+
AgentDecideInput,
|
|
5
|
+
AgentStrategy,
|
|
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
|
+
import { randomId } from '../utils';
|
|
17
|
+
|
|
18
|
+
const ajv = new Ajv();
|
|
19
|
+
|
|
20
|
+
function observedStatesEqual(
|
|
21
|
+
state1: ObservedState<any>,
|
|
22
|
+
state2: ObservedState<any>
|
|
23
|
+
) {
|
|
24
|
+
// check state value && state context
|
|
25
|
+
return (
|
|
26
|
+
JSON.stringify(state1.value) === JSON.stringify(state2.value) &&
|
|
27
|
+
JSON.stringify(state1.context) === JSON.stringify(state2.context)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function trimSteps(steps: AgentStep<any>[], currentState: ObservedState<any>) {
|
|
32
|
+
const index = steps.findIndex(
|
|
33
|
+
(step) => step.state && observedStatesEqual(step.state, currentState)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (index === -1) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return steps.slice(index + 1, steps.length);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function experimental_shortestPathStrategy<T extends AnyAgent>(
|
|
44
|
+
agent: T,
|
|
45
|
+
input: AgentDecideInput<any>
|
|
46
|
+
): Promise<AgentDecision<any> | undefined> {
|
|
47
|
+
const costFunction: CostFunction<any> =
|
|
48
|
+
input.costFunction ?? ((path) => path.weight ?? Infinity);
|
|
49
|
+
const existingDecision = agent
|
|
50
|
+
.getDecisions()
|
|
51
|
+
.find((p) => p.strategy === 'shortestPath' && p.goal === input.goal);
|
|
52
|
+
|
|
53
|
+
let paths = existingDecision?.paths;
|
|
54
|
+
|
|
55
|
+
if (existingDecision) {
|
|
56
|
+
console.log('Existing decision found');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!input.machine && !existingDecision) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (input.machine && !existingDecision) {
|
|
64
|
+
const contextSchema = zodToJsonSchema(z.object(agent.context));
|
|
65
|
+
const result = await generateObject({
|
|
66
|
+
model: agent.model,
|
|
67
|
+
system: input.system ?? agent.description,
|
|
68
|
+
prompt: `
|
|
69
|
+
<goal>
|
|
70
|
+
${input.goal}
|
|
71
|
+
</goal>
|
|
72
|
+
<contextSchema>
|
|
73
|
+
${contextSchema}
|
|
74
|
+
</contextSchema>
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
Update the context JSON schema so that it validates the context to determine that it reaches the goal. Return the result as a diff.
|
|
78
|
+
|
|
79
|
+
The contextSchema properties must not change. Do not add or remove properties, or modify the name of the properties.
|
|
80
|
+
Use "const" for exact required values and define ranges/types for flexible conditions.
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
1. For "user is logged in with admin role":
|
|
84
|
+
{
|
|
85
|
+
"contextSchema": "{"type": "object", "properties": {"role": {"const": "admin"}, "lastLogin": {"type": "string"}}, "required": ["role"]}"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
2. For "score is above 100":
|
|
89
|
+
{
|
|
90
|
+
"contextSchema": "{"type": "object", "properties": {"score": {"type": "number", "minimum": 100}}, "required": ["score"]}"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
3. For "fruits contain apple, orange, banana":
|
|
94
|
+
{
|
|
95
|
+
"type": "array",
|
|
96
|
+
"allOf": [
|
|
97
|
+
{ "contains": { "const": "apple" } },
|
|
98
|
+
{ "contains": { "const": "orange" } },
|
|
99
|
+
{ "contains": { "const": "banana" } }
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
`.trim(),
|
|
103
|
+
schema: z.object({
|
|
104
|
+
// valueSchema: z
|
|
105
|
+
// .string()
|
|
106
|
+
// .describe('The JSON Schema representing the goal state value'),
|
|
107
|
+
contextSchema: z
|
|
108
|
+
.object({
|
|
109
|
+
type: z.literal('object'),
|
|
110
|
+
properties: z.object(
|
|
111
|
+
Object.keys((contextSchema as any).properties).reduce(
|
|
112
|
+
(acc, key) => {
|
|
113
|
+
acc[key] = z.any();
|
|
114
|
+
return acc;
|
|
115
|
+
},
|
|
116
|
+
{} as any
|
|
117
|
+
)
|
|
118
|
+
),
|
|
119
|
+
required: z.array(z.string()).optional(),
|
|
120
|
+
})
|
|
121
|
+
.describe('The JSON Schema representing the goal state context'),
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log(result.object);
|
|
126
|
+
const validateContext = ajv.compile(result.object.contextSchema);
|
|
127
|
+
|
|
128
|
+
const stateFilter = (state: AnyMachineSnapshot) => {
|
|
129
|
+
return validateContext(state.context);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const resolvedState = input.machine.resolveState({
|
|
133
|
+
...input.state,
|
|
134
|
+
context: input.state.context ?? {},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
paths = getShortestPaths(input.machine, {
|
|
138
|
+
fromState: resolvedState,
|
|
139
|
+
toState: stateFilter,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!paths) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const trimmedPaths = paths
|
|
148
|
+
.map((path) => {
|
|
149
|
+
const trimmedSteps = trimSteps(path.steps, input.state);
|
|
150
|
+
if (!trimmedSteps) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...path,
|
|
155
|
+
steps: trimmedSteps,
|
|
156
|
+
};
|
|
157
|
+
})
|
|
158
|
+
.filter((p): p is NonNullable<typeof p> => p !== undefined);
|
|
159
|
+
|
|
160
|
+
// Sort paths from least weight to most weight
|
|
161
|
+
const sortedPaths = trimmedPaths.sort(
|
|
162
|
+
(a, b) => costFunction(a) - costFunction(b)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const leastWeightPath = sortedPaths[0];
|
|
166
|
+
const nextStep = leastWeightPath?.steps[0];
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
id: randomId(),
|
|
170
|
+
strategy: 'shortestPath',
|
|
171
|
+
episodeId: agent.episodeId,
|
|
172
|
+
goal: input.goal,
|
|
173
|
+
goalState: paths[0]?.state,
|
|
174
|
+
nextEvent: nextStep?.event,
|
|
175
|
+
paths,
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { CoreMessage, generateText } from 'ai';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
AgentDecision,
|
|
4
|
+
AgentDecideInput,
|
|
5
|
+
PromptTemplate,
|
|
6
|
+
AnyAgent,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import { convertToXml, randomId } from '../utils';
|
|
4
9
|
import { getNextSnapshot } from 'xstate';
|
|
5
|
-
import { defaultTextTemplate } from '../templates/defaultText';
|
|
6
10
|
import { getMessages } from '../text';
|
|
7
11
|
import { getToolMap } from '../decide';
|
|
8
12
|
|
|
9
|
-
const
|
|
13
|
+
const simpleStrategyPromptTemplate: PromptTemplate<any> = (data) => {
|
|
10
14
|
return `
|
|
11
|
-
${
|
|
15
|
+
${convertToXml(data)}
|
|
12
16
|
|
|
13
17
|
Make at most one tool call to achieve the above goal. If the goal cannot be achieved with any tool calls, do not make any tool call.
|
|
14
18
|
`.trim();
|
|
15
19
|
};
|
|
16
20
|
|
|
17
|
-
export async function
|
|
21
|
+
export async function simpleStrategy<T extends AnyAgent>(
|
|
18
22
|
agent: T,
|
|
19
|
-
input:
|
|
20
|
-
): Promise<
|
|
23
|
+
input: AgentDecideInput<any>
|
|
24
|
+
): Promise<AgentDecision<any> | undefined> {
|
|
21
25
|
const toolMap = getToolMap(agent, input);
|
|
22
26
|
|
|
23
27
|
if (!toolMap) {
|
|
@@ -27,8 +31,9 @@ export async function simplePlanner<T extends AnyAgent>(
|
|
|
27
31
|
|
|
28
32
|
// Create a prompt with the given context and goal.
|
|
29
33
|
// The template is used to ensure that a single tool call at most is made.
|
|
30
|
-
const prompt =
|
|
31
|
-
|
|
34
|
+
const prompt = simpleStrategyPromptTemplate({
|
|
35
|
+
stateValue: input.state.value,
|
|
36
|
+
context: input.context ?? input.state.context,
|
|
32
37
|
goal: input.goal,
|
|
33
38
|
});
|
|
34
39
|
|
|
@@ -36,22 +41,15 @@ export async function simplePlanner<T extends AnyAgent>(
|
|
|
36
41
|
|
|
37
42
|
const model = input.model ? agent.wrap(input.model) : agent.model;
|
|
38
43
|
|
|
39
|
-
const {
|
|
40
|
-
state,
|
|
41
|
-
machine,
|
|
42
|
-
previousPlan,
|
|
43
|
-
events,
|
|
44
|
-
goal,
|
|
45
|
-
model: _,
|
|
46
|
-
...rest
|
|
47
|
-
} = input;
|
|
44
|
+
const { state, machine, events, goal, model: _, ...rest } = input;
|
|
48
45
|
|
|
49
|
-
const machineState =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
const machineState =
|
|
47
|
+
input.machine && input.state
|
|
48
|
+
? input.machine.resolveState({
|
|
49
|
+
...input.state,
|
|
50
|
+
context: input.state.context ?? {},
|
|
51
|
+
})
|
|
52
|
+
: undefined;
|
|
55
53
|
|
|
56
54
|
const result = await generateText({
|
|
57
55
|
...rest,
|
|
@@ -81,7 +79,8 @@ export async function simplePlanner<T extends AnyAgent>(
|
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
return {
|
|
84
|
-
|
|
82
|
+
id: randomId(),
|
|
83
|
+
strategy: 'simple',
|
|
85
84
|
goal: input.goal,
|
|
86
85
|
goalState: input.state,
|
|
87
86
|
nextEvent: singleResult.result,
|
|
@@ -3,6 +3,9 @@ import { wrapInXml } from '../utils';
|
|
|
3
3
|
|
|
4
4
|
export const defaultTextTemplate: PromptTemplate<any> = (data) => {
|
|
5
5
|
const preamble = [
|
|
6
|
+
data.stateValue
|
|
7
|
+
? wrapInXml('stateValue', JSON.stringify(data.stateValue))
|
|
8
|
+
: undefined,
|
|
6
9
|
data.context
|
|
7
10
|
? wrapInXml('context', JSON.stringify(data.context))
|
|
8
11
|
: undefined,
|
package/src/text.ts
CHANGED
|
@@ -28,10 +28,10 @@ import {
|
|
|
28
28
|
* @param options
|
|
29
29
|
* @returns
|
|
30
30
|
*/
|
|
31
|
-
export async function getMessages(
|
|
32
|
-
agent:
|
|
31
|
+
export async function getMessages<TAgent extends AnyAgent>(
|
|
32
|
+
agent: TAgent,
|
|
33
33
|
prompt: string,
|
|
34
|
-
options: Omit<AgentGenerateTextOptions
|
|
34
|
+
options: Omit<AgentGenerateTextOptions<TAgent>, 'prompt'>
|
|
35
35
|
): Promise<CoreMessage[]> {
|
|
36
36
|
let messages: CoreMessage[] = [];
|
|
37
37
|
if (typeof options.messages === 'function') {
|
|
@@ -48,13 +48,13 @@ export async function getMessages(
|
|
|
48
48
|
return messages;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function fromTextStream<
|
|
52
|
-
agent:
|
|
53
|
-
options?: AgentStreamTextOptions
|
|
51
|
+
export function fromTextStream<TAgent extends AnyAgent>(
|
|
52
|
+
agent: TAgent,
|
|
53
|
+
options?: AgentStreamTextOptions<TAgent>
|
|
54
54
|
): ObservableActorLogic<
|
|
55
55
|
{ textDelta: string },
|
|
56
|
-
Omit<AgentStreamTextOptions
|
|
57
|
-
context?:
|
|
56
|
+
Omit<AgentStreamTextOptions<TAgent>, 'context'> & {
|
|
57
|
+
context?: Record<string, any>;
|
|
58
58
|
}
|
|
59
59
|
> {
|
|
60
60
|
const template = options?.template ?? defaultTextTemplate;
|
|
@@ -106,13 +106,13 @@ export function fromTextStream<T extends AnyAgent>(
|
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
export function fromText<
|
|
110
|
-
agent:
|
|
111
|
-
options?: AgentGenerateTextOptions
|
|
109
|
+
export function fromText<TAgent extends AnyAgent>(
|
|
110
|
+
agent: TAgent,
|
|
111
|
+
options?: AgentGenerateTextOptions<TAgent>
|
|
112
112
|
): PromiseActorLogic<
|
|
113
113
|
GenerateTextResult<Record<string, CoreTool<any, any>>>,
|
|
114
|
-
Omit<AgentGenerateTextOptions
|
|
115
|
-
context?:
|
|
114
|
+
Omit<AgentGenerateTextOptions<TAgent>, 'context'> & {
|
|
115
|
+
context?: Record<string, any>;
|
|
116
116
|
}
|
|
117
117
|
> {
|
|
118
118
|
const resolvedOptions = {
|