From bedec2ce5e5f690a428e691f4c8925c94a7abdfb Mon Sep 17 00:00:00 2001 From: Brett Lamy Date: Wed, 1 Mar 2023 23:17:18 -0800 Subject: [PATCH] chore(prompts): add test --- packages/promptable/src/prompts/Prompt.ts | 42 ++++++------ .../src/prompts/__tests__/Parser.test.ts | 48 ++++++++++++++ .../src/prompts/__tests__/Prompt.test.ts | 64 ++++++++++--------- .../__tests__/prompt-templates.test.ts | 45 +++++++++++++ packages/promptable/src/utils/type-utils.ts | 8 +-- 5 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 packages/promptable/src/prompts/__tests__/Parser.test.ts create mode 100644 packages/promptable/src/prompts/__tests__/prompt-templates.test.ts diff --git a/packages/promptable/src/prompts/Prompt.ts b/packages/promptable/src/prompts/Prompt.ts index 78fea42..2a0b59a 100644 --- a/packages/promptable/src/prompts/Prompt.ts +++ b/packages/promptable/src/prompts/Prompt.ts @@ -1,10 +1,8 @@ import { injectVariables } from "@utils/inject-variables"; -import { NoopParser, Parser } from "@prompts/Parser"; -import { ExtractFormatObject, ExtractVariableNames } from "@utils/type-utils"; -import { extractVariableNames } from "@utils/extract-variable-names"; +import { ExtractFormatObject } from "@utils/type-utils"; -export type PromptVariables = { - [K in keyof ExtractFormatObject]: Required[K]>; +export type PromptVariables = { + [K in keyof ExtractFormatObject]: Required[K]>; }; export interface PromptConfiguration { @@ -19,15 +17,15 @@ export const DEFAULT_PROMPT_CONFIGURATION: PromptConfiguration = { max_tokens: 128, }; -export class Prompt> { - readonly template: PromptTemplate; +export class Prompt> { + readonly template: PromptTemplate; constructor( - initTemplate: PromptTemplate | T, - readonly variables: V, + initTemplate: PromptTemplate | TPromptText, + readonly variables: TPromptVariables, readonly configuration: PromptConfiguration = DEFAULT_PROMPT_CONFIGURATION ) { if (typeof initTemplate === "string") { - this.template = new PromptTemplate(initTemplate); + this.template = new PromptTemplate(initTemplate, configuration); } else { this.template = initTemplate; } @@ -37,12 +35,12 @@ export class Prompt> { } clone({ - variables, - configuration, + variables = {}, + configuration = {}, }: { - variables?: Partial | V; + variables?: Partial | TPromptVariables; configuration?: Partial | PromptConfiguration; - }): Prompt { + } = {}): Prompt { return new Prompt( this.template, { @@ -70,12 +68,12 @@ export class Prompt> { } } -export class PromptTemplate> { - readonly text: T; +export class PromptTemplate> { + readonly text: TPromptText; readonly configuration: PromptConfiguration; constructor( - text: T, + text: TPromptText, configuration: PromptConfiguration = DEFAULT_PROMPT_CONFIGURATION ) { this.text = text; @@ -88,8 +86,8 @@ export class PromptTemplate> { * @param variables * @returns Prompt */ - build(variables: V, configuration?: PromptConfiguration) { - return new Prompt(this, variables, configuration); + build(variables: TPromptVariables, configuration?: PromptConfiguration) { + return new Prompt(this, variables, configuration); } toJSON() { @@ -100,9 +98,9 @@ export class PromptTemplate> { } } -export const prompt = ( - template: T, - variables: PromptVariables, +export const prompt = ( + template: TPromptText, + variables: PromptVariables, configuration?: PromptConfiguration ) => { return new PromptTemplate(template, configuration).build(variables); diff --git a/packages/promptable/src/prompts/__tests__/Parser.test.ts b/packages/promptable/src/prompts/__tests__/Parser.test.ts new file mode 100644 index 0000000..026ff5e --- /dev/null +++ b/packages/promptable/src/prompts/__tests__/Parser.test.ts @@ -0,0 +1,48 @@ +import { NoopParser, JSONParser, CSVParser, ListParser } from "@prompts/Parser" + +test("NoopParser", () => { + expect(new NoopParser().parse("test")).toEqual("test"); +}) + +describe("JSONParser", () => { + test("should throw error for invalid json", () => { + const parser = new JSONParser(); + expect(() => parser.parse("")).toThrow(""); + }); + + test("should parse valid json", () => { + const parser = new JSONParser(); + expect(parser.parse('{ "hello": "world" }')).toEqual({ hello: "world" }); + }); +}); + +describe("CSVParser", () => { + test("should throw error for invalid csv", () => { + const parser = new CSVParser(); + // @ts-expect-error + expect(() => parser.parse(1)).toThrow("") + }); + + test("should parse valid csv", () => { + const parser = new CSVParser(); + expect(parser.parse("a,b,c\n1,2,3")).toEqual([{ + a: "1", + b: "2", + c: "3" + }]) + }); +}); + + +describe("ListParser", () => { + test("should throw error for invalid json", () => { + const parser = new ListParser(); + // @ts-expect-error + expect(() => parser.parse(1)).toThrow("") + }); + + test("should parse valid json", () => { + const parser = new ListParser(); + expect(parser.parse("a, b, c")).toEqual(["a", "b", "c"]) + }); +}); \ No newline at end of file diff --git a/packages/promptable/src/prompts/__tests__/Prompt.test.ts b/packages/promptable/src/prompts/__tests__/Prompt.test.ts index 5eebc83..7d3c924 100644 --- a/packages/promptable/src/prompts/__tests__/Prompt.test.ts +++ b/packages/promptable/src/prompts/__tests__/Prompt.test.ts @@ -1,33 +1,37 @@ -import { Prompt } from "@prompts/Prompt"; +import { prompt } from "@prompts/Prompt"; + +const PROMPT_TEXT = "this is a {{test}}"; + +describe("prompt", () => { + test("should throw type errors if prompt requires variables but does not provide them", () => { + // @ts-expect-error + prompt(PROMPT_TEXT, {}); + + // @ts-expect-error + expect(prompt(PROMPT_TEXT).text).toEqual(PROMPT_TEXT); + }); + + test("Can serialize prompt to JSON", () => { + const variables = { test: "test" }; + const p = prompt(PROMPT_TEXT, variables) + const configuration = { max_tokens: 128, stop: null, temperature: 0.7 }; + expect(p.toJSON()).toEqual({ + configuration, + variables, + text: "this is a test", + template: { + configuration, + text: PROMPT_TEXT, + } + }); + }); + + test("Can clone prompt", () => { + const variables = { test: "test" }; + const p = prompt(PROMPT_TEXT, variables); + const pClone = p.clone({ variables: { test: "test2" } }) + expect(pClone.text).toEqual("this is a test2") + }); -test("Prompt Class", () => { - const p = new Prompt("this is a {{test}}", { test: "123" }); - - // saves the template - expect(p.template).toEqual("this is a {{test}}"); - - // Valid, returns a new prompt - const formattedPrompt = p.format({ test: "123" }); - expect(formattedPrompt.text).toEqual("this is a 123"); - expect(formattedPrompt).not.toBe(p); - - // Invalid - // @ts-expect-error - expect(p.format({ invalid: "123" }).text).toEqual("this is a {{test}}"); }); -test("Prompt without variables", () => { - const p = new Prompt("this is a test", {}); - - // saves the template - expect(p.template).toEqual("this is a test"); - - // Valid, returns a new prompt - const formattedPrompt = p.format({ test: "123" }); - expect(formattedPrompt.text).toEqual("this is a test"); - expect(formattedPrompt).not.toBe(p); - - // Invalid - // @ts-expect-error - expect(p.format({ invalid: "123" }).text).toEqual("this is a test"); -}); diff --git a/packages/promptable/src/prompts/__tests__/prompt-templates.test.ts b/packages/promptable/src/prompts/__tests__/prompt-templates.test.ts new file mode 100644 index 0000000..29c5245 --- /dev/null +++ b/packages/promptable/src/prompts/__tests__/prompt-templates.test.ts @@ -0,0 +1,45 @@ +import { + QA, + ExtractText, + Summarize, + Chatbot, + ExtractJSON, + ExtractCSV, + FixMarkup, +} from "@prompts/prompt-templates"; +import { Expect, Equal } from "@utils/__tests__/type-utils.test"; +import { ExtractVariableNames } from "@utils/type-utils"; + +test("ExtractVariableNames", () => { + // These tests will cause a compile time error if the types don't match + type tests = [ + Expect, + Array<"document" | "question"> + >>, + Expect, + Array<"document" | "question"> + >>, + Expect, + Array<"document"> + >>, + Expect, + Array<"memory" | "userInput"> + >>, + Expect, + Array<"data" | "type"> + >>, + Expect, + Array<"data" | "headers"> + >>, + Expect, + Array<"markupLanguage" | "documentType" | "markup"> + >>, + ]; +}); diff --git a/packages/promptable/src/utils/type-utils.ts b/packages/promptable/src/utils/type-utils.ts index 6956a64..2f77fca 100644 --- a/packages/promptable/src/utils/type-utils.ts +++ b/packages/promptable/src/utils/type-utils.ts @@ -6,11 +6,11 @@ type Split = string extends S ? [Head, ...Split] : [S]; -export type ExtractFormatObject = { - [K in Split[number] as K extends `${infer TName}}}${string}` +export type ExtractFormatObject = { + [K in Split[number] as K extends `${infer TName}}}${string}` ? TName : never]: string; }; -export type ExtractVariableNames = Array< - keyof ExtractFormatObject +export type ExtractVariableNames = Array< + keyof ExtractFormatObject >;