@statelyai/agent 0.0.7 → 0.1.0

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.
Files changed (50) hide show
  1. package/.vscode/launch.json +12 -1
  2. package/CHANGELOG.md +47 -0
  3. package/dist/index.d.mts +3 -0
  4. package/dist/index.d.ts +282 -71
  5. package/dist/index.js +4389 -183
  6. package/dist/index.mjs +7 -0
  7. package/examples/chatbot.ts +79 -0
  8. package/examples/cot.ts +91 -0
  9. package/examples/email.ts +118 -0
  10. package/examples/example.ts +81 -0
  11. package/examples/goal.ts +94 -0
  12. package/examples/joke.ts +117 -110
  13. package/examples/multi.ts +103 -0
  14. package/examples/newspaper.ts +324 -0
  15. package/examples/number.ts +102 -0
  16. package/examples/raffle.ts +105 -0
  17. package/examples/simple.ts +39 -0
  18. package/examples/support.ts +147 -0
  19. package/examples/ticTacToe.ts +89 -124
  20. package/examples/todo.ts +132 -0
  21. package/examples/tutor.ts +100 -0
  22. package/examples/verify.ts +120 -0
  23. package/examples/weather.ts +65 -47
  24. package/examples/wiki.ts +30 -0
  25. package/examples/word.ts +168 -0
  26. package/package.json +18 -11
  27. package/readme.md +9 -38
  28. package/src/adapters/vercel.ts +7 -0
  29. package/src/agent-experimental.ts +221 -0
  30. package/src/agent.test.ts +187 -0
  31. package/src/agent.ts +260 -6
  32. package/src/decision.test.ts +179 -0
  33. package/src/decision.ts +83 -0
  34. package/src/index.ts +3 -2
  35. package/src/memory.ts +25 -0
  36. package/src/planners/shortestPathPlanner.ts +22 -0
  37. package/src/planners/simplePlanner.ts +126 -0
  38. package/src/schemas.ts +13 -38
  39. package/src/strategies/chain-of-note.ts +155 -0
  40. package/src/templates/defaultText.ts +18 -0
  41. package/src/templates/defaultToolCall.ts +10 -0
  42. package/src/text.ts +232 -0
  43. package/src/types.ts +363 -46
  44. package/src/utils.ts +13 -50
  45. package/tsconfig.json +1 -1
  46. package/examples/multiAgentCollaboration.ts +0 -0
  47. package/examples/numberGuesser.ts +0 -128
  48. package/examples/wordGuesser.ts +0 -156
  49. package/src/adapter.test.ts +0 -217
  50. package/src/adapters/openai.ts +0 -298
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/agent",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,26 +10,33 @@
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
12
  "@changesets/changelog-github": "^0.5.0",
13
- "@changesets/cli": "^2.27.1",
14
- "@types/node": "^20.10.6",
15
- "dotenv": "^16.3.1",
16
- "json-schema-to-ts": "^3.0.0",
17
- "openai": "^4.24.1",
13
+ "@changesets/cli": "^2.27.5",
14
+ "@langchain/community": "^0.0.53",
15
+ "@langchain/core": "^0.1.63",
16
+ "@langchain/openai": "^0.0.28",
17
+ "@types/node": "^20.14.2",
18
+ "dotenv": "^16.4.5",
19
+ "json-schema-to-ts": "^3.1.0",
18
20
  "ts-node": "^10.9.2",
19
- "tsup": "^8.0.1",
20
- "typescript": "^5.3.3",
21
- "vitest": "^1.2.2"
21
+ "tsup": "^8.1.0",
22
+ "typescript": "^5.4.5",
23
+ "vitest": "^1.6.0",
24
+ "wikipedia": "^2.1.2",
25
+ "zod": "^3.23.8"
22
26
  },
23
27
  "publishConfig": {
24
28
  "access": "public"
25
29
  },
26
30
  "dependencies": {
27
- "xstate": "^5.8.0"
31
+ "@ai-sdk/openai": "^0.0.13",
32
+ "@xstate/graph": "^2.0.0",
33
+ "ai": "^3.1.32",
34
+ "xstate": "^5.13.2"
28
35
  },
29
36
  "packageManager": "pnpm@8.11.0",
30
37
  "scripts": {
31
38
  "build": "tsup src/index.ts --format cjs,esm --dts",
32
- "lint": "tsc",
39
+ "lint": "tsc --noEmit",
33
40
  "test": "vitest",
34
41
  "example": "ts-node examples/helpers/runner.ts",
35
42
  "changeset": "changeset",
package/readme.md CHANGED
@@ -1,42 +1,13 @@
1
- # Stately Agent (alpha)
1
+ # Stately Agent
2
2
 
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.
3
+ > [!WARNING]
4
+ > Alpha software! Not ready for production yet.
5
5
 
6
- ## Installation
6
+ Stately Agent is a flexible framework for building AI agents using state machines. Stately agents go beyond normal LLM-based AI agents by:
7
7
 
8
- Install `openai`, and `@statelyai/agent`:
8
+ - Using state machines to guide the agent's behavior, powered by [XState](https://stately.ai/docs/xstate)
9
+ - Incorporating **observations**, **message history**, and **feedback** to the agent decision-making and text-generation processes, as needed
10
+ - Enabling custom **planning** abilities for agents to achieve specific goals based on state machine logic, observations, and feedback
11
+ - Wrapping the [Vercel AI SDK](https://sdk.vercel.ai/) to easily support multiple model providers, such as OpenAI, Anthropic, Google, Mistral, Groq, Perplexity, and more
9
12
 
10
- ```bash
11
- pnpm install openai @statelyai/agent
12
- ```
13
-
14
- ## Usage
15
-
16
- Work in progress. For now, see the examples:
17
-
18
- - [Joke generator](https://github.com/statelyai/agent/tree/main/examples/joke.ts)
19
- - Demonstrates `agent.fromChatCompletion(...)` to generate a joke and provide a joke rating
20
- - Demonstrates `agent.fromEvent(...)` to choose whether to keep generating jokes or stop
21
- - [Tic-tac-toe](https://github.com/statelyai/agent/tree/main/examples/ticTacToe.ts)
22
- - Demonstrates `agent.fromEvent(...)` to have an agent play itself in a game of tic-tac-toe with precise events
23
- - Demonstrates `agent.fromChatCompletionStream(...)` to produce a game report at the end of the game
24
- - [Weather](https://github.com/statelyai/agent/tree/main/examples/weather.ts)
25
- - Demonstrates using [Tavily](https://tavily.com/) as an external API
26
- - Demonstrates `agent.fromEvent(...)` to only use Tavily to get the weather if the user provides a valid location
27
-
28
- ## Examples
29
-
30
- First, clone this repo locally. To run the examples in this repo, create a `.env` file at the root of the repo with the following contents:
31
-
32
- ```bash
33
- OPENAI_API_KEY="your-openai-api-key"
34
- ```
35
-
36
- Then, install the dependencies (`npm install`) and run the examples:
37
-
38
- ```bash
39
- pnpm run example joke
40
- # or:
41
- # pnpm run example ticTacToe
42
- ```
13
+ _Documentation coming soon_
@@ -0,0 +1,7 @@
1
+ import { generateText, streamText } from 'ai';
2
+ import { AIAdapter } from '../types';
3
+
4
+ export const vercelAdapter: AIAdapter = {
5
+ generateText,
6
+ streamText,
7
+ };
@@ -0,0 +1,221 @@
1
+ // // type AgentExperiences<TState, TReward> = Record<
2
+ // // string, // serialized state
3
+ // // Record<
4
+ // // string, // serialized event
5
+ // // {
6
+ // // state: TState;
7
+ // // reward: TReward;
8
+ // // }
9
+ // // >
10
+
11
+ // import OpenAI from 'openai';
12
+ // import {
13
+ // AnyEventObject,
14
+ // EventObject,
15
+ // AnyActorLogic,
16
+ // AnyMachineSnapshot,
17
+ // ActorRef,
18
+ // AnyActorRef,
19
+ // SnapshotFrom,
20
+ // EventFrom,
21
+ // } from 'xstate';
22
+ // import { getToolCalls } from './adapters/openai';
23
+ // import { ZodEventMapping } from './schemas';
24
+
25
+ // // >;
26
+ // interface AgentExperience<TState, TEvent extends AnyEventObject> {
27
+ // prevState: TState | undefined;
28
+ // event: TEvent;
29
+ // nextState: TState;
30
+ // }
31
+
32
+ // type AgentPlan<TState, TEvent extends EventObject> = Array<{
33
+ // /**
34
+ // * The current state
35
+ // */
36
+ // state: TState;
37
+ // /**
38
+ // * The event to execute
39
+ // */
40
+ // event: TEvent;
41
+ // /**
42
+ // * The expected next state
43
+ // */
44
+ // nextState?: TState;
45
+ // }>;
46
+
47
+ // interface AgentModel<
48
+ // // TLogic extends AnyActorLogic,
49
+ // TReward,
50
+ // TState,
51
+ // TEvent extends EventObject
52
+ // > {
53
+ // // policy: ({
54
+ // // logic,
55
+ // // state,
56
+ // // goal,
57
+ // // }: {
58
+ // // logic: TLogic;
59
+ // // state: TState;
60
+ // // goal: string;
61
+ // // }) => Promise<AgentPlan<TState>>;
62
+ // getExperiences: () => Promise<Array<AgentExperience<TState, TEvent>>>; // TODO: TLogic instead?
63
+ // addExperience: (experience: AgentExperience<TState, TEvent>) => void;
64
+ // getLogic: ({
65
+ // experiences,
66
+ // }: {
67
+ // experiences: Array<AgentExperience<TState, TEvent>>; // TODO: TLogic instead?
68
+ // }) => Promise<AnyActorLogic>;
69
+ // getNextEvents: ({
70
+ // logic,
71
+ // state,
72
+ // }: {
73
+ // logic: AnyActorLogic;
74
+ // state: TState;
75
+ // }) => Promise<AnyEventObject[]>;
76
+ // getPlans: ({
77
+ // logic,
78
+ // state,
79
+ // goals,
80
+ // }: {
81
+ // logic: AnyActorLogic;
82
+ // state: TState;
83
+ // goals: string[];
84
+ // }) => Promise<Array<AgentPlan<TState, TEvent>>>;
85
+ // getReward: ({
86
+ // logic,
87
+ // state,
88
+ // goals,
89
+ // action,
90
+ // }: {
91
+ // logic: AnyActorLogic;
92
+ // state: TState;
93
+ // goals: string[];
94
+ // action: EventObject;
95
+ // }) => Promise<TReward>;
96
+ // }
97
+
98
+ // interface AgentLogic<T> {
99
+ // /**
100
+ // * The next possible actions (represented by events) that the agent can take
101
+ // * based on the current state of the environment
102
+ // */
103
+ // getActions(state: T): Promise<AnyEventObject[]>;
104
+ // getPlan(state: T, goal: any): Promise<Array<[T, EventObject]>>;
105
+ // }
106
+
107
+ // interface Agent<TState extends AnyMachineSnapshot, TEvent extends EventObject> {
108
+ // act: (env: ActorRef<TState, TEvent>) => Promise<void>;
109
+ // }
110
+
111
+ // function createAgent2<TEnvironment extends AnyActorRef>(
112
+ // openai: OpenAI,
113
+ // // logic: AnyActorLogic,
114
+ // // input: InputFrom<TLogic>,
115
+ // getGoals: (state: SnapshotFrom<TEnvironment>) => string | string[],
116
+ // schemas: ZodEventMapping
117
+ // ): Agent<SnapshotFrom<TEnvironment>, EventFrom<TEnvironment>> {
118
+ // const experiences: Array<AgentExperience<any, any>> = [];
119
+
120
+ // const agentModel: AgentModel<
121
+ // any,
122
+ // SnapshotFrom<TEnvironment>,
123
+ // EventFrom<TEnvironment>
124
+ // > = {
125
+ // // policy: async ({ logic, state, goal }) => {
126
+ // // const toolEvents = await getToolCalls(
127
+ // // openai,
128
+ // // goal,
129
+ // // state,
130
+ // // 'gpt-4-1106-preview',
131
+ // // (eventType) => eventType.startsWith('agent.'),
132
+ // // schemas
133
+ // // );
134
+ // // console.log(toolEvents);
135
+ // // return toolEvents.map((te) => ({
136
+ // // state,
137
+ // // event: te as EventFromLogic<TLogic>,
138
+ // // }));
139
+ // // },
140
+ // addExperience: (experience) => {
141
+ // experiences.push(experience);
142
+ // },
143
+ // getExperiences: async () => experiences,
144
+ // getLogic: async ({ experiences }) => {
145
+ // return null as any; // TODO
146
+ // },
147
+ // getNextEvents: async ({ logic, state }) => {
148
+ // return [];
149
+ // },
150
+ // getReward: async ({ logic, state, goals, action }) => {
151
+ // return 0;
152
+ // },
153
+ // getPlans: async ({ logic, state, goals }) => {
154
+ // if (!goals[0]) {
155
+ // return [];
156
+ // }
157
+
158
+ // const toolEvents = await getToolCalls(
159
+ // openai,
160
+ // goals[0] + '\nOnly make a single tool call.',
161
+ // state as any,
162
+ // 'gpt-3.5-turbo-16k-0613',
163
+ // (eventType) => eventType.startsWith('agent.'),
164
+ // schemas
165
+ // );
166
+
167
+ // console.log(toolEvents);
168
+
169
+ // return [
170
+ // toolEvents.map((toolEvent) => ({
171
+ // state,
172
+ // event: toolEvent as EventFrom<TEnvironment>,
173
+ // })),
174
+ // ];
175
+ // },
176
+ // };
177
+
178
+ // // const actor = createActor(logic, {
179
+ // // input,
180
+ // // inspect: (inspEv) => {
181
+ // // if (inspEv.type === '@xstate.snapshot') {
182
+ // // agentModel.addExperience({
183
+ // // prevState: experiences[experiences.length - 1]?.nextState,
184
+ // // nextState: (inspEv.snapshot as AnyMachineSnapshot).value,
185
+ // // event: inspEv.event as EventFromLogic<TLogic>,
186
+ // // });
187
+ // // }
188
+ // // },
189
+ // // });
190
+
191
+ // return {
192
+ // act: async (actorRef) => {
193
+ // const state = actorRef.getSnapshot();
194
+ // // @ts-ignore
195
+ // console.log(state.value, state.context);
196
+ // const experiences = await agentModel.getExperiences();
197
+ // const goals = toArray(getGoals(state));
198
+ // console.log('Goal:', goals);
199
+
200
+ // const nextPlans = await agentModel.getPlans({
201
+ // logic: await agentModel.getLogic({ experiences }),
202
+ // goals,
203
+ // state,
204
+ // });
205
+
206
+ // const nextStep = nextPlans?.[0]?.[0];
207
+
208
+ // // TODO: race conditions!
209
+ // if (nextStep) {
210
+ // console.log('nextStep', nextStep?.event);
211
+ // actorRef.send(nextStep.event);
212
+ // } else {
213
+ // console.log('No next step');
214
+ // }
215
+ // },
216
+ // } satisfies Agent<SnapshotFrom<TEnvironment>, EventFrom<TEnvironment>>; // TODO: fix types
217
+ // }
218
+
219
+ // function toArray<T>(value: T | T[]): T[] {
220
+ // return Array.isArray(value) ? value : [value];
221
+ // }
@@ -0,0 +1,187 @@
1
+ import { test, expect } from 'vitest';
2
+ import { createAgent, type AIAdapter } from './';
3
+ import { createActor, createMachine } from 'xstate';
4
+
5
+ test('an agent has the expected interface', () => {
6
+ const agent = createAgent({
7
+ name: 'test',
8
+ events: {},
9
+ model: {} as any,
10
+ });
11
+
12
+ expect(agent.decide).toBeDefined();
13
+ expect(agent.generateText).toBeDefined();
14
+ expect(agent.streamText).toBeDefined();
15
+
16
+ expect(agent.addFeedback).toBeDefined();
17
+ expect(agent.addMessage).toBeDefined();
18
+ expect(agent.addObservation).toBeDefined();
19
+ expect(agent.addPlan).toBeDefined();
20
+
21
+ expect(agent.interact).toBeDefined();
22
+ });
23
+
24
+ test('agent.addMessage() adds to message history', () => {
25
+ const agent = createAgent({
26
+ name: 'test',
27
+ events: {},
28
+ model: {} as any,
29
+ });
30
+
31
+ agent.addMessage({
32
+ content: 'msg 1',
33
+ role: 'user',
34
+ });
35
+
36
+ const messageHistory = agent.addMessage({
37
+ content: 'response 1',
38
+ role: 'assistant',
39
+ });
40
+
41
+ expect(messageHistory.sessionId).toEqual(agent.sessionId);
42
+
43
+ expect(agent.select((c) => c.messages)).toContainEqual(
44
+ expect.objectContaining({
45
+ content: 'msg 1',
46
+ })
47
+ );
48
+
49
+ expect(agent.select((c) => c.messages)).toContainEqual(
50
+ expect.objectContaining({
51
+ content: 'response 1',
52
+ sessionId: expect.any(String),
53
+ timestamp: expect.any(Number),
54
+ })
55
+ );
56
+ });
57
+
58
+ test('agent.addFeedback() adds to feedback', () => {
59
+ const agent = createAgent({
60
+ name: 'test',
61
+ events: {},
62
+ model: {} as any,
63
+ });
64
+
65
+ const feedback = agent.addFeedback({
66
+ attributes: {
67
+ score: -1,
68
+ },
69
+ goal: 'Win the game',
70
+ observationId: 'obs-1',
71
+ });
72
+
73
+ expect(feedback.sessionId).toEqual(agent.sessionId);
74
+
75
+ expect(agent.select((c) => c.feedback)).toContainEqual(
76
+ expect.objectContaining({
77
+ attributes: {
78
+ score: -1,
79
+ },
80
+ goal: 'Win the game',
81
+ observationId: 'obs-1',
82
+ sessionId: expect.any(String),
83
+ timestamp: expect.any(Number),
84
+ })
85
+ );
86
+ });
87
+
88
+ test('agent.addObservation() adds to observations', () => {
89
+ const agent = createAgent({
90
+ name: 'test',
91
+ events: {},
92
+ model: {} as any,
93
+ });
94
+
95
+ const observation = agent.addObservation({
96
+ prevState: { value: 'playing', context: {} },
97
+ event: { type: 'play', position: 3 },
98
+ state: { value: 'lost', context: {} },
99
+ });
100
+
101
+ expect(observation.sessionId).toEqual(agent.sessionId);
102
+
103
+ expect(agent.select((c) => c.observations)).toContainEqual(
104
+ expect.objectContaining({
105
+ prevState: { value: 'playing', context: {} },
106
+ event: { type: 'play', position: 3 },
107
+ state: { value: 'lost', context: {} },
108
+ sessionId: expect.any(String),
109
+ timestamp: expect.any(Number),
110
+ })
111
+ );
112
+ });
113
+
114
+ test('agent.interact() observes machine actors (no 2nd arg)', () => {
115
+ const machine = createMachine({
116
+ initial: 'a',
117
+ states: {
118
+ a: {
119
+ on: { NEXT: 'b' },
120
+ },
121
+ b: {},
122
+ },
123
+ });
124
+
125
+ const agent = createAgent({
126
+ name: 'test',
127
+ events: {},
128
+ model: {} as any,
129
+ });
130
+
131
+ const actor = createActor(machine);
132
+
133
+ agent.interact(actor);
134
+
135
+ actor.start();
136
+
137
+ expect(agent.select((c) => c.observations)).toContainEqual(
138
+ expect.objectContaining({
139
+ prevState: undefined,
140
+ state: expect.objectContaining({ value: 'a' }),
141
+ })
142
+ );
143
+
144
+ actor.send({ type: 'NEXT' });
145
+
146
+ expect(agent.select((c) => c.observations)).toContainEqual(
147
+ expect.objectContaining({
148
+ prevState: expect.objectContaining({ value: 'a' }),
149
+ event: { type: 'NEXT' },
150
+ state: expect.objectContaining({ value: 'b' }),
151
+ })
152
+ );
153
+ });
154
+
155
+ test('Agents can use a custom adapter', async () => {
156
+ const adapter = {
157
+ generateText: async () => {
158
+ return {
159
+ text: 'Response',
160
+ } as any;
161
+ },
162
+ } as unknown as AIAdapter;
163
+
164
+ const agent = createAgent({
165
+ name: 'test',
166
+ events: {},
167
+ adapter,
168
+ model: {} as any,
169
+ });
170
+
171
+ const res = await agent.generateText({
172
+ prompt: 'Question?',
173
+ });
174
+
175
+ expect(res.text).toEqual('Response');
176
+ });
177
+
178
+ test.skip('You can listen for emitted agent events', () => {
179
+ const agent = createAgent({
180
+ name: 'test',
181
+ events: {},
182
+ adapter: {} as any,
183
+ model: {} as any,
184
+ });
185
+
186
+ agent.on('feedback', (x) => {});
187
+ });