@statelyai/agent 1.1.6 → 2.0.0-next.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/light-hats-drive.md +9 -0
- package/.changeset/pre.json +10 -0
- package/.vscode/launch.json +6 -0
- package/CHANGELOG.md +10 -0
- package/dist/index.d.mts +262 -165
- package/dist/index.d.ts +262 -165
- package/dist/index.js +368 -263
- package/dist/index.mjs +371 -261
- package/examples/chatbot-alt.ts +57 -0
- package/examples/chatbot.ts +11 -16
- package/examples/cot.ts +25 -22
- package/examples/customer-service-sim.ts +107 -0
- package/examples/email.ts +14 -14
- package/examples/example.ts +5 -5
- package/examples/executor.ts +66 -0
- package/examples/goal.ts +11 -11
- package/examples/helpers/helpers.ts +26 -14
- package/examples/joke.ts +78 -75
- package/examples/jugs.ts +125 -0
- package/examples/multi.ts +4 -4
- package/examples/newspaper.ts +98 -104
- package/examples/number.ts +5 -4
- package/examples/raffle.ts +10 -11
- package/examples/river-crossing.ts +140 -0
- package/examples/sandbox.ts +1 -1
- package/examples/simple.ts +4 -2
- package/examples/summary.ts +121 -0
- package/examples/support.ts +5 -5
- package/examples/ticTacToe.ts +86 -45
- package/examples/todo.ts +6 -6
- package/examples/tutor.ts +13 -13
- package/examples/verify.ts +2 -2
- package/examples/weather.ts +5 -8
- package/examples/wiki.ts +26 -7
- package/examples/word.ts +15 -10
- package/package.json +13 -10
- package/readme.md +1 -1
- package/src/agent-experimental.ts +1 -1
- package/src/agent.test.ts +117 -228
- package/src/agent.ts +469 -81
- package/src/{decision.test.ts → decide.test.ts} +26 -50
- package/src/decide.ts +153 -0
- package/src/index.ts +1 -1
- package/src/middleware.ts +103 -0
- package/src/mockModel.ts +47 -0
- package/src/planners/shortestPathPlanner.ts +151 -13
- package/src/planners/simplePlanner.ts +57 -85
- package/src/strategies/chain-of-note.ts +6 -55
- package/src/text.ts +51 -144
- package/src/types.ts +172 -204
- package/src/utils.ts +37 -4
- package/src/adapters/vercel.ts +0 -7
- package/src/decision.ts +0 -84
- package/src/memory.ts +0 -25
|
@@ -1,61 +1,39 @@
|
|
|
1
1
|
import { test, expect } from 'vitest';
|
|
2
|
-
import { createAgent, fromDecision
|
|
2
|
+
import { createAgent, fromDecision } from '.';
|
|
3
3
|
import { createActor, createMachine, waitFor } from 'xstate';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
5
|
+
import { LanguageModelV1CallOptions } from 'ai';
|
|
6
|
+
import { dummyResponseValues, MockLanguageModelV1 } from './mockModel';
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
const keys =
|
|
9
|
-
|
|
10
|
-
if (keys.length > 1) {
|
|
11
|
-
throw new Error('Expected only 1 choice');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (keys.length === 0) {
|
|
15
|
-
return {
|
|
16
|
-
toolResults: [],
|
|
17
|
-
} as any as GenerateTextResult<any>;
|
|
18
|
-
}
|
|
8
|
+
const doGenerate = async (params: LanguageModelV1CallOptions) => {
|
|
9
|
+
const keys =
|
|
10
|
+
params.mode.type === 'regular' ? params.mode.tools?.map((t) => t.name) : [];
|
|
19
11
|
|
|
20
12
|
return {
|
|
21
|
-
|
|
13
|
+
...dummyResponseValues,
|
|
14
|
+
finishReason: 'tool-calls',
|
|
15
|
+
toolCalls: [
|
|
22
16
|
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
toolCallType: 'function',
|
|
18
|
+
toolCallId: 'call-1',
|
|
19
|
+
toolName: keys![0],
|
|
20
|
+
args: `{ "type": "${keys?.[0]}" }`,
|
|
26
21
|
},
|
|
27
22
|
],
|
|
28
|
-
} as any
|
|
23
|
+
} as any;
|
|
29
24
|
};
|
|
30
25
|
|
|
31
26
|
test('fromDecision() makes a decision', async () => {
|
|
27
|
+
const model = new MockLanguageModelV1({
|
|
28
|
+
doGenerate,
|
|
29
|
+
});
|
|
32
30
|
const agent = createAgent({
|
|
33
31
|
name: 'test',
|
|
34
|
-
model
|
|
32
|
+
model,
|
|
35
33
|
events: {
|
|
36
34
|
doFirst: z.object({}),
|
|
37
35
|
doSecond: z.object({}),
|
|
38
36
|
},
|
|
39
|
-
adapter: {
|
|
40
|
-
generateText: async (arg) => {
|
|
41
|
-
const keys = Object.keys(arg.tools!);
|
|
42
|
-
|
|
43
|
-
if (keys.length !== 1) {
|
|
44
|
-
throw new Error('Expected only 1 choice');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
toolResults: [
|
|
49
|
-
{
|
|
50
|
-
result: {
|
|
51
|
-
type: keys[0],
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
} as any as GenerateTextResult<any>;
|
|
56
|
-
},
|
|
57
|
-
streamText: {} as any,
|
|
58
|
-
},
|
|
59
37
|
});
|
|
60
38
|
|
|
61
39
|
const machine = createMachine({
|
|
@@ -91,17 +69,16 @@ test('fromDecision() makes a decision', async () => {
|
|
|
91
69
|
});
|
|
92
70
|
|
|
93
71
|
test('interacts with an actor', async () => {
|
|
72
|
+
const model = new MockLanguageModelV1({
|
|
73
|
+
doGenerate,
|
|
74
|
+
});
|
|
94
75
|
const agent = createAgent({
|
|
95
76
|
name: 'test',
|
|
96
|
-
model
|
|
77
|
+
model,
|
|
97
78
|
events: {
|
|
98
79
|
doFirst: z.object({}),
|
|
99
80
|
doSecond: z.object({}),
|
|
100
81
|
},
|
|
101
|
-
adapter: {
|
|
102
|
-
generateText: mockToolDecision,
|
|
103
|
-
streamText: {} as any,
|
|
104
|
-
},
|
|
105
82
|
});
|
|
106
83
|
|
|
107
84
|
const machine = createMachine({
|
|
@@ -135,17 +112,16 @@ test('interacts with an actor', async () => {
|
|
|
135
112
|
});
|
|
136
113
|
|
|
137
114
|
test('interacts with an actor (late interaction)', async () => {
|
|
115
|
+
const model = new MockLanguageModelV1({
|
|
116
|
+
doGenerate,
|
|
117
|
+
});
|
|
138
118
|
const agent = createAgent({
|
|
139
119
|
name: 'test',
|
|
140
|
-
model
|
|
120
|
+
model,
|
|
141
121
|
events: {
|
|
142
122
|
doFirst: z.object({}),
|
|
143
123
|
doSecond: z.object({}),
|
|
144
124
|
},
|
|
145
|
-
adapter: {
|
|
146
|
-
generateText: mockToolDecision,
|
|
147
|
-
streamText: {} as any,
|
|
148
|
-
},
|
|
149
125
|
});
|
|
150
126
|
|
|
151
127
|
const machine = createMachine({
|
package/src/decide.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
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/simplePlanner';
|
|
14
|
+
import { getTransitions } from './utils';
|
|
15
|
+
import { CoreTool, tool } from 'ai';
|
|
16
|
+
|
|
17
|
+
export async function agentDecide<T extends AnyAgent>(
|
|
18
|
+
agent: T,
|
|
19
|
+
options: AgentDecideOptions
|
|
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
|
+
...otherPlanInput
|
|
33
|
+
} = resolvedOptions;
|
|
34
|
+
|
|
35
|
+
const plan = await planner(agent, {
|
|
36
|
+
model,
|
|
37
|
+
goal,
|
|
38
|
+
events,
|
|
39
|
+
state,
|
|
40
|
+
machine,
|
|
41
|
+
...otherPlanInput,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (plan?.nextEvent) {
|
|
45
|
+
agent.addPlan(plan);
|
|
46
|
+
await resolvedOptions.execute?.(plan.nextEvent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return plan;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function fromDecision(
|
|
53
|
+
agent: AnyAgent,
|
|
54
|
+
defaultInput?: AgentDecisionInput
|
|
55
|
+
): AgentDecisionLogic<any> {
|
|
56
|
+
return fromPromise(async ({ input, self }) => {
|
|
57
|
+
const parentRef = self._parent;
|
|
58
|
+
if (!parentRef) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const snapshot = parentRef.getSnapshot() as AnyMachineSnapshot;
|
|
63
|
+
const inputObject = typeof input === 'string' ? { goal: input } : input;
|
|
64
|
+
const resolvedInput = {
|
|
65
|
+
...defaultInput,
|
|
66
|
+
...inputObject,
|
|
67
|
+
};
|
|
68
|
+
const state = {
|
|
69
|
+
value: snapshot.value,
|
|
70
|
+
context: resolvedInput.context,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const plan = await agentDecide(agent, {
|
|
74
|
+
machine: (parentRef as AnyActor).logic,
|
|
75
|
+
state,
|
|
76
|
+
execute: async (event) => {
|
|
77
|
+
parentRef.send(event);
|
|
78
|
+
},
|
|
79
|
+
...resolvedInput,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return plan;
|
|
83
|
+
}) as AgentDecisionLogic<any>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getToolMap<T extends AnyAgent>(
|
|
87
|
+
_agent: T,
|
|
88
|
+
input: AgentPlanInput<any>
|
|
89
|
+
): Record<string, CoreTool<any, any>> | undefined {
|
|
90
|
+
// Get all of the possible next transitions
|
|
91
|
+
const transitions: TransitionData[] = input.machine
|
|
92
|
+
? getTransitions(input.state, input.machine)
|
|
93
|
+
: Object.entries(input.events).map(([eventType, { description }]) => ({
|
|
94
|
+
eventType,
|
|
95
|
+
description,
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// Only keep the transitions that match the event types that are in the event mapping
|
|
99
|
+
// TODO: allow for custom filters
|
|
100
|
+
const filter = (eventType: string) =>
|
|
101
|
+
Object.keys(input.events).includes(eventType);
|
|
102
|
+
|
|
103
|
+
// Mapping of each event type (e.g. "mouse.click")
|
|
104
|
+
// to a valid function name (e.g. "mouse_click")
|
|
105
|
+
const functionNameMapping: Record<string, string> = {};
|
|
106
|
+
|
|
107
|
+
const toolTransitions = transitions
|
|
108
|
+
.filter((t) => {
|
|
109
|
+
return filter(t.eventType);
|
|
110
|
+
})
|
|
111
|
+
.map((t) => {
|
|
112
|
+
const name = t.eventType.replace(/\./g, '_');
|
|
113
|
+
functionNameMapping[name] = t.eventType;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
type: 'function',
|
|
117
|
+
eventType: t.eventType,
|
|
118
|
+
description: t.description,
|
|
119
|
+
name,
|
|
120
|
+
} as const;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Convert the transition data to a tool map that the
|
|
124
|
+
// Vercel AI SDK can use
|
|
125
|
+
const toolMap: Record<string, CoreTool<any, any>> = {};
|
|
126
|
+
for (const toolTransitionData of toolTransitions) {
|
|
127
|
+
const toolZodType = input.events?.[toolTransitionData.eventType];
|
|
128
|
+
|
|
129
|
+
if (!toolZodType) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
toolMap[toolTransitionData.name] = tool({
|
|
134
|
+
description: toolZodType?.description ?? toolTransitionData.description,
|
|
135
|
+
parameters: toolZodType,
|
|
136
|
+
execute: async (params: Record<string, any>) => {
|
|
137
|
+
const event = {
|
|
138
|
+
type: toolTransitionData.eventType,
|
|
139
|
+
...params,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return event;
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!Object.keys(toolMap).length) {
|
|
148
|
+
// No valid transitions for the specified tools
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return toolMap;
|
|
153
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
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((p) => {
|
|
21
|
+
agent.addMessage({
|
|
22
|
+
id,
|
|
23
|
+
...p,
|
|
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
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const result = await doGenerate();
|
|
33
|
+
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
wrapStream: async ({ doStream, params }) => {
|
|
38
|
+
const id = randomId();
|
|
39
|
+
|
|
40
|
+
params.prompt.forEach((message) => {
|
|
41
|
+
message.content;
|
|
42
|
+
agent.addMessage({
|
|
43
|
+
id,
|
|
44
|
+
...message,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
correlationId: params.providerMetadata
|
|
47
|
+
?.correlationId as unknown as string,
|
|
48
|
+
parentCorrelationId: params.providerMetadata
|
|
49
|
+
?.parentCorrelationId as unknown as string,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const { stream, ...rest } = await doStream();
|
|
54
|
+
|
|
55
|
+
let generatedText = '';
|
|
56
|
+
|
|
57
|
+
const transformStream = new TransformStream<
|
|
58
|
+
LanguageModelV1StreamPart,
|
|
59
|
+
LanguageModelV1StreamPart
|
|
60
|
+
>({
|
|
61
|
+
transform(chunk, controller) {
|
|
62
|
+
if (chunk.type === 'text-delta') {
|
|
63
|
+
generatedText += chunk.textDelta;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
controller.enqueue(chunk);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
flush() {
|
|
70
|
+
const content: (
|
|
71
|
+
| LanguageModelV1TextPart
|
|
72
|
+
| LanguageModelV1ToolCallPart
|
|
73
|
+
)[] = [];
|
|
74
|
+
|
|
75
|
+
if (generatedText) {
|
|
76
|
+
content.push({
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: generatedText,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
agent.addMessage({
|
|
83
|
+
id: randomId(),
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
role: 'assistant',
|
|
86
|
+
content,
|
|
87
|
+
responseId: id,
|
|
88
|
+
correlationId: params.providerMetadata
|
|
89
|
+
?.correlationId as unknown as string,
|
|
90
|
+
parentCorrelationId: params.providerMetadata
|
|
91
|
+
?.parentCorrelationId as unknown as string,
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
stream: stream.pipeThrough(transformStream),
|
|
98
|
+
...rest,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
return middleware;
|
|
103
|
+
}
|
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
|
+
};
|
|
@@ -1,22 +1,160 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
2
11
|
import { getShortestPaths } from '@xstate/graph';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
14
|
+
import Ajv from 'ajv';
|
|
3
15
|
|
|
4
|
-
|
|
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>(
|
|
5
39
|
agent: T,
|
|
6
40
|
input: AgentPlanInput<any>
|
|
7
41
|
): Promise<AgentPlan<any> | undefined> {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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);
|
|
11
142
|
|
|
12
|
-
//
|
|
13
|
-
|
|
143
|
+
// Sort paths from least weight to most weight
|
|
144
|
+
const sortedPaths = trimmedPaths.sort(
|
|
145
|
+
(a, b) => costFunction(a) - costFunction(b)
|
|
146
|
+
);
|
|
14
147
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// possible events
|
|
18
|
-
void 0;
|
|
148
|
+
const leastWeightPath = sortedPaths[0];
|
|
149
|
+
const nextStep = leastWeightPath?.steps[0];
|
|
19
150
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
};
|
|
22
160
|
}
|