Skip to content

Commit

Permalink
use Zod for input schema
Browse files Browse the repository at this point in the history
  • Loading branch information
phughesmcr committed Sep 11, 2024
1 parent 0a45c7e commit 6329e1a
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 55 deletions.
2 changes: 1 addition & 1 deletion components/CustomPersonalityInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MAX_PERSONALITY_LENGTH } from "lib/debate/inputValidation.ts";
import { personalities } from "lib/debate/personalities.ts";
import { MAX_PERSONALITY_LENGTH } from "lib/debate/schema.ts";
import { cleanContent, sanitizeInput } from "lib/utils.ts";
import { useEffect, useState } from "preact/hooks";

Expand Down
4 changes: 2 additions & 2 deletions components/DebateFormInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import AgentSelector from "islands/AgentSelector.tsx";
import type { Personality } from "lib/debate/personalities.ts";
import {
MAX_AGENTS,
MAX_DEBATE_CONTEXT_LENGTH,
MAX_DEBATE_ROUNDS,
MAX_POSITION_LENGTH,
MIN_AGENTS,
MIN_DEBATE_ROUNDS,
} from "lib/debate/inputValidation.ts";
import type { Personality } from "lib/debate/personalities.ts";
} from "lib/debate/schema.ts";
import { cleanContent, sanitizeInput } from "lib/utils.ts";
import type { VoiceType } from "routes/api/voicesynth.tsx";

Expand Down
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"routes/": "./routes/",
"tailwindcss": "npm:[email protected]",
"tailwindcss/": "npm:/[email protected]/",
"tailwindcss/plugin": "npm:/[email protected]/plugin.js"
"tailwindcss/plugin": "npm:/[email protected]/plugin.js",
"zod": "https://deno.land/x/[email protected]/mod.ts"
},
"compilerOptions": {
"jsx": "react-jsx",
Expand Down
21 changes: 12 additions & 9 deletions hooks/useDebateState.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { validateDebateInput } from "lib/debate/inputValidation.ts";
import { getRandomPersonalities, Personality } from "lib/debate/personalities.ts";
import {
MAX_AGENTS,
MAX_DEBATE_ROUNDS,
MIN_AGENTS,
MIN_DEBATE_ROUNDS,
validateDebateInput,
} from "lib/debate/inputValidation.ts";
import { getRandomPersonalities, Personality } from "lib/debate/personalities.ts";
} from "lib/debate/schema.ts";
import { clamp, sanitizeInput } from "lib/utils.ts";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import type { AgentDetails } from "routes/api/debate.tsx";
import { DEFAULT_VOICE, type VoiceType } from "routes/api/voicesynth.tsx";

type DebateMessage = { role: string; content: string };
Expand Down Expand Up @@ -73,15 +72,19 @@ export function useDebateState() {
const sanitizedPosition = sanitizeInput(position);
const sanitizedContext = sanitizeInput(context);

const { errors, valid } = validateDebateInput({
agentDetails: agentDetails as AgentDetails[],
context: sanitizedContext,
const requestPayload = {
position: sanitizedPosition,
context: sanitizedContext,
numAgents,
numDebateRounds,
uuid: userUUID,
agentDetails,
moderatorVoice,
});
uuid: userUUID,
};

console.log("Request payload:", requestPayload);

const { errors, valid } = validateDebateInput(requestPayload);

if (!valid) {
setErrors(errors);
Expand Down
2 changes: 1 addition & 1 deletion islands/AgentSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CustomPersonalityInput from "components/CustomPersonalityInput.tsx";
import { MAX_NAME_LENGTH } from "lib/debate/inputValidation.ts";
import type { Personality } from "lib/debate/personalities.ts";
import { MAX_NAME_LENGTH } from "lib/debate/schema.ts";
import { cleanContent, sanitizeInput } from "lib/utils.ts";

interface AgentSelectorProps {
Expand Down
2 changes: 1 addition & 1 deletion lib/debate/debate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DebateRequest } from "lib/debate/schema.ts";
import type { OpenAI } from "openai";
import type { DebateRequest } from "routes/api/debate.tsx";
import { Moderator } from "./moderator.ts";
import { createSystemMessage, makeAPIRequest } from "./submit.ts";

Expand Down
10 changes: 1 addition & 9 deletions lib/debate/inputValidation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { MAX_AGENTS, MAX_DEBATE_CONTEXT_LENGTH, MAX_DEBATE_ROUNDS, MAX_NAME_LENGTH, MAX_PERSONALITY_LENGTH, MAX_POSITION_LENGTH, MIN_AGENTS, MIN_DEBATE_ROUNDS } from "lib/debate/schema.ts";
import type { DebateRequest } from "routes/api/debate.tsx";

export const MIN_AGENTS = 2;
export const MAX_AGENTS = 4;
export const MAX_DEBATE_CONTEXT_LENGTH = 1028;
export const MIN_DEBATE_ROUNDS = 1;
export const MAX_DEBATE_ROUNDS = 3;
export const MAX_NAME_LENGTH = 32;
export const MAX_POSITION_LENGTH = 256;
export const MAX_PERSONALITY_LENGTH = 256;

export interface InputValidationResponse {
valid: boolean;
errors: string[];
Expand Down
34 changes: 34 additions & 0 deletions lib/debate/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { type VoiceType } from "routes/api/voicesynth.tsx";
import { z } from "zod";

export const MIN_AGENTS = 2;
export const MAX_AGENTS = 4;
export const MAX_DEBATE_CONTEXT_LENGTH = 1028;
export const MIN_DEBATE_ROUNDS = 1;
export const MAX_DEBATE_ROUNDS = 3;
export const MAX_NAME_LENGTH = 32;
export const MAX_POSITION_LENGTH = 256;
export const MAX_PERSONALITY_LENGTH = 256;

export const AgentDetailsSchema = z.object({
name: z.string().min(1).max(MAX_NAME_LENGTH),
personality: z.string().min(1).max(MAX_PERSONALITY_LENGTH),
stance: z.enum(["for", "against", "undecided", "moderator"]),
voice: z.enum([z.custom<VoiceType>()]),
});

export const DebateRequestSchema = z.object({
position: z.string().min(1).max(MAX_POSITION_LENGTH),
context: z.string().max(MAX_DEBATE_CONTEXT_LENGTH).default(""),
numAgents: z.number().int().min(MIN_AGENTS).max(MAX_AGENTS),
agentDetails: z.array(AgentDetailsSchema),
uuid: z.string().uuid(),
numDebateRounds: z.number().int().min(MIN_DEBATE_ROUNDS).max(MAX_DEBATE_ROUNDS),
moderatorVoice: z.union([z.enum(["none"]), z.custom<VoiceType>()]),
}).refine(data => data.agentDetails.length === data.numAgents, {
message: "Number of agent details must match numAgents",
path: ["agentDetails"],
});

export type AgentDetails = z.infer<typeof AgentDetailsSchema>;
export type DebateRequest = z.infer<typeof DebateRequestSchema>;
48 changes: 17 additions & 31 deletions routes/api/debate.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
import { Handlers } from "$fresh/server.ts";
import { conductDebateStream } from "lib/debate/debate.ts";
import { validateDebateInput } from "lib/debate/inputValidation.ts";
import { DebateRequestSchema } from "lib/debate/schema.ts";
import { compressJson, kv } from "lib/kv.ts";
import type { VoiceType } from "routes/api/voicesynth.tsx";

export interface AgentDetails {
name: string;
personality: string;
stance: "for" | "against" | "undecided" | "moderator";
voice: string;
}

export interface DebateRequest {
position: string;
context: string;
numAgents: number;
agentDetails: AgentDetails[];
uuid: string;
numDebateRounds: number;
moderatorVoice: VoiceType | "none";
}

export type { AgentDetails, DebateRequest } from "lib/debate/schema.ts";
export type DebateResponse = ReadableStream | { errors: string[] };

export const handler: Handlers = {
Expand All @@ -34,34 +17,37 @@ export const handler: Handlers = {
});
}

const input = await req.json() as DebateRequest;
const input = await req.json();

const { errors, valid } = validateDebateInput(input);
if (!valid) {
return new Response(JSON.stringify({ errors }), {
const result = DebateRequestSchema.safeParse(input);
if (!result.success) {
console.error("Validation errors:", result.error.errors);
return new Response(JSON.stringify({ errors: result.error.errors.map(e => e.message) }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}

const validatedInput = result.data;

try {
const timestamp = new Date().toISOString();
const data = JSON.stringify({
position: input.position,
context: input.context,
numAgents: input.numAgents,
agentDetails: input.agentDetails,
numDebateRounds: input.numDebateRounds,
moderatorVoice: input.moderatorVoice,
position: validatedInput.position,
context: validatedInput.context,
numAgents: validatedInput.numAgents,
agentDetails: validatedInput.agentDetails,
numDebateRounds: validatedInput.numDebateRounds,
moderatorVoice: validatedInput.moderatorVoice,
timestamp,
});
await kv.set(["debates", input.uuid, timestamp], compressJson(data));
await kv.set(["debates", validatedInput.uuid, timestamp], compressJson(data));
} catch (error) {
console.error("Error logging debate details:", error);
}

try {
const stream = await conductDebateStream(input);
const stream = await conductDebateStream(validatedInput);
if (!(stream instanceof ReadableStream)) {
throw new Error("Invalid stream returned from conductDebateStream");
}
Expand Down

0 comments on commit 6329e1a

Please sign in to comment.