UNPKG

@statelyai/agent

Version:

Stateful agents that make decisions based on finite-state machine models

226 lines (217 loc) 6.01 kB
import { assign, createActor, fromCallback, log, setup } from 'xstate'; import { createAgent, fromDecision } from '../src'; import { loadingAnimation } from './helpers/loader'; import { z } from 'zod'; import { openai } from '@ai-sdk/openai'; import { getFromTerminal } from './helpers/helpers'; export function getRandomFunnyPhrase() { const funnyPhrases = [ 'Concocting chuckles...', 'Brewing belly laughs...', 'Fabricating funnies...', 'Assembling amusement...', 'Molding merriment...', 'Whipping up wisecracks...', 'Generating guffaws...', 'Inventing hilarity...', 'Cultivating chortles...', 'Hatching howlers...', ]; return funnyPhrases[Math.floor(Math.random() * funnyPhrases.length)]!; } export function getRandomRatingPhrase() { const ratingPhrases = [ 'Assessing amusement...', 'Evaluating hilarity...', 'Ranking chuckles...', 'Classifying cackles...', 'Scoring snickers...', 'Rating roars...', 'Judging jollity...', 'Measuring merriment...', 'Rating rib-ticklers...', ]; return ratingPhrases[Math.floor(Math.random() * ratingPhrases.length)]!; } const loader = fromCallback(({ input }: { input: string }) => { const anim = loadingAnimation(input); return () => { anim.stop(); }; }); const agent = createAgent({ name: 'joke-teller', model: openai('gpt-4-turbo'), events: { askForTopic: z.object({ topic: z.string().describe('The topic for the joke'), }), 'agent.tellJoke': z.object({ joke: z.string().describe('The joke text'), }), 'agent.endJokes': z.object({}).describe('End the jokes'), 'agent.rateJoke': z.object({ rating: z.number().min(1).max(10), explanation: z.string(), }), 'agent.continue': z.object({}).describe('Continue'), 'agent.markAsIrrelevant': z .object({ explanation: z.string(), }) .describe('Explains why the joke was irrelevant'), 'agent.markAsRelevant': z.object({}).describe('The joke was relevant'), }, context: { topic: z.string().describe('The topic for the joke'), jokes: z.array(z.string()).describe('The jokes told so far'), desire: z.string().nullable().describe('The user desire'), lastRating: z.number().nullable().describe('The last joke rating'), loader: z.string().nullable().describe('The loader text'), }, }); const jokeMachine = setup({ types: agent.types, actors: { agent: fromDecision(agent), loader, getFromTerminal, }, }).createMachine({ id: 'joke', context: () => ({ topic: '', jokes: [], desire: null, lastRating: null, loader: null, }), initial: 'waitingForTopic', states: { waitingForTopic: { invoke: { src: 'getFromTerminal', input: 'Give me a joke topic.', onDone: { actions: assign({ topic: ({ event }) => event.output, }), target: 'tellingJoke', }, }, }, tellingJoke: { invoke: [ { src: 'agent', input: ({ context }) => ({ context: { topic: context.topic, }, goal: `Tell me a joke about the topic. Do not make any joke that is not relevant to the topic.`, }), }, { src: 'loader', input: getRandomFunnyPhrase, }, ], on: { 'agent.tellJoke': { actions: [ assign({ jokes: ({ context, event }) => [...context.jokes, event.joke], }), log((x) => x.event.joke), ], target: 'relevance', }, }, }, relevance: { invoke: { src: 'agent', input: (x) => ({ context: { topic: x.context.topic, lastJoke: x.context.jokes[x.context.jokes.length - 1], }, goal: 'An irrelevant joke has no reference to the topic. If the last joke is completely irrelevant to the topic, ask for a new joke topic. Otherwise, continue.', }), }, on: { 'agent.markAsIrrelevant': { actions: log((x) => 'Irrelevant joke: ' + x.event.explanation), target: 'waitingForTopic', description: 'Continue', }, 'agent.markAsRelevant': { actions: log('Joke was relevant'), target: 'rateJoke', }, }, }, rateJoke: { invoke: [ { src: 'agent', input: ({ context }) => ({ context: { jokes: context.jokes, }, goal: `Rate the last joke on a scale of 1 to 10.`, }), }, { src: 'loader', input: getRandomRatingPhrase, }, ], on: { 'agent.rateJoke': { actions: [ assign({ lastRating: ({ event }) => event.rating, }), log( ({ event }) => `Rating: ${event.rating}\n\n${event.explanation}` ), ], target: 'decide', }, }, }, decide: { invoke: { src: 'agent', input: ({ context }) => ({ context: { lastRating: context.lastRating, }, goal: `Choose what to do next, given the previous rating of the joke.`, }), }, on: { askForTopic: { target: 'waitingForTopic', actions: log("That joke wasn't good enough. Let's try again."), description: 'Ask for a new topic, because the last joke rated 6 or lower', }, 'agent.endJokes': { target: 'end', actions: log('That joke was good enough. Goodbye!'), description: 'End the jokes, since the last joke rated 7 or higher', }, }, }, end: { type: 'final', }, }, exit: () => { process.exit(); }, }); const actor = createActor(jokeMachine); actor.start();