Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zod-to-ts #290

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .prettierignore

This file was deleted.

11 changes: 11 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"recommendations": [
"GitHub.copilot",
"ms-vscode.vscode-typescript-next",
"esbenp.prettier-vscode",
"yoavbls.pretty-ts-errors",
"WallabyJs.quokka-vscode",
"WallabyJs.wallaby-vscode",
"WallabyJs.console-ninja"
]
}
6 changes: 3 additions & 3 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const webpack = require("webpack");
const PromptCompiler = require("./scripts/PromptCompiler");
// const webpack = require("webpack");
// const PromptCompiler = require("./scripts/PromptCompiler");

/** @type {import('next').NextConfig} */
const nextConfig = {
Expand All @@ -8,7 +8,7 @@ const nextConfig = {
},
webpack: (config, { isServer, buildId }) => {
// This will read the prompts in from the prompts directory compile them and assign them to process.env
config.plugins.push(new webpack.DefinePlugin(new PromptCompiler().build()));
// config.plugins.push(new webpack.DefinePlugin(new PromptCompiler().build()));
config.module.rules.push({
test: /\.txt$/,
use: 'raw-loader',
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"ts-dedent": "^2.2.0",
"typescript": "5.1.0-dev.20230311",
"window.ai": "^0.1.1",
"zod": "^3.21.4"
"zod": "^3.21.4",
"zod-to-ts": "^1.1.4"
},
"devDependencies": {
"@storybook/addon-actions": "^6.5.0",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 53 additions & 18 deletions src/ai/prompts/JokeGenerator.Prompt.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import '@/ai/prompts/preambles/basic.turbo.Prompt.txt';
import '@/ai/prompts/examples/JokeGenerator.Examples.json';
import { z } from 'zod';

import { z } from "zod";
import { ChatCompletionRequestMessage } from "openai";

export const prompt = "Can you tell {{count}} {{jokeType}} jokes?";

export const jokeTypeSchema = z.union([
z.literal("funny"),
z.literal("dumb"),
z.literal("dad"),
z.literal("funny"),
z.literal("dumb"),
z.literal("dad"),
]);

export const inputSchema = z.object({
Expand All @@ -15,15 +17,48 @@ export const inputSchema = z.object({

export const outputSchema = z.array(
z.object({
setup: z.string(),
punchline: z.string(),
explanation: z.custom<`This is a ${z.infer<typeof jokeTypeSchema>} joke because ${string}`>((val) => {
return /This is a (funny|dad|dumb) joke because (.*)/.test(val as string);
}),
})
)

export type Prompt = "Can you tell {{count}} {{jokeType}} jokes?"
export type Input = z.infer<typeof inputSchema>
export type Output = z.infer<typeof outputSchema>
export type Errors = "prompt injection attempt detected" | "json parse error" | "zod validation error" | "output formatting" | "unknown"
setup: z.string(),
punchline: z.string(),
// TODO this no longer works with zod-to-ts maybe I can create my own fork
// explanation: z.custom<`This is a ${z.infer<typeof jokeTypeSchema>} joke because ${string}`>((val) => {
// return /This is a (funny|dad|dumb) joke because (.*)/.test(val as string);
// }),
})
);

export const examples = [
{ role: "user", content: JSON.stringify({ count: 3, jokeType: "funny" }) },
{
role: "assistant",
content: JSON.stringify([
{
setup: "Why did the tomato turn red?",
punchline: "Because it saw the salad dressing!",
explanation:
"This is a funny joke because of the pun in salad dressing.",
},
{
setup: "Why did the scarecrow win an award?",
punchline: "Because he was outstanding in his field.",
explanation:
"This is a funny joke because of the pun on the word field.",
},
{
setup: "Why don't scientists trust atoms?",
punchline: "Because they make up everything.",
explanation:
"This is a funny joke because atoms are the basic building block for everything and therefore there is a pun for making everything up",
},
]),
},
] as ChatCompletionRequestMessage[];


export type Prompt = typeof prompt;
export type Input = z.infer<typeof inputSchema>;
export type Output = z.infer<typeof outputSchema>;
export type Errors =
| "prompt injection attempt detected"
| "type validation error"
| "output formatting"
| "unknown";
69 changes: 62 additions & 7 deletions src/ai/prompts/NFLScores.Prompt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import '@/ai/prompts/preambles/tools.turbo.Prompt.txt';
import '@/ai/prompts/examples/NFLScores.Examples.json';
import { z } from 'zod';
import { ChatCompletionRequestMessage } from 'openai';
import { search, calculator } from '../tools';
import { evaluate } from "mathjs";

export const prompt = "Can you tell me the results to the most recent {{teamName}} NFL game then calculate the spread.";

export const examples = [
{"role":"user","content":"{ \"teamName\": \"49ers\"}"},
{"role":"assistant","content":"{ \"tool\": \"search\", \"args\":{ \"query\": \"Score to most recent 49ers NFL game\"}}"},
{"role":"system","content":"ull highlights, analysis and recap of 49ers win over Seahawks in NFC wild-card game. The NFL wild-card weekend kicked off Saturday with the 49ers beating the Seahawks 41-23 in the 2 seed-7 seed matchup of the NFC playoffs. Check in with The Athletic for all the latest news, highlights, reaction and analysis."},
{"role":"assistant","content":"{ \"tool\": \"calculator\", \"args\": {\"equation\": \"41-23\"}}"},
{"role":"system","content":"18"},
{"role":"assistant","content":"{ \"winningTeam\": \"49ers\", \"homeTeam\": \"49ers\",\"awayTeam\": \"Seahawks\",\"homeScore\": 41,\"awayScore\": 23,\"spread\": 18}"}
] as const;

const nflTeamSchema = z.union([
z.literal("Cardinals"), z.literal("Falcons"), z.literal("Ravens"), z.literal("Bills"), z.literal("Panthers"),
Expand All @@ -25,8 +37,51 @@ export const outputSchema = z.object({
spread: z.number().positive()
});

export type Prompt = "Can you tell me the results to the most recent {{teamName}} NFL game then calculate the spread.";;
export type Input = z.infer<typeof inputSchema>;
export type Output = z.infer<typeof outputSchema>;
export type Errors = "no game found" | "tool error" | "prompt injection attempt detected" | "json parse error" | "type error" | "output format error" | "unknown";
export type Tools = { tool: 'search', args: { query: string } } | { tool: 'calculator', args: { equation: string } };
export const errorHandlers = {
"json parse error": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {},
"no game found": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {},
"output format error": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {},
"prompt injection attempt detected": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {
// ratelimiter.banUser();
},
"tool error": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {},
"type error": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {},
unknown: async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {},
"type validation error": async (
error: string,
messages: ChatCompletionRequestMessage[]
) => {
return [
...messages,
{
role: "system" as const,
content:
`USER: I tried to parse your output against the schema and I got this error ${error}. Did your previous response match the expected Output format? Remember no values in your response cannot be null or undefined unless they are marked with a Question Mark in the typescript type. If you believe your output is correct please repeat it. If not, please print an updated valid output or an error. Remember nothing other than valid JSON can be sent to the user
ASSISTANT: My previous response did not match the expected Output format. Here is either the updated valid output or a relevant error from the Errors union:\n` as const,
},
];
},
};

export const tools = { search, calculator }
29 changes: 24 additions & 5 deletions src/ai/prompts/NumberGenerator.Prompt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import '@/ai/prompts/preambles/basic.turbo.Prompt.txt';
import '@/ai/prompts/examples/NumberGenerator.Examples.json';
import { z } from 'zod';

export type Prompt = "Can you tell me a number between {{min}} and {{max}}?"
export type Input = { min: number, max: number }
export type Output = { result: number }
export const prompt = "Can you tell me a number between {{min}} and {{max}}?";

export const messages = [
{"role":"user","content":"{ \"min\": 1, \"max\": 100}"},
{"role":"assistant","content":"{ \"result\": 42}"},
{"role":"user","content":"{ \"min\": 1, \"max\": 100}"},
{"role":"assistant","content":"{ \"result\": 69}"},
{"role":"user","content":"{ \"min\": 100, \"max\": 1}"},
{"role":"assistant","content":"{ \"error\":{ \"type\": \"InvalidInput\", \"msg\": \"min must be less than max\"}}"}
] as const;

export const inputSchema = z.object({
min: z.number().min(1).max(100),
max: z.number().min(1).max(100),
});

export const outputSchema = z.object({
result: z.number().min(1).max(100),
});

export type Prompt = typeof prompt;
export type Input = z.infer<typeof inputSchema>
export type Output = z.infer<typeof outputSchema>
4 changes: 0 additions & 4 deletions src/ai/prompts/examples/JokeGenerator.Examples.json

This file was deleted.

8 changes: 0 additions & 8 deletions src/ai/prompts/examples/NFLScores.Examples.json

This file was deleted.

8 changes: 0 additions & 8 deletions src/ai/prompts/examples/NumberGenerator.Examples.json

This file was deleted.

31 changes: 4 additions & 27 deletions src/ai/prompts/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { z } from "zod";

// Prompts can be imported using:
// ```typescript
// import Prompts from "@/ai/prompts";
Expand All @@ -12,28 +10,7 @@ import { z } from "zod";
// ]);
// const output: PromptTypes.NFLScores.Output = PromptTypes.JokeGenerator.outputSchema.parse(res.);
// ```
import * as NFLScores from "./NFLScores.Prompt";
import * as JokeGenerator from "./JokeGenerator.Prompt";
import * as NumberGenerator from "./NumberGenerator.Prompt";

// This is a zod schema that validates the environment variables
export const EnvSchema = z.object({
NFLScores: z.string(),
JokeGenerator: z.string(),
NumberGenerator: z.string(),
BankRun: z.string(),
PoemGenerator: z.string(),
});

// This gives the default export auto-completion for the prompts
export default EnvSchema.parse({
// Note these variables do not actually exist in the environment
// They are replaced at build time by the DefinePlugin
NFLScores: process.env.NFLScoresPrompt,
JokeGenerator: process.env.JokeGeneratorPrompt,
NumberGenerator: process.env.NumberGeneratorPrompt,
BankRun: process.env.BankRunPrompt,
PoemGenerator: process.env.PoemGeneratorPrompt,
});

export { NFLScores, JokeGenerator, NumberGenerator };
export * as NFLScores from "./NFLScores.Prompt";
export * as JokeGenerator from "./JokeGenerator.Prompt";
export * as NumberGenerator from "./NumberGenerator.Prompt";
export { default as PoemGenerator } from './PoemGenerator.Prompt.txt'
7 changes: 6 additions & 1 deletion src/ai/tools/CalculatorTool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { z } from 'zod';
import { evaluate } from "mathjs";

export default function calculator({ equation }: { equation: string }) {
export const inputSchema = z.object({
equation: z.string()
});

export default function calculator({ equation }: z.infer<typeof inputSchema>) {
return evaluate(equation);
}
6 changes: 5 additions & 1 deletion src/ai/tools/SearchTool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { z } from "zod";

const inputSchema = z.object({
query: z.string(),
});

// Ideally the latest score will just pop up in a block at the top of the search results
const gameSpotlightSchema = z.object({
sports_results: z.object({
Expand Down Expand Up @@ -40,7 +44,7 @@ const organicResultsSchema = z.object({
),
});

export default async function search({ query }: { query: string }) {
export default async function search({ query }: z.infer<typeof inputSchema>) {
const res = await fetch(
`https://serpapi.com/search.json?engine=google&q=${query}&api_key=${process.env.SERP_API_KEY}`
);
Expand Down
Loading