@statelyai/agent 0.0.2 → 0.0.4

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,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3
- "changelog": "@changesets/cli/changelog",
3
+ "changelog": ["@changesets/changelog-github", { "repo": "statelyai/agent" }],
4
4
  "commit": false,
5
5
  "fixed": [],
6
6
  "linked": [],
package/.env.template ADDED
@@ -0,0 +1,6 @@
1
+
2
+ # Get your OpenAI API key from: https://platform.openai.com/signup/
3
+ OPENAI_API_KEY="sk-..."
4
+
5
+ # Get your Tavily API key from: https://app.tavily.com/
6
+ TAVILY_API_KEY="tvly-..."
@@ -0,0 +1,24 @@
1
+ name: Setup Workflow
2
+ description: Composite action that sets up pnpm
3
+ runs:
4
+ using: 'composite'
5
+ steps:
6
+ - uses: pnpm/action-setup@v2
7
+ - uses: actions/setup-node@v4
8
+ with:
9
+ node-version: 20.x
10
+
11
+ - name: Get pnpm store directory
12
+ shell: bash
13
+ id: pnpm-cache
14
+ run: |
15
+ echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
16
+ - uses: actions/cache@v4
17
+ name: Setup pnpm cache
18
+ with:
19
+ path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
20
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
21
+ restore-keys: |
22
+ ${{ runner.os }}-pnpm-store-
23
+ - run: pnpm install
24
+ shell: bash
@@ -0,0 +1,35 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ concurrency: ${{ github.workflow }}-${{ github.ref }}
9
+
10
+ permissions: {}
11
+ jobs:
12
+ release:
13
+ permissions:
14
+ contents: write # to create release (changesets/action)
15
+ issues: write # to post issue comments (changesets/action)
16
+ pull-requests: write # to create pull request (changesets/action)
17
+
18
+ if: github.repository == 'statelyai/agent'
19
+
20
+ timeout-minutes: 20
21
+
22
+ runs-on: ubuntu-latest
23
+
24
+ steps:
25
+ - uses: actions/checkout@v3
26
+ - uses: ./.github/actions/ci-setup
27
+
28
+ - name: Create Release Pull Request or Publish to npm
29
+ uses: changesets/action@v1
30
+ with:
31
+ publish: pnpm run release
32
+ version: pnpm run version
33
+ env:
34
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @statelyai/agent
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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)
8
+
9
+ - [#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`
10
+
11
+ ## 0.0.3
12
+
13
+ ### Patch Changes
14
+
15
+ - [#1](https://github.com/statelyai/agent/pull/1) [`3dc2880`](https://github.com/statelyai/agent/commit/3dc28809a7ffd915a69d9f3374531c31fc1ee357) Thanks [@mellson](https://github.com/mellson)! - Adds a convenient way to run the examples with `pnpm example ${exampleName}`. If no example name is provided, the script will print the available examples. Also, adds a fun little loading animation to the joke example.
16
+
3
17
  ## 0.0.2
4
18
 
5
19
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
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 OpenAI from 'openai';
5
6
  import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
6
7
  import { ChatCompletionCreateParamsBase, ChatCompletionCreateParamsStreaming } from 'openai/resources/chat/completions';
7
8
 
@@ -35,52 +36,45 @@ 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, 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, 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, machineTypes: {
60
- schemas: {
61
- context: ContextSchema;
62
- events: EventSchemas;
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>;
45
+ types: {
46
+ context: FromSchema<ConvertContextToJSONSchema<TContextSchema>>;
47
+ events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
63
48
  };
64
- }, inputFn: (input: TInput) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming): PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
65
- interface CreateAgentOutput<T extends {
49
+ };
50
+
51
+ declare function createAgent<T extends AnyStateMachine>(...args: Parameters<typeof createActor<T>>): xstate.Actor<T>;
52
+
53
+ interface OpenAIAdapterOutput<T extends {
66
54
  model: ChatCompletionCreateParamsBase['model'];
67
- context: ContextSchema;
68
- events: EventSchemas;
69
55
  }> {
70
56
  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>;
57
+ /**
58
+ * Determines which event to send to the parent state machine actor based on the prompt.
59
+ */
60
+ fromEventChoice: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, options?: {
61
+ /**
62
+ * Immediately execute sending the event to the parent actor.
63
+ * @default true
64
+ */
65
+ execute?: boolean;
66
+ }) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
67
+ /**
68
+ * Creates promise actor logic that resolves with a chat completion.
69
+ */
70
+ fromChat: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
71
+ /**
72
+ * Creates observable actor logic that emits a chat completion stream.
73
+ */
74
+ fromChatStream: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming) => ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
79
75
  }
80
- declare function createAgent<T extends {
76
+ declare function createOpenAIAdapter<T extends {
81
77
  model: ChatCompletionCreateParamsBase['model'];
82
- context: ContextSchema;
83
- events: EventSchemas;
84
- }>(openai: OpenAI, settings: T): CreateAgentOutput<T>;
78
+ }>(openai: OpenAI, settings: T): OpenAIAdapterOutput<T>;
85
79
 
86
- export { createAgent, fromChatCompletion, fromChatCompletionStream, fromEventChoice };
80
+ 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,13 +51,38 @@ function createEventSchemas(eventSchemaMap) {
55
51
  return resolvedEventSchemaMap;
56
52
  }
57
53
 
58
- // src/openai.ts
59
- function fromChatCompletion(openai, inputFn) {
60
- return (0, import_xstate.fromPromise)(
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");
80
+ function fromChatCompletion(openai, agentSettings, inputFn) {
81
+ return (0, import_xstate2.fromPromise)(
61
82
  async ({ input }) => {
62
83
  const openAiInput = inputFn(input);
63
84
  const params = typeof openAiInput === "string" ? {
64
- model: "gpt-3.5-turbo-1106",
85
+ model: agentSettings.model,
65
86
  messages: [
66
87
  {
67
88
  role: "user",
@@ -74,14 +95,14 @@ function fromChatCompletion(openai, inputFn) {
74
95
  }
75
96
  );
76
97
  }
77
- function fromChatCompletionStream(openai, 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 () => {
82
103
  const openAiInput = inputFn(input);
83
104
  const resolvedParams = typeof openAiInput === "string" ? {
84
- model: "gpt-3.5-turbo-1106",
105
+ model: agentSettings.model,
85
106
  messages: [
86
107
  {
87
108
  role: "user",
@@ -101,7 +122,7 @@ function fromChatCompletionStream(openai, 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, inputFn) {
113
134
  }
114
135
  );
115
136
  }
116
- function fromEventChoice(openai, machineTypes, inputFn) {
117
- return (0, import_xstate.fromPromise)(
118
- async ({ input, self }) => {
137
+ function fromEventChoice(openai, agentSettings, inputFn, options) {
138
+ return (0, import_xstate2.fromPromise)(
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,17 +154,17 @@ function fromEventChoice(openai, machineTypes, inputFn) {
127
154
  type: "function",
128
155
  function: {
129
156
  name,
130
- description: t.description ?? machineTypes.schemas.events[t.eventType]?.description,
157
+ description: t.description ?? eventSchemaMap[t.eventType]?.description,
131
158
  parameters: {
132
159
  type: "object",
133
- properties: machineTypes.schemas.events[t.eventType]?.properties ?? {}
160
+ properties: eventSchemaMap[t.eventType]?.properties ?? {}
134
161
  }
135
162
  }
136
163
  };
137
164
  });
138
165
  const openAiInput = inputFn(input);
139
166
  const completionParams = typeof openAiInput === "string" ? {
140
- model: "gpt-4-1106-preview",
167
+ model: agentSettings.model,
141
168
  messages: [
142
169
  {
143
170
  role: "user",
@@ -151,39 +178,37 @@ function fromEventChoice(openai, machineTypes, inputFn) {
151
178
  });
152
179
  const toolCalls = completion.choices[0]?.message.tool_calls;
153
180
  if (toolCalls) {
154
- return toolCalls.map((tc) => {
181
+ const events = toolCalls.map((tc) => {
155
182
  return {
156
183
  type: functionNameMapping[tc.function.name],
157
184
  ...JSON.parse(tc.function.arguments)
158
185
  };
159
186
  });
187
+ if (options?.execute) {
188
+ events.forEach((event) => {
189
+ system._relay(self, self._parent, event);
190
+ });
191
+ }
160
192
  }
161
193
  return void 0;
162
194
  }
163
195
  );
164
196
  }
165
- function createAgent(openai, settings) {
166
- const obj = {
197
+ function createOpenAIAdapter(openai, settings) {
198
+ const agentSettings = {
167
199
  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)
200
+ fromEventChoice: (input) => (
201
+ // @ts-ignore infinitely deep
202
+ fromEventChoice(openai, agentSettings, input, { execute: true })
203
+ ),
204
+ fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
205
+ fromChatStream: (input) => fromChatStream(openai, agentSettings, input)
180
206
  };
181
- return obj;
207
+ return agentSettings;
182
208
  }
183
209
  // Annotate the CommonJS export names for ESM import in node:
184
210
  0 && (module.exports = {
185
211
  createAgent,
186
- fromChatCompletion,
187
- fromChatCompletionStream,
188
- fromEventChoice
212
+ createOpenAIAdapter,
213
+ createSchemas
189
214
  });
@@ -0,0 +1,17 @@
1
+ import { fromPromise } from 'xstate';
2
+
3
+ export const getFromTerminal = fromPromise<string, string>(
4
+ async ({ input }) => {
5
+ const topic = await new Promise<string>((res) => {
6
+ console.log(input + '\n');
7
+ const listener = (data: Buffer) => {
8
+ const result = data.toString().trim();
9
+ process.stdin.off('data', listener);
10
+ res(result);
11
+ };
12
+ process.stdin.on('data', listener);
13
+ });
14
+
15
+ return topic;
16
+ }
17
+ );
@@ -0,0 +1,32 @@
1
+ // Adapted from https://stackoverflow.com/questions/34848505/how-to-make-a-loading-animation-in-console-application-written-in-javascript-or
2
+
3
+ /**
4
+ * Create and display a loader in the console.
5
+ *
6
+ * @param {string} [text=""] Text to display after loader
7
+ * @param {array.<string>} [chars=["⠙", "⠘", "⠰", "⠴", "⠤", "⠦", "⠆", "⠃", "⠋", "⠉"]]
8
+ * Array of characters representing loader steps
9
+ * @param {number} [delay=100] Delay in ms between loader steps
10
+ * @example
11
+ * let loader = loadingAnimation("Loading…");
12
+ *
13
+ * // Stop loader after 1 second
14
+ * setTimeout(() => clearInterval(loader), 1000);
15
+ * @returns {number} An interval that can be cleared to stop the animation
16
+ */
17
+ export function loadingAnimation(
18
+ text: string = '',
19
+ chars: Array<string> = ['⠙', '⠘', '⠰', '⠴', '⠤', '⠦', '⠆', '⠃', '⠋', '⠉'],
20
+ delay: number = 100
21
+ ) {
22
+ let x = 0;
23
+
24
+ const i = setInterval(function () {
25
+ process.stdout.write('\r' + chars[x++] + ' ' + text);
26
+ x = x % chars.length;
27
+ }, delay);
28
+
29
+ return {
30
+ stop: () => clearInterval(i),
31
+ };
32
+ }
@@ -0,0 +1,27 @@
1
+ import dotenv from 'dotenv';
2
+ import { existsSync, readdirSync } from 'fs';
3
+ dotenv.config();
4
+
5
+ function showExamples() {
6
+ const exampleFiles = readdirSync('./examples', { withFileTypes: true });
7
+ exampleFiles.forEach((file) => {
8
+ if (file.isDirectory()) return;
9
+ const exampleName = file.name.split('.')[0];
10
+ console.log(`- ${exampleName}`);
11
+ });
12
+ process.exit();
13
+ }
14
+
15
+ const exampleParams = process.argv.slice(2);
16
+ if (exampleParams.length === 0) {
17
+ console.error('No example specified, you can choose from:');
18
+ showExamples();
19
+ }
20
+ const exampleName = exampleParams[0];
21
+ const filePath = `./examples/${exampleName}.ts`;
22
+ if (existsSync(filePath)) {
23
+ require(`../${exampleName}.ts`);
24
+ } else {
25
+ console.error(`Example ${exampleName} does not exist, you can choose from:`);
26
+ showExamples();
27
+ }