@statelyai/agent 0.0.4 → 0.0.5
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 +32 -0
- package/dist/index.d.ts +25 -11
- package/dist/index.js +49 -4
- package/examples/joke.ts +1 -1
- package/examples/ticTacToe.ts +1 -1
- package/examples/weather.ts +1 -1
- package/package.json +3 -2
- package/src/adapter.test.ts +217 -0
- package/src/adapters/openai.ts +97 -6
- package/src/types.ts +69 -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,37 @@
|
|
|
1
1
|
# @statelyai/agent
|
|
2
2
|
|
|
3
|
+
## 0.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#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.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const actor = adapter.fromTool(() => "Draw me a picture of a donut", {
|
|
11
|
+
// tools
|
|
12
|
+
makeIllustration: {
|
|
13
|
+
description: "Makes an illustration",
|
|
14
|
+
run: async (input) => {
|
|
15
|
+
/* ... */
|
|
16
|
+
},
|
|
17
|
+
inputSchema: {
|
|
18
|
+
/* ... */
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
getWeather: {
|
|
22
|
+
description: "Gets the weather",
|
|
23
|
+
run: async (input) => {
|
|
24
|
+
/* ... */
|
|
25
|
+
},
|
|
26
|
+
inputSchema: {
|
|
27
|
+
/* ... */
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
//...
|
|
33
|
+
```
|
|
34
|
+
|
|
3
35
|
## 0.0.4
|
|
4
36
|
|
|
5
37
|
### 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]: {
|
|
@@ -50,14 +50,9 @@ declare function createSchemas<TContextSchema extends ContextSchema, TEventSchem
|
|
|
50
50
|
|
|
51
51
|
declare function createAgent<T extends AnyStateMachine>(...args: Parameters<typeof createActor<T>>): xstate.Actor<T>;
|
|
52
52
|
|
|
53
|
-
interface
|
|
54
|
-
model:
|
|
55
|
-
|
|
56
|
-
model: T['model'];
|
|
57
|
-
/**
|
|
58
|
-
* Determines which event to send to the parent state machine actor based on the prompt.
|
|
59
|
-
*/
|
|
60
|
-
fromEventChoice: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, options?: {
|
|
53
|
+
interface StatelyAgentAdapter {
|
|
54
|
+
model: string;
|
|
55
|
+
fromEvent: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, options?: {
|
|
61
56
|
/**
|
|
62
57
|
* Immediately execute sending the event to the parent actor.
|
|
63
58
|
* @default true
|
|
@@ -72,9 +67,28 @@ interface OpenAIAdapterOutput<T extends {
|
|
|
72
67
|
* Creates observable actor logic that emits a chat completion stream.
|
|
73
68
|
*/
|
|
74
69
|
fromChatStream: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming) => ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
|
|
70
|
+
fromTool: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, tools: {
|
|
71
|
+
[key: string]: Tool<any, any>;
|
|
72
|
+
}, options?: {
|
|
73
|
+
/**
|
|
74
|
+
* Immediately execute sending the event to the parent actor.
|
|
75
|
+
* @default true
|
|
76
|
+
*/
|
|
77
|
+
execute?: boolean;
|
|
78
|
+
}) => PromiseActorLogic<{
|
|
79
|
+
result: any;
|
|
80
|
+
tool: string;
|
|
81
|
+
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
|
|
82
|
+
} | undefined, TInput>;
|
|
75
83
|
}
|
|
84
|
+
interface Tool<TInput, TOutput> {
|
|
85
|
+
description: string;
|
|
86
|
+
inputSchema: any;
|
|
87
|
+
run: (input: TInput) => TOutput;
|
|
88
|
+
}
|
|
89
|
+
|
|
76
90
|
declare function createOpenAIAdapter<T extends {
|
|
77
91
|
model: ChatCompletionCreateParamsBase['model'];
|
|
78
|
-
}>(openai: OpenAI, settings: T):
|
|
92
|
+
}>(openai: OpenAI, settings: T): StatelyAgentAdapter;
|
|
79
93
|
|
|
80
94
|
export { createAgent, createOpenAIAdapter, createSchemas };
|
package/dist/index.js
CHANGED
|
@@ -134,7 +134,7 @@ function fromChatStream(openai, agentSettings, inputFn) {
|
|
|
134
134
|
}
|
|
135
135
|
);
|
|
136
136
|
}
|
|
137
|
-
function
|
|
137
|
+
function fromEvent(openai, agentSettings, inputFn, options) {
|
|
138
138
|
return (0, import_xstate2.fromPromise)(
|
|
139
139
|
async ({ input, self, system }) => {
|
|
140
140
|
const parentSnapshot = self._parent?.getSnapshot();
|
|
@@ -194,15 +194,60 @@ function fromEventChoice(openai, agentSettings, inputFn, options) {
|
|
|
194
194
|
}
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
|
+
function fromTool(openai, agentSettings, tools, inputFn) {
|
|
198
|
+
return (0, import_xstate2.fromPromise)(async ({ input, self, system }) => {
|
|
199
|
+
const functionNameMapping = {};
|
|
200
|
+
const resolvedTools = Object.entries(tools).map(([key, value]) => {
|
|
201
|
+
return {
|
|
202
|
+
type: "function",
|
|
203
|
+
function: {
|
|
204
|
+
name: key,
|
|
205
|
+
description: value.description,
|
|
206
|
+
parameters: value.inputSchema
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
const openAiInput = inputFn(input);
|
|
211
|
+
const completionParams = typeof openAiInput === "string" ? {
|
|
212
|
+
model: agentSettings.model,
|
|
213
|
+
messages: [
|
|
214
|
+
{
|
|
215
|
+
role: "user",
|
|
216
|
+
content: openAiInput
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
} : openAiInput;
|
|
220
|
+
const completion = await openai.chat.completions.create({
|
|
221
|
+
...completionParams,
|
|
222
|
+
tools: resolvedTools
|
|
223
|
+
});
|
|
224
|
+
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
225
|
+
if (toolCalls?.length) {
|
|
226
|
+
const toolCall = toolCalls[0];
|
|
227
|
+
const tool = tools[toolCall.function.name];
|
|
228
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
229
|
+
if (tool) {
|
|
230
|
+
const result = await tool.run(args);
|
|
231
|
+
return {
|
|
232
|
+
toolCall,
|
|
233
|
+
tool: toolCall.function.name,
|
|
234
|
+
result
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return void 0;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
197
241
|
function createOpenAIAdapter(openai, settings) {
|
|
198
242
|
const agentSettings = {
|
|
199
243
|
model: settings.model,
|
|
200
|
-
|
|
244
|
+
fromEvent: (input) => (
|
|
201
245
|
// @ts-ignore infinitely deep
|
|
202
|
-
|
|
246
|
+
fromEvent(openai, agentSettings, input, { execute: true })
|
|
203
247
|
),
|
|
204
248
|
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
205
|
-
fromChatStream: (input) => fromChatStream(openai, agentSettings, input)
|
|
249
|
+
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
250
|
+
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input)
|
|
206
251
|
};
|
|
207
252
|
return agentSettings;
|
|
208
253
|
}
|
package/examples/joke.ts
CHANGED
|
@@ -61,7 +61,7 @@ const getTopic = fromPromise(async () => {
|
|
|
61
61
|
return topic;
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
const decide = adapter.
|
|
64
|
+
const decide = adapter.fromEvent(
|
|
65
65
|
(lastRating: string) =>
|
|
66
66
|
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
67
67
|
);
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -80,7 +80,7 @@ const initialContext = {
|
|
|
80
80
|
events: [],
|
|
81
81
|
} satisfies typeof schemas.types.context;
|
|
82
82
|
|
|
83
|
-
const bot = adapter.
|
|
83
|
+
const bot = adapter.fromEvent(
|
|
84
84
|
({ context }: { context: typeof schemas.types.context }) => `
|
|
85
85
|
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
86
|
|
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.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -17,7 +17,8 @@
|
|
|
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"
|
|
@@ -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
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ChatCompletionCreateParamsBase,
|
|
16
16
|
ChatCompletionCreateParamsStreaming,
|
|
17
17
|
} from 'openai/resources/chat/completions';
|
|
18
|
+
import { StatelyAgentAdapter, Tool } from '../types';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Creates [promise actor logic](https://stately.ai/docs/promise-actors) that uses the OpenAI API to generate a completion.
|
|
@@ -117,7 +118,7 @@ export function fromChatStream<TInput>(
|
|
|
117
118
|
* @param openai The OpenAI instance to use.
|
|
118
119
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
119
120
|
*/
|
|
120
|
-
export function
|
|
121
|
+
export function fromEvent<TInput>(
|
|
121
122
|
openai: OpenAI,
|
|
122
123
|
agentSettings: OpenAIAdapterOutput<any>,
|
|
123
124
|
inputFn: (
|
|
@@ -206,6 +207,95 @@ export function fromEventChoice<TInput>(
|
|
|
206
207
|
);
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
export function createTool<TInput, T>({
|
|
211
|
+
description,
|
|
212
|
+
inputSchema,
|
|
213
|
+
run,
|
|
214
|
+
}: Tool<TInput, T>): Tool<TInput, T> {
|
|
215
|
+
return {
|
|
216
|
+
description,
|
|
217
|
+
inputSchema,
|
|
218
|
+
run,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 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.
|
|
224
|
+
*
|
|
225
|
+
* @param openai The OpenAI instance to use.
|
|
226
|
+
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
227
|
+
*/
|
|
228
|
+
export function fromTool<TInput>(
|
|
229
|
+
openai: OpenAI,
|
|
230
|
+
agentSettings: StatelyAgentAdapter,
|
|
231
|
+
tools: {
|
|
232
|
+
[key: string]: Tool<any, any>;
|
|
233
|
+
},
|
|
234
|
+
inputFn: (
|
|
235
|
+
input: TInput
|
|
236
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
237
|
+
) {
|
|
238
|
+
return fromPromise<
|
|
239
|
+
| {
|
|
240
|
+
result: any;
|
|
241
|
+
tool: string;
|
|
242
|
+
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
|
|
243
|
+
}
|
|
244
|
+
| undefined,
|
|
245
|
+
TInput
|
|
246
|
+
>(async ({ input, self, system }) => {
|
|
247
|
+
const functionNameMapping: Record<string, string> = {};
|
|
248
|
+
const resolvedTools = Object.entries(tools).map(([key, value]) => {
|
|
249
|
+
return {
|
|
250
|
+
type: 'function',
|
|
251
|
+
function: {
|
|
252
|
+
name: key,
|
|
253
|
+
description: value.description,
|
|
254
|
+
parameters: value.inputSchema,
|
|
255
|
+
},
|
|
256
|
+
} as const;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const openAiInput = inputFn(input);
|
|
260
|
+
const completionParams: ChatCompletionCreateParamsNonStreaming =
|
|
261
|
+
typeof openAiInput === 'string'
|
|
262
|
+
? {
|
|
263
|
+
model: agentSettings.model,
|
|
264
|
+
messages: [
|
|
265
|
+
{
|
|
266
|
+
role: 'user',
|
|
267
|
+
content: openAiInput,
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
}
|
|
271
|
+
: openAiInput;
|
|
272
|
+
const completion = await openai.chat.completions.create({
|
|
273
|
+
...completionParams,
|
|
274
|
+
tools: resolvedTools,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
278
|
+
|
|
279
|
+
if (toolCalls?.length) {
|
|
280
|
+
const toolCall = toolCalls[0]!;
|
|
281
|
+
const tool = tools[toolCall.function.name];
|
|
282
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
283
|
+
|
|
284
|
+
if (tool) {
|
|
285
|
+
const result = await tool.run(args);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
toolCall,
|
|
289
|
+
tool: toolCall.function.name,
|
|
290
|
+
result,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return undefined;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
209
299
|
interface OpenAIAdapterOutput<
|
|
210
300
|
T extends {
|
|
211
301
|
model: ChatCompletionCreateParamsBase['model'];
|
|
@@ -215,7 +305,7 @@ interface OpenAIAdapterOutput<
|
|
|
215
305
|
/**
|
|
216
306
|
* Determines which event to send to the parent state machine actor based on the prompt.
|
|
217
307
|
*/
|
|
218
|
-
|
|
308
|
+
fromEvent: <TInput>(
|
|
219
309
|
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming,
|
|
220
310
|
options?: {
|
|
221
311
|
/**
|
|
@@ -246,14 +336,15 @@ export function createOpenAIAdapter<
|
|
|
246
336
|
T extends {
|
|
247
337
|
model: ChatCompletionCreateParamsBase['model'];
|
|
248
338
|
}
|
|
249
|
-
>(openai: OpenAI, settings: T):
|
|
250
|
-
const agentSettings:
|
|
339
|
+
>(openai: OpenAI, settings: T): StatelyAgentAdapter {
|
|
340
|
+
const agentSettings: StatelyAgentAdapter = {
|
|
251
341
|
model: settings.model,
|
|
252
|
-
|
|
342
|
+
fromEvent: (input) =>
|
|
253
343
|
// @ts-ignore infinitely deep
|
|
254
|
-
|
|
344
|
+
fromEvent(openai, agentSettings, input, { execute: true }) as any,
|
|
255
345
|
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
256
346
|
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
347
|
+
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input),
|
|
257
348
|
};
|
|
258
349
|
|
|
259
350
|
return agentSettings;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import {
|
|
3
|
+
ChatCompletionCreateParamsNonStreaming,
|
|
4
|
+
ChatCompletionCreateParamsStreaming,
|
|
5
|
+
} from 'openai/resources';
|
|
6
|
+
import {
|
|
7
|
+
AnyActorLogic,
|
|
8
|
+
AnyActorRef,
|
|
9
|
+
AnyEventObject,
|
|
10
|
+
ObservableActorLogic,
|
|
11
|
+
PromiseActorLogic,
|
|
12
|
+
} from 'xstate';
|
|
13
|
+
|
|
14
|
+
export interface StatelyAgentAdapter {
|
|
15
|
+
model: string;
|
|
16
|
+
fromEvent: <TInput>(
|
|
17
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming,
|
|
18
|
+
options?: {
|
|
19
|
+
/**
|
|
20
|
+
* Immediately execute sending the event to the parent actor.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
execute?: boolean;
|
|
24
|
+
}
|
|
25
|
+
) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
26
|
+
/**
|
|
27
|
+
* Creates promise actor logic that resolves with a chat completion.
|
|
28
|
+
*/
|
|
29
|
+
fromChat: <TInput>(
|
|
30
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
31
|
+
) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
32
|
+
/**
|
|
33
|
+
* Creates observable actor logic that emits a chat completion stream.
|
|
34
|
+
*/
|
|
35
|
+
fromChatStream: <TInput>(
|
|
36
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming
|
|
37
|
+
) => ObservableActorLogic<
|
|
38
|
+
OpenAI.Chat.Completions.ChatCompletionChunk,
|
|
39
|
+
TInput
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
fromTool: <TInput>(
|
|
43
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming,
|
|
44
|
+
tools: {
|
|
45
|
+
[key: string]: Tool<any, any>;
|
|
46
|
+
},
|
|
47
|
+
options?: {
|
|
48
|
+
/**
|
|
49
|
+
* Immediately execute sending the event to the parent actor.
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
execute?: boolean;
|
|
53
|
+
}
|
|
54
|
+
) => PromiseActorLogic<
|
|
55
|
+
| {
|
|
56
|
+
result: any;
|
|
57
|
+
tool: string;
|
|
58
|
+
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
|
|
59
|
+
}
|
|
60
|
+
| undefined,
|
|
61
|
+
TInput
|
|
62
|
+
>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Tool<TInput, TOutput> {
|
|
66
|
+
description: string;
|
|
67
|
+
inputSchema: any;
|
|
68
|
+
run: (input: TInput) => TOutput;
|
|
69
|
+
}
|