@statelyai/agent 0.0.5 → 0.0.7
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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +30 -31
- package/dist/index.js +7 -19
- package/examples/joke.ts +11 -7
- package/examples/numberGuesser.ts +128 -0
- package/examples/ticTacToe.ts +30 -26
- package/examples/wordGuesser.ts +156 -0
- package/package.json +3 -3
- package/readme.md +5 -4
- package/src/adapters/openai.ts +12 -65
- package/src/schemas.ts +14 -12
- package/src/types.ts +12 -20
- package/src/utils.ts +4 -13
- package/vitest.config.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @statelyai/agent
|
|
2
2
|
|
|
3
|
+
## 0.0.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#18](https://github.com/statelyai/agent/pull/18) [`dcaabab`](https://github.com/statelyai/agent/commit/dcaababe69255b7eaff3347d0cf09469d3e6cc78) Thanks [@davidkpiano](https://github.com/davidkpiano)! - `context` is now optional for `createSchemas(…)`
|
|
8
|
+
|
|
9
|
+
## 0.0.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#16](https://github.com/statelyai/agent/pull/16) [`3ba5fb2`](https://github.com/statelyai/agent/commit/3ba5fb2392b51dee71f2585ed662b4ee9ecd6c41) Thanks [@davidkpiano](https://github.com/davidkpiano)! - Update to XState 5.8.0
|
|
14
|
+
|
|
3
15
|
## 0.0.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -14,36 +14,38 @@ type EventSchemas = {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
17
|
+
type ContextSchema = JSONSchema7 & {
|
|
18
|
+
type: 'object';
|
|
19
|
+
};
|
|
20
20
|
type ConvertToJSONSchemas<T> = {
|
|
21
21
|
[K in keyof T]: {
|
|
22
22
|
properties: {
|
|
23
23
|
type: {
|
|
24
24
|
const: K;
|
|
25
25
|
};
|
|
26
|
-
}
|
|
26
|
+
} & Prop<T[K], 'properties'>;
|
|
27
27
|
type: 'object';
|
|
28
|
-
required: Array<keyof Prop<T[K], 'properties'> | 'type'>;
|
|
28
|
+
required: Array<(keyof Prop<T[K], 'properties'> & string) | 'type'>;
|
|
29
29
|
additionalProperties: false;
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
} & {};
|
|
32
|
-
type ConvertContextToJSONSchema<T extends ContextSchema> = {
|
|
33
|
-
type: 'object';
|
|
34
|
-
properties: T;
|
|
35
|
-
readonly required: Array<keyof T & string>;
|
|
36
|
-
additionalProperties: false;
|
|
37
|
-
};
|
|
38
32
|
|
|
39
|
-
declare function createSchemas<TContextSchema extends ContextSchema, TEventSchemas extends EventSchemas>({ context, events, }: {
|
|
40
|
-
|
|
33
|
+
declare function createSchemas<const TContextSchema extends ContextSchema, const TEventSchemas extends EventSchemas>({ context, events, }: {
|
|
34
|
+
/**
|
|
35
|
+
* The JSON schema for the context object.
|
|
36
|
+
*
|
|
37
|
+
* Must be of `{ type: 'object' }`.
|
|
38
|
+
*/
|
|
39
|
+
context?: TContextSchema;
|
|
40
|
+
/**
|
|
41
|
+
* An object mapping event types to each event object's JSON Schema.
|
|
42
|
+
*/
|
|
41
43
|
events: TEventSchemas;
|
|
42
44
|
}): {
|
|
43
|
-
context:
|
|
45
|
+
context: TContextSchema | undefined;
|
|
44
46
|
events: ConvertToJSONSchemas<TEventSchemas>;
|
|
45
47
|
types: {
|
|
46
|
-
context: FromSchema<
|
|
48
|
+
context: FromSchema<TContextSchema>;
|
|
47
49
|
events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
|
|
48
50
|
};
|
|
49
51
|
};
|
|
@@ -52,29 +54,26 @@ declare function createAgent<T extends AnyStateMachine>(...args: Parameters<type
|
|
|
52
54
|
|
|
53
55
|
interface StatelyAgentAdapter {
|
|
54
56
|
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
57
|
/**
|
|
63
|
-
* Creates
|
|
58
|
+
* Creates actor logic that chooses an event from all of the
|
|
59
|
+
* possible next events of the parent state machine
|
|
60
|
+
* and sends it to the parent actor.
|
|
61
|
+
*/
|
|
62
|
+
fromEvent: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
63
|
+
/**
|
|
64
|
+
* Creates actor logic that resolves with a chat completion.
|
|
64
65
|
*/
|
|
65
66
|
fromChat: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
66
67
|
/**
|
|
67
|
-
* Creates
|
|
68
|
+
* Creates actor logic that emits a chat completion stream.
|
|
68
69
|
*/
|
|
69
70
|
fromChatStream: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming) => ObservableActorLogic<OpenAI.Chat.Completions.ChatCompletionChunk, TInput>;
|
|
71
|
+
/**
|
|
72
|
+
* Creates actor logic that chooses a tool from the provided
|
|
73
|
+
* tools and runs that tool.
|
|
74
|
+
*/
|
|
70
75
|
fromTool: <TInput>(inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming, tools: {
|
|
71
76
|
[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
77
|
}) => PromiseActorLogic<{
|
|
79
78
|
result: any;
|
|
80
79
|
tool: string;
|
package/dist/index.js
CHANGED
|
@@ -57,12 +57,7 @@ function createSchemas({
|
|
|
57
57
|
events
|
|
58
58
|
}) {
|
|
59
59
|
return {
|
|
60
|
-
context
|
|
61
|
-
type: "object",
|
|
62
|
-
properties: context,
|
|
63
|
-
additionalProperties: false,
|
|
64
|
-
required: Object.keys(context)
|
|
65
|
-
},
|
|
60
|
+
context,
|
|
66
61
|
events: createEventSchemas(events),
|
|
67
62
|
types: {}
|
|
68
63
|
};
|
|
@@ -134,7 +129,7 @@ function fromChatStream(openai, agentSettings, inputFn) {
|
|
|
134
129
|
}
|
|
135
130
|
);
|
|
136
131
|
}
|
|
137
|
-
function fromEvent(openai, agentSettings, inputFn
|
|
132
|
+
function fromEvent(openai, agentSettings, inputFn) {
|
|
138
133
|
return (0, import_xstate2.fromPromise)(
|
|
139
134
|
async ({ input, self, system }) => {
|
|
140
135
|
const parentSnapshot = self._parent?.getSnapshot();
|
|
@@ -177,26 +172,22 @@ function fromEvent(openai, agentSettings, inputFn, options) {
|
|
|
177
172
|
tools
|
|
178
173
|
});
|
|
179
174
|
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
180
|
-
if (toolCalls) {
|
|
175
|
+
if (toolCalls?.length) {
|
|
181
176
|
const events = toolCalls.map((tc) => {
|
|
182
177
|
return {
|
|
183
178
|
type: functionNameMapping[tc.function.name],
|
|
184
179
|
...JSON.parse(tc.function.arguments)
|
|
185
180
|
};
|
|
186
181
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
system._relay(self, self._parent, event);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
182
|
+
const event = events[0];
|
|
183
|
+
system._relay(self, self._parent, event);
|
|
192
184
|
}
|
|
193
185
|
return void 0;
|
|
194
186
|
}
|
|
195
187
|
);
|
|
196
188
|
}
|
|
197
189
|
function fromTool(openai, agentSettings, tools, inputFn) {
|
|
198
|
-
return (0, import_xstate2.fromPromise)(async ({ input
|
|
199
|
-
const functionNameMapping = {};
|
|
190
|
+
return (0, import_xstate2.fromPromise)(async ({ input }) => {
|
|
200
191
|
const resolvedTools = Object.entries(tools).map(([key, value]) => {
|
|
201
192
|
return {
|
|
202
193
|
type: "function",
|
|
@@ -241,10 +232,7 @@ function fromTool(openai, agentSettings, tools, inputFn) {
|
|
|
241
232
|
function createOpenAIAdapter(openai, settings) {
|
|
242
233
|
const agentSettings = {
|
|
243
234
|
model: settings.model,
|
|
244
|
-
fromEvent: (input) => (
|
|
245
|
-
// @ts-ignore infinitely deep
|
|
246
|
-
fromEvent(openai, agentSettings, input, { execute: true })
|
|
247
|
-
),
|
|
235
|
+
fromEvent: (input) => fromEvent(openai, agentSettings, input),
|
|
248
236
|
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
249
237
|
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
250
238
|
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input)
|
package/examples/joke.ts
CHANGED
|
@@ -9,15 +9,19 @@ const openai = new OpenAI({
|
|
|
9
9
|
|
|
10
10
|
const schemas = createSchemas({
|
|
11
11
|
context: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type: '
|
|
15
|
-
|
|
16
|
-
type: '
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
topic: { type: 'string' },
|
|
15
|
+
jokes: {
|
|
16
|
+
type: 'array',
|
|
17
|
+
items: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
},
|
|
17
20
|
},
|
|
21
|
+
desire: { type: ['string', 'null'] },
|
|
22
|
+
lastRating: { type: ['string', 'null'] },
|
|
18
23
|
},
|
|
19
|
-
|
|
20
|
-
lastRating: { type: ['string', 'null'] as const },
|
|
24
|
+
required: ['topic', 'jokes', 'desire', 'lastRating'],
|
|
21
25
|
},
|
|
22
26
|
events: {
|
|
23
27
|
askForTopic: {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
3
|
+
import { assign, setup } from 'xstate';
|
|
4
|
+
const openai = new OpenAI({
|
|
5
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const adapter = createOpenAIAdapter(openai, {
|
|
9
|
+
model: 'gpt-3.5-turbo-1106',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const guessLogic = adapter.fromEvent(
|
|
13
|
+
({
|
|
14
|
+
previousGuesses,
|
|
15
|
+
lastResult,
|
|
16
|
+
}: {
|
|
17
|
+
previousGuesses: number[];
|
|
18
|
+
lastResult: string;
|
|
19
|
+
}) => `
|
|
20
|
+
Guess the number between 1 and 10. The previous guesses were ${
|
|
21
|
+
previousGuesses.length ? previousGuesses.join(', ') : 'not made yet'
|
|
22
|
+
} and the last result was ${lastResult}.
|
|
23
|
+
`
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const schemas = createSchemas({
|
|
27
|
+
context: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
lastGuess: {
|
|
31
|
+
type: ['number', 'null'],
|
|
32
|
+
description: 'The last guess',
|
|
33
|
+
},
|
|
34
|
+
previousGuesses: {
|
|
35
|
+
type: 'array',
|
|
36
|
+
items: {
|
|
37
|
+
type: 'number',
|
|
38
|
+
},
|
|
39
|
+
description: 'The previous guesses',
|
|
40
|
+
},
|
|
41
|
+
answer: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
description: 'The answer',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
events: {
|
|
48
|
+
guess: {
|
|
49
|
+
properties: {
|
|
50
|
+
number: {
|
|
51
|
+
// integer
|
|
52
|
+
type: 'number',
|
|
53
|
+
minimum: 1,
|
|
54
|
+
maximum: 10,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ['number'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const machine = setup({
|
|
63
|
+
types: {
|
|
64
|
+
context: {} as {
|
|
65
|
+
previousGuesses: number[];
|
|
66
|
+
answer: number;
|
|
67
|
+
},
|
|
68
|
+
input: {} as { answer: number },
|
|
69
|
+
events: schemas.types.events,
|
|
70
|
+
},
|
|
71
|
+
schemas,
|
|
72
|
+
actors: {
|
|
73
|
+
guessLogic,
|
|
74
|
+
},
|
|
75
|
+
}).createMachine({
|
|
76
|
+
context: ({ input }) => ({
|
|
77
|
+
answer: input.answer,
|
|
78
|
+
previousGuesses: [],
|
|
79
|
+
}),
|
|
80
|
+
initial: 'guessing',
|
|
81
|
+
states: {
|
|
82
|
+
guessing: {
|
|
83
|
+
always: {
|
|
84
|
+
guard: ({ context }) =>
|
|
85
|
+
context.answer === context.previousGuesses.at(-1),
|
|
86
|
+
target: 'winner',
|
|
87
|
+
},
|
|
88
|
+
invoke: {
|
|
89
|
+
src: 'guessLogic',
|
|
90
|
+
input: ({ context }) => ({
|
|
91
|
+
previousGuesses: context.previousGuesses,
|
|
92
|
+
lastResult:
|
|
93
|
+
context.previousGuesses.length === 0
|
|
94
|
+
? 'not given yet'
|
|
95
|
+
: context.previousGuesses.at(-1)! - context.answer > 0
|
|
96
|
+
? 'too high'
|
|
97
|
+
: 'too low',
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
on: {
|
|
101
|
+
guess: {
|
|
102
|
+
actions: assign({
|
|
103
|
+
previousGuesses: ({ context, event }) => [
|
|
104
|
+
...context.previousGuesses,
|
|
105
|
+
event.number,
|
|
106
|
+
],
|
|
107
|
+
}),
|
|
108
|
+
target: 'guessing',
|
|
109
|
+
reenter: true,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
winner: {
|
|
114
|
+
type: 'final',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const agent = createAgent(machine, {
|
|
120
|
+
input: { answer: 4 },
|
|
121
|
+
inspect: (ev) => {
|
|
122
|
+
if (ev.type === '@xstate.event') {
|
|
123
|
+
console.log(ev.event);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
agent.start();
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -10,36 +10,40 @@ type Player = 'x' | 'o';
|
|
|
10
10
|
|
|
11
11
|
const schemas = createSchemas({
|
|
12
12
|
context: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type:
|
|
17
|
-
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
board: {
|
|
16
|
+
type: 'array',
|
|
17
|
+
items: {
|
|
18
|
+
type: ['null', 'string'],
|
|
19
|
+
enum: [null, 'x', 'o'],
|
|
20
|
+
},
|
|
21
|
+
minItems: 9,
|
|
22
|
+
maxItems: 9,
|
|
23
|
+
description: 'The board of the tic-tac-toe game',
|
|
18
24
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type: 'number',
|
|
25
|
-
description: 'The number of moves that have been played',
|
|
26
|
-
},
|
|
27
|
-
player: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
enum: ['x', 'o'],
|
|
30
|
-
description: 'The player whose turn it is',
|
|
31
|
-
},
|
|
32
|
-
gameReport: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
description: 'The game report',
|
|
35
|
-
},
|
|
36
|
-
events: {
|
|
37
|
-
type: 'array',
|
|
38
|
-
items: {
|
|
25
|
+
moves: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
description: 'The number of moves that have been played',
|
|
28
|
+
},
|
|
29
|
+
player: {
|
|
39
30
|
type: 'string',
|
|
31
|
+
enum: ['x', 'o'],
|
|
32
|
+
description: 'The player whose turn it is',
|
|
33
|
+
},
|
|
34
|
+
gameReport: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'The game report',
|
|
37
|
+
},
|
|
38
|
+
events: {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
},
|
|
40
43
|
},
|
|
41
44
|
},
|
|
42
|
-
|
|
45
|
+
required: ['board', 'moves', 'player', 'gameReport', 'events'],
|
|
46
|
+
},
|
|
43
47
|
events: {
|
|
44
48
|
'x.play': {
|
|
45
49
|
properties: {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { assign, log, setup } from 'xstate';
|
|
2
|
+
import { getFromTerminal } from './helpers/helpers';
|
|
3
|
+
import { createAgent, createOpenAIAdapter, createSchemas } from '../src';
|
|
4
|
+
import OpenAI from 'openai';
|
|
5
|
+
|
|
6
|
+
const openAI = new OpenAI({
|
|
7
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const adapter = createOpenAIAdapter(openAI, {
|
|
11
|
+
model: 'gpt-4-1106-preview',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const schemas = createSchemas({
|
|
15
|
+
events: {
|
|
16
|
+
guessLetter: {
|
|
17
|
+
description: 'Player guesses a letter',
|
|
18
|
+
properties: {
|
|
19
|
+
letter: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'The letter guessed',
|
|
22
|
+
maxLength: 1,
|
|
23
|
+
minLength: 1,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
guessWord: {
|
|
28
|
+
description: 'Player guesses the full word',
|
|
29
|
+
properties: {
|
|
30
|
+
word: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'The word guessed',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const context = {
|
|
40
|
+
word: null as string | null,
|
|
41
|
+
guessedWord: null as string | null,
|
|
42
|
+
letters: [] as string[],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const wordGuesserMachine = setup({
|
|
46
|
+
types: {
|
|
47
|
+
context: {} as typeof context,
|
|
48
|
+
events: schemas.types.events,
|
|
49
|
+
},
|
|
50
|
+
actors: {
|
|
51
|
+
getFromTerminal,
|
|
52
|
+
guesser: adapter.fromEvent(
|
|
53
|
+
(input: typeof context) => `
|
|
54
|
+
You are trying to guess the word. The word has ${
|
|
55
|
+
input.word!.length
|
|
56
|
+
} letters. You have guessed the following letters so far: ${input.letters.join(
|
|
57
|
+
', '
|
|
58
|
+
)}. These letters matched: ${input
|
|
59
|
+
.word!.split('')
|
|
60
|
+
.map((letter) =>
|
|
61
|
+
input.letters.includes(letter.toUpperCase())
|
|
62
|
+
? letter.toUpperCase()
|
|
63
|
+
: '_'
|
|
64
|
+
)
|
|
65
|
+
.join('')}
|
|
66
|
+
Please make your next guess - type a letter or the full word. You can only make 10 total guesses.
|
|
67
|
+
`
|
|
68
|
+
),
|
|
69
|
+
},
|
|
70
|
+
schemas,
|
|
71
|
+
}).createMachine({
|
|
72
|
+
initial: 'providingWord',
|
|
73
|
+
context,
|
|
74
|
+
states: {
|
|
75
|
+
providingWord: {
|
|
76
|
+
invoke: {
|
|
77
|
+
src: 'getFromTerminal',
|
|
78
|
+
input: 'Enter a word',
|
|
79
|
+
onDone: {
|
|
80
|
+
actions: assign({
|
|
81
|
+
word: ({ event }) => event.output,
|
|
82
|
+
}),
|
|
83
|
+
target: 'guessing',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
guessing: {
|
|
88
|
+
always: {
|
|
89
|
+
guard: ({ context }) => context.letters.length > 10,
|
|
90
|
+
target: 'finalGuess',
|
|
91
|
+
},
|
|
92
|
+
invoke: {
|
|
93
|
+
src: 'guesser',
|
|
94
|
+
input: ({ context }) => context,
|
|
95
|
+
},
|
|
96
|
+
on: {
|
|
97
|
+
guessLetter: {
|
|
98
|
+
actions: assign({
|
|
99
|
+
letters: ({ context, event }) => {
|
|
100
|
+
return [...context.letters, event.letter.toUpperCase()];
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
target: 'guessing',
|
|
104
|
+
reenter: true,
|
|
105
|
+
},
|
|
106
|
+
guessWord: {
|
|
107
|
+
actions: assign({
|
|
108
|
+
guessedWord: ({ event }) => event.word,
|
|
109
|
+
}),
|
|
110
|
+
target: 'gameOver',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
finalGuess: {
|
|
115
|
+
invoke: {
|
|
116
|
+
src: 'guesser',
|
|
117
|
+
input: ({ context }) => context,
|
|
118
|
+
},
|
|
119
|
+
on: {
|
|
120
|
+
guessWord: {
|
|
121
|
+
actions: assign({
|
|
122
|
+
guessedWord: ({ event }) => event.word,
|
|
123
|
+
}),
|
|
124
|
+
target: 'gameOver',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
gameOver: {
|
|
129
|
+
entry: log(({ context }) => {
|
|
130
|
+
if (
|
|
131
|
+
context.guessedWord?.toUpperCase() === context.word?.toUpperCase()
|
|
132
|
+
) {
|
|
133
|
+
return 'You won!';
|
|
134
|
+
} else {
|
|
135
|
+
return 'You lost! The word was ' + context.word;
|
|
136
|
+
}
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
exit: () => process.exit(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const actor = createAgent(wordGuesserMachine, {
|
|
144
|
+
inspect: (ev) => {
|
|
145
|
+
if (ev.type === '@xstate.event') {
|
|
146
|
+
console.log(ev.event);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
actor.subscribe((s) => {
|
|
152
|
+
console.log(s.value);
|
|
153
|
+
console.log(s.context);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
actor.start();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"xstate": "^5.
|
|
27
|
+
"xstate": "^5.8.0"
|
|
28
28
|
},
|
|
29
29
|
"packageManager": "pnpm@8.11.0",
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
32
32
|
"lint": "tsc",
|
|
33
|
-
"test": "vitest
|
|
33
|
+
"test": "vitest",
|
|
34
34
|
"example": "ts-node examples/helpers/runner.ts",
|
|
35
35
|
"changeset": "changeset",
|
|
36
36
|
"release": "changeset publish",
|
package/readme.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Stately Agent (alpha)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
- Read [the documentation](https://stately.ai/docs/agents)
|
|
4
|
+
- See [the examples directory](https://github.com/statelyai/agent/tree/main/examples) for working examples.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
7
8
|
Install `openai`, and `@statelyai/agent`:
|
|
8
9
|
|
|
9
10
|
```bash
|
|
10
|
-
|
|
11
|
+
pnpm install openai @statelyai/agent
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
## Usage
|
|
@@ -35,7 +36,7 @@ OPENAI_API_KEY="your-openai-api-key"
|
|
|
35
36
|
Then, install the dependencies (`npm install`) and run the examples:
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
|
-
|
|
39
|
+
pnpm run example joke
|
|
39
40
|
# or:
|
|
40
|
-
#
|
|
41
|
+
# pnpm run example ticTacToe
|
|
41
42
|
```
|
package/src/adapters/openai.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type OpenAI from 'openai';
|
|
2
2
|
import {
|
|
3
3
|
AnyEventObject,
|
|
4
|
-
ObservableActorLogic,
|
|
5
4
|
Observer,
|
|
6
|
-
PromiseActorLogic,
|
|
7
5
|
fromObservable,
|
|
8
6
|
fromPromise,
|
|
9
7
|
isMachineSnapshot,
|
|
@@ -11,10 +9,7 @@ import {
|
|
|
11
9
|
} from 'xstate';
|
|
12
10
|
import { getAllTransitions } from '../utils';
|
|
13
11
|
import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
14
|
-
import {
|
|
15
|
-
ChatCompletionCreateParamsBase,
|
|
16
|
-
ChatCompletionCreateParamsStreaming,
|
|
17
|
-
} from 'openai/resources/chat/completions';
|
|
12
|
+
import { ChatCompletionCreateParamsBase } from 'openai/resources/chat/completions';
|
|
18
13
|
import { StatelyAgentAdapter, Tool } from '../types';
|
|
19
14
|
|
|
20
15
|
/**
|
|
@@ -26,7 +21,7 @@ import { StatelyAgentAdapter, Tool } from '../types';
|
|
|
26
21
|
*/
|
|
27
22
|
export function fromChatCompletion<TInput>(
|
|
28
23
|
openai: OpenAI,
|
|
29
|
-
agentSettings:
|
|
24
|
+
agentSettings: StatelyAgentAdapter,
|
|
30
25
|
inputFn: (
|
|
31
26
|
input: TInput
|
|
32
27
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
@@ -61,7 +56,7 @@ export function fromChatCompletion<TInput>(
|
|
|
61
56
|
*/
|
|
62
57
|
export function fromChatStream<TInput>(
|
|
63
58
|
openai: OpenAI,
|
|
64
|
-
agentSettings:
|
|
59
|
+
agentSettings: StatelyAgentAdapter,
|
|
65
60
|
inputFn: (
|
|
66
61
|
input: TInput
|
|
67
62
|
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
|
@@ -120,17 +115,10 @@ export function fromChatStream<TInput>(
|
|
|
120
115
|
*/
|
|
121
116
|
export function fromEvent<TInput>(
|
|
122
117
|
openai: OpenAI,
|
|
123
|
-
agentSettings:
|
|
118
|
+
agentSettings: StatelyAgentAdapter,
|
|
124
119
|
inputFn: (
|
|
125
120
|
input: TInput
|
|
126
|
-
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
127
|
-
options?: {
|
|
128
|
-
/**
|
|
129
|
-
* Immediately execute sending the event to the parent actor.
|
|
130
|
-
* @default false
|
|
131
|
-
*/
|
|
132
|
-
execute?: boolean;
|
|
133
|
-
}
|
|
121
|
+
) => string | OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming
|
|
134
122
|
) {
|
|
135
123
|
return fromPromise<AnyEventObject[] | undefined, TInput>(
|
|
136
124
|
async ({ input, self, system }) => {
|
|
@@ -186,7 +174,7 @@ export function fromEvent<TInput>(
|
|
|
186
174
|
|
|
187
175
|
const toolCalls = completion.choices[0]?.message.tool_calls;
|
|
188
176
|
|
|
189
|
-
if (toolCalls) {
|
|
177
|
+
if (toolCalls?.length) {
|
|
190
178
|
const events = toolCalls.map((tc) => {
|
|
191
179
|
return {
|
|
192
180
|
type: functionNameMapping[tc.function.name],
|
|
@@ -194,12 +182,10 @@ export function fromEvent<TInput>(
|
|
|
194
182
|
};
|
|
195
183
|
});
|
|
196
184
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
202
|
-
}
|
|
185
|
+
const event = events[0]!;
|
|
186
|
+
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
system._relay(self, self._parent, event);
|
|
203
189
|
}
|
|
204
190
|
|
|
205
191
|
return undefined;
|
|
@@ -243,8 +229,7 @@ export function fromTool<TInput>(
|
|
|
243
229
|
}
|
|
244
230
|
| undefined,
|
|
245
231
|
TInput
|
|
246
|
-
>(async ({ input
|
|
247
|
-
const functionNameMapping: Record<string, string> = {};
|
|
232
|
+
>(async ({ input }) => {
|
|
248
233
|
const resolvedTools = Object.entries(tools).map(([key, value]) => {
|
|
249
234
|
return {
|
|
250
235
|
type: 'function',
|
|
@@ -296,42 +281,6 @@ export function fromTool<TInput>(
|
|
|
296
281
|
});
|
|
297
282
|
}
|
|
298
283
|
|
|
299
|
-
interface OpenAIAdapterOutput<
|
|
300
|
-
T extends {
|
|
301
|
-
model: ChatCompletionCreateParamsBase['model'];
|
|
302
|
-
}
|
|
303
|
-
> {
|
|
304
|
-
model: T['model'];
|
|
305
|
-
/**
|
|
306
|
-
* Determines which event to send to the parent state machine actor based on the prompt.
|
|
307
|
-
*/
|
|
308
|
-
fromEvent: <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>(
|
|
322
|
-
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
323
|
-
) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
324
|
-
/**
|
|
325
|
-
* Creates observable actor logic that emits a chat completion stream.
|
|
326
|
-
*/
|
|
327
|
-
fromChatStream: <TInput>(
|
|
328
|
-
inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming
|
|
329
|
-
) => ObservableActorLogic<
|
|
330
|
-
OpenAI.Chat.Completions.ChatCompletionChunk,
|
|
331
|
-
TInput
|
|
332
|
-
>;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
284
|
export function createOpenAIAdapter<
|
|
336
285
|
T extends {
|
|
337
286
|
model: ChatCompletionCreateParamsBase['model'];
|
|
@@ -339,9 +288,7 @@ export function createOpenAIAdapter<
|
|
|
339
288
|
>(openai: OpenAI, settings: T): StatelyAgentAdapter {
|
|
340
289
|
const agentSettings: StatelyAgentAdapter = {
|
|
341
290
|
model: settings.model,
|
|
342
|
-
fromEvent: (input) =>
|
|
343
|
-
// @ts-ignore infinitely deep
|
|
344
|
-
fromEvent(openai, agentSettings, input, { execute: true }) as any,
|
|
291
|
+
fromEvent: (input) => fromEvent(openai, agentSettings, input),
|
|
345
292
|
fromChat: (input) => fromChatCompletion(openai, agentSettings, input),
|
|
346
293
|
fromChatStream: (input) => fromChatStream(openai, agentSettings, input),
|
|
347
294
|
fromTool: (input, tools) => fromTool(openai, agentSettings, tools, input),
|
package/src/schemas.ts
CHANGED
|
@@ -2,36 +2,38 @@ import { Values } from 'xstate';
|
|
|
2
2
|
import {
|
|
3
3
|
ContextSchema,
|
|
4
4
|
EventSchemas,
|
|
5
|
-
ConvertContextToJSONSchema,
|
|
6
5
|
ConvertToJSONSchemas,
|
|
7
6
|
createEventSchemas,
|
|
8
7
|
} from './utils';
|
|
9
8
|
import { FromSchema } from 'json-schema-to-ts';
|
|
10
9
|
|
|
11
10
|
export function createSchemas<
|
|
12
|
-
TContextSchema extends ContextSchema,
|
|
13
|
-
TEventSchemas extends EventSchemas
|
|
11
|
+
const TContextSchema extends ContextSchema,
|
|
12
|
+
const TEventSchemas extends EventSchemas
|
|
14
13
|
>({
|
|
15
14
|
context,
|
|
16
15
|
events,
|
|
17
16
|
}: {
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The JSON schema for the context object.
|
|
19
|
+
*
|
|
20
|
+
* Must be of `{ type: 'object' }`.
|
|
21
|
+
*/
|
|
22
|
+
context?: TContextSchema;
|
|
23
|
+
/**
|
|
24
|
+
* An object mapping event types to each event object's JSON Schema.
|
|
25
|
+
*/
|
|
19
26
|
events: TEventSchemas;
|
|
20
27
|
}): {
|
|
21
|
-
context:
|
|
28
|
+
context: TContextSchema | undefined;
|
|
22
29
|
events: ConvertToJSONSchemas<TEventSchemas>;
|
|
23
30
|
types: {
|
|
24
|
-
context: FromSchema<
|
|
31
|
+
context: FromSchema<TContextSchema>;
|
|
25
32
|
events: FromSchema<Values<ConvertToJSONSchemas<TEventSchemas>>>;
|
|
26
33
|
};
|
|
27
34
|
} {
|
|
28
35
|
return {
|
|
29
|
-
context
|
|
30
|
-
type: 'object',
|
|
31
|
-
properties: context,
|
|
32
|
-
additionalProperties: false,
|
|
33
|
-
required: Object.keys(context),
|
|
34
|
-
},
|
|
36
|
+
context,
|
|
35
37
|
events: createEventSchemas(events),
|
|
36
38
|
types: {} as any,
|
|
37
39
|
};
|
package/src/types.ts
CHANGED
|
@@ -4,8 +4,6 @@ import {
|
|
|
4
4
|
ChatCompletionCreateParamsStreaming,
|
|
5
5
|
} from 'openai/resources';
|
|
6
6
|
import {
|
|
7
|
-
AnyActorLogic,
|
|
8
|
-
AnyActorRef,
|
|
9
7
|
AnyEventObject,
|
|
10
8
|
ObservableActorLogic,
|
|
11
9
|
PromiseActorLogic,
|
|
@@ -13,24 +11,22 @@ import {
|
|
|
13
11
|
|
|
14
12
|
export interface StatelyAgentAdapter {
|
|
15
13
|
model: string;
|
|
14
|
+
/**
|
|
15
|
+
* Creates actor logic that chooses an event from all of the
|
|
16
|
+
* possible next events of the parent state machine
|
|
17
|
+
* and sends it to the parent actor.
|
|
18
|
+
*/
|
|
16
19
|
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
|
-
}
|
|
20
|
+
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
25
21
|
) => PromiseActorLogic<AnyEventObject[] | undefined, TInput>;
|
|
26
22
|
/**
|
|
27
|
-
* Creates
|
|
23
|
+
* Creates actor logic that resolves with a chat completion.
|
|
28
24
|
*/
|
|
29
25
|
fromChat: <TInput>(
|
|
30
26
|
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming
|
|
31
27
|
) => PromiseActorLogic<OpenAI.Chat.Completions.ChatCompletion, TInput>;
|
|
32
28
|
/**
|
|
33
|
-
* Creates
|
|
29
|
+
* Creates actor logic that emits a chat completion stream.
|
|
34
30
|
*/
|
|
35
31
|
fromChatStream: <TInput>(
|
|
36
32
|
inputFn: (input: TInput) => string | ChatCompletionCreateParamsStreaming
|
|
@@ -38,18 +34,14 @@ export interface StatelyAgentAdapter {
|
|
|
38
34
|
OpenAI.Chat.Completions.ChatCompletionChunk,
|
|
39
35
|
TInput
|
|
40
36
|
>;
|
|
41
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Creates actor logic that chooses a tool from the provided
|
|
39
|
+
* tools and runs that tool.
|
|
40
|
+
*/
|
|
42
41
|
fromTool: <TInput>(
|
|
43
42
|
inputFn: (input: TInput) => string | ChatCompletionCreateParamsNonStreaming,
|
|
44
43
|
tools: {
|
|
45
44
|
[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
45
|
}
|
|
54
46
|
) => PromiseActorLogic<
|
|
55
47
|
| {
|
package/src/utils.ts
CHANGED
|
@@ -20,26 +20,17 @@ export type EventSchemas = {
|
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export
|
|
24
|
-
[key: string]: JSONSchema7;
|
|
25
|
-
}
|
|
23
|
+
export type ContextSchema = JSONSchema7 & { type: 'object' };
|
|
26
24
|
|
|
27
25
|
export type ConvertToJSONSchemas<T> = {
|
|
28
26
|
[K in keyof T]: {
|
|
29
|
-
properties: { type: { const: K } }
|
|
27
|
+
properties: { type: { const: K } } & Prop<T[K], 'properties'>;
|
|
30
28
|
type: 'object';
|
|
31
|
-
required: Array<keyof Prop<T[K], 'properties'> | 'type'>;
|
|
29
|
+
required: Array<(keyof Prop<T[K], 'properties'> & string) | 'type'>;
|
|
32
30
|
additionalProperties: false;
|
|
33
|
-
}
|
|
31
|
+
};
|
|
34
32
|
} & {};
|
|
35
33
|
|
|
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
34
|
export function createEventSchemas<T extends EventSchemas>(
|
|
44
35
|
eventSchemaMap: T
|
|
45
36
|
): ConvertToJSONSchemas<T> {
|