@statelyai/agent 2.0.0-next.3 → 2.0.0-next.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.
Files changed (37) hide show
  1. package/.changeset/calm-beans-talk.md +5 -0
  2. package/.changeset/long-guests-explode.md +5 -0
  3. package/.changeset/nice-pants-rule.md +10 -0
  4. package/.changeset/odd-kiwis-compare.md +5 -0
  5. package/.changeset/pre.json +4 -0
  6. package/CHANGELOG.md +23 -0
  7. package/architecture.tldr +797 -0
  8. package/dist/index.d.mts +198 -140
  9. package/dist/index.d.ts +198 -140
  10. package/dist/index.js +4397 -148
  11. package/dist/index.mjs +4397 -149
  12. package/examples/chatbot.ts +9 -5
  13. package/examples/cot.ts +2 -4
  14. package/examples/jugs.ts +2 -2
  15. package/examples/learn-from-feedback.ts +7 -7
  16. package/examples/newspaper.ts +1 -1
  17. package/examples/rewoo.ts +62 -0
  18. package/examples/river-crossing.ts +2 -2
  19. package/examples/serverless.ts +71 -0
  20. package/examples/simple.ts +1 -1
  21. package/examples/ticTacToe.ts +6 -2
  22. package/examples/wiki.ts +2 -2
  23. package/package.json +14 -12
  24. package/readme.md +57 -0
  25. package/src/agent.test.ts +387 -30
  26. package/src/agent.ts +177 -64
  27. package/src/decide.test.ts +24 -2
  28. package/src/decide.ts +34 -78
  29. package/src/index.ts +1 -0
  30. package/src/{strategies/chainOfThought.ts → policies/chainOfThoughtPolicy.ts} +7 -9
  31. package/src/policies/index.ts +3 -0
  32. package/src/{strategies/shortestPath.test.ts → policies/shortestPathPolicy.test.ts} +2 -2
  33. package/src/{strategies/shortestPath.ts → policies/shortestPathPolicy.ts} +8 -8
  34. package/src/{strategies/simple.ts → policies/toolPolicy.ts} +27 -26
  35. package/src/text.ts +17 -22
  36. package/src/types.ts +162 -166
  37. package/src/agent-experimental.ts +0 -221
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { createAgent, fromDecision, TypesFromAgent } from '../src';
3
3
  import { openai } from '@ai-sdk/openai';
4
4
  import { assign, createActor, log, setup } from 'xstate';
5
- import { fromTerminal } from './helpers/helpers';
5
+ import { fromTerminal, getFromTerminal } from './helpers/helpers';
6
6
 
7
7
  const agent = createAgent({
8
8
  id: 'chatbot',
@@ -60,7 +60,11 @@ const machine = setup({
60
60
 
61
61
  const actor = createActor(machine).start();
62
62
 
63
- agent.interact(actor, () => ({
64
- goal: 'Respond to the user, unless they want to end the conversation.',
65
- messages: agent.getMessages(),
66
- }));
63
+ agent.interact(actor, (s) => {
64
+ if (s.state.matches('responding')) {
65
+ return {
66
+ goal: 'Respond to the user, unless they want to end the conversation.',
67
+ messages: agent.getMessages(),
68
+ };
69
+ }
70
+ });
package/examples/cot.ts CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { createAgent } from '../src';
3
3
  import { openai } from '@ai-sdk/openai';
4
4
  import { getFromTerminal } from './helpers/helpers';
5
- import { chainOfThoughtStrategy } from '../src/strategies/chainOfThought';
5
+ import { chainOfThoughtPolicy } from '../src/policies/chainOfThoughtPolicy';
6
6
 
7
7
  const agent = createAgent({
8
8
  id: 'chain-of-thought',
@@ -15,11 +15,9 @@ const agent = createAgent({
15
15
  context: {
16
16
  question: z.string().nullable(),
17
17
  },
18
- strategy: chainOfThoughtStrategy,
18
+ policy: chainOfThoughtPolicy,
19
19
  });
20
20
 
21
- // agent.onMessage((msg) => console.log(msg.content));
22
-
23
21
  async function main() {
24
22
  const msg = await getFromTerminal('what?');
25
23
 
package/examples/jugs.ts CHANGED
@@ -2,7 +2,7 @@ import { createAgent, TypesFromAgent } from '../src';
2
2
  import { assign, createActor, setup } from 'xstate';
3
3
  import { openai } from '@ai-sdk/openai';
4
4
  import { z } from 'zod';
5
- import { experimental_shortestPathStrategy } from '../src/strategies/shortestPath';
5
+ import { experimental_shortestPathPolicy } from '../src/policies/shortestPathPolicy';
6
6
 
7
7
  const agent = createAgent({
8
8
  id: 'die-hard-solver',
@@ -103,7 +103,7 @@ async function main() {
103
103
  machine: waterJugMachine,
104
104
  goal: 'Get exactly 4 gallons of water in the 5-gallon jug',
105
105
  state: waterJugActor.getSnapshot(),
106
- strategy: experimental_shortestPathStrategy,
106
+ policy: experimental_shortestPathPolicy,
107
107
  });
108
108
 
109
109
  console.log(decision?.nextEvent);
@@ -35,7 +35,7 @@ async function main() {
35
35
  const relevantFeedback = await agent
36
36
  .getFeedback()
37
37
  .filter((f) =>
38
- relevantObservations.find((o) => o.id === f.observationId)
38
+ relevantObservations.find((o) => o.decisionId === f.decisionId)
39
39
  );
40
40
 
41
41
  const decision = await agent.decide({
@@ -79,24 +79,24 @@ Achieve the goal. Consider both exploring unknown actions (high exploration_valu
79
79
  });
80
80
 
81
81
  if (decision?.nextEvent?.type === 'submit') {
82
- const observation = await agent.addObservation({
83
- goal: decision.goal,
82
+ const observation = agent.addObservation({
83
+ decisionId: decision.id,
84
84
  prevState: { value: 'editing' },
85
85
  event: { type: 'submit' },
86
86
  state: { value: 'editing' },
87
87
  });
88
88
 
89
89
  // don't change the status; pretend submit button is broken
90
- await agent.addFeedback({
91
- observationId: observation.id,
92
- score: 0,
90
+ agent.addFeedback({
91
+ decisionId: observation.id,
92
+ reward: 0,
93
93
  comment: 'Form not submitted',
94
94
  });
95
95
  } else if (decision?.nextEvent?.type === 'pressEnter') {
96
96
  status = 'submitted';
97
97
 
98
98
  await agent.addObservation({
99
- goal: decision.goal,
99
+ decisionId: decision.id,
100
100
  prevState: { value: 'editing' },
101
101
  event: { type: 'pressEnter' },
102
102
  state: { value: 'submitted' },
@@ -8,7 +8,7 @@ import { assign, createActor, fromPromise, setup } from 'xstate';
8
8
  import { createAgent } from '../src';
9
9
  import { openai } from '@ai-sdk/openai';
10
10
  import { z } from 'zod';
11
- import { generateObject, generateText } from 'ai';
11
+ import { generateObject } from 'ai';
12
12
 
13
13
  interface AgentState {
14
14
  topic: string;
@@ -0,0 +1,62 @@
1
+ import { setup } from 'xstate';
2
+
3
+ interface RewooContext {
4
+ task: string;
5
+ planString: string;
6
+ steps: string[][];
7
+ results: Record<string, any>;
8
+ result: string;
9
+ }
10
+
11
+ const createTemplate = (input: { task: string }) =>
12
+ `For the following task, make plans that can solve the problem step by step. For each plan, indicate
13
+ which external tool together with tool input to retrieve evidence. You can store the evidence into a
14
+ variable #E that can be called by later tools. (Plan, #E1, Plan, #E2, Plan, ...)
15
+
16
+ Tools can be one of the following:
17
+ (1) Google[input]: Worker that searches results from Google. Useful when you need to find short
18
+ and succinct answers about a specific topic. The input should be a search query.
19
+ (2) LLM[input]: A pre-trained LLM like yourself. Useful when you need to act with general
20
+ world knowledge and common sense. Prioritize it when you are confident in solving the problem
21
+ yourself. Input can be any instruction.
22
+
23
+ For example,
24
+ Task: Thomas, Toby, and Rebecca worked a total of 157 hours in one week. Thomas worked x
25
+ hours. Toby worked 10 hours less than twice what Thomas worked, and Rebecca worked 8 hours
26
+ less than Toby. How many hours did Rebecca work?
27
+ Plan: Given Thomas worked x hours, translate the problem into algebraic expressions and solve with Wolfram Alpha.
28
+ #E1 = WolframAlpha[Solve x + (2x - 10) + ((2x - 10) - 8) = 157]
29
+ Plan: Find out the number of hours Thomas worked.
30
+ #E2 = LLM[What is x, given #E1]
31
+ Plan: Calculate the number of hours Rebecca worked.
32
+ #E3 = Calculator[(2 * #E2 - 10) - 8]
33
+
34
+ Important!
35
+ Variables/results MUST be referenced using the # symbol!
36
+ The plan will be executed as a program, so no coreference resolution apart from naive variable replacement is allowed.
37
+ The ONLY way for steps to share context is by including #E<step> within the arguments of the tool.
38
+
39
+ Begin!
40
+ Describe your plans with rich details. Each Plan should be followed by only one #E.
41
+
42
+ Task: ${input.task}`;
43
+
44
+ const machine = setup({
45
+ types: {
46
+ context: {} as RewooContext,
47
+ },
48
+ }).createMachine({
49
+ context: {
50
+ planString: '',
51
+ result: '',
52
+ steps: [],
53
+ task: '',
54
+ results: [],
55
+ },
56
+ initial: 'plan',
57
+ states: {
58
+ plan: {},
59
+ tool: {},
60
+ solve: {},
61
+ },
62
+ });
@@ -2,7 +2,7 @@ import { createAgent, TypesFromAgent } from '../src';
2
2
  import { assign, createActor, setup } from 'xstate';
3
3
  import { openai } from '@ai-sdk/openai';
4
4
  import { z } from 'zod';
5
- import { experimental_shortestPathStrategy } from '../src/strategies/shortestPath';
5
+ import { experimental_shortestPathPolicy } from '../src/policies/shortestPathPolicy';
6
6
 
7
7
  const agent = createAgent({
8
8
  id: 'river-crossing-solver',
@@ -118,7 +118,7 @@ async function main() {
118
118
  machine: riverCrossingMachine,
119
119
  goal: 'Get all items safely across the river. Remember: Cannot leave wolf with goat or goat with cabbage unattended.',
120
120
  state: riverActor.getSnapshot(),
121
- strategy: experimental_shortestPathStrategy,
121
+ policy: experimental_shortestPathPolicy,
122
122
  });
123
123
 
124
124
  console.log(decision?.nextEvent);
@@ -0,0 +1,71 @@
1
+ import { openai } from '@ai-sdk/openai';
2
+ import {
3
+ AgentDecision,
4
+ AgentFeedback,
5
+ AgentInsight,
6
+ AgentMessage,
7
+ AgentObservation,
8
+ createAgent,
9
+ } from '../src';
10
+ import { z } from 'zod';
11
+
12
+ const agent = createAgent({
13
+ id: 'simple',
14
+ model: openai('gpt-4o-mini'),
15
+ events: {
16
+ 'agent.moveLeft': z.object({}),
17
+ 'agent.moveRight': z.object({}),
18
+ 'agent.doNothing': z.object({}),
19
+ },
20
+ });
21
+
22
+ const db = {
23
+ observations: [] as AgentObservation<any>[],
24
+ feedbackItems: [] as AgentFeedback[],
25
+ decisions: [] as AgentDecision[],
26
+ messages: [] as AgentMessage[],
27
+ insights: [] as AgentInsight[],
28
+ };
29
+
30
+ // async function postObservation(req: unknown) {
31
+ // db.observations.push(req.body);
32
+ // }
33
+
34
+ async function getDecision(req: {
35
+ query: {
36
+ episodeId: string;
37
+ goal: string;
38
+ };
39
+ }) {
40
+ // Get relevant observations
41
+ const observations = db.observations
42
+ .filter((obs) => obs.episodeId === req.query.episodeId)
43
+ .at(-1);
44
+ const similarObservations = db.observations.filter(
45
+ (obs) => obs.prevState?.value === observations?.prevState?.value
46
+ );
47
+
48
+ // Get relevant feedback
49
+ const similarFeedback = db.feedbackItems.filter((fb) => {
50
+ similarObservations.map((obs) => obs.decisionId).includes(fb.decisionId);
51
+ });
52
+
53
+ // Get relevant insights
54
+ const insights = db.insights.filter((insight) =>
55
+ similarObservations.map((obs) => obs.id).includes(insight.observationId)
56
+ );
57
+
58
+ const decision = await agent.decide({
59
+ goal: req.query.goal,
60
+ state: observations?.state,
61
+ observations: similarObservations,
62
+ feedback: similarFeedback,
63
+ insights,
64
+ allowedEvents: ['agent.moveLeft', 'agent.moveRight'],
65
+ });
66
+
67
+ db.decisions.push(...agent.getDecisions());
68
+ db.messages.push(...agent.getMessages());
69
+
70
+ return decision;
71
+ }
@@ -2,7 +2,7 @@ import { createAgent, fromDecision } from '../src';
2
2
  import { z } from 'zod';
3
3
  import { setup, createActor, createMachine } from 'xstate';
4
4
  import { openai } from '@ai-sdk/openai';
5
- import { chainOfThoughtStrategy } from '../src/strategies/chainOfThought';
5
+ import { chainOfThoughtPolicy } from '../src/policies/chainOfThoughtPolicy';
6
6
 
7
7
  const agent = createAgent({
8
8
  id: 'simple',
@@ -267,8 +267,12 @@ xAgent.interact(actor, (observed) => {
267
267
 
268
268
  console.log('Similar:', similarObservations);
269
269
 
270
- const similarFeedbacks = similarObservations.map((o) => {
271
- return xAgent.getFeedback().filter((f) => f.observationId === o.id);
270
+ const similarFeedbacks = similarObservations.map((observation) => {
271
+ return xAgent
272
+ .getFeedback()
273
+ .filter(
274
+ (feedbackItem) => feedbackItem.decisionId === observation.decisionId
275
+ );
272
276
  });
273
277
 
274
278
  console.log('Feedbacks:', similarFeedbacks);
package/examples/wiki.ts CHANGED
@@ -21,12 +21,12 @@ agent.onMessage((msg) => {
21
21
  });
22
22
 
23
23
  async function main() {
24
- const response = await generateText({
24
+ const result = await generateText({
25
25
  model: agent.model,
26
26
  prompt: 'When was Deadpool 2 released?',
27
27
  });
28
28
 
29
- for (const msg of await response.responseMessages) {
29
+ for (const msg of await result.response.messages) {
30
30
  agent.addMessage(msg);
31
31
  }
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/agent",
3
- "version": "2.0.0-next.3",
3
+ "version": "2.0.0-next.5",
4
4
  "description": "Stateful agents that make decisions based on finite-state machine models",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -18,33 +18,35 @@
18
18
  "@ai-sdk/anthropic": "^0.0.54",
19
19
  "@ai-sdk/openai": "^0.0.40",
20
20
  "@changesets/changelog-github": "^0.5.0",
21
- "@changesets/cli": "^2.27.9",
21
+ "@changesets/cli": "^2.27.12",
22
22
  "@langchain/community": "^0.0.53",
23
23
  "@langchain/core": "^0.1.63",
24
24
  "@langchain/openai": "^0.0.28",
25
25
  "@tavily/core": "^0.0.2",
26
- "@types/node": "^20.17.1",
26
+ "@types/node": "^20.17.17",
27
27
  "@types/object-hash": "^3.0.6",
28
- "@vitest/coverage-v8": "^2.1.4",
29
- "dotenv": "^16.4.5",
28
+ "@vitest/coverage-v8": "^2.1.9",
29
+ "dotenv": "^16.4.7",
30
30
  "json-schema-to-ts": "^3.1.1",
31
31
  "ts-node": "^10.9.2",
32
- "tsup": "^8.3.5",
33
- "typescript": "^5.6.3",
34
- "vitest": "^2.1.3",
32
+ "tsup": "^8.3.6",
33
+ "typescript": "^5.7.3",
34
+ "vitest": "^2.1.9",
35
35
  "wikipedia": "^2.1.2",
36
- "zod": "^3.23.8"
36
+ "zod": "^3.24.2"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
42
  "@xstate/graph": "^2.0.1",
43
- "ai": "^3.4.31",
44
43
  "ajv": "^8.17.1",
45
44
  "object-hash": "^3.0.0",
46
- "xstate": "^5.18.2",
47
- "zod-to-json-schema": "^3.23.5"
45
+ "xstate": "^5.19.2",
46
+ "zod-to-json-schema": "^3.24.1"
47
+ },
48
+ "peerDependencies": {
49
+ "ai": "^4.1.36"
48
50
  },
49
51
  "scripts": {
50
52
  "build": "tsup src/index.ts --format cjs,esm --dts",
package/readme.md CHANGED
@@ -8,3 +8,60 @@ Stately Agent is a flexible framework for building AI agents using state machine
8
8
  - First-class integration with 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
9
 
10
10
  **Read the documentation: [stately.ai/docs/agents](https://stately.ai/docs/agents)**
11
+
12
+ # Stately Agent
13
+
14
+ Stately Agent is a framework for building intelligent AI agents that are guided by state machines and learn from experience. Rather than relying solely on LLM responses, agents use structured observations, feedback, and insights to make informed decisions and improve over time.
15
+
16
+ ## Overview
17
+
18
+ Stately Agent combines state machines with reinforcement learning concepts to create agents that:
19
+
20
+ - Make decisions based on clear state transitions and goals
21
+ - Learn from past experiences and feedback
22
+ - Generate insights about state changes
23
+ - Improve decision-making through structured rewards
24
+ - Support multiple decision-making policies
25
+
26
+ The framework is built on [XState](https://stately.ai/docs/xstate) for state machine management and integrates with the [Vercel AI SDK](https://sdk.vercel.ai/) for flexible LLM support.
27
+
28
+ ## Key Concepts
29
+
30
+ - **Observations**: Records of state transitions, containing:
31
+
32
+ - Previous state
33
+ - Event/action taken
34
+ - Resulting state
35
+ - Metadata about the transition
36
+
37
+ - **Decisions**: Actions the agent chooses to take based on:
38
+
39
+ - Current state
40
+ - Goal state
41
+ - Past observations
42
+ - Available feedback and insights
43
+ - Decision-making policy
44
+
45
+ - **Feedback**: Rewards or evaluations given to decisions, helping the agent learn which actions are effective
46
+
47
+ - **Insights**: Additional context about state transitions, helping the agent understand cause and effect
48
+
49
+ - **Episodes**: Complete sequences of state transitions, from initial state to goal state (similar to RL episodes)
50
+
51
+ ## Quick Start
52
+
53
+ TODO
54
+
55
+ ## Why Stately Agent?
56
+
57
+ Traditional LLM-based agents often make decisions with limited context and no ability to learn from experience. Stately Agent provides:
58
+
59
+ 1. **Structured Decision Making**: State machines provide clear boundaries and valid transitions
60
+
61
+ 2. **Learning from Experience**: Agents improve through feedback and observations
62
+
63
+ 3. **Contextual Awareness**: Insights and observations inform better decisions
64
+
65
+ 4. **Flexible Policies**: Different approaches for different needs
66
+
67
+ 5. **Storage Integration**: Optional persistence of experiences and learning