@statelyai/agent 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.vscode/launch.json +17 -0
- package/CHANGELOG.md +7 -0
- package/dist/index.d.ts +64 -6
- package/dist/index.js +72 -7
- package/examples/joke.ts +119 -153
- package/examples/ticTacToe.ts +112 -77
- package/package.json +12 -8
- package/src/index.ts +1 -0
- package/src/openai.ts +124 -9
- package/src/utils.ts +59 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changesets
|
|
2
|
+
|
|
3
|
+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
4
|
+
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
5
|
+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
6
|
+
|
|
7
|
+
We have a quick list of common questions to get you started engaging with this project in
|
|
8
|
+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "restricted",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Launch Program",
|
|
11
|
+
"skipFiles": ["<node_internals>/**"],
|
|
12
|
+
"program": "${file}",
|
|
13
|
+
"preLaunchTask": "tsc: build - tsconfig.json",
|
|
14
|
+
"outFiles": ["${workspaceFolder}/**/*.js"]
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
package/CHANGELOG.md
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
-
import * as xstate from 'xstate';
|
|
2
|
-
import { AnyEventObject } from 'xstate';
|
|
3
1
|
import OpenAI from 'openai';
|
|
2
|
+
import { Prop, PromiseActorLogic, ObservableActorLogic, AnyEventObject, Values } from 'xstate';
|
|
3
|
+
import { JSONSchema7 } from 'json-schema-to-ts/lib/types/definitions';
|
|
4
|
+
import { FromSchema } from 'json-schema-to-ts';
|
|
5
|
+
import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
6
|
+
import { ChatCompletionCreateParamsBase, ChatCompletionCreateParamsStreaming } from 'openai/resources/chat/completions';
|
|
7
|
+
|
|
8
|
+
type EventSchemas = {
|
|
9
|
+
[key: string]: {
|
|
10
|
+
description?: string;
|
|
11
|
+
properties?: {
|
|
12
|
+
[key: string]: JSONSchema7;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
interface ContextSchema {
|
|
17
|
+
[key: string]: JSONSchema7;
|
|
18
|
+
}
|
|
19
|
+
type ConvertToJSONSchemas<T> = {
|
|
20
|
+
[K in keyof T]: {
|
|
21
|
+
properties: {
|
|
22
|
+
type: {
|
|
23
|
+
const: K;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
type: 'object';
|
|
27
|
+
required: Array<keyof Prop<T[K], 'properties'> | 'type'>;
|
|
28
|
+
additionalProperties: false;
|
|
29
|
+
} & T[K];
|
|
30
|
+
} & {};
|
|
31
|
+
type ConvertContextToJSONSchema<T extends ContextSchema> = {
|
|
32
|
+
type: 'object';
|
|
33
|
+
properties: T;
|
|
34
|
+
readonly required: Array<keyof T & string>;
|
|
35
|
+
additionalProperties: false;
|
|
36
|
+
};
|
|
4
37
|
|
|
5
38
|
/**
|
|
6
39
|
* Creates [promise actor logic](https://stately.ai/docs/promise-actors) that uses the OpenAI API to generate a completion.
|
|
@@ -9,20 +42,45 @@ import OpenAI from 'openai';
|
|
|
9
42
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
10
43
|
*
|
|
11
44
|
*/
|
|
12
|
-
declare function fromChatCompletion<TInput>(openai: OpenAI, inputFn: (input: TInput) => OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming):
|
|
45
|
+
declare function fromChatCompletion<TInput>(openai: OpenAI, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming): PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
13
46
|
/**
|
|
14
47
|
* Creates [observable actor logic](https://stately.ai/docs/observable-actors) that uses the OpenAI API to generate a completion stream.
|
|
15
48
|
*
|
|
16
49
|
* @param openai The OpenAI instance to use.
|
|
17
50
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
18
51
|
*/
|
|
19
|
-
declare function fromChatCompletionStream<TInput>(openai: OpenAI, inputFn: (input: TInput) => OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming):
|
|
52
|
+
declare function fromChatCompletionStream<TInput>(openai: OpenAI, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming): ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
|
|
20
53
|
/**
|
|
21
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.
|
|
22
55
|
*
|
|
23
56
|
* @param openai The OpenAI instance to use.
|
|
24
57
|
* @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
|
|
25
58
|
*/
|
|
26
|
-
declare function fromEventChoice<TInput>(openai: OpenAI,
|
|
59
|
+
declare function fromEventChoice<TInput>(openai: OpenAI, machineTypes: {
|
|
60
|
+
schemas: {
|
|
61
|
+
context: ContextSchema;
|
|
62
|
+
events: EventSchemas;
|
|
63
|
+
};
|
|
64
|
+
}, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming): PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
65
|
+
interface CreateAgentOutput<T extends {
|
|
66
|
+
model: ChatCompletionCreateParamsBase['model'];
|
|
67
|
+
context: ContextSchema;
|
|
68
|
+
events: EventSchemas;
|
|
69
|
+
}> {
|
|
70
|
+
model: T['model'];
|
|
71
|
+
schemas: T;
|
|
72
|
+
types: {
|
|
73
|
+
context: FromSchema<ConvertContextToJSONSchema<T['context']>>;
|
|
74
|
+
events: FromSchema<Values<ConvertToJSONSchemas<T['events']>>>;
|
|
75
|
+
};
|
|
76
|
+
fromEventChoice: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<FromSchema<Values<ConvertToJSONSchemas<T['events']>>>[] | undefined, TInput>;
|
|
77
|
+
fromChatCompletion: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
78
|
+
fromChatCompletionStream: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming) => ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
|
|
79
|
+
}
|
|
80
|
+
declare function createAgent<T extends {
|
|
81
|
+
model: ChatCompletionCreateParamsBase['model'];
|
|
82
|
+
context: ContextSchema;
|
|
83
|
+
events: EventSchemas;
|
|
84
|
+
}>(openai: OpenAI, settings: T): CreateAgentOutput<T>;
|
|
27
85
|
|
|
28
|
-
export { fromChatCompletion, fromChatCompletionStream, fromEventChoice };
|
|
86
|
+
export { createAgent, fromChatCompletion, fromChatCompletionStream, fromEventChoice };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
createAgent: () => createAgent,
|
|
23
24
|
fromChatCompletion: () => fromChatCompletion,
|
|
24
25
|
fromChatCompletionStream: () => fromChatCompletionStream,
|
|
25
26
|
fromEventChoice: () => fromEventChoice
|
|
@@ -35,13 +36,40 @@ function getAllTransitions(state) {
|
|
|
35
36
|
const transitions = nodes.map((node) => [...node.transitions.values()]).flat(2);
|
|
36
37
|
return transitions;
|
|
37
38
|
}
|
|
39
|
+
function createEventSchemas(eventSchemaMap) {
|
|
40
|
+
const resolvedEventSchemaMap = {};
|
|
41
|
+
for (const [key, schema] of Object.entries(eventSchemaMap)) {
|
|
42
|
+
resolvedEventSchemaMap[key] = {
|
|
43
|
+
type: "object",
|
|
44
|
+
required: ["type"],
|
|
45
|
+
properties: {
|
|
46
|
+
type: {
|
|
47
|
+
const: key
|
|
48
|
+
},
|
|
49
|
+
...schema.properties
|
|
50
|
+
},
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
...schema
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return resolvedEventSchemaMap;
|
|
56
|
+
}
|
|
38
57
|
|
|
39
58
|
// src/openai.ts
|
|
40
59
|
function fromChatCompletion(openai, inputFn) {
|
|
41
60
|
return (0, import_xstate.fromPromise)(
|
|
42
61
|
async ({ input }) => {
|
|
43
62
|
const openAiInput = inputFn(input);
|
|
44
|
-
const
|
|
63
|
+
const params = typeof openAiInput === "string" ? {
|
|
64
|
+
model: "gpt-3.5-turbo-1106",
|
|
65
|
+
messages: [
|
|
66
|
+
{
|
|
67
|
+
role: "user",
|
|
68
|
+
content: openAiInput
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
} : openAiInput;
|
|
72
|
+
const response = await openai.chat.completions.create(params);
|
|
45
73
|
return response;
|
|
46
74
|
}
|
|
47
75
|
);
|
|
@@ -52,8 +80,17 @@ function fromChatCompletionStream(openai, inputFn) {
|
|
|
52
80
|
const observers = /* @__PURE__ */ new Set();
|
|
53
81
|
(async () => {
|
|
54
82
|
const openAiInput = inputFn(input);
|
|
83
|
+
const resolvedParams = typeof openAiInput === "string" ? {
|
|
84
|
+
model: "gpt-3.5-turbo-1106",
|
|
85
|
+
messages: [
|
|
86
|
+
{
|
|
87
|
+
role: "user",
|
|
88
|
+
content: openAiInput
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
} : openAiInput;
|
|
55
92
|
const stream = await openai.chat.completions.create({
|
|
56
|
-
...
|
|
93
|
+
...resolvedParams,
|
|
57
94
|
stream: true
|
|
58
95
|
});
|
|
59
96
|
for await (const part of stream) {
|
|
@@ -76,7 +113,7 @@ function fromChatCompletionStream(openai, inputFn) {
|
|
|
76
113
|
}
|
|
77
114
|
);
|
|
78
115
|
}
|
|
79
|
-
function fromEventChoice(openai, inputFn) {
|
|
116
|
+
function fromEventChoice(openai, machineTypes, inputFn) {
|
|
80
117
|
return (0, import_xstate.fromPromise)(
|
|
81
118
|
async ({ input, self }) => {
|
|
82
119
|
const transitions = getAllTransitions(self._parent.getSnapshot());
|
|
@@ -90,17 +127,26 @@ function fromEventChoice(openai, inputFn) {
|
|
|
90
127
|
type: "function",
|
|
91
128
|
function: {
|
|
92
129
|
name,
|
|
93
|
-
description: t.description,
|
|
130
|
+
description: t.description ?? machineTypes.schemas.events[t.eventType]?.description,
|
|
94
131
|
parameters: {
|
|
95
132
|
type: "object",
|
|
96
|
-
properties: t.
|
|
133
|
+
properties: machineTypes.schemas.events[t.eventType]?.properties ?? {}
|
|
97
134
|
}
|
|
98
135
|
}
|
|
99
136
|
};
|
|
100
137
|
});
|
|
101
138
|
const openAiInput = inputFn(input);
|
|
139
|
+
const completionParams = typeof openAiInput === "string" ? {
|
|
140
|
+
model: "gpt-4-1106-preview",
|
|
141
|
+
messages: [
|
|
142
|
+
{
|
|
143
|
+
role: "user",
|
|
144
|
+
content: openAiInput
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
} : openAiInput;
|
|
102
148
|
const completion = await openai.chat.completions.create({
|
|
103
|
-
...
|
|
149
|
+
...completionParams,
|
|
104
150
|
tools
|
|
105
151
|
});
|
|
106
152
|
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
@@ -112,12 +158,31 @@ function fromEventChoice(openai, inputFn) {
|
|
|
112
158
|
};
|
|
113
159
|
});
|
|
114
160
|
}
|
|
115
|
-
return
|
|
161
|
+
return void 0;
|
|
116
162
|
}
|
|
117
163
|
);
|
|
118
164
|
}
|
|
165
|
+
function createAgent(openai, settings) {
|
|
166
|
+
const obj = {
|
|
167
|
+
model: settings.model,
|
|
168
|
+
schemas: {
|
|
169
|
+
context: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: settings.context,
|
|
172
|
+
additionalProperties: false
|
|
173
|
+
},
|
|
174
|
+
events: createEventSchemas(settings.events)
|
|
175
|
+
},
|
|
176
|
+
types: {},
|
|
177
|
+
fromEventChoice: (input) => fromEventChoice(openai, obj, input),
|
|
178
|
+
fromChatCompletion: (input) => fromChatCompletion(openai, input),
|
|
179
|
+
fromChatCompletionStream: (input) => fromChatCompletionStream(openai, input)
|
|
180
|
+
};
|
|
181
|
+
return obj;
|
|
182
|
+
}
|
|
119
183
|
// Annotate the CommonJS export names for ESM import in node:
|
|
120
184
|
0 && (module.exports = {
|
|
185
|
+
createAgent,
|
|
121
186
|
fromChatCompletion,
|
|
122
187
|
fromChatCompletionStream,
|
|
123
188
|
fromEventChoice
|
package/examples/joke.ts
CHANGED
|
@@ -1,185 +1,151 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { assign, fromPromise, createActor,
|
|
3
|
-
import {
|
|
2
|
+
import { assign, fromPromise, createActor, setup, log, raise } from 'xstate';
|
|
3
|
+
import { createAgent } from '../src';
|
|
4
4
|
|
|
5
5
|
const openai = new OpenAI({
|
|
6
6
|
apiKey: process.env.OPENAI_API_KEY,
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return res.choices[0]?.message.content;
|
|
26
|
-
}
|
|
27
|
-
);
|
|
9
|
+
const agent = createAgent(openai, {
|
|
10
|
+
model: 'gpt-3.5-turbo-1106',
|
|
11
|
+
context: {
|
|
12
|
+
topic: { type: 'string' },
|
|
13
|
+
jokes: {
|
|
14
|
+
type: 'array',
|
|
15
|
+
items: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
},
|
|
18
|
+
desire: { type: ['string', 'null'] },
|
|
19
|
+
lastRating: { type: ['string', 'null'] },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
events: {},
|
|
23
|
+
});
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
async ({ input }: { input: { joke: string } }) => {
|
|
31
|
-
const res = await openai.chat.completions.create({
|
|
32
|
-
messages: [
|
|
33
|
-
{
|
|
34
|
-
role: 'user',
|
|
35
|
-
content: `Rate this joke on a scale of 1 to 10: ${input.joke}`,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
model: 'gpt-3.5-turbo',
|
|
39
|
-
n: 1,
|
|
40
|
-
});
|
|
25
|
+
const promptTemplate = (topic: string) => `Tell me a joke about ${topic}.`;
|
|
41
26
|
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
);
|
|
27
|
+
const getJokeCompletion = agent.fromChatCompletion(promptTemplate);
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
process.stdin.on('data', (data) => {
|
|
50
|
-
const eventType = data.toString().trim();
|
|
51
|
-
res(eventType);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
29
|
+
const rateJoke = agent.fromChatCompletion(
|
|
30
|
+
(joke: string) => `Rate this joke on a scale of 1 to 10: ${joke}`
|
|
31
|
+
);
|
|
54
32
|
|
|
55
|
-
|
|
33
|
+
const getTopic = fromPromise(async () => {
|
|
34
|
+
const topic = await new Promise<string>((res) => {
|
|
35
|
+
console.log('Give me a topic: \n\n');
|
|
36
|
+
const listener = (data: Buffer) => {
|
|
37
|
+
const result = data.toString().trim();
|
|
38
|
+
process.stdin.off('data', listener);
|
|
39
|
+
res(result);
|
|
40
|
+
};
|
|
41
|
+
process.stdin.on('data', listener);
|
|
56
42
|
});
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
context: {} as {
|
|
61
|
-
topic: string;
|
|
62
|
-
jokes: string[];
|
|
63
|
-
desire: string | null;
|
|
64
|
-
lastRating: string | null;
|
|
65
|
-
},
|
|
66
|
-
input: {} as { topic: string },
|
|
67
|
-
},
|
|
68
|
-
actors: {
|
|
69
|
-
getJokeCompletion,
|
|
70
|
-
getTopic,
|
|
71
|
-
rateJoke,
|
|
72
|
-
decide: fromEventChoice(openai, (desire: string) => ({
|
|
73
|
-
model: 'gpt-4-1106-preview',
|
|
74
|
-
messages: [
|
|
75
|
-
{
|
|
76
|
-
role: 'user',
|
|
77
|
-
content: `Execute the function that best satisfies this desire:
|
|
44
|
+
return topic;
|
|
45
|
+
});
|
|
78
46
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
47
|
+
const decide = agent.fromEventChoice(
|
|
48
|
+
(lastRating: string) =>
|
|
49
|
+
`Choose what to do next, given the previous rating of the joke: ${lastRating}`
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const jokeMachine = setup({
|
|
53
|
+
types: {
|
|
54
|
+
context: {} as {
|
|
55
|
+
topic: string;
|
|
56
|
+
jokes: string[];
|
|
57
|
+
desire: string | null;
|
|
58
|
+
lastRating: string | null;
|
|
84
59
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
60
|
+
input: {} as { topic: string },
|
|
61
|
+
},
|
|
62
|
+
actors: {
|
|
63
|
+
getJokeCompletion,
|
|
64
|
+
getTopic,
|
|
65
|
+
rateJoke,
|
|
66
|
+
decide,
|
|
67
|
+
},
|
|
68
|
+
}).createMachine({
|
|
69
|
+
context: ({ input }) => ({
|
|
70
|
+
topic: input.topic,
|
|
71
|
+
jokes: [],
|
|
72
|
+
desire: null,
|
|
73
|
+
lastRating: null,
|
|
74
|
+
}),
|
|
75
|
+
initial: 'waitingForTopic',
|
|
76
|
+
states: {
|
|
77
|
+
waitingForTopic: {
|
|
78
|
+
invoke: {
|
|
79
|
+
src: 'getTopic',
|
|
80
|
+
onDone: {
|
|
81
|
+
actions: assign({
|
|
82
|
+
topic: ({ event }) => event.output,
|
|
83
|
+
}),
|
|
84
|
+
target: 'tellingJoke',
|
|
103
85
|
},
|
|
104
86
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
87
|
+
},
|
|
88
|
+
tellingJoke: {
|
|
89
|
+
invoke: {
|
|
90
|
+
src: 'getJokeCompletion',
|
|
91
|
+
input: ({ context }) => context.topic,
|
|
92
|
+
onDone: {
|
|
93
|
+
actions: [
|
|
94
|
+
assign({
|
|
111
95
|
jokes: ({ context, event }) =>
|
|
112
|
-
context.jokes.concat(event.output
|
|
96
|
+
context.jokes.concat(event.output.choices[0]!.message.content!),
|
|
113
97
|
}),
|
|
114
|
-
|
|
115
|
-
|
|
98
|
+
log((x) => x.context.jokes.at(-1)),
|
|
99
|
+
],
|
|
100
|
+
target: 'rateJoke',
|
|
116
101
|
},
|
|
117
102
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
lastRating: ({ event }) =>
|
|
103
|
+
},
|
|
104
|
+
rateJoke: {
|
|
105
|
+
invoke: {
|
|
106
|
+
src: 'rateJoke',
|
|
107
|
+
input: ({ context }) => context.jokes[context.jokes.length - 1]!,
|
|
108
|
+
onDone: {
|
|
109
|
+
actions: [
|
|
110
|
+
assign({
|
|
111
|
+
lastRating: ({ event }) =>
|
|
112
|
+
event.output.choices[0]!.message.content!,
|
|
127
113
|
}),
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
log(({ context }) => context.lastRating),
|
|
115
|
+
],
|
|
116
|
+
target: 'decide',
|
|
130
117
|
},
|
|
131
118
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
119
|
+
},
|
|
120
|
+
decide: {
|
|
121
|
+
invoke: {
|
|
122
|
+
src: 'decide',
|
|
123
|
+
input: ({ context }) => context.lastRating!,
|
|
124
|
+
onDone: {
|
|
125
|
+
actions: [
|
|
126
|
+
log(({ event }) => event),
|
|
127
|
+
raise(({ event }) => event.output![0]!),
|
|
128
|
+
],
|
|
141
129
|
},
|
|
142
130
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
131
|
+
on: {
|
|
132
|
+
askForTopic: {
|
|
133
|
+
target: 'waitingForTopic',
|
|
134
|
+
description:
|
|
135
|
+
'Ask for a new topic, because the last joke rated 6 or lower',
|
|
147
136
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
description:
|
|
152
|
-
'Ask for a new topic, because the last joke was almost perfect',
|
|
153
|
-
},
|
|
154
|
-
endJokes: {
|
|
155
|
-
target: 'end',
|
|
156
|
-
description: 'End the jokes, since the last joke was not too good',
|
|
157
|
-
},
|
|
137
|
+
endJokes: {
|
|
138
|
+
target: 'end',
|
|
139
|
+
description: 'End the jokes, since the last joke rated 7 or higher',
|
|
158
140
|
},
|
|
159
141
|
},
|
|
160
|
-
end: {},
|
|
161
142
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const actor = createActor(chain, {
|
|
165
|
-
input: {
|
|
166
|
-
topic: 'donuts',
|
|
143
|
+
end: {
|
|
144
|
+
type: 'final',
|
|
167
145
|
},
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
actor.subscribe((st) => {
|
|
171
|
-
console.log('State: ', st.value);
|
|
172
|
-
|
|
173
|
-
if (st.context.jokes) {
|
|
174
|
-
console.log('Joke: ', st.context.jokes[st.context.jokes.length - 1]);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
actor.start();
|
|
146
|
+
},
|
|
147
|
+
});
|
|
179
148
|
|
|
180
|
-
|
|
181
|
-
timeout: Infinity,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
149
|
+
const actor = createActor(jokeMachine);
|
|
184
150
|
|
|
185
|
-
start();
|
|
151
|
+
actor.start();
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assign, setup, assertEvent, createActor, raise } from 'xstate';
|
|
2
|
-
import { fromChatCompletionStream, fromEventChoice } from '../src/openai';
|
|
3
2
|
import OpenAI from 'openai';
|
|
3
|
+
import { createAgent } from '../src/openai';
|
|
4
4
|
|
|
5
5
|
const openai = new OpenAI({
|
|
6
6
|
apiKey: process.env.OPENAI_API_KEY,
|
|
@@ -8,64 +8,113 @@ const openai = new OpenAI({
|
|
|
8
8
|
|
|
9
9
|
type Player = 'x' | 'o';
|
|
10
10
|
|
|
11
|
+
const agent = createAgent(openai, {
|
|
12
|
+
model: 'gpt-3.5-turbo-1106',
|
|
13
|
+
context: {
|
|
14
|
+
board: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: {
|
|
17
|
+
type: ['null', 'string'],
|
|
18
|
+
enum: [null, 'x', 'o'],
|
|
19
|
+
},
|
|
20
|
+
minItems: 9,
|
|
21
|
+
maxItems: 9,
|
|
22
|
+
description: 'The board of the tic-tac-toe game',
|
|
23
|
+
},
|
|
24
|
+
moves: {
|
|
25
|
+
type: 'number',
|
|
26
|
+
description: 'The number of moves that have been played',
|
|
27
|
+
},
|
|
28
|
+
player: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
enum: ['x', 'o'],
|
|
31
|
+
description: 'The player whose turn it is',
|
|
32
|
+
},
|
|
33
|
+
winner: {
|
|
34
|
+
type: ['null', 'string'],
|
|
35
|
+
enum: [null, 'x', 'o'],
|
|
36
|
+
description: 'The player who won the game',
|
|
37
|
+
},
|
|
38
|
+
gameReport: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'The game report',
|
|
41
|
+
},
|
|
42
|
+
events: {
|
|
43
|
+
type: 'array',
|
|
44
|
+
items: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
} as const,
|
|
49
|
+
events: {
|
|
50
|
+
'x.play': {
|
|
51
|
+
properties: {
|
|
52
|
+
index: {
|
|
53
|
+
description: 'The index of the cell to play on',
|
|
54
|
+
type: 'number',
|
|
55
|
+
|
|
56
|
+
minimum: 0,
|
|
57
|
+
maximum: 8,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
'o.play': {
|
|
62
|
+
properties: {
|
|
63
|
+
index: {
|
|
64
|
+
description: 'The index of the cell to play on',
|
|
65
|
+
type: 'number',
|
|
66
|
+
minimum: 0,
|
|
67
|
+
maximum: 8,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
reset: {
|
|
72
|
+
properties: {},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
11
77
|
const initialContext = {
|
|
12
78
|
board: Array(9).fill(null) as Array<Player | null>,
|
|
13
79
|
moves: 0,
|
|
14
80
|
player: 'x' as Player,
|
|
15
|
-
winner:
|
|
81
|
+
winner: null as Player | null,
|
|
16
82
|
gameReport: '',
|
|
17
|
-
|
|
83
|
+
events: [],
|
|
84
|
+
} satisfies typeof agent.types.context;
|
|
85
|
+
|
|
86
|
+
const bot = agent.fromEventChoice(
|
|
87
|
+
({ context }: { context: typeof agent.types.context }) => `
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
${JSON.stringify(context, null, 2)}
|
|
91
|
+
|
|
92
|
+
Execute the single best next move to try to win the game. Do not play on an existing cell.`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const gameReporter = agent.fromChatCompletionStream(
|
|
96
|
+
({
|
|
97
|
+
context,
|
|
98
|
+
}: {
|
|
99
|
+
context: typeof agent.types.context;
|
|
100
|
+
}) => `The tic-tac-toe game is over. The winner is ${
|
|
101
|
+
context.winner ?? 'nobody'
|
|
102
|
+
}. This was the ending board state, represented as a 9-element array:
|
|
18
103
|
|
|
19
|
-
export const ticTacToeMachine = setup({
|
|
20
|
-
types: {} as {
|
|
21
|
-
context: typeof initialContext;
|
|
22
|
-
events:
|
|
23
|
-
| { type: 'x.play'; index: number }
|
|
24
|
-
| {
|
|
25
|
-
type: 'o.play';
|
|
26
|
-
index: number;
|
|
27
|
-
}
|
|
28
|
-
| { type: 'RESET' };
|
|
29
|
-
},
|
|
30
|
-
actors: {
|
|
31
|
-
bot: fromEventChoice(
|
|
32
|
-
openai,
|
|
33
|
-
({ context }: { context: typeof initialContext }) => ({
|
|
34
|
-
model: 'gpt-4-1106-preview',
|
|
35
|
-
messages: [
|
|
36
|
-
{
|
|
37
|
-
role: 'system',
|
|
38
|
-
content: `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.
|
|
39
|
-
|
|
40
|
-
${JSON.stringify(context, null, 2)}`,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
role: 'user',
|
|
44
|
-
content:
|
|
45
|
-
'Execute the single best next move to try to win the game. Do not play on an existing cell.',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
})
|
|
49
|
-
),
|
|
50
|
-
gameReporter: fromChatCompletionStream(
|
|
51
|
-
openai,
|
|
52
|
-
({ context }: { context: typeof initialContext }) => ({
|
|
53
|
-
model: 'gpt-4-1106-preview',
|
|
54
|
-
messages: [
|
|
55
|
-
{
|
|
56
|
-
role: 'user',
|
|
57
|
-
content: `The tic-tac-toe game is over. The winner is ${
|
|
58
|
-
context.winner ?? 'nobody'
|
|
59
|
-
}. This was the ending board state:
|
|
60
|
-
|
|
61
104
|
${JSON.stringify(context.board, null, 2)}
|
|
62
105
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
And here are the events that led to this game state:
|
|
107
|
+
|
|
108
|
+
${context.events.join('\n')}
|
|
109
|
+
|
|
110
|
+
Provide a very short game report analyzing the game.`
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
export const ticTacToeMachine = setup({
|
|
114
|
+
types: agent.types,
|
|
115
|
+
actors: {
|
|
116
|
+
bot,
|
|
117
|
+
gameReporter,
|
|
69
118
|
},
|
|
70
119
|
actions: {
|
|
71
120
|
updateBoard: assign({
|
|
@@ -77,11 +126,19 @@ Provide a game report analyzing the game.`,
|
|
|
77
126
|
},
|
|
78
127
|
moves: ({ context }) => context.moves + 1,
|
|
79
128
|
player: ({ context }) => (context.player === 'x' ? 'o' : 'x'),
|
|
129
|
+
events: ({ context, event }) => {
|
|
130
|
+
return [...context.events, JSON.stringify(event)];
|
|
131
|
+
},
|
|
80
132
|
}),
|
|
81
133
|
resetGame: assign(initialContext),
|
|
82
134
|
setWinner: assign({
|
|
83
135
|
winner: ({ context }) => (context.player === 'x' ? 'o' : 'x'),
|
|
84
136
|
}),
|
|
137
|
+
recordEvent: assign({
|
|
138
|
+
events: ({ context, event }) => {
|
|
139
|
+
return [...context.events, JSON.stringify(event)];
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
85
142
|
},
|
|
86
143
|
guards: {
|
|
87
144
|
checkWin: ({ context }) => {
|
|
@@ -157,18 +214,8 @@ Provide a game report analyzing the game.`,
|
|
|
157
214
|
target: 'o',
|
|
158
215
|
guard: 'isValidMove',
|
|
159
216
|
actions: 'updateBoard',
|
|
160
|
-
meta: {
|
|
161
|
-
parameters: {
|
|
162
|
-
index: {
|
|
163
|
-
description: 'The index of the cell to play on',
|
|
164
|
-
type: 'number',
|
|
165
|
-
min: 0,
|
|
166
|
-
max: 8,
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
217
|
},
|
|
171
|
-
{ reenter: true },
|
|
218
|
+
{ target: 'x', reenter: true },
|
|
172
219
|
],
|
|
173
220
|
},
|
|
174
221
|
},
|
|
@@ -177,10 +224,8 @@ Provide a game report analyzing the game.`,
|
|
|
177
224
|
src: 'bot',
|
|
178
225
|
input: ({ context }) => ({ context }),
|
|
179
226
|
onDone: {
|
|
180
|
-
// @ts-ignore
|
|
181
227
|
actions: raise(({ event }) => {
|
|
182
|
-
|
|
183
|
-
return event.output![0];
|
|
228
|
+
return event.output![0]!;
|
|
184
229
|
}),
|
|
185
230
|
},
|
|
186
231
|
},
|
|
@@ -190,18 +235,8 @@ Provide a game report analyzing the game.`,
|
|
|
190
235
|
target: 'x',
|
|
191
236
|
guard: 'isValidMove',
|
|
192
237
|
actions: 'updateBoard',
|
|
193
|
-
meta: {
|
|
194
|
-
parameters: {
|
|
195
|
-
index: {
|
|
196
|
-
description: 'The index of the cell to play on',
|
|
197
|
-
type: 'number',
|
|
198
|
-
min: 0,
|
|
199
|
-
max: 8,
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
238
|
},
|
|
204
|
-
{ reenter: true },
|
|
239
|
+
{ target: 'o', reenter: true },
|
|
205
240
|
],
|
|
206
241
|
},
|
|
207
242
|
},
|
|
@@ -233,7 +268,7 @@ Provide a game report analyzing the game.`,
|
|
|
233
268
|
},
|
|
234
269
|
},
|
|
235
270
|
on: {
|
|
236
|
-
|
|
271
|
+
reset: {
|
|
237
272
|
target: 'playing',
|
|
238
273
|
actions: 'resetGame',
|
|
239
274
|
},
|
package/package.json
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
10
|
-
"lint": "tsc",
|
|
11
|
-
"test": "vitest run",
|
|
12
|
-
"prepublishOnly": "tsup src/index.ts --dts"
|
|
13
|
-
},
|
|
14
8
|
"keywords": [],
|
|
15
9
|
"author": "",
|
|
16
10
|
"license": "MIT",
|
|
17
11
|
"devDependencies": {
|
|
12
|
+
"@changesets/cli": "^2.27.1",
|
|
18
13
|
"@types/node": "^20.10.6",
|
|
14
|
+
"json-schema-to-ts": "^3.0.0",
|
|
19
15
|
"tsup": "^8.0.1",
|
|
20
16
|
"typescript": "^5.3.3"
|
|
21
17
|
},
|
|
22
18
|
"dependencies": {
|
|
23
19
|
"openai": "^4.24.1",
|
|
24
20
|
"xstate": "^5.3.1"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
27
|
+
"lint": "tsc",
|
|
28
|
+
"test": "vitest run"
|
|
25
29
|
}
|
|
26
|
-
}
|
|
30
|
+
}
|
package/src/index.ts
CHANGED
package/src/openai.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import {
|
|
3
3
|
AnyEventObject,
|
|
4
|
+
ObservableActorLogic,
|
|
4
5
|
Observer,
|
|
6
|
+
PromiseActorLogic,
|
|
7
|
+
Values,
|
|
5
8
|
fromObservable,
|
|
6
9
|
fromPromise,
|
|
10
|
+
setup,
|
|
7
11
|
toObserver,
|
|
8
12
|
} from 'xstate';
|
|
9
13
|
import { getAllTransitions } from './utils';
|
|
14
|
+
import {
|
|
15
|
+
ContextSchema,
|
|
16
|
+
EventSchemas,
|
|
17
|
+
ConvertContextToJSONSchema,
|
|
18
|
+
ConvertToJSONSchemas,
|
|
19
|
+
createEventSchemas,
|
|
20
|
+
} from './utils';
|
|
21
|
+
import { FromSchema } from 'json-schema-to-ts';
|
|
22
|
+
import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
23
|
+
import {
|
|
24
|
+
ChatCompletionCreateParamsBase,
|
|
25
|
+
ChatCompletionCreateParamsStreaming,
|
|
26
|
+
} from 'openai/resources/chat/completions';
|
|
10
27
|
|
|
11
28
|
/**
|
|
12
29
|
* Creates [promise actor logic](https://stately.ai/docs/promise-actors) that uses the OpenAI API to generate a completion.
|
|
@@ -19,12 +36,24 @@ export function fromChatCompletion<TInput>(
|
|
|
19
36
|
openai: OpenAI,
|
|
20
37
|
inputFn: (
|
|
21
38
|
input: TInput
|
|
22
|
-
) => OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
39
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
23
40
|
) {
|
|
24
41
|
return fromPromise<OpenAI.Chat.Completions.ChatCompletion, TInput>(
|
|
25
42
|
async ({ input }) => {
|
|
26
43
|
const openAiInput = inputFn(input);
|
|
27
|
-
const
|
|
44
|
+
const params: ChatCompletionCreateParamsNonStreaming =
|
|
45
|
+
typeof openAiInput === 'string'
|
|
46
|
+
? {
|
|
47
|
+
model: 'gpt-3.5-turbo-1106',
|
|
48
|
+
messages: [
|
|
49
|
+
{
|
|
50
|
+
role: 'user',
|
|
51
|
+
content: openAiInput,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
: openAiInput;
|
|
56
|
+
const response = await openai.chat.completions.create(params);
|
|
28
57
|
|
|
29
58
|
return response;
|
|
30
59
|
}
|
|
@@ -41,7 +70,7 @@ export function fromChatCompletionStream<TInput>(
|
|
|
41
70
|
openai: OpenAI,
|
|
42
71
|
inputFn: (
|
|
43
72
|
input: TInput
|
|
44
|
-
) => OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
|
73
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
|
45
74
|
) {
|
|
46
75
|
return fromObservable<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>(
|
|
47
76
|
({ input }) => {
|
|
@@ -49,8 +78,20 @@ export function fromChatCompletionStream<TInput>(
|
|
|
49
78
|
|
|
50
79
|
(async () => {
|
|
51
80
|
const openAiInput = inputFn(input);
|
|
81
|
+
const resolvedParams: ChatCompletionCreateParamsBase =
|
|
82
|
+
typeof openAiInput === 'string'
|
|
83
|
+
? {
|
|
84
|
+
model: 'gpt-3.5-turbo-1106',
|
|
85
|
+
messages: [
|
|
86
|
+
{
|
|
87
|
+
role: 'user',
|
|
88
|
+
content: openAiInput,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
: openAiInput;
|
|
52
93
|
const stream = await openai.chat.completions.create({
|
|
53
|
-
...
|
|
94
|
+
...resolvedParams,
|
|
54
95
|
stream: true,
|
|
55
96
|
});
|
|
56
97
|
|
|
@@ -85,9 +126,10 @@ export function fromChatCompletionStream<TInput>(
|
|
|
85
126
|
*/
|
|
86
127
|
export function fromEventChoice<TInput>(
|
|
87
128
|
openai: OpenAI,
|
|
129
|
+
machineTypes: { schemas: { context: ContextSchema; events: EventSchemas } },
|
|
88
130
|
inputFn: (
|
|
89
131
|
input: TInput
|
|
90
|
-
) => OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
132
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
91
133
|
) {
|
|
92
134
|
return fromPromise<AnyEventObject[] | undefined, TInput>(
|
|
93
135
|
async ({ input, self }) => {
|
|
@@ -104,17 +146,33 @@ export function fromEventChoice<TInput>(
|
|
|
104
146
|
type: 'function',
|
|
105
147
|
function: {
|
|
106
148
|
name,
|
|
107
|
-
description:
|
|
149
|
+
description:
|
|
150
|
+
t.description ??
|
|
151
|
+
machineTypes.schemas.events[t.eventType]?.description,
|
|
108
152
|
parameters: {
|
|
109
153
|
type: 'object',
|
|
110
|
-
properties:
|
|
154
|
+
properties:
|
|
155
|
+
machineTypes.schemas.events[t.eventType]?.properties ?? {},
|
|
111
156
|
},
|
|
112
157
|
},
|
|
113
158
|
} as const;
|
|
114
159
|
});
|
|
160
|
+
|
|
115
161
|
const openAiInput = inputFn(input);
|
|
162
|
+
const completionParams: ChatCompletionCreateParamsNonStreaming =
|
|
163
|
+
typeof openAiInput === 'string'
|
|
164
|
+
? {
|
|
165
|
+
model: 'gpt-4-1106-preview',
|
|
166
|
+
messages: [
|
|
167
|
+
{
|
|
168
|
+
role: 'user',
|
|
169
|
+
content: openAiInput,
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
}
|
|
173
|
+
: openAiInput;
|
|
116
174
|
const completion = await openai.chat.completions.create({
|
|
117
|
-
...
|
|
175
|
+
...completionParams,
|
|
118
176
|
tools,
|
|
119
177
|
});
|
|
120
178
|
|
|
@@ -129,7 +187,64 @@ export function fromEventChoice<TInput>(
|
|
|
129
187
|
});
|
|
130
188
|
}
|
|
131
189
|
|
|
132
|
-
return
|
|
190
|
+
return undefined;
|
|
133
191
|
}
|
|
134
192
|
);
|
|
135
193
|
}
|
|
194
|
+
|
|
195
|
+
interface CreateAgentOutput<
|
|
196
|
+
T extends {
|
|
197
|
+
model: ChatCompletionCreateParamsBase['model'];
|
|
198
|
+
context: ContextSchema;
|
|
199
|
+
events: EventSchemas;
|
|
200
|
+
}
|
|
201
|
+
> {
|
|
202
|
+
model: T['model'];
|
|
203
|
+
schemas: T;
|
|
204
|
+
types: {
|
|
205
|
+
context: FromSchema<ConvertContextToJSONSchema<T['context']>>;
|
|
206
|
+
events: FromSchema<Values<ConvertToJSONSchemas<T['events']>>>;
|
|
207
|
+
};
|
|
208
|
+
fromEventChoice: <TInput>(
|
|
209
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
210
|
+
) => PromiseActorLogic<
|
|
211
|
+
FromSchema<Values<ConvertToJSONSchemas<T['events']>>>[] | undefined,
|
|
212
|
+
TInput
|
|
213
|
+
>;
|
|
214
|
+
fromChatCompletion: <TInput>(
|
|
215
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
216
|
+
) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
217
|
+
fromChatCompletionStream: <TInput>(
|
|
218
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming
|
|
219
|
+
) => ObservableActorLogic<
|
|
220
|
+
OpenAI.Chat.Completions.ChatCompletionChunk,
|
|
221
|
+
TInput
|
|
222
|
+
>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function createAgent<
|
|
226
|
+
T extends {
|
|
227
|
+
model: ChatCompletionCreateParamsBase['model'];
|
|
228
|
+
context: ContextSchema;
|
|
229
|
+
events: EventSchemas;
|
|
230
|
+
}
|
|
231
|
+
>(openai: OpenAI, settings: T): CreateAgentOutput<T> {
|
|
232
|
+
const obj: CreateAgentOutput<T> = {
|
|
233
|
+
model: settings.model,
|
|
234
|
+
schemas: {
|
|
235
|
+
context: {
|
|
236
|
+
type: 'object',
|
|
237
|
+
properties: settings.context,
|
|
238
|
+
additionalProperties: false,
|
|
239
|
+
},
|
|
240
|
+
events: createEventSchemas(settings.events),
|
|
241
|
+
} as any,
|
|
242
|
+
types: {} as any,
|
|
243
|
+
fromEventChoice: (input) => fromEventChoice(openai, obj, input) as any,
|
|
244
|
+
fromChatCompletion: (input) => fromChatCompletion(openai, input),
|
|
245
|
+
fromChatCompletionStream: (input) =>
|
|
246
|
+
fromChatCompletionStream(openai, input),
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return obj as any;
|
|
250
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { AnyMachineSnapshot, AnyStateNode } from 'xstate';
|
|
1
|
+
import { AnyMachineSnapshot, AnyStateNode, Prop, Values } from 'xstate';
|
|
2
|
+
import { FromSchema } from 'json-schema-to-ts';
|
|
3
|
+
import { JSONSchema7 } from 'json-schema-to-ts/lib/types/definitions';
|
|
2
4
|
|
|
3
5
|
export function getAllTransitions(state: AnyMachineSnapshot) {
|
|
4
6
|
const nodes = state._nodes;
|
|
@@ -8,3 +10,59 @@ export function getAllTransitions(state: AnyMachineSnapshot) {
|
|
|
8
10
|
|
|
9
11
|
return transitions;
|
|
10
12
|
}
|
|
13
|
+
|
|
14
|
+
export type EventSchemas = {
|
|
15
|
+
[key: string]: {
|
|
16
|
+
description?: string;
|
|
17
|
+
properties?: {
|
|
18
|
+
[key: string]: JSONSchema7;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export interface ContextSchema {
|
|
24
|
+
[key: string]: JSONSchema7;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ConvertToJSONSchemas<T> = {
|
|
28
|
+
[K in keyof T]: {
|
|
29
|
+
properties: { type: { const: K } };
|
|
30
|
+
type: 'object';
|
|
31
|
+
required: Array<keyof Prop<T[K], 'properties'> | 'type'>;
|
|
32
|
+
additionalProperties: false;
|
|
33
|
+
} & T[K];
|
|
34
|
+
} & {};
|
|
35
|
+
|
|
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
|
+
export function createEventSchemas<T extends EventSchemas>(
|
|
44
|
+
eventSchemaMap: T
|
|
45
|
+
): ConvertToJSONSchemas<T> {
|
|
46
|
+
const resolvedEventSchemaMap = {};
|
|
47
|
+
|
|
48
|
+
for (const [key, schema] of Object.entries(eventSchemaMap)) {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
resolvedEventSchemaMap[key] = {
|
|
51
|
+
type: 'object',
|
|
52
|
+
required: ['type'],
|
|
53
|
+
properties: {
|
|
54
|
+
type: {
|
|
55
|
+
const: key,
|
|
56
|
+
},
|
|
57
|
+
...schema.properties,
|
|
58
|
+
},
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
...schema,
|
|
61
|
+
} as JSONSchema7;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return resolvedEventSchemaMap as ConvertToJSONSchemas<T>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type InferEventsFromSchemas<T extends ConvertToJSONSchemas<any>> =
|
|
68
|
+
FromSchema<Values<T>>;
|