Skip to content

Commit

Permalink
Update examples
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkpiano committed Apr 28, 2024
1 parent ef5c9f2 commit 5da9fc1
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 110 deletions.
40 changes: 16 additions & 24 deletions examples/joke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,14 @@ import {
log,
setup,
} from 'xstate';
import { createAgent, createOpenAIAdapter, defineEvents } from '../src';
import { createAgent } from '../src';
import { loadingAnimation } from './helpers/loader';
import { z } from 'zod';

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

const events = defineEvents({
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(),
}),
});

const adapter = createOpenAIAdapter(openai, {
model: 'gpt-3.5-turbo-1106',
});

const getTopic = fromPromise(async () => {
const topic = await new Promise<string>((res) => {
console.log('Give me a joke topic:');
Expand Down Expand Up @@ -89,12 +70,23 @@ const loader = fromCallback(({ input }: { input: string }) => {

const agent = createAgent(openai, {
model: 'gpt-3.5-turbo-1106',
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(),
}),
},
});

const jokeMachine = setup({
schemas: {
events: events.schemas,
},
types: {
context: {} as {
topic: string;
Expand All @@ -103,7 +95,7 @@ const jokeMachine = setup({
lastRating: number | null;
loader: string | null;
},
events: events.types,
events: agent.eventTypes,
},
actors: {
getTopic,
Expand Down
16 changes: 6 additions & 10 deletions examples/numberGuesser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ const openai = new OpenAI({

const agent = createAgent(openai, {
model: 'gpt-3.5-turbo-1106',
});

const events = defineEvents({
'agent.guess': z.object({
number: z.number().min(1).max(10).describe('The number guessed'),
}),
events: {
'agent.guess': z.object({
number: z.number().min(1).max(10).describe('The number guessed'),
}),
},
});

const machine = setup({
Expand All @@ -23,10 +22,7 @@ const machine = setup({
answer: number;
},
input: {} as { answer: number },
events: events.types,
},
schemas: {
events: events.schemas,
events: agent.eventTypes,
},
actors: {
agent,
Expand Down
41 changes: 17 additions & 24 deletions examples/ticTacToe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,27 @@ const openai = new OpenAI({

const agent = createAgent(openai, {
model: 'gpt-4-0125-preview',
events: {
'agent.x.play': z.object({
index: z
.number()
.min(0)
.max(8)
.describe('The index of the cell to play on'),
}),
'agent.o.play': z.object({
index: z
.number()
.min(0)
.max(8)
.describe('The index of the cell to play on'),
}),
},
});

type Player = 'x' | 'o';

const events = defineEvents({
'agent.x.play': z.object({
index: z
.number()
.min(0)
.max(8)
.describe('The index of the cell to play on'),
}),
'agent.o.play': z.object({
index: z
.number()
.min(0)
.max(8)
.describe('The index of the cell to play on'),
}),
reset: z.object({}).describe('Reset the game to the initial state'),
});

Expand All @@ -51,15 +53,6 @@ const initialContext = {
events: [],
} satisfies GameContext;

const bot = adapter.fromEvent(
({ context }: { context: GameContext }) => `
You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty. The value of x means that the cell is occupied by an x. The value of o means that the cell is occupied by an o.
${JSON.stringify(context, null, 2)}
Execute the single best next move to try to win the game. Do not play on an existing cell.`
);

const gameReporter = adapter.fromChatStream(
({ context }: { context: GameContext }) => `Here is the game board:
Expand Down Expand Up @@ -108,7 +101,7 @@ export const ticTacToeMachine = setup({
},
types: {
context: {} as GameContext,
events: events.types,
events: {} as typeof events.types | typeof agent.eventTypes,
},
actors: {
agent,
Expand Down
44 changes: 20 additions & 24 deletions examples/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,35 @@ const openai = new OpenAI({

const agent = createAgent(openai, {
model: 'gpt-3.5-turbo-16k-0613',
});

const events = defineEvents({
'agent.validateAnswer': z.object({
isValid: z.boolean(),
feedback: z.string(),
}),
'agent.answerQuestion': z.object({
answer: z.string().describe('The answer from the agent'),
}),
'agent.validateQuestion': z.object({
isValid: z
.boolean()
.describe(
'Whether the question is a valid question; that is, is it possible to even answer this question in a verifiably correct way?'
),
explanation: z
.string()
.describe('An explanation for why the question is or is not valid'),
}),
events: {
'agent.validateAnswer': z.object({
isValid: z.boolean(),
feedback: z.string(),
}),
'agent.answerQuestion': z.object({
answer: z.string().describe('The answer from the agent'),
}),
'agent.validateQuestion': z.object({
isValid: z
.boolean()
.describe(
'Whether the question is a valid question; that is, is it possible to even answer this question in a verifiably correct way?'
),
explanation: z
.string()
.describe('An explanation for why the question is or is not valid'),
}),
},
});

const machine = setup({
schemas: {
events: events.schemas,
},
types: {
context: {} as {
question: string | null;
answer: string | null;
validation: string | null;
},
events: events.types,
events: agent.eventTypes,
},
actors: {
getFromTerminal,
Expand Down
28 changes: 10 additions & 18 deletions examples/wordGuesser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,6 @@ const openAI = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

const adapter = createOpenAIAdapter(openAI, {
model: 'gpt-4-1106-preview',
});

const events = defineEvents({
'agent.guessLetter': z.object({
letter: z.string().min(1).max(1).describe('The letter guessed'),
}),

'agent.guessWord': z.object({
word: z.string().describe('The word guessed'),
}),
});

const context = {
word: null as string | null,
guessedWord: null as string | null,
Expand All @@ -30,20 +16,26 @@ const context = {

const agent = createAgent(openAI, {
model: 'gpt-3.5-turbo-16k-0613',
events: {
'agent.guessLetter': z.object({
letter: z.string().min(1).max(1).describe('The letter guessed'),
}),

'agent.guessWord': z.object({
word: z.string().describe('The word guessed'),
}),
},
});

const wordGuesserMachine = setup({
types: {
context: {} as typeof context,
events: events.types,
events: agent.eventTypes,
},
actors: {
agent,
getFromTerminal,
},
schemas: {
events: events.schemas,
},
}).createMachine({
initial: 'providingWord',
context,
Expand Down
39 changes: 34 additions & 5 deletions src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
import { AnyMachineSnapshot, fromPromise, PromiseActorLogic } from 'xstate';
import {
AnyMachineSnapshot,
fromPromise,
PromiseActorLogic,
Values,
} from 'xstate';
import OpenAI from 'openai';
import { getToolCalls } from './adapters/openai';
import { ChatCompletionCreateParamsBase } from 'openai/resources/chat/completions';
import { ZodEventTypes, EventSchemas } from './schemas';
import { createZodEventSchemas } from './utils';
import { TypeOf } from 'zod';

export function createAgent(
export function createAgent<const TEventSchemas extends ZodEventTypes>(
openai: OpenAI,
{
model,
events,
}: {
model: ChatCompletionCreateParamsBase['model'];
events?: TEventSchemas;
}
): PromiseActorLogic<
void,
{
goal: string;
model?: ChatCompletionCreateParamsBase['model'];
}
> {
return fromPromise(async ({ input, self }) => {
> & {
eventTypes: Values<{
[K in keyof TEventSchemas]: {
type: K;
} & TypeOf<TEventSchemas[K]>;
}>;
eventSchemas: EventSchemas<keyof TEventSchemas & string>;
} {
const eventSchemas = events ? createZodEventSchemas(events) : undefined;

const logic = fromPromise<
void,
{
goal: string;
model?: ChatCompletionCreateParamsBase['model'];
}
>(async ({ input, self }) => {
const parentRef = self._parent;
if (!parentRef) {
return;
Expand All @@ -30,7 +55,7 @@ export function createAgent(
state,
input.model ?? model,
(eventType) => eventType.startsWith('agent.'),
(state.machine.schemas as any)?.events
eventSchemas ?? (state.machine.schemas as any)?.events
);

if (toolEvents.length > 0) {
Expand All @@ -39,4 +64,8 @@ export function createAgent(

return;
});

(logic as any).eventSchemas = eventSchemas;

return logic as any;
}
3 changes: 2 additions & 1 deletion src/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Values } from 'xstate';
import { createZodEventSchemas } from './utils';
import { SomeZodObject, TypeOf } from 'zod';
import { JsonSchema7Type } from 'zod-to-json-schema';

export type ZodEventTypes = {
// map event types to Zod types
[eventType: string]: SomeZodObject;
};

export type EventSchemas<TEventType extends string> = {
[K in TEventType]: unknown;
[K in TEventType]: JsonSchema7Type;
};

export function defineEvents<const TEventSchemas extends ZodEventTypes>(
Expand Down
Loading

0 comments on commit 5da9fc1

Please sign in to comment.