@statelyai/agent 0.0.4 → 0.0.6
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/.vscode/launch.json +7 -7
- package/CHANGELOG.md +38 -0
- package/dist/index.d.ts +45 -32
- package/dist/index.js +51 -18
- package/examples/joke.ts +12 -8
- package/examples/numberGuesser.ts +128 -0
- package/examples/ticTacToe.ts +31 -27
- package/examples/weather.ts +1 -1
- package/package.json +5 -4
- package/readme.md +5 -4
- package/src/adapter.test.ts +217 -0
- package/src/adapters/openai.ts +101 -63
- package/src/schemas.ts +13 -11
- package/src/types.ts +61 -0
- package/src/utils.ts +4 -13
- package/vitest.config.ts +9 -0
package/.vscode/launch.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
// Use IntelliSense to learn about possible attributes.
|
|
3
|
-
// Hover to view descriptions of existing attributes.
|
|
4
2
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
3
|
"version": "0.2.0",
|
|
6
4
|
"configurations": [
|
|
7
5
|
{
|
|
8
6
|
"type": "node",
|
|
9
7
|
"request": "launch",
|
|
10
|
-
"name": "
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
8
|
+
"name": "Debug Current Test File",
|
|
9
|
+
"autoAttachChildProcesses": true,
|
|
10
|
+
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
|
|
11
|
+
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
|
|
12
|
+
"args": ["run", "${relativeFile}"],
|
|
13
|
+
"smartStep": true,
|
|
14
|
+
"console": "integratedTerminal"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @statelyai/agent
|
|
2
2
|
|
|
3
|
+
## 0.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#16](https://github.com/statelyai/agent/pull/16) [`3ba5fb2`](https://github.com/statelyai/agent/commit/3ba5fb2392b51dee71f2585ed662b4ee9ecd6c41) Thanks [@davidkpiano](https://github.com/davidkpiano)! - Update to XState 5.8.0
|
|
8
|
+
|
|
9
|
+
## 0.0.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#9](https://github.com/statelyai/agent/pull/9) [`d8e7b67`](https://github.com/statelyai/agent/commit/d8e7b673f6d265f37b2096b25d75310845860271) Thanks [@davidkpiano](https://github.com/davidkpiano)! - Add `adapter.fromTool(…)`, which creates an actor that chooses agent logic based on a input.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const actor = adapter.fromTool(() => "Draw me a picture of a donut", {
|
|
17
|
+
// tools
|
|
18
|
+
makeIllustration: {
|
|
19
|
+
description: "Makes an illustration",
|
|
20
|
+
run: async (input) => {
|
|
21
|
+
/* ... */
|
|
22
|
+
},
|
|
23
|
+
inputSchema: {
|
|
24
|
+
/* ... */
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
getWeather: {
|
|
28
|
+
description: "Gets the weather",
|
|
29
|
+
run: async (input) => {
|
|
30
|
+
/* ... */
|
|
31
|
+
},
|
|
32
|
+
inputSchema: {
|
|
33
|
+
/* ... */
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
//...
|
|
39
|
+
```
|
|
40
|
+
|
|
3
41
|
## 0.0.4
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { Prop, Values, AnyStateMachine, createActor, PromiseActorLogic, AnyEvent
|
|
|
3
3
|
import { JSONSchema7 } from 'json-schema-to-ts/lib/types/definitions';
|
|
4
4
|
import { FromSchema } from 'json-schema-to-ts';
|
|
5
5
|
import OpenAI from 'openai';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ChatCompletionCreateParamsBase } from 'openai/resources/chat/completions';
|
|
7
|
+
import { ChatCompletionCreateParamsNonStreaming, ChatCompletionCreateParamsStreaming } from 'openai/resources';
|
|
8
8
|
|
|
9
9
|
type EventSchemas = {
|
|
10
10
|
[key: string]: {
|
|
@@ -14,67 +14,80 @@ type EventSchemas = {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
17
|
+
type ContextSchema = JSONSchema7 & {
|
|
18
|
+
type: 'object';
|
|
19
|
+
};
|
|
20
20
|
type ConvertToJSONSchemas<T> = {
|
|
21
21
|
[K in keyof T]: {
|
|
22
22
|
properties: {
|
|
23
23
|
type: {
|
|
24
24
|
const: K;
|
|
25
25
|
};
|
|
26
|
-
}
|
|
26
|
+
} & Prop<T[K], 'properties'>;
|
|
27
27
|
type: 'object';
|
|
28
|
-
required: Array<keyof Prop<T[K], 'properties'> | 'type'>;
|
|
28
|
+
required: Array<(keyof Prop<T[K], 'properties'> & string) | 'type'>;
|
|
29
29
|
additionalProperties: false;
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
} & {};
|
|
32
|
-
type ConvertContextToJSONSchema<T extends ContextSchema> = {
|
|
33
|
-
type: 'object';
|
|
34
|
-
properties: T;
|
|
35
|
-
readonly required: Array<keyof T & string>;
|
|
36
|
-
additionalProperties: false;
|
|
37
|
-
};
|
|
38
32
|
|
|
39
|
-
declare function createSchemas<TContextSchema extends ContextSchema, TEventSchemas extends EventSchemas>({ context, events, }: {
|
|
33
|
+
declare function createSchemas<const TContextSchema extends ContextSchema, const TEventSchemas extends EventSchemas>({ context, events, }: {
|
|
34
|
+
/**
|
|
35
|
+
* The JSON schema for the context object.
|
|
36
|
+
*
|
|
37
|
+
* Must be of `{ type: 'object' }`.
|
|
38
|
+
*/
|
|
40
39
|
context: TContextSchema;
|
|
40
|
+
/**
|
|
41
|
+
* An object mapping event types to each event object's JSON Schema.
|
|
42
|
+
*/
|
|
41
43
|
events: TEventSchemas;
|
|
42
44
|
}): {
|
|
43
|
-
context:
|
|
45
|
+
context: TContextSchema;
|
|
44
46
|
events: ConvertToJSONSchemas<TEventSchemas>;
|
|
45
47
|
types: {
|
|
46
|
-
context: FromSchema<
|
|
48
|
+
context: FromSchema<TContextSchema>;
|
|
47
49
|
events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
|
|
48
50
|
};
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
declare function createAgent<T extends AnyStateMachine>(...args: Parameters<typeof createActor<T>>): xstate.Actor<T>;
|
|
52
54
|
|
|
53
|
-
interface
|
|
54
|
-
model:
|
|
55
|
-
}> {
|
|
56
|
-
model: T['model'];
|
|
55
|
+
interface StatelyAgentAdapter {
|
|
56
|
+
model: string;
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Creates actor logic that chooses an event from all of the
|
|
59
|
+
* possible next events of the parent state machine
|
|
60
|
+
* and sends it to the parent actor.
|
|
59
61
|
*/
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Immediately execute sending the event to the parent actor.
|
|
63
|
-
* @default true
|
|
64
|
-
*/
|
|
65
|
-
execute?: boolean;
|
|
66
|
-
}) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
62
|
+
fromEvent: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
67
63
|
/**
|
|
68
|
-
* Creates
|
|
64
|
+
* Creates actor logic that resolves with a chat completion.
|
|
69
65
|
*/
|
|
70
66
|
fromChat: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
71
67
|
/**
|
|
72
|
-
* Creates
|
|
68
|
+
* Creates actor logic that emits a chat completion stream.
|
|
73
69
|
*/
|
|
74
70
|
fromChatStream: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming) => ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
|
|
71
|
+
/**
|
|
72
|
+
* Creates actor logic that chooses a tool from the provided
|
|
73
|
+
* tools and runs that tool.
|
|
74
|
+
*/
|
|
75
|
+
fromTool: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, tools: {
|
|
76
|
+
[key: string]: Tool<any, any>;
|
|
77
|
+
}) => PromiseActorLogic<{
|
|
78
|
+
result: any;
|
|
79
|
+
tool: string;
|
|
80
|
+
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
|
|
81
|
+
} | undefined, TInput>;
|
|
75
82
|
}
|
|
83
|
+
interface Tool<TInput, TOutput> {
|
|
84
|
+
description: string;
|
|
85
|
+
inputSchema: any;
|
|
86
|
+
run: (input: TInput) => TOutput;
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
declare function createOpenAIAdapter<T extends {
|
|
77
90
|
model: ChatCompletionCreateParamsBase['model'];
|
|
78
|
-
}>(openai: OpenAI, settings: T):
|
|
91
|
+
}>(openai: OpenAI, settings: T): StatelyAgentAdapter;
|
|
79
92
|
|
|
80
93
|
export { createAgent, createOpenAIAdapter, createSchemas };
|
package/dist/index.js
CHANGED
|
@@ -57,12 +57,7 @@ function createSchemas({
|
|
|
57
57
|
events
|
|
58
58
|
}) {
|
|
59
59
|
return {
|
|
60
|
-
context
|
|
61
|
-
type: "object",
|
|
62
|
-
properties: context,
|
|
63
|
-
additionalProperties: false,
|
|
64
|
-
required: Object.keys(context)
|
|
65
|
-
},
|
|
60
|
+
context,
|
|
66
61
|
events: createEventSchemas(events),
|
|
67
62
|
types: {}
|
|
68
63
|
};
|
|
@@ -134,7 +129,7 @@ function fromChatStream(openai, agentSettings, inputFn) {
|
|
|
134
129
|
}
|
|
135
130
|
);
|
|
136
131
|
}
|
|
137
|
-
function
|
|
132
|
+
function fromEvent(openai, agentSettings, inputFn) {
|
|
138
133
|
return (0, import_xstate2.fromPromise)(
|
|
139
134
|
async ({ input, self, system }) => {
|
|
140
135
|
const parentSnapshot = self._parent?.getSnapshot();
|
|
@@ -177,32 +172,70 @@ function fromEventChoice(openai, agentSettings, inputFn, options) {
|
|
|
177
172
|
tools
|
|
178
173
|
});
|
|
179
174
|
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
180
|
-
if (toolCalls) {
|
|
175
|
+
if (toolCalls?.length) {
|
|
181
176
|
const events = toolCalls.map((tc) => {
|
|
182
177
|
return {
|
|
183
178
|
type: functionNameMapping[tc.function.name],
|
|
184
179
|
...JSON.parse(tc.function.arguments)
|
|
185
180
|
};
|
|
186
181
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
system._relay(self, self._parent, event);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
182
|
+
const event = events[0];
|
|
183
|
+
system._relay(self, self._parent, event);
|
|
192
184
|
}
|
|
193
185
|
return void 0;
|
|
194
186
|
}
|
|
195
187
|
);
|
|
196
188
|
}
|
|
189
|
+
function fromTool(openai, agentSettings, tools, inputFn) {
|
|
190
|
+
return (0, import_xstate2.fromPromise)(async ({ input }) => {
|
|
191
|
+
const resolvedTools = Object.entries(tools).map(([key, value]) => {
|
|
192
|
+
return {
|
|
193
|
+
type: "function",
|
|
194
|
+
function: {
|
|
195
|
+
name: key,
|
|
196
|
+
description: value.description,
|
|
197
|
+
parameters: value.inputSchema
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
const openAiInput = inputFn(input);
|
|
202
|
+
const completionParams = typeof openAiInput === "string" ? {
|
|
203
|
+
model: agentSettings.model,
|
|
204
|
+
messages: [
|
|
205
|
+
{
|
|
206
|
+
role: "user",
|
|
207
|
+
content: openAiInput
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
} : openAiInput;
|
|
211
|
+
const completion = await openai.chat.completions.create({
|
|
212
|
+
...completionParams,
|
|
213
|
+
tools: resolvedTools
|
|
214
|
+
});
|
|
215
|
+
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
216
|
+
if (toolCalls?.length) {
|
|
217
|
+
const toolCall = toolCalls[0];
|
|
218
|
+
const tool = tools[toolCall.function.name];
|
|
219
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
220
|
+
if (tool) {
|
|
221
|
+
const result = await tool.run(args);
|
|
222
|
+
return {
|
|
223
|
+
toolCall,
|
|
224
|
+
tool: toolCall.function.name,
|
|
225
|
+
result
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return void 0;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
197
232
|
function createOpenAIAdapter(openai, settings) {
|
|
198
233
|
const agentSettings = {
|
|
199
234
|
model: settings.model,
|
|
200
|
-
|
|
201
|
-
// @ts-ignore infinitely deep
|
|
202
|
-
fromEventChoice(openai, agentSettings, input, { execute: true })
|
|
203
|
-
),
|
|
235
|
+
fromEvent: (input) => fromEvent(openai, agentSettings, input),
|
|
204
236
|
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
205
|
-
fromChatStream: (input) => fromChatStream(openai, agentSettings, input)
|
|
237
|
+
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
238
|
+
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input)
|
|
206
239
|
};
|
|
207
240
|
return agentSettings;
|
|
208
241
|
}
|
package/examples/joke.ts
CHANGED
|
@@ -9,15 +9,19 @@ const openai = new OpenAI({
|
|
|
9
9
|
|
|
10
10
|
const schemas = createSchemas({
|
|
11
11
|
context: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type: '
|
|
15
|
-
|
|
16
|
-
type: '
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
topic: { type: 'string' },
|
|
15
|
+
jokes: {
|
|
16
|
+
type: 'array',
|
|
17
|
+
items: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
},
|
|
17
20
|
},
|
|
21
|
+
desire: { type: ['string', 'null'] },
|
|
22
|
+
lastRating: { type: ['string', 'null'] },
|
|
18
23
|
},
|
|
19
|
-
|
|
20
|
-
lastRating: { type: ['string', 'null'] as const },
|
|
24
|
+
required: ['topic', 'jokes', 'desire', 'lastRating'],
|
|
21
25
|
},
|
|
22
26
|
events: {
|
|
23
27
|
askForTopic: {
|
|
@@ -61,7 +65,7 @@ const getTopic = fromPromise(async () => {
|
|
|
61
65
|
return topic;
|
|
62
66
|
});
|
|
63
67
|
|
|
64
|
-
const decide = adapter.
|
|
68
|
+
const decide = adapter.fromEvent(
|
|
65
69
|
(lastRating: string) =>
|
|
66
70
|
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
67
71
|
);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
3
|
+
import { assign, setup } from 'xstate';
|
|
4
|
+
const openai = new OpenAI({
|
|
5
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
9
|
+
model: 'gpt-3.5-turbo-1106',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const guessLogic = adapter.fromEvent(
|
|
13
|
+
({
|
|
14
|
+
previousGuesses,
|
|
15
|
+
lastResult,
|
|
16
|
+
}: {
|
|
17
|
+
previousGuesses: number[];
|
|
18
|
+
lastResult: string;
|
|
19
|
+
}) => `
|
|
20
|
+
Guess the number between 1 and 10. The previous guesses were ${
|
|
21
|
+
previousGuesses.length ? previousGuesses.join(', ') : 'not made yet'
|
|
22
|
+
} and the last result was ${lastResult}.
|
|
23
|
+
`
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const schemas = createSchemas({
|
|
27
|
+
context: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
lastGuess: {
|
|
31
|
+
type: ['number', 'null'],
|
|
32
|
+
description: 'The last guess',
|
|
33
|
+
},
|
|
34
|
+
previousGuesses: {
|
|
35
|
+
type: 'array',
|
|
36
|
+
items: {
|
|
37
|
+
type: 'number',
|
|
38
|
+
},
|
|
39
|
+
description: 'The previous guesses',
|
|
40
|
+
},
|
|
41
|
+
answer: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
description: 'The answer',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
events: {
|
|
48
|
+
guess: {
|
|
49
|
+
properties: {
|
|
50
|
+
number: {
|
|
51
|
+
// integer
|
|
52
|
+
type: 'number',
|
|
53
|
+
minimum: 1,
|
|
54
|
+
maximum: 10,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ['number'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const machine = setup({
|
|
63
|
+
types: {
|
|
64
|
+
context: {} as {
|
|
65
|
+
previousGuesses: number[];
|
|
66
|
+
answer: number;
|
|
67
|
+
},
|
|
68
|
+
input: {} as { answer: number },
|
|
69
|
+
events: schemas.types.events,
|
|
70
|
+
},
|
|
71
|
+
schemas,
|
|
72
|
+
actors: {
|
|
73
|
+
guessLogic,
|
|
74
|
+
},
|
|
75
|
+
}).createMachine({
|
|
76
|
+
context: ({ input }) => ({
|
|
77
|
+
answer: input.answer,
|
|
78
|
+
previousGuesses: [],
|
|
79
|
+
}),
|
|
80
|
+
initial: 'guessing',
|
|
81
|
+
states: {
|
|
82
|
+
guessing: {
|
|
83
|
+
always: {
|
|
84
|
+
guard: ({ context }) =>
|
|
85
|
+
context.answer === context.previousGuesses.at(-1),
|
|
86
|
+
target: 'winner',
|
|
87
|
+
},
|
|
88
|
+
invoke: {
|
|
89
|
+
src: 'guessLogic',
|
|
90
|
+
input: ({ context }) => ({
|
|
91
|
+
previousGuesses: context.previousGuesses,
|
|
92
|
+
lastResult:
|
|
93
|
+
context.previousGuesses.length === 0
|
|
94
|
+
? 'not given yet'
|
|
95
|
+
: context.previousGuesses.at(-1)! - context.answer > 0
|
|
96
|
+
? 'too high'
|
|
97
|
+
: 'too low',
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
on: {
|
|
101
|
+
guess: {
|
|
102
|
+
actions: assign({
|
|
103
|
+
previousGuesses: ({ context, event }) => [
|
|
104
|
+
...context.previousGuesses,
|
|
105
|
+
event.number,
|
|
106
|
+
],
|
|
107
|
+
}),
|
|
108
|
+
target: 'guessing',
|
|
109
|
+
reenter: true,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
winner: {
|
|
114
|
+
type: 'final',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const agent = createAgent(machine, {
|
|
120
|
+
input: { answer: 4 },
|
|
121
|
+
inspect: (ev) => {
|
|
122
|
+
if (ev.type === '@xstate.event') {
|
|
123
|
+
console.log(ev.event);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
agent.start();
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -10,36 +10,40 @@ type Player = 'x' | 'o';
|
|
|
10
10
|
|
|
11
11
|
const schemas = createSchemas({
|
|
12
12
|
context: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type:
|
|
17
|
-
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
board: {
|
|
16
|
+
type: 'array',
|
|
17
|
+
items: {
|
|
18
|
+
type: ['null', 'string'],
|
|
19
|
+
enum: [null, 'x', 'o'],
|
|
20
|
+
},
|
|
21
|
+
minItems: 9,
|
|
22
|
+
maxItems: 9,
|
|
23
|
+
description: 'The board of the tic-tac-toe game',
|
|
18
24
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type: 'number',
|
|
25
|
-
description: 'The number of moves that have been played',
|
|
26
|
-
},
|
|
27
|
-
player: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
enum: ['x', 'o'],
|
|
30
|
-
description: 'The player whose turn it is',
|
|
31
|
-
},
|
|
32
|
-
gameReport: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
description: 'The game report',
|
|
35
|
-
},
|
|
36
|
-
events: {
|
|
37
|
-
type: 'array',
|
|
38
|
-
items: {
|
|
25
|
+
moves: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
description: 'The number of moves that have been played',
|
|
28
|
+
},
|
|
29
|
+
player: {
|
|
39
30
|
type: 'string',
|
|
31
|
+
enum: ['x', 'o'],
|
|
32
|
+
description: 'The player whose turn it is',
|
|
33
|
+
},
|
|
34
|
+
gameReport: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'The game report',
|
|
37
|
+
},
|
|
38
|
+
events: {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
},
|
|
40
43
|
},
|
|
41
44
|
},
|
|
42
|
-
|
|
45
|
+
required: ['board', 'moves', 'player', 'gameReport', 'events'],
|
|
46
|
+
},
|
|
43
47
|
events: {
|
|
44
48
|
'x.play': {
|
|
45
49
|
properties: {
|
|
@@ -80,7 +84,7 @@ const initialContext = {
|
|
|
80
84
|
events: [],
|
|
81
85
|
} satisfies typeof schemas.types.context;
|
|
82
86
|
|
|
83
|
-
const bot = adapter.
|
|
87
|
+
const bot = adapter.fromEvent(
|
|
84
88
|
({ context }: { context: typeof schemas.types.context }) => `
|
|
85
89
|
You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty. The value of x means that the cell is occupied by an x. The value of o means that the cell is occupied by an o.
|
|
86
90
|
|
package/examples/weather.ts
CHANGED
|
@@ -84,7 +84,7 @@ const machine = setup({
|
|
|
84
84
|
types: schemas.types,
|
|
85
85
|
actors: {
|
|
86
86
|
getWeather,
|
|
87
|
-
decide: adapter.
|
|
87
|
+
decide: adapter.fromEvent(
|
|
88
88
|
(input: string) =>
|
|
89
89
|
`Decide what to do based on the given input, which may or may not be a location: ${input}`
|
|
90
90
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -17,19 +17,20 @@
|
|
|
17
17
|
"openai": "^4.24.1",
|
|
18
18
|
"ts-node": "^10.9.2",
|
|
19
19
|
"tsup": "^8.0.1",
|
|
20
|
-
"typescript": "^5.3.3"
|
|
20
|
+
"typescript": "^5.3.3",
|
|
21
|
+
"vitest": "^1.2.2"
|
|
21
22
|
},
|
|
22
23
|
"publishConfig": {
|
|
23
24
|
"access": "public"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"xstate": "^5.
|
|
27
|
+
"xstate": "^5.8.0"
|
|
27
28
|
},
|
|
28
29
|
"packageManager": "pnpm@8.11.0",
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
31
32
|
"lint": "tsc",
|
|
32
|
-
"test": "vitest
|
|
33
|
+
"test": "vitest",
|
|
33
34
|
"example": "ts-node examples/helpers/runner.ts",
|
|
34
35
|
"changeset": "changeset",
|
|
35
36
|
"release": "changeset publish",
|
package/readme.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Stately Agent (alpha)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
- Read [the documentation](https://stately.ai/docs/agents)
|
|
4
|
+
- See [the examples directory](https://github.com/statelyai/agent/tree/main/examples) for working examples.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
7
8
|
Install `openai`, and `@statelyai/agent`:
|
|
8
9
|
|
|
9
10
|
```bash
|
|
10
|
-
|
|
11
|
+
pnpm install openai @statelyai/agent
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
## Usage
|
|
@@ -35,7 +36,7 @@ OPENAI_API_KEY="your-openai-api-key"
|
|
|
35
36
|
Then, install the dependencies (`npm install`) and run the examples:
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
|
-
|
|
39
|
+
pnpm run example joke
|
|
39
40
|
# or:
|
|
40
|
-
#
|
|
41
|
+
# pnpm run example ticTacToe
|
|
41
42
|
```
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { createOpenAIAdapter, createTool } from './adapters/openai';
|
|
3
|
+
import OpenAI from 'openai';
|
|
4
|
+
import { createActor, toPromise } from 'xstate';
|
|
5
|
+
|
|
6
|
+
test('fromTool - weather or illustration', async () => {
|
|
7
|
+
const openAi = new OpenAI({
|
|
8
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const adapter = createOpenAIAdapter(openAi, {
|
|
12
|
+
model: 'gpt-3.5-turbo',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const toolChoice = adapter.fromTool(() => 'Create an image of a donut', {
|
|
16
|
+
makeIllustration: {
|
|
17
|
+
description: 'Make an illustration',
|
|
18
|
+
run: async () => 'Illustration',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
name: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'The name of the illustration',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ['name'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
getWeather: {
|
|
31
|
+
description: 'Get the weather for a location',
|
|
32
|
+
run: async () => 'Weather',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
location: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
city: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'The name of the city',
|
|
42
|
+
},
|
|
43
|
+
state: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'The name of the state',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ['city', 'state'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['location'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const actor = createActor(toolChoice);
|
|
57
|
+
|
|
58
|
+
actor.start();
|
|
59
|
+
|
|
60
|
+
const res = await toPromise(actor);
|
|
61
|
+
|
|
62
|
+
expect(res?.result).toBe('Illustration');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('fromTool - GitHub PR description inserter', async () => {
|
|
66
|
+
const openAi = new OpenAI({
|
|
67
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const adapter = createOpenAIAdapter(openAi, {
|
|
71
|
+
model: 'gpt-3.5-turbo-16k-0613',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const toolChoice = adapter.fromTool(
|
|
75
|
+
(input: string) =>
|
|
76
|
+
`Create a GitHub PR description for the following: ${input}`,
|
|
77
|
+
{
|
|
78
|
+
fetchGitHubPR: {
|
|
79
|
+
description: 'Fetch a GitHub PR',
|
|
80
|
+
run: async (input: string) => {
|
|
81
|
+
return {
|
|
82
|
+
title: 'Title',
|
|
83
|
+
body: input,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
repo: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'The name of the repo',
|
|
92
|
+
},
|
|
93
|
+
number: {
|
|
94
|
+
type: 'number',
|
|
95
|
+
description: 'The number of the PR',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['repo', 'number'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
createPullRequestDescription: {
|
|
102
|
+
description: 'Create a GitHub PR description',
|
|
103
|
+
run: () => 'Description',
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
title: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'The title of the PR',
|
|
110
|
+
},
|
|
111
|
+
body: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description: 'The body of the PR',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ['title', 'body'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const actor = createActor(toolChoice, {
|
|
123
|
+
input:
|
|
124
|
+
// 'Get the details from this: https://github.com/microsoft/TypeScript/pull/47198',
|
|
125
|
+
'Make a summary of this PR: (some code here)',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
actor.start();
|
|
129
|
+
|
|
130
|
+
const res = await toPromise(actor);
|
|
131
|
+
|
|
132
|
+
expect(res?.tool).toEqual('createPullRequestDescription');
|
|
133
|
+
expect(res?.result).toEqual('Description');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('fromTool - joke creator or rater', async () => {
|
|
137
|
+
const openAi = new OpenAI({
|
|
138
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const adapter = createOpenAIAdapter(openAi, {
|
|
142
|
+
model: 'gpt-4-1106-preview',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const rateJoke = createTool({
|
|
146
|
+
description: 'Rate a joke',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
joke: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'The joke to rate',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
run: async ({ topic }: { topic: string }) => {
|
|
157
|
+
return `Here is a joke about ${topic}`;
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const createJoke = createTool({
|
|
162
|
+
description: 'Create a joke',
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
category: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
description: 'The category of the joke',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ['category'],
|
|
172
|
+
},
|
|
173
|
+
run: async () => {
|
|
174
|
+
return 'Some joke';
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const toolChoice = adapter.fromTool(
|
|
179
|
+
(input: string) => `
|
|
180
|
+
The user provided this input:
|
|
181
|
+
|
|
182
|
+
<input>
|
|
183
|
+
${input}
|
|
184
|
+
</input>
|
|
185
|
+
|
|
186
|
+
Determine what to do:
|
|
187
|
+
- If the input is asking for a joke, create a joke,
|
|
188
|
+
- But if the input is providing a joke, then rate the joke.
|
|
189
|
+
`,
|
|
190
|
+
{
|
|
191
|
+
rateJoke,
|
|
192
|
+
createJoke,
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const actor = createActor(toolChoice, {
|
|
197
|
+
// input: 'Why did the chicken cross the road? To get to the other side!',
|
|
198
|
+
input: 'Tell me a joke about chickens',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
actor.start();
|
|
202
|
+
|
|
203
|
+
const res = await toPromise(actor);
|
|
204
|
+
|
|
205
|
+
expect(res?.tool).toEqual('createJoke');
|
|
206
|
+
expect(res?.result).toEqual('Some joke');
|
|
207
|
+
|
|
208
|
+
const actor2 = createActor(toolChoice, {
|
|
209
|
+
input:
|
|
210
|
+
'Check this joke out: Why did the chicken cross the road? To get to the other side!',
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
actor2.start();
|
|
214
|
+
|
|
215
|
+
const res2 = await toPromise(actor2);
|
|
216
|
+
expect(res2?.tool).toEqual('rateJoke');
|
|
217
|
+
});
|
package/src/adapters/openai.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type OpenAI from 'openai';
|
|
2
2
|
import {
|
|
3
3
|
AnyEventObject,
|
|
4
|
-
ObservableActorLogic,
|
|
5
4
|
Observer,
|
|
6
|
-
PromiseActorLogic,
|
|
7
5
|
fromObservable,
|
|
8
6
|
fromPromise,
|
|
9
7
|
isMachineSnapshot,
|
|
@@ -11,10 +9,8 @@ import {
|
|
|
11
9
|
} from 'xstate';
|
|
12
10
|
import { getAllTransitions } from '../utils';
|
|
13
11
|
import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
ChatCompletionCreateParamsStreaming,
|
|
17
|
-
} from 'openai/resources/chat/completions';
|
|
12
|
+
import { ChatCompletionCreateParamsBase } from 'openai/resources/chat/completions';
|
|
13
|
+
import { StatelyAgentAdapter, Tool } from '../types';
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
16
|
* Creates [promise actor logic](https://stately.ai/docs/promise-actors) that uses the OpenAI API to generate a completion.
|
|
@@ -25,7 +21,7 @@ import {
|
|
|
25
21
|
*/
|
|
26
22
|
export function fromChatCompletion<TInput>(
|
|
27
23
|
openai: OpenAI,
|
|
28
|
-
agentSettings:
|
|
24
|
+
agentSettings: StatelyAgentAdapter,
|
|
29
25
|
inputFn: (
|
|
30
26
|
input: TInput
|
|
31
27
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
@@ -60,7 +56,7 @@ export function fromChatCompletion<TInput>(
|
|
|
60
56
|
*/
|
|
61
57
|
export function fromChatStream<TInput>(
|
|
62
58
|
openai: OpenAI,
|
|
63
|
-
agentSettings:
|
|
59
|
+
agentSettings: StatelyAgentAdapter,
|
|
64
60
|
inputFn: (
|
|
65
61
|
input: TInput
|
|
66
62
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
|
@@ -117,19 +113,12 @@ export function fromChatStream<TInput>(
|
|
|
117
113
|
* @param openai The OpenAI instance to use.
|
|
118
114
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
119
115
|
*/
|
|
120
|
-
export function
|
|
116
|
+
export function fromEvent<TInput>(
|
|
121
117
|
openai: OpenAI,
|
|
122
|
-
agentSettings:
|
|
118
|
+
agentSettings: StatelyAgentAdapter,
|
|
123
119
|
inputFn: (
|
|
124
120
|
input: TInput
|
|
125
|
-
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
126
|
-
options?: {
|
|
127
|
-
/**
|
|
128
|
-
* Immediately execute sending the event to the parent actor.
|
|
129
|
-
* @default false
|
|
130
|
-
*/
|
|
131
|
-
execute?: boolean;
|
|
132
|
-
}
|
|
121
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
133
122
|
) {
|
|
134
123
|
return fromPromise<AnyEventObject[] | undefined, TInput>(
|
|
135
124
|
async ({ input, self, system }) => {
|
|
@@ -185,7 +174,7 @@ export function fromEventChoice<TInput>(
|
|
|
185
174
|
|
|
186
175
|
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
187
176
|
|
|
188
|
-
if (toolCalls) {
|
|
177
|
+
if (toolCalls?.length) {
|
|
189
178
|
const events = toolCalls.map((tc) => {
|
|
190
179
|
return {
|
|
191
180
|
type: functionNameMapping[tc.function.name],
|
|
@@ -193,12 +182,10 @@ export function fromEventChoice<TInput>(
|
|
|
193
182
|
};
|
|
194
183
|
});
|
|
195
184
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
201
|
-
}
|
|
185
|
+
const event = events[0]!;
|
|
186
|
+
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
system._relay(self, self._parent, event);
|
|
202
189
|
}
|
|
203
190
|
|
|
204
191
|
return undefined;
|
|
@@ -206,54 +193,105 @@ export function fromEventChoice<TInput>(
|
|
|
206
193
|
);
|
|
207
194
|
}
|
|
208
195
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
> {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
196
|
+
export function createTool<TInput, T>({
|
|
197
|
+
description,
|
|
198
|
+
inputSchema,
|
|
199
|
+
run,
|
|
200
|
+
}: Tool<TInput, T>): Tool<TInput, T> {
|
|
201
|
+
return {
|
|
202
|
+
description,
|
|
203
|
+
inputSchema,
|
|
204
|
+
run,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Creates [promise actor logic](https://stately.ai/docs/promise-actors) that passes the next possible transitions as functions to [OpenAI tool calls](https://platform.openai.com/docs/guides/function-calling) and returns an array of potential next events.
|
|
210
|
+
*
|
|
211
|
+
* @param openai The OpenAI instance to use.
|
|
212
|
+
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
213
|
+
*/
|
|
214
|
+
export function fromTool<TInput>(
|
|
215
|
+
openai: OpenAI,
|
|
216
|
+
agentSettings: StatelyAgentAdapter,
|
|
217
|
+
tools: {
|
|
218
|
+
[key: string]: Tool<any, any>;
|
|
219
|
+
},
|
|
220
|
+
inputFn: (
|
|
221
|
+
input: TInput
|
|
222
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
223
|
+
) {
|
|
224
|
+
return fromPromise<
|
|
225
|
+
| {
|
|
226
|
+
result: any;
|
|
227
|
+
tool: string;
|
|
228
|
+
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
|
|
229
|
+
}
|
|
230
|
+
| undefined,
|
|
241
231
|
TInput
|
|
242
|
-
|
|
232
|
+
>(async ({ input }) => {
|
|
233
|
+
const resolvedTools = Object.entries(tools).map(([key, value]) => {
|
|
234
|
+
return {
|
|
235
|
+
type: 'function',
|
|
236
|
+
function: {
|
|
237
|
+
name: key,
|
|
238
|
+
description: value.description,
|
|
239
|
+
parameters: value.inputSchema,
|
|
240
|
+
},
|
|
241
|
+
} as const;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const openAiInput = inputFn(input);
|
|
245
|
+
const completionParams: ChatCompletionCreateParamsNonStreaming =
|
|
246
|
+
typeof openAiInput === 'string'
|
|
247
|
+
? {
|
|
248
|
+
model: agentSettings.model,
|
|
249
|
+
messages: [
|
|
250
|
+
{
|
|
251
|
+
role: 'user',
|
|
252
|
+
content: openAiInput,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
}
|
|
256
|
+
: openAiInput;
|
|
257
|
+
const completion = await openai.chat.completions.create({
|
|
258
|
+
...completionParams,
|
|
259
|
+
tools: resolvedTools,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
263
|
+
|
|
264
|
+
if (toolCalls?.length) {
|
|
265
|
+
const toolCall = toolCalls[0]!;
|
|
266
|
+
const tool = tools[toolCall.function.name];
|
|
267
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
268
|
+
|
|
269
|
+
if (tool) {
|
|
270
|
+
const result = await tool.run(args);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
toolCall,
|
|
274
|
+
tool: toolCall.function.name,
|
|
275
|
+
result,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return undefined;
|
|
281
|
+
});
|
|
243
282
|
}
|
|
244
283
|
|
|
245
284
|
export function createOpenAIAdapter<
|
|
246
285
|
T extends {
|
|
247
286
|
model: ChatCompletionCreateParamsBase['model'];
|
|
248
287
|
}
|
|
249
|
-
>(openai: OpenAI, settings: T):
|
|
250
|
-
const agentSettings:
|
|
288
|
+
>(openai: OpenAI, settings: T): StatelyAgentAdapter {
|
|
289
|
+
const agentSettings: StatelyAgentAdapter = {
|
|
251
290
|
model: settings.model,
|
|
252
|
-
|
|
253
|
-
// @ts-ignore infinitely deep
|
|
254
|
-
fromEventChoice(openai, agentSettings, input, { execute: true }) as any,
|
|
291
|
+
fromEvent: (input) => fromEvent(openai, agentSettings, input),
|
|
255
292
|
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
256
293
|
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
294
|
+
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input),
|
|
257
295
|
};
|
|
258
296
|
|
|
259
297
|
return agentSettings;
|
package/src/schemas.ts
CHANGED
|
@@ -2,36 +2,38 @@ import { Values } from 'xstate';
|
|
|
2
2
|
import {
|
|
3
3
|
ContextSchema,
|
|
4
4
|
EventSchemas,
|
|
5
|
-
ConvertContextToJSONSchema,
|
|
6
5
|
ConvertToJSONSchemas,
|
|
7
6
|
createEventSchemas,
|
|
8
7
|
} from './utils';
|
|
9
8
|
import { FromSchema } from 'json-schema-to-ts';
|
|
10
9
|
|
|
11
10
|
export function createSchemas<
|
|
12
|
-
TContextSchema extends ContextSchema,
|
|
13
|
-
TEventSchemas extends EventSchemas
|
|
11
|
+
const TContextSchema extends ContextSchema,
|
|
12
|
+
const TEventSchemas extends EventSchemas
|
|
14
13
|
>({
|
|
15
14
|
context,
|
|
16
15
|
events,
|
|
17
16
|
}: {
|
|
17
|
+
/**
|
|
18
|
+
* The JSON schema for the context object.
|
|
19
|
+
*
|
|
20
|
+
* Must be of `{ type: 'object' }`.
|
|
21
|
+
*/
|
|
18
22
|
context: TContextSchema;
|
|
23
|
+
/**
|
|
24
|
+
* An object mapping event types to each event object's JSON Schema.
|
|
25
|
+
*/
|
|
19
26
|
events: TEventSchemas;
|
|
20
27
|
}): {
|
|
21
|
-
context:
|
|
28
|
+
context: TContextSchema;
|
|
22
29
|
events: ConvertToJSONSchemas<TEventSchemas>;
|
|
23
30
|
types: {
|
|
24
|
-
context: FromSchema<
|
|
31
|
+
context: FromSchema<TContextSchema>;
|
|
25
32
|
events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
|
|
26
33
|
};
|
|
27
34
|
} {
|
|
28
35
|
return {
|
|
29
|
-
context
|
|
30
|
-
type: 'object',
|
|
31
|
-
properties: context,
|
|
32
|
-
additionalProperties: false,
|
|
33
|
-
required: Object.keys(context),
|
|
34
|
-
},
|
|
36
|
+
context,
|
|
35
37
|
events: createEventSchemas(events),
|
|
36
38
|
types: {} as any,
|
|
37
39
|
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import {
|
|
3
|
+
ChatCompletionCreateParamsNonStreaming,
|
|
4
|
+
ChatCompletionCreateParamsStreaming,
|
|
5
|
+
} from 'openai/resources';
|
|
6
|
+
import {
|
|
7
|
+
AnyEventObject,
|
|
8
|
+
ObservableActorLogic,
|
|
9
|
+
PromiseActorLogic,
|
|
10
|
+
} from 'xstate';
|
|
11
|
+
|
|
12
|
+
export interface StatelyAgentAdapter {
|
|
13
|
+
model: string;
|
|
14
|
+
/**
|
|
15
|
+
* Creates actor logic that chooses an event from all of the
|
|
16
|
+
* possible next events of the parent state machine
|
|
17
|
+
* and sends it to the parent actor.
|
|
18
|
+
*/
|
|
19
|
+
fromEvent: <TInput>(
|
|
20
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
21
|
+
) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
22
|
+
/**
|
|
23
|
+
* Creates actor logic that resolves with a chat completion.
|
|
24
|
+
*/
|
|
25
|
+
fromChat: <TInput>(
|
|
26
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
27
|
+
) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates actor logic that emits a chat completion stream.
|
|
30
|
+
*/
|
|
31
|
+
fromChatStream: <TInput>(
|
|
32
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming
|
|
33
|
+
) => ObservableActorLogic<
|
|
34
|
+
OpenAI.Chat.Completions.ChatCompletionChunk,
|
|
35
|
+
TInput
|
|
36
|
+
>;
|
|
37
|
+
/**
|
|
38
|
+
* Creates actor logic that chooses a tool from the provided
|
|
39
|
+
* tools and runs that tool.
|
|
40
|
+
*/
|
|
41
|
+
fromTool: <TInput>(
|
|
42
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming,
|
|
43
|
+
tools: {
|
|
44
|
+
[key: string]: Tool<any, any>;
|
|
45
|
+
}
|
|
46
|
+
) => PromiseActorLogic<
|
|
47
|
+
| {
|
|
48
|
+
result: any;
|
|
49
|
+
tool: string;
|
|
50
|
+
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
|
|
51
|
+
}
|
|
52
|
+
| undefined,
|
|
53
|
+
TInput
|
|
54
|
+
>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Tool<TInput, TOutput> {
|
|
58
|
+
description: string;
|
|
59
|
+
inputSchema: any;
|
|
60
|
+
run: (input: TInput) => TOutput;
|
|
61
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -20,26 +20,17 @@ export type EventSchemas = {
|
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export
|
|
24
|
-
[key: string]: JSONSchema7;
|
|
25
|
-
}
|
|
23
|
+
export type ContextSchema = JSONSchema7 & { type: 'object' };
|
|
26
24
|
|
|
27
25
|
export type ConvertToJSONSchemas<T> = {
|
|
28
26
|
[K in keyof T]: {
|
|
29
|
-
properties: { type: { const: K } }
|
|
27
|
+
properties: { type: { const: K } } & Prop<T[K], 'properties'>;
|
|
30
28
|
type: 'object';
|
|
31
|
-
required: Array<keyof Prop<T[K], 'properties'> | 'type'>;
|
|
29
|
+
required: Array<(keyof Prop<T[K], 'properties'> & string) | 'type'>;
|
|
32
30
|
additionalProperties: false;
|
|
33
|
-
}
|
|
31
|
+
};
|
|
34
32
|
} & {};
|
|
35
33
|
|
|
36
|
-
export type ConvertContextToJSONSchema<T extends ContextSchema> = {
|
|
37
|
-
type: 'object';
|
|
38
|
-
properties: T;
|
|
39
|
-
readonly required: Array<keyof T & string>;
|
|
40
|
-
additionalProperties: false;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
34
|
export function createEventSchemas<T extends EventSchemas>(
|
|
44
35
|
eventSchemaMap: T
|
|
45
36
|
): ConvertToJSONSchemas<T> {
|