@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.
@@ -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": "Launch Program",
11
- "skipFiles": ["<node_internals>/**"],
12
- "program": "${file}",
13
- "preLaunchTask": "tsc: build - tsconfig.json",
14
- "outFiles": ["${workspaceFolder}/**/*.js"]
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 OpenAI from 'openai';
2
- import { Prop, PromiseActorLogic, ObservableActorLogic, AnyEventObject, Values } from 'xstate';
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 { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
6
- import { ChatCompletionCreateParamsBase, ChatCompletionCreateParamsStreaming } from 'openai/resources/chat/completions';
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
- * Creates [promise actor logic](https://stately.ai/docs/promise-actors) that uses the OpenAI API to generate a completion.
40
- *
41
- * @param openai The OpenAI instance.
42
- * @param inputFn A function that maps arbitrary input to OpenAI chat completion input.
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<T['context']>>;
75
- events: FromSchema<Values<ConvertToJSONSchemas<T['events']>>>;
46
+ context: FromSchema<ConvertContextToJSONSchema<TContextSchema>>;
47
+ events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
76
48
  };
77
- fromEvent: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<FromSchema<Values<ConvertToJSONSchemas<T['events']>>>[] | undefined, TInput>;
78
- fromEventChoice: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<FromSchema<Values<ConvertToJSONSchemas<T['events']>>>[] | undefined, TInput>;
79
- fromChatCompletion: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
80
- fromChatCompletionStream: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming) => ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
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
- declare function createAgent<T extends {
89
+
90
+ declare function createOpenAIAdapter<T extends {
83
91
  model: ChatCompletionCreateParamsBase['model'];
84
- context: ContextSchema;
85
- events: EventSchemas;
86
- }>(openai: OpenAI, settings: T): CreateAgentOutput<T>;
92
+ }>(openai: OpenAI, settings: T): StatelyAgentAdapter;
87
93
 
88
- export { createAgent, fromChatCompletion, fromChatCompletionStream, fromEventChoice };
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
- fromChatCompletion: () => fromChatCompletion,
25
- fromChatCompletionStream: () => fromChatCompletionStream,
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/openai.ts
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, import_xstate.fromPromise)(
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 fromChatCompletionStream(openai, agentSettings, inputFn) {
78
- return (0, import_xstate.fromObservable)(
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, import_xstate.toObserver)(...args);
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 fromEventChoice(openai, agentSettings, inputFn, options) {
117
- return (0, import_xstate.fromPromise)(
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 ?? agentSettings.schemas.events[t.eventType]?.description,
157
+ description: t.description ?? eventSchemaMap[t.eventType]?.description,
131
158
  parameters: {
132
159
  type: "object",
133
- properties: agentSettings.schemas.events[t.eventType]?.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 createAgent(openai, settings) {
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
- fromEventChoice(openai, agentSettings, input, { execute: true })
245
+ // @ts-ignore infinitely deep
246
+ fromEvent(openai, agentSettings, input, { execute: true })
185
247
  ),
186
- // @ts-ignore infinitely deep
187
- fromEventChoice: (input) => fromEventChoice(openai, agentSettings, input),
188
- fromChatCompletion: (input) => fromChatCompletion(openai, agentSettings, input),
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
- fromChatCompletion,
197
- fromChatCompletionStream,
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
- assign,
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 agent = createAgent(openai, {
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
- desire: { type: ['string', 'null'] },
27
- lastRating: { type: ['string', 'null'] },
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 getJokeCompletion = agent.fromChatCompletion(
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 = agent.fromChatCompletion(
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 = agent.fromEvent(
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
- types: {
100
- context: {} as {
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 actor = createActor(jokeMachine);
214
- actor.start();
215
+ const agent = createAgent(jokeMachine);
216
+ agent.start();
File without changes
@@ -1,6 +1,6 @@
1
- import { assign, setup, assertEvent, createActor } from 'xstate';
1
+ import { assign, setup, assertEvent } from 'xstate';
2
2
  import OpenAI from 'openai';
3
- import { createAgent } from '../src/openai';
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 agent = createAgent(openai, {
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 agent.types.context;
81
+ } satisfies typeof schemas.types.context;
79
82
 
80
- const bot = agent.fromEvent(
81
- ({ context }: { context: typeof agent.types.context }) => `
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 = agent.fromChatCompletionStream(
92
+ const gameReporter = adapter.fromChatStream(
90
93
  ({
91
94
  context,
92
95
  }: {
93
- context: typeof agent.types.context;
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
- types: agent.types,
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 actor = createActor(ticTacToeMachine, {
252
- inspect: (e) => {
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
- actor.start();
259
+ agent.start();
@@ -1,6 +1,6 @@
1
1
  import OpenAI from 'openai';
2
- import { createAgent, fromEventChoice } from '../src';
3
- import { assign, createActor, fromPromise, log, setup } from 'xstate';
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 agent = createAgent(openai, {
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
- types: agent.types,
83
+ schemas,
84
+ types: schemas.types,
69
85
  actors: {
70
- searchTavily: fromPromise(async ({ input }: { input: string }) => {
71
- const results = await searchTavily(input, {
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: 'searchTavily',
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
- createActor(machine).start();
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",
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.5.1"
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
- setup,
9
+ isMachineSnapshot,
11
10
  toObserver,
12
11
  } from 'xstate';
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';
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: CreateAgentOutput<any>,
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 fromChatCompletionStream<TInput>(
62
+ export function fromChatStream<TInput>(
71
63
  openai: OpenAI,
72
- agentSettings: CreateAgentOutput<any>,
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 fromEventChoice<TInput>(
121
+ export function fromEvent<TInput>(
130
122
  openai: OpenAI,
131
- agentSettings: CreateAgentOutput<any>,
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
- interface CreateAgentOutput<
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
- schemas: T;
220
- types: {
221
- context: FromSchema<ConvertContextToJSONSchema<T['context']>>;
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
- ) => PromiseActorLogic<
227
- FromSchema<Values<ConvertToJSONSchemas<T['events']>>>[] | undefined,
228
- TInput
229
- >;
230
- fromEventChoice: <TInput>(
231
- inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
232
- ) => PromiseActorLogic<
233
- FromSchema<Values<ConvertToJSONSchemas<T['events']>>>[] | undefined,
234
- TInput
235
- >;
236
- fromChatCompletion: <TInput>(
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
- fromChatCompletionStream: <TInput>(
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 createAgent<
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): CreateAgentOutput<T> {
254
- const agentSettings: CreateAgentOutput<T> = {
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
- fromEventChoice(openai, agentSettings, input, { execute: true }),
268
- // @ts-ignore infinitely deep
269
- fromEventChoice: (input) => fromEventChoice(openai, agentSettings, input),
270
- fromChatCompletion: (input) =>
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 as any;
350
+ return agentSettings;
277
351
  }
package/src/agent.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { ActorOptions, AnyStateMachine, createActor } from 'xstate';
2
+
3
+ export function createAgent<T extends AnyStateMachine>(
4
+ ...args: Parameters<typeof createActor<T>>
5
+ ) {
6
+ const [machine, options] = args;
7
+ return createActor(machine, options);
8
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,3 @@
1
- export {
2
- fromChatCompletion,
3
- fromChatCompletionStream,
4
- fromEventChoice,
5
- createAgent,
6
- } from './openai';
1
+ export { createSchemas } from './schemas';
2
+ export { createAgent } from './agent';
3
+ export { createOpenAIAdapter } from './adapters/openai';
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
+ }