@statelyai/agent 0.0.3 → 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 +40 -0
- package/dist/index.d.ts +56 -50
- package/dist/index.js +94 -34
- package/examples/helpers/helpers.ts +1 -1
- package/examples/joke.ts +32 -30
- package/examples/multiAgentCollaboration.ts +0 -0
- package/examples/ticTacToe.ts +17 -19
- package/examples/weather.ts +30 -17
- package/package.json +4 -3
- package/src/adapter.test.ts +217 -0
- package/src/{openai.ts → adapters/openai.ts} +138 -64
- package/src/agent.ts +8 -0
- package/src/index.ts +3 -6
- package/src/schemas.ts +38 -0
- 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,45 @@
|
|
|
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
|
+
|
|
35
|
+
## 0.0.4
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- [#5](https://github.com/statelyai/agent/pull/5) [`ae473d7`](https://github.com/statelyai/agent/commit/ae473d73399a15ac3199d77d00eb44a0ea5626db) Thanks [@davidkpiano](https://github.com/davidkpiano)! - Simplify API (WIP)
|
|
40
|
+
|
|
41
|
+
- [#5](https://github.com/statelyai/agent/pull/5) [`687bed8`](https://github.com/statelyai/agent/commit/687bed87f29bd1d13447cc53b5154da0fe6fdcab) Thanks [@davidkpiano](https://github.com/davidkpiano)! - Add `createSchemas`, `createOpenAIAdapter`, and change `createAgent`
|
|
42
|
+
|
|
3
43
|
## 0.0.3
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Prop,
|
|
1
|
+
import * as xstate from 'xstate';
|
|
2
|
+
import { Prop, Values, AnyStateMachine, createActor, PromiseActorLogic, AnyEventObject, ObservableActorLogic } from 'xstate';
|
|
3
3
|
import { JSONSchema7 } from 'json-schema-to-ts/lib/types/definitions';
|
|
4
4
|
import { FromSchema } from 'json-schema-to-ts';
|
|
5
|
-
import
|
|
6
|
-
import { ChatCompletionCreateParamsBase
|
|
5
|
+
import OpenAI from 'openai';
|
|
6
|
+
import { ChatCompletionCreateParamsBase } from 'openai/resources/chat/completions';
|
|
7
|
+
import { ChatCompletionCreateParamsNonStreaming, ChatCompletionCreateParamsStreaming } from 'openai/resources';
|
|
7
8
|
|
|
8
9
|
type EventSchemas = {
|
|
9
10
|
[key: string]: {
|
|
@@ -35,54 +36,59 @@ type ConvertContextToJSONSchema<T extends ContextSchema> = {
|
|
|
35
36
|
additionalProperties: false;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
45
|
-
declare function fromChatCompletion<TInput>(openai: OpenAI, agentSettings: CreateAgentOutput<any>, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming): PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
46
|
-
/**
|
|
47
|
-
* Creates [observable actor logic](https://stately.ai/docs/observable-actors) that uses the OpenAI API to generate a completion stream.
|
|
48
|
-
*
|
|
49
|
-
* @param openai The OpenAI instance to use.
|
|
50
|
-
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
51
|
-
*/
|
|
52
|
-
declare function fromChatCompletionStream<TInput>(openai: OpenAI, agentSettings: CreateAgentOutput<any>, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming): ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
|
|
53
|
-
/**
|
|
54
|
-
* 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.
|
|
55
|
-
*
|
|
56
|
-
* @param openai The OpenAI instance to use.
|
|
57
|
-
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
58
|
-
*/
|
|
59
|
-
declare function fromEventChoice<TInput>(openai: OpenAI, agentSettings: CreateAgentOutput<any>, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming, options?: {
|
|
60
|
-
/**
|
|
61
|
-
* Immediately execute sending the event to the parent actor.
|
|
62
|
-
* @default false
|
|
63
|
-
*/
|
|
64
|
-
execute?: boolean;
|
|
65
|
-
}): PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
66
|
-
interface CreateAgentOutput<T extends {
|
|
67
|
-
model: ChatCompletionCreateParamsBase['model'];
|
|
68
|
-
context: ContextSchema;
|
|
69
|
-
events: EventSchemas;
|
|
70
|
-
}> {
|
|
71
|
-
model: T['model'];
|
|
72
|
-
schemas: T;
|
|
39
|
+
declare function createSchemas<TContextSchema extends ContextSchema, TEventSchemas extends EventSchemas>({ context, events, }: {
|
|
40
|
+
context: TContextSchema;
|
|
41
|
+
events: TEventSchemas;
|
|
42
|
+
}): {
|
|
43
|
+
context: ConvertContextToJSONSchema<TContextSchema>;
|
|
44
|
+
events: ConvertToJSONSchemas<TEventSchemas>;
|
|
73
45
|
types: {
|
|
74
|
-
context: FromSchema<ConvertContextToJSONSchema<
|
|
75
|
-
events: FromSchema<Values<ConvertToJSONSchemas<
|
|
46
|
+
context: FromSchema<ConvertContextToJSONSchema<TContextSchema>>;
|
|
47
|
+
events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
|
|
76
48
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
declare function createAgent<T extends AnyStateMachine>(...args: Parameters<typeof createActor<T>>): xstate.Actor<T>;
|
|
52
|
+
|
|
53
|
+
interface StatelyAgentAdapter {
|
|
54
|
+
model: string;
|
|
55
|
+
fromEvent: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, options?: {
|
|
56
|
+
/**
|
|
57
|
+
* Immediately execute sending the event to the parent actor.
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
execute?: boolean;
|
|
61
|
+
}) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
62
|
+
/**
|
|
63
|
+
* Creates promise actor logic that resolves with a chat completion.
|
|
64
|
+
*/
|
|
65
|
+
fromChat: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
66
|
+
/**
|
|
67
|
+
* Creates observable actor logic that emits a chat completion stream.
|
|
68
|
+
*/
|
|
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>;
|
|
83
|
+
}
|
|
84
|
+
interface Tool<TInput, TOutput> {
|
|
85
|
+
description: string;
|
|
86
|
+
inputSchema: any;
|
|
87
|
+
run: (input: TInput) => TOutput;
|
|
81
88
|
}
|
|
82
|
-
|
|
89
|
+
|
|
90
|
+
declare function createOpenAIAdapter<T extends {
|
|
83
91
|
model: ChatCompletionCreateParamsBase['model'];
|
|
84
|
-
|
|
85
|
-
events: EventSchemas;
|
|
86
|
-
}>(openai: OpenAI, settings: T): CreateAgentOutput<T>;
|
|
92
|
+
}>(openai: OpenAI, settings: T): StatelyAgentAdapter;
|
|
87
93
|
|
|
88
|
-
export { createAgent,
|
|
94
|
+
export { createAgent, createOpenAIAdapter, createSchemas };
|
package/dist/index.js
CHANGED
|
@@ -21,15 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
createAgent: () => createAgent,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
fromEventChoice: () => fromEventChoice
|
|
24
|
+
createOpenAIAdapter: () => createOpenAIAdapter,
|
|
25
|
+
createSchemas: () => createSchemas
|
|
27
26
|
});
|
|
28
27
|
module.exports = __toCommonJS(src_exports);
|
|
29
28
|
|
|
30
|
-
// src/openai.ts
|
|
31
|
-
var import_xstate = require("xstate");
|
|
32
|
-
|
|
33
29
|
// src/utils.ts
|
|
34
30
|
function getAllTransitions(state) {
|
|
35
31
|
const nodes = state._nodes;
|
|
@@ -55,9 +51,34 @@ function createEventSchemas(eventSchemaMap) {
|
|
|
55
51
|
return resolvedEventSchemaMap;
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
// src/
|
|
54
|
+
// src/schemas.ts
|
|
55
|
+
function createSchemas({
|
|
56
|
+
context,
|
|
57
|
+
events
|
|
58
|
+
}) {
|
|
59
|
+
return {
|
|
60
|
+
context: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: context,
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
required: Object.keys(context)
|
|
65
|
+
},
|
|
66
|
+
events: createEventSchemas(events),
|
|
67
|
+
types: {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/agent.ts
|
|
72
|
+
var import_xstate = require("xstate");
|
|
73
|
+
function createAgent(...args) {
|
|
74
|
+
const [machine, options] = args;
|
|
75
|
+
return (0, import_xstate.createActor)(machine, options);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/adapters/openai.ts
|
|
79
|
+
var import_xstate2 = require("xstate");
|
|
59
80
|
function fromChatCompletion(openai, agentSettings, inputFn) {
|
|
60
|
-
return (0,
|
|
81
|
+
return (0, import_xstate2.fromPromise)(
|
|
61
82
|
async ({ input }) => {
|
|
62
83
|
const openAiInput = inputFn(input);
|
|
63
84
|
const params = typeof openAiInput === "string" ? {
|
|
@@ -74,8 +95,8 @@ function fromChatCompletion(openai, agentSettings, inputFn) {
|
|
|
74
95
|
}
|
|
75
96
|
);
|
|
76
97
|
}
|
|
77
|
-
function
|
|
78
|
-
return (0,
|
|
98
|
+
function fromChatStream(openai, agentSettings, inputFn) {
|
|
99
|
+
return (0, import_xstate2.fromObservable)(
|
|
79
100
|
({ input }) => {
|
|
80
101
|
const observers = /* @__PURE__ */ new Set();
|
|
81
102
|
(async () => {
|
|
@@ -101,7 +122,7 @@ function fromChatCompletionStream(openai, agentSettings, inputFn) {
|
|
|
101
122
|
})();
|
|
102
123
|
return {
|
|
103
124
|
subscribe: (...args) => {
|
|
104
|
-
const observer = (0,
|
|
125
|
+
const observer = (0, import_xstate2.toObserver)(...args);
|
|
105
126
|
observers.add(observer);
|
|
106
127
|
return {
|
|
107
128
|
unsubscribe: () => {
|
|
@@ -113,9 +134,15 @@ function fromChatCompletionStream(openai, agentSettings, inputFn) {
|
|
|
113
134
|
}
|
|
114
135
|
);
|
|
115
136
|
}
|
|
116
|
-
function
|
|
117
|
-
return (0,
|
|
137
|
+
function fromEvent(openai, agentSettings, inputFn, options) {
|
|
138
|
+
return (0, import_xstate2.fromPromise)(
|
|
118
139
|
async ({ input, self, system }) => {
|
|
140
|
+
const parentSnapshot = self._parent?.getSnapshot();
|
|
141
|
+
if (!parentSnapshot || !(0, import_xstate2.isMachineSnapshot)(parentSnapshot)) {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
const schemas = parentSnapshot.machine.schemas;
|
|
145
|
+
const eventSchemaMap = schemas.events ?? {};
|
|
119
146
|
const transitions = getAllTransitions(self._parent.getSnapshot());
|
|
120
147
|
const functionNameMapping = {};
|
|
121
148
|
const tools = transitions.filter((t) => {
|
|
@@ -127,10 +154,10 @@ function fromEventChoice(openai, agentSettings, inputFn, options) {
|
|
|
127
154
|
type: "function",
|
|
128
155
|
function: {
|
|
129
156
|
name,
|
|
130
|
-
description: t.description ??
|
|
157
|
+
description: t.description ?? eventSchemaMap[t.eventType]?.description,
|
|
131
158
|
parameters: {
|
|
132
159
|
type: "object",
|
|
133
|
-
properties:
|
|
160
|
+
properties: eventSchemaMap[t.eventType]?.properties ?? {}
|
|
134
161
|
}
|
|
135
162
|
}
|
|
136
163
|
};
|
|
@@ -167,33 +194,66 @@ function fromEventChoice(openai, agentSettings, inputFn, options) {
|
|
|
167
194
|
}
|
|
168
195
|
);
|
|
169
196
|
}
|
|
170
|
-
function
|
|
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
|
+
}
|
|
241
|
+
function createOpenAIAdapter(openai, settings) {
|
|
171
242
|
const agentSettings = {
|
|
172
243
|
model: settings.model,
|
|
173
|
-
schemas: {
|
|
174
|
-
context: {
|
|
175
|
-
type: "object",
|
|
176
|
-
properties: settings.context,
|
|
177
|
-
additionalProperties: false
|
|
178
|
-
},
|
|
179
|
-
events: createEventSchemas(settings.events)
|
|
180
|
-
},
|
|
181
|
-
types: {},
|
|
182
244
|
fromEvent: (input) => (
|
|
183
|
-
// @ts-ignore
|
|
184
|
-
|
|
245
|
+
// @ts-ignore infinitely deep
|
|
246
|
+
fromEvent(openai, agentSettings, input, { execute: true })
|
|
185
247
|
),
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
fromChatCompletionStream: (input) => fromChatCompletionStream(openai, agentSettings, input)
|
|
248
|
+
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
249
|
+
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
250
|
+
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input)
|
|
190
251
|
};
|
|
191
252
|
return agentSettings;
|
|
192
253
|
}
|
|
193
254
|
// Annotate the CommonJS export names for ESM import in node:
|
|
194
255
|
0 && (module.exports = {
|
|
195
256
|
createAgent,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
fromEventChoice
|
|
257
|
+
createOpenAIAdapter,
|
|
258
|
+
createSchemas
|
|
199
259
|
});
|
|
@@ -3,7 +3,7 @@ import { fromPromise } from 'xstate';
|
|
|
3
3
|
export const getFromTerminal = fromPromise<string, string>(
|
|
4
4
|
async ({ input }) => {
|
|
5
5
|
const topic = await new Promise<string>((res) => {
|
|
6
|
-
console.log(input);
|
|
6
|
+
console.log(input + '\n');
|
|
7
7
|
const listener = (data: Buffer) => {
|
|
8
8
|
const result = data.toString().trim();
|
|
9
9
|
process.stdin.off('data', listener);
|
package/examples/joke.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
createActor,
|
|
5
|
-
fromCallback,
|
|
6
|
-
fromPromise,
|
|
7
|
-
log,
|
|
8
|
-
setup,
|
|
9
|
-
} from 'xstate';
|
|
10
|
-
import { createAgent } from '../src';
|
|
2
|
+
import { assign, fromCallback, fromPromise, log, setup } from 'xstate';
|
|
3
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
11
4
|
import { loadingAnimation } from './helpers/loader';
|
|
12
5
|
|
|
13
6
|
const openai = new OpenAI({
|
|
14
7
|
apiKey: process.env.OPENAI_API_KEY,
|
|
15
8
|
});
|
|
16
9
|
|
|
17
|
-
const
|
|
18
|
-
model: 'gpt-3.5-turbo-1106',
|
|
10
|
+
const schemas = createSchemas({
|
|
19
11
|
context: {
|
|
20
12
|
topic: { type: 'string' },
|
|
21
13
|
jokes: {
|
|
@@ -23,18 +15,35 @@ const agent = createAgent(openai, {
|
|
|
23
15
|
items: {
|
|
24
16
|
type: 'string',
|
|
25
17
|
},
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
},
|
|
19
|
+
desire: { type: ['string', 'null'] as const },
|
|
20
|
+
lastRating: { type: ['string', 'null'] as const },
|
|
21
|
+
},
|
|
22
|
+
events: {
|
|
23
|
+
askForTopic: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
topic: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
endJokes: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {},
|
|
28
34
|
},
|
|
29
35
|
},
|
|
30
|
-
events: {},
|
|
31
36
|
});
|
|
32
37
|
|
|
33
|
-
const
|
|
38
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
39
|
+
model: 'gpt-3.5-turbo-1106',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const getJokeCompletion = adapter.fromChat(
|
|
34
43
|
(topic: string) => `Tell me a joke about ${topic}.`
|
|
35
44
|
);
|
|
36
45
|
|
|
37
|
-
const rateJoke =
|
|
46
|
+
const rateJoke = adapter.fromChat(
|
|
38
47
|
(joke: string) => `Rate this joke on a scale of 1 to 10: ${joke}`
|
|
39
48
|
);
|
|
40
49
|
|
|
@@ -52,7 +61,7 @@ const getTopic = fromPromise(async () => {
|
|
|
52
61
|
return topic;
|
|
53
62
|
});
|
|
54
63
|
|
|
55
|
-
const decide =
|
|
64
|
+
const decide = adapter.fromEvent(
|
|
56
65
|
(lastRating: string) =>
|
|
57
66
|
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
58
67
|
);
|
|
@@ -96,15 +105,8 @@ const loader = fromCallback(({ input }: { input: string }) => {
|
|
|
96
105
|
});
|
|
97
106
|
|
|
98
107
|
const jokeMachine = setup({
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
topic: string;
|
|
102
|
-
jokes: string[];
|
|
103
|
-
desire: string | null;
|
|
104
|
-
lastRating: string | null;
|
|
105
|
-
},
|
|
106
|
-
input: {} as { topic: string },
|
|
107
|
-
},
|
|
108
|
+
schemas,
|
|
109
|
+
types: schemas.types,
|
|
108
110
|
actors: {
|
|
109
111
|
getJokeCompletion,
|
|
110
112
|
getTopic,
|
|
@@ -146,7 +148,7 @@ const jokeMachine = setup({
|
|
|
146
148
|
event.output.choices[0]!.message.content!
|
|
147
149
|
),
|
|
148
150
|
}),
|
|
149
|
-
log((x) => x.context.jokes.at(-1)),
|
|
151
|
+
log((x) => `\n` + x.context.jokes.at(-1)),
|
|
150
152
|
],
|
|
151
153
|
target: 'rateJoke',
|
|
152
154
|
},
|
|
@@ -168,7 +170,7 @@ const jokeMachine = setup({
|
|
|
168
170
|
lastRating: ({ event }) =>
|
|
169
171
|
event.output.choices[0]!.message.content!,
|
|
170
172
|
}),
|
|
171
|
-
log(({ context }) => context.lastRating),
|
|
173
|
+
log(({ context }) => '\n' + context.lastRating),
|
|
172
174
|
],
|
|
173
175
|
target: 'decide',
|
|
174
176
|
},
|
|
@@ -210,5 +212,5 @@ const jokeMachine = setup({
|
|
|
210
212
|
},
|
|
211
213
|
});
|
|
212
214
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
+
const agent = createAgent(jokeMachine);
|
|
216
|
+
agent.start();
|
|
File without changes
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { assign, setup, assertEvent
|
|
1
|
+
import { assign, setup, assertEvent } from 'xstate';
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
|
-
import { createAgent } from '../src
|
|
3
|
+
import { createOpenAIAdapter, createSchemas, createAgent } from '../src';
|
|
4
4
|
|
|
5
5
|
const openai = new OpenAI({
|
|
6
6
|
apiKey: process.env.OPENAI_API_KEY,
|
|
@@ -8,8 +8,7 @@ const openai = new OpenAI({
|
|
|
8
8
|
|
|
9
9
|
type Player = 'x' | 'o';
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
model: 'gpt-4-1106-preview',
|
|
11
|
+
const schemas = createSchemas({
|
|
13
12
|
context: {
|
|
14
13
|
board: {
|
|
15
14
|
type: 'array',
|
|
@@ -69,16 +68,20 @@ const agent = createAgent(openai, {
|
|
|
69
68
|
},
|
|
70
69
|
});
|
|
71
70
|
|
|
71
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
72
|
+
model: 'gpt-4-1106-preview',
|
|
73
|
+
});
|
|
74
|
+
|
|
72
75
|
const initialContext = {
|
|
73
76
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
74
77
|
moves: 0,
|
|
75
78
|
player: 'x' as Player,
|
|
76
79
|
gameReport: '',
|
|
77
80
|
events: [],
|
|
78
|
-
} satisfies typeof
|
|
81
|
+
} satisfies typeof schemas.types.context;
|
|
79
82
|
|
|
80
|
-
const bot =
|
|
81
|
-
({ context }: { context: typeof
|
|
83
|
+
const bot = adapter.fromEvent(
|
|
84
|
+
({ context }: { context: typeof schemas.types.context }) => `
|
|
82
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.
|
|
83
86
|
|
|
84
87
|
${JSON.stringify(context, null, 2)}
|
|
@@ -86,11 +89,11 @@ ${JSON.stringify(context, null, 2)}
|
|
|
86
89
|
Execute the single best next move to try to win the game. Do not play on an existing cell.`
|
|
87
90
|
);
|
|
88
91
|
|
|
89
|
-
const gameReporter =
|
|
92
|
+
const gameReporter = adapter.fromChatStream(
|
|
90
93
|
({
|
|
91
94
|
context,
|
|
92
95
|
}: {
|
|
93
|
-
context: typeof
|
|
96
|
+
context: typeof schemas.types.context;
|
|
94
97
|
}) => `Here is the game board:
|
|
95
98
|
|
|
96
99
|
${JSON.stringify(context.board, null, 2)}
|
|
@@ -124,7 +127,8 @@ function getWinner(board: typeof initialContext.board): Player | null {
|
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
export const ticTacToeMachine = setup({
|
|
127
|
-
|
|
130
|
+
schemas,
|
|
131
|
+
types: schemas.types,
|
|
128
132
|
actors: {
|
|
129
133
|
bot,
|
|
130
134
|
gameReporter,
|
|
@@ -248,14 +252,8 @@ export const ticTacToeMachine = setup({
|
|
|
248
252
|
},
|
|
249
253
|
});
|
|
250
254
|
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
if (e.type === '@xstate.event') {
|
|
254
|
-
console.log(e.event);
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
actor.subscribe((s) => {
|
|
255
|
+
const agent = createAgent(ticTacToeMachine);
|
|
256
|
+
agent.subscribe((s) => {
|
|
259
257
|
console.log(s.value, s.context);
|
|
260
258
|
});
|
|
261
|
-
|
|
259
|
+
agent.start();
|
package/examples/weather.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { createAgent,
|
|
3
|
-
import { assign,
|
|
2
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
3
|
+
import { assign, fromPromise, log, setup } from 'xstate';
|
|
4
4
|
import { getFromTerminal } from './helpers/helpers';
|
|
5
5
|
|
|
6
6
|
async function searchTavily(
|
|
@@ -23,6 +23,7 @@ async function searchTavily(
|
|
|
23
23
|
},
|
|
24
24
|
body: JSON.stringify(body),
|
|
25
25
|
});
|
|
26
|
+
|
|
26
27
|
const json = await response.json();
|
|
27
28
|
if (!response.ok) {
|
|
28
29
|
throw new Error(
|
|
@@ -39,8 +40,7 @@ const openai = new OpenAI({
|
|
|
39
40
|
apiKey: process.env.OPENAI_API_KEY,
|
|
40
41
|
});
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
-
model: 'gpt-4-1106-preview',
|
|
43
|
+
const schemas = createSchemas({
|
|
44
44
|
context: {
|
|
45
45
|
location: { type: 'string' },
|
|
46
46
|
history: { type: 'array', items: { type: 'string' } },
|
|
@@ -64,17 +64,27 @@ const agent = createAgent(openai, {
|
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
68
|
+
model: 'gpt-4-1106-preview',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const getWeather = fromPromise(async ({ input }: { input: string }) => {
|
|
72
|
+
const results = await searchTavily(
|
|
73
|
+
`Get the weather for this location: ${input}`,
|
|
74
|
+
{
|
|
75
|
+
maxResults: 5,
|
|
76
|
+
apiKey: process.env.TAVILY_API_KEY!,
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
return results;
|
|
80
|
+
});
|
|
81
|
+
|
|
67
82
|
const machine = setup({
|
|
68
|
-
|
|
83
|
+
schemas,
|
|
84
|
+
types: schemas.types,
|
|
69
85
|
actors: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
maxResults: 5,
|
|
73
|
-
apiKey: process.env.TAVILY_API_KEY!,
|
|
74
|
-
});
|
|
75
|
-
return results;
|
|
76
|
-
}),
|
|
77
|
-
decide: agent.fromEvent(
|
|
86
|
+
getWeather,
|
|
87
|
+
decide: adapter.fromEvent(
|
|
78
88
|
(input: string) =>
|
|
79
89
|
`Decide what to do based on the given input, which may or may not be a location: ${input}`
|
|
80
90
|
),
|
|
@@ -121,9 +131,8 @@ const machine = setup({
|
|
|
121
131
|
gettingWeather: {
|
|
122
132
|
entry: log('Getting weather...'),
|
|
123
133
|
invoke: {
|
|
124
|
-
src: '
|
|
125
|
-
input: ({ context }) =>
|
|
126
|
-
`Get the weather for this location: ${context.location}`,
|
|
134
|
+
src: 'getWeather',
|
|
135
|
+
input: ({ context }) => context.location,
|
|
127
136
|
onDone: {
|
|
128
137
|
actions: [
|
|
129
138
|
log(({ event }) => event.output),
|
|
@@ -144,4 +153,8 @@ const machine = setup({
|
|
|
144
153
|
},
|
|
145
154
|
});
|
|
146
155
|
|
|
147
|
-
|
|
156
|
+
createAgent(machine, {
|
|
157
|
+
input: {
|
|
158
|
+
location: 'New York',
|
|
159
|
+
},
|
|
160
|
+
}).start();
|
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,13 +17,14 @@
|
|
|
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.6.0"
|
|
27
28
|
},
|
|
28
29
|
"packageManager": "pnpm@8.11.0",
|
|
29
30
|
"scripts": {
|
|
@@ -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
|
+
});
|
|
@@ -4,26 +4,18 @@ import {
|
|
|
4
4
|
ObservableActorLogic,
|
|
5
5
|
Observer,
|
|
6
6
|
PromiseActorLogic,
|
|
7
|
-
Values,
|
|
8
7
|
fromObservable,
|
|
9
8
|
fromPromise,
|
|
10
|
-
|
|
9
|
+
isMachineSnapshot,
|
|
11
10
|
toObserver,
|
|
12
11
|
} from 'xstate';
|
|
13
|
-
import { getAllTransitions } from '
|
|
14
|
-
import {
|
|
15
|
-
ContextSchema,
|
|
16
|
-
EventSchemas,
|
|
17
|
-
ConvertContextToJSONSchema,
|
|
18
|
-
ConvertToJSONSchemas,
|
|
19
|
-
createEventSchemas,
|
|
20
|
-
} from './utils';
|
|
21
|
-
import { FromSchema } from 'json-schema-to-ts';
|
|
12
|
+
import { getAllTransitions } from '../utils';
|
|
22
13
|
import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
23
14
|
import {
|
|
24
15
|
ChatCompletionCreateParamsBase,
|
|
25
16
|
ChatCompletionCreateParamsStreaming,
|
|
26
17
|
} from 'openai/resources/chat/completions';
|
|
18
|
+
import { StatelyAgentAdapter, Tool } from '../types';
|
|
27
19
|
|
|
28
20
|
/**
|
|
29
21
|
* Creates [promise actor logic](https://stately.ai/docs/promise-actors) that uses the OpenAI API to generate a completion.
|
|
@@ -34,7 +26,7 @@ import {
|
|
|
34
26
|
*/
|
|
35
27
|
export function fromChatCompletion<TInput>(
|
|
36
28
|
openai: OpenAI,
|
|
37
|
-
agentSettings:
|
|
29
|
+
agentSettings: OpenAIAdapterOutput<any>,
|
|
38
30
|
inputFn: (
|
|
39
31
|
input: TInput
|
|
40
32
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
@@ -67,9 +59,9 @@ export function fromChatCompletion<TInput>(
|
|
|
67
59
|
* @param openai The OpenAI instance to use.
|
|
68
60
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
69
61
|
*/
|
|
70
|
-
export function
|
|
62
|
+
export function fromChatStream<TInput>(
|
|
71
63
|
openai: OpenAI,
|
|
72
|
-
agentSettings:
|
|
64
|
+
agentSettings: OpenAIAdapterOutput<any>,
|
|
73
65
|
inputFn: (
|
|
74
66
|
input: TInput
|
|
75
67
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
|
@@ -126,9 +118,9 @@ export function fromChatCompletionStream<TInput>(
|
|
|
126
118
|
* @param openai The OpenAI instance to use.
|
|
127
119
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
128
120
|
*/
|
|
129
|
-
export function
|
|
121
|
+
export function fromEvent<TInput>(
|
|
130
122
|
openai: OpenAI,
|
|
131
|
-
agentSettings:
|
|
123
|
+
agentSettings: OpenAIAdapterOutput<any>,
|
|
132
124
|
inputFn: (
|
|
133
125
|
input: TInput
|
|
134
126
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming,
|
|
@@ -142,6 +134,15 @@ export function fromEventChoice<TInput>(
|
|
|
142
134
|
) {
|
|
143
135
|
return fromPromise<AnyEventObject[] | undefined, TInput>(
|
|
144
136
|
async ({ input, self, system }) => {
|
|
137
|
+
const parentSnapshot = self._parent?.getSnapshot();
|
|
138
|
+
|
|
139
|
+
if (!parentSnapshot || !isMachineSnapshot(parentSnapshot)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const schemas = parentSnapshot.machine.schemas as any;
|
|
144
|
+
const eventSchemaMap = schemas.events ?? {};
|
|
145
|
+
|
|
145
146
|
const transitions = getAllTransitions(self._parent!.getSnapshot());
|
|
146
147
|
const functionNameMapping: Record<string, string> = {};
|
|
147
148
|
const tools = transitions
|
|
@@ -156,12 +157,10 @@ export function fromEventChoice<TInput>(
|
|
|
156
157
|
function: {
|
|
157
158
|
name,
|
|
158
159
|
description:
|
|
159
|
-
t.description ??
|
|
160
|
-
agentSettings.schemas.events[t.eventType]?.description,
|
|
160
|
+
t.description ?? eventSchemaMap[t.eventType]?.description,
|
|
161
161
|
parameters: {
|
|
162
162
|
type: 'object',
|
|
163
|
-
properties:
|
|
164
|
-
agentSettings.schemas.events[t.eventType]?.properties ?? {},
|
|
163
|
+
properties: eventSchemaMap[t.eventType]?.properties ?? {},
|
|
165
164
|
},
|
|
166
165
|
},
|
|
167
166
|
} as const;
|
|
@@ -208,35 +207,124 @@ export function fromEventChoice<TInput>(
|
|
|
208
207
|
);
|
|
209
208
|
}
|
|
210
209
|
|
|
211
|
-
|
|
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
|
+
|
|
299
|
+
interface OpenAIAdapterOutput<
|
|
212
300
|
T extends {
|
|
213
301
|
model: ChatCompletionCreateParamsBase['model'];
|
|
214
|
-
context: ContextSchema;
|
|
215
|
-
events: EventSchemas;
|
|
216
302
|
}
|
|
217
303
|
> {
|
|
218
304
|
model: T['model'];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
events: FromSchema<Values<ConvertToJSONSchemas<T['events']>>>;
|
|
223
|
-
};
|
|
305
|
+
/**
|
|
306
|
+
* Determines which event to send to the parent state machine actor based on the prompt.
|
|
307
|
+
*/
|
|
224
308
|
fromEvent: <TInput>(
|
|
225
|
-
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
309
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming,
|
|
310
|
+
options?: {
|
|
311
|
+
/**
|
|
312
|
+
* Immediately execute sending the event to the parent actor.
|
|
313
|
+
* @default true
|
|
314
|
+
*/
|
|
315
|
+
execute?: boolean;
|
|
316
|
+
}
|
|
317
|
+
) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
318
|
+
/**
|
|
319
|
+
* Creates promise actor logic that resolves with a chat completion.
|
|
320
|
+
*/
|
|
321
|
+
fromChat: <TInput>(
|
|
237
322
|
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
238
323
|
) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
239
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Creates observable actor logic that emits a chat completion stream.
|
|
326
|
+
*/
|
|
327
|
+
fromChatStream: <TInput>(
|
|
240
328
|
inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming
|
|
241
329
|
) => ObservableActorLogic<
|
|
242
330
|
OpenAI.Chat.Completions.ChatCompletionChunk,
|
|
@@ -244,34 +332,20 @@ interface CreateAgentOutput<
|
|
|
244
332
|
>;
|
|
245
333
|
}
|
|
246
334
|
|
|
247
|
-
export function
|
|
335
|
+
export function createOpenAIAdapter<
|
|
248
336
|
T extends {
|
|
249
337
|
model: ChatCompletionCreateParamsBase['model'];
|
|
250
|
-
context: ContextSchema;
|
|
251
|
-
events: EventSchemas;
|
|
252
338
|
}
|
|
253
|
-
>(openai: OpenAI, settings: T):
|
|
254
|
-
const agentSettings:
|
|
339
|
+
>(openai: OpenAI, settings: T): StatelyAgentAdapter {
|
|
340
|
+
const agentSettings: StatelyAgentAdapter = {
|
|
255
341
|
model: settings.model,
|
|
256
|
-
schemas: {
|
|
257
|
-
context: {
|
|
258
|
-
type: 'object',
|
|
259
|
-
properties: settings.context,
|
|
260
|
-
additionalProperties: false,
|
|
261
|
-
},
|
|
262
|
-
events: createEventSchemas(settings.events),
|
|
263
|
-
} as any,
|
|
264
|
-
types: {} as any,
|
|
265
342
|
fromEvent: (input) =>
|
|
266
|
-
// @ts-ignore
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
fromChatCompletion(openai, agentSettings, input),
|
|
272
|
-
fromChatCompletionStream: (input) =>
|
|
273
|
-
fromChatCompletionStream(openai, agentSettings, input),
|
|
343
|
+
// @ts-ignore infinitely deep
|
|
344
|
+
fromEvent(openai, agentSettings, input, { execute: true }) as any,
|
|
345
|
+
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
346
|
+
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
347
|
+
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input),
|
|
274
348
|
};
|
|
275
349
|
|
|
276
|
-
return agentSettings
|
|
350
|
+
return agentSettings;
|
|
277
351
|
}
|
package/src/agent.ts
ADDED
package/src/index.ts
CHANGED
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Values } from 'xstate';
|
|
2
|
+
import {
|
|
3
|
+
ContextSchema,
|
|
4
|
+
EventSchemas,
|
|
5
|
+
ConvertContextToJSONSchema,
|
|
6
|
+
ConvertToJSONSchemas,
|
|
7
|
+
createEventSchemas,
|
|
8
|
+
} from './utils';
|
|
9
|
+
import { FromSchema } from 'json-schema-to-ts';
|
|
10
|
+
|
|
11
|
+
export function createSchemas<
|
|
12
|
+
TContextSchema extends ContextSchema,
|
|
13
|
+
TEventSchemas extends EventSchemas
|
|
14
|
+
>({
|
|
15
|
+
context,
|
|
16
|
+
events,
|
|
17
|
+
}: {
|
|
18
|
+
context: TContextSchema;
|
|
19
|
+
events: TEventSchemas;
|
|
20
|
+
}): {
|
|
21
|
+
context: ConvertContextToJSONSchema<TContextSchema>;
|
|
22
|
+
events: ConvertToJSONSchemas<TEventSchemas>;
|
|
23
|
+
types: {
|
|
24
|
+
context: FromSchema<ConvertContextToJSONSchema<TContextSchema>>;
|
|
25
|
+
events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
|
|
26
|
+
};
|
|
27
|
+
} {
|
|
28
|
+
return {
|
|
29
|
+
context: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: context,
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
required: Object.keys(context),
|
|
34
|
+
},
|
|
35
|
+
events: createEventSchemas(events),
|
|
36
|
+
types: {} as any,
|
|
37
|
+
};
|
|
38
|
+
}
|
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
|
+
}
|