diff --git a/README.md b/README.md index 3ce989f..fe1be98 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,9 @@ The following are the functions available on the `PromptEngine` class and those For more examples and insights into using the prompt-engine library, have a look at the [examples](https://github.com/microsoft/prompt-engine/tree/main/examples) folder +## YAML Representation +It can be useful to represent prompts as standalone files, versus code. This can allow easy swapping between different prompts, prompt versioning, and other advanced capabiliites. With this in mind, prompt-engine offers a way to represent prompts as YAML and to load that YAML into a prompt-engine class. See `examples/yaml-examples` for examples of YAML prompts and how they're loaded into prompt-engine. + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/examples/yaml-examples/chat.yaml b/examples/yaml-examples/chat.yaml new file mode 100644 index 0000000..32a0645 --- /dev/null +++ b/examples/yaml-examples/chat.yaml @@ -0,0 +1,18 @@ +type: chat-engine +config: + model-config: + max-tokens: 1024 + user-name: "Abhishek" + bot-name: "Bot" +description: "What is the possibility of an event happening?" +examples: + - input: "Roam around Mars" + response: "This will be possible in a couple years" + - input: "Drive a car" + response: "This is possible after you get a learner drivers license" +flow-reset-text: "Starting a new conversation" +dialog: + - input: "Drink water" + response: "Uhm...You don't do that 8 times a day?" + - input: "Walk on air" + response: "For that you'll need a special device" \ No newline at end of file diff --git a/examples/yaml-examples/code.yaml b/examples/yaml-examples/code.yaml new file mode 100644 index 0000000..fa2d203 --- /dev/null +++ b/examples/yaml-examples/code.yaml @@ -0,0 +1,15 @@ +type: code-engine +config: + model-config: + max-tokens: 1024 +description: "Natural Language Commands to Math Code" +examples: + - input: "what's 10 plus 18" + response: "console.log(10 + 18)" + - input: "what's 10 times 18" + response: "console.log(10 * 18)" +dialog: + - input: "what's 18 divided by 10" + response: "console.log(10 / 18)" + - input: "what's 18 factorial 10" + response: "console.log(10 % 18)" \ No newline at end of file diff --git a/examples/yaml-examples/general.yaml b/examples/yaml-examples/general.yaml new file mode 100644 index 0000000..567a8d5 --- /dev/null +++ b/examples/yaml-examples/general.yaml @@ -0,0 +1,23 @@ +type: prompt-engine +config: + model-config: + max-tokens: 1024 + description-prefix: ">>" + description-postfix: "" + newline-operator: "\n" + input-prefix: "Human:" + input-postfix: "" + output-prefix: "Bot:" + output-postfix: "" +description: "What is the possibility of an event happening?" +examples: + - input: "Roam around Mars" + response: "This will be possible in a couple years" + - input: "Drive a car" + response: "This is possible after you get a learner drivers license" +flow-reset-text: "Starting a new conversation" +dialog: + - input: "Drink water" + response: "Uhm...You don't do that 8 times a day?" + - input: "Walk on air" + response: "For that you'll need a special device" \ No newline at end of file diff --git a/examples/yaml-examples/yaml-chat-example.js b/examples/yaml-examples/yaml-chat-example.js new file mode 100644 index 0000000..f793527 --- /dev/null +++ b/examples/yaml-examples/yaml-chat-example.js @@ -0,0 +1,31 @@ +const { ChatEngine } = require("./../../out/ChatEngine"); +const { readFileSync } = require("fs"); + +const promptEngine = new ChatEngine(); + +const yamlConfig = readFileSync("./examples/yaml-examples/chat.yaml", "utf8"); +promptEngine.loadYAML(yamlConfig); + +console.log(promptEngine.buildContext("", true)) + +/* Output for this example is: + +PROMPT + +What is the possibility of an event happening? + +Abhishek: Roam around Mars +Bot: This will be possible in a couple years + +Abhishek: Drive a car +Bot: This is possible after you get a learner drivers license + +Starting a new conversation + +Abhishek: Drink water +Bot: Uhm...You don't do that 8 times a day? + +Abhishek: Walk on air +Bot: For that you'll need a special device + +*/ \ No newline at end of file diff --git a/examples/yaml-examples/yaml-code-example.js b/examples/yaml-examples/yaml-code-example.js new file mode 100644 index 0000000..0de7645 --- /dev/null +++ b/examples/yaml-examples/yaml-code-example.js @@ -0,0 +1,30 @@ +const { CodeEngine } = require("./../../out/CodeEngine"); +const { readFileSync } = require("fs"); + +const promptEngine = new CodeEngine(); + +const yamlConfig = readFileSync("./examples/yaml-examples/code.yaml", "utf8"); +promptEngine.loadYAML(yamlConfig); + +const prompt = promptEngine.buildPrompt("what's 18 to the power of 10"); + +console.log(prompt) + +// Output for this example is: +// +// /* Natural Language Commands to Math Code */ +// +// /* what's 10 plus 18 */ +// console.log(10 + 18) +// +// /* what's 10 times 18 */ +// console.log(10 * 18) +// +// /* what's 18 divided by 10 */ +// console.log(10 / 18) +// +// /* what's 18 factorial 10 */ +// console.log(10 % 18) +// +// /* what's 18 to the power of 10 */ + diff --git a/examples/yaml-examples/yaml-general-example.js b/examples/yaml-examples/yaml-general-example.js new file mode 100644 index 0000000..b5b93fe --- /dev/null +++ b/examples/yaml-examples/yaml-general-example.js @@ -0,0 +1,29 @@ +const { PromptEngine } = require("./../../out/PromptEngine"); +const { readFileSync } = require("fs"); + +const promptEngine = new PromptEngine(); + +const yamlConfig = readFileSync("./examples/yaml-examples/general.yaml", "utf8"); +promptEngine.loadYAML(yamlConfig); + +console.log(promptEngine.buildContext("", true)) + +/* +Output of this example is: + +>> What is the possibility of an event happening? ! + +Human: Roam around Mars +Bot: This will be possible in a couple years + +Human: Drive a car +Bot: This is possible after you get a learner drivers license + +>> Starting a new conversation ! + +Human: Drink water +Bot: Uhm...You don't do that 8 times a day? + +Human: Walk on air +Bot: For that you'll need a special device +*/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 62317a1..68f8c59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "prompt-engine", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prompt-engine", - "version": "0.0.1", + "version": "0.0.2", "license": "ISC", "dependencies": { - "gpt3-tokenizer": "^1.1.2" + "gpt3-tokenizer": "^1.1.2", + "yaml": "^2.1.1" }, "devDependencies": { "@types/jest": "^28.1.1", @@ -3897,6 +3898,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.5.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", @@ -6873,6 +6882,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==" + }, "yargs": { "version": "17.5.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", diff --git a/package.json b/package.json index e04bd9d..f5b2e54 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "typescript": "^4.7.3" }, "dependencies": { - "gpt3-tokenizer": "^1.1.2" + "gpt3-tokenizer": "^1.1.2", + "yaml": "^2.1.1" } } diff --git a/src/ChatEngine.ts b/src/ChatEngine.ts index a0ce7f1..a429f12 100644 --- a/src/ChatEngine.ts +++ b/src/ChatEngine.ts @@ -1,10 +1,11 @@ import { DefaultModelConfig, PromptEngine } from "./PromptEngine"; import { Interaction, IModelConfig, IChatConfig } from "./types"; +import { dashesToCamelCase } from "./utils/utils"; export const DefaultChatConfig: IChatConfig = { userName: "USER", botName: "BOT", - newLineOperator: "\n", + newlineOperator: "\n", }; export class ChatEngine extends PromptEngine { @@ -26,7 +27,40 @@ export class ChatEngine extends PromptEngine { outputPostfix: "", descriptionPrefix: "", descriptionPostfix: "", - newLineOperator: languageConfig.newLineOperator, + newlineOperator: languageConfig.newlineOperator, }; } + + protected loadConfigYAML(parsedYAML: Record) { + if (parsedYAML["type"] == "chat-engine") { + if (parsedYAML.hasOwnProperty("config")){ + const configData = parsedYAML["config"] + if (configData.hasOwnProperty("model-config")) { + const modelConfig = configData["model-config"]; + const camelCaseModelConfig = {}; + for (const key in modelConfig) { + camelCaseModelConfig[dashesToCamelCase(key)] = modelConfig[key]; + } + this.modelConfig = { ...this.modelConfig, ...camelCaseModelConfig }; + delete configData["model-config"]; + } + const camelCaseConfig = {}; + for (const key in configData) { + camelCaseConfig[dashesToCamelCase(key)] = configData[key]; + } + this.languageConfig = { ...this.languageConfig, ...camelCaseConfig }; + this.promptConfig = { + inputPrefix: this.languageConfig.userName + ":", + inputPostfix: "", + outputPrefix: this.languageConfig.botName + ":", + outputPostfix: "", + descriptionPrefix: "", + descriptionPostfix: "", + newlineOperator: this.languageConfig.newlineOperator, + }; + } + } else { + throw Error("Invalid yaml file type"); + } + } } \ No newline at end of file diff --git a/src/CodeEngine.ts b/src/CodeEngine.ts index 93d197c..9ded2ce 100644 --- a/src/CodeEngine.ts +++ b/src/CodeEngine.ts @@ -1,12 +1,13 @@ import { DefaultModelConfig, PromptEngine } from "./PromptEngine"; import { Interaction, IModelConfig, ICodePromptConfig } from "./types"; +import { dashesToCamelCase } from "./utils/utils"; export const JavaScriptConfig: ICodePromptConfig = { descriptionCommentOperator: "/*/", descriptionCloseCommentOperator: "/*/", commentOperator: "/*", closeCommentOperator: "*/", - newLineOperator: "\n", + newlineOperator: "\n", }; export class CodeEngine extends PromptEngine { @@ -31,4 +32,38 @@ export class CodeEngine extends PromptEngine { newLineOperator: languageConfig.newLineOperator, }; } + + protected loadConfigYAML(parsedYAML: Record) { + if (parsedYAML["type"] == "code-engine") { + if (parsedYAML.hasOwnProperty("config")){ + const configData = parsedYAML["config"] + if (configData.hasOwnProperty("model-config")) { + const modelConfig = configData["model-config"]; + const camelCaseModelConfig = {}; + for (const key in modelConfig) { + camelCaseModelConfig[dashesToCamelCase(key)] = modelConfig[key]; + } + this.modelConfig = { ...this.modelConfig, ...camelCaseModelConfig }; + delete configData["model-config"]; + } + const camelCaseConfig = {}; + for (const key in configData) { + camelCaseConfig[dashesToCamelCase(key)] = configData[key]; + } + this.languageConfig = { ...this.languageConfig, ...camelCaseConfig }; + this.promptConfig = { + inputPrefix: this.languageConfig.commentOperator, + inputPostfix: this.languageConfig.closeCommentOperator, + outputPrefix: "", + outputPostfix: "", + descriptionPrefix: this.languageConfig.commentOperator, + descriptionPostfix: this.languageConfig.closeCommentOperator, + newlineOperator: this.languageConfig.newlineOperator, + }; + } + } else { + throw Error("Invalid yaml file type"); + } + } + } diff --git a/src/PromptEngine.ts b/src/PromptEngine.ts index bf8c900..9ba534e 100644 --- a/src/PromptEngine.ts +++ b/src/PromptEngine.ts @@ -7,8 +7,9 @@ import { Context, Dialog, } from "./types"; - +import { parse } from 'yaml'; import GPT3Tokenizer from "gpt3-tokenizer"; +import { dashesToCamelCase } from "./utils/utils"; export const DefaultPromptConfig: IPromptConfig = { inputPrefix: "", @@ -17,7 +18,7 @@ export const DefaultPromptConfig: IPromptConfig = { outputPostfix: "", descriptionPrefix: "", descriptionPostfix: "", - newLineOperator: "\n", + newlineOperator: "\n", }; export const DefaultModelConfig: IModelConfig = { @@ -63,14 +64,69 @@ export class PromptEngine implements IPromptEngine { context += this.promptConfig.descriptionPostfix ? ` ${this.promptConfig.descriptionPostfix}` : ""; - context += this.promptConfig.newLineOperator; - context += this.promptConfig.newLineOperator; + context += this.promptConfig.newlineOperator; + context += this.promptConfig.newlineOperator; return context; } else { return ""; } } + /** + * + * @param yaml + * + **/ + public loadYAML(yamlData: string) { + const parsedYAML = parse(yamlData); + + if (parsedYAML.hasOwnProperty("type")) { + this.loadConfigYAML(parsedYAML); + } else { + throw Error("Invalid yaml file type"); + } + + if (parsedYAML.hasOwnProperty("description")) { + this.description = parsedYAML['description']; + } + + if (parsedYAML.hasOwnProperty("examples")) { + this.examples = parsedYAML['examples']; + } + + if (parsedYAML.hasOwnProperty("flow-reset-text")) { + this.flowResetText = parsedYAML['flow-reset-text']; + } + + if (parsedYAML.hasOwnProperty("dialog")) { + this.dialog = parsedYAML['dialog']; + } + } + + protected loadConfigYAML(parsedYAML: Record) { + if (parsedYAML["type"] == "prompt-engine") { + if (parsedYAML.hasOwnProperty("config")){ + const configData = parsedYAML["config"] + if (configData.hasOwnProperty("model-config")) { + const modelConfig = configData["model-config"]; + const camelCaseModelConfig = {}; + for (const key in modelConfig) { + camelCaseModelConfig[dashesToCamelCase(key)] = modelConfig[key]; + } + this.modelConfig = { ...this.modelConfig, ...camelCaseModelConfig }; + delete configData["model-config"]; + } + const camelCaseConfig = {}; + for (const key in configData) { + camelCaseConfig[dashesToCamelCase(key)] = configData[key]; + } + this.promptConfig = { ...this.promptConfig, ...camelCaseConfig }; + } + } else { + throw Error("Invalid yaml file type"); + } + } + /** * * @param context ongoing context to add examples to @@ -97,8 +153,8 @@ export class PromptEngine implements IPromptEngine { context += this.promptConfig.descriptionPostfix ? ` ${this.promptConfig.descriptionPostfix}` : ""; - context += this.promptConfig.newLineOperator; - context += this.promptConfig.newLineOperator; + context += this.promptConfig.newlineOperator; + context += this.promptConfig.newlineOperator; } return context; @@ -187,7 +243,7 @@ export class PromptEngine implements IPromptEngine { * @param inputLength Length of the input string - used to determine how long the context can be * @returns A context string containing description, examples and ongoing interactions with the model */ - public buildContext(userInput?: string, multiTurn?: boolean): Context { + public buildContext(userInput?: string, multiTurn: boolean = true): Context { let context = ""; context = this.insertDescription(context); context = this.insertExamples(context); @@ -260,8 +316,8 @@ export class PromptEngine implements IPromptEngine { formatted += this.promptConfig.inputPostfix ? ` ${this.promptConfig.inputPostfix}` : ""; - formatted += this.promptConfig.newLineOperator - ? this.promptConfig.newLineOperator + formatted += this.promptConfig.newlineOperator + ? this.promptConfig.newlineOperator : ""; return formatted; }; @@ -275,8 +331,8 @@ export class PromptEngine implements IPromptEngine { formatted += this.promptConfig.outputPostfix ? ` ${this.promptConfig.outputPostfix}` : ""; - formatted += this.promptConfig.newLineOperator - ? this.promptConfig.newLineOperator + formatted += this.promptConfig.newlineOperator + ? this.promptConfig.newlineOperator : ""; return formatted; }; @@ -285,7 +341,7 @@ export class PromptEngine implements IPromptEngine { let stringInteraction = ""; stringInteraction += this.formatInput(interaction.input); stringInteraction += this.formatOutput(interaction.response); - stringInteraction += this.promptConfig.newLineOperator; + stringInteraction += this.promptConfig.newlineOperator; return stringInteraction; }; diff --git a/src/types.ts b/src/types.ts index c57f8c0..e97abf4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,7 +23,7 @@ export interface IPromptConfig { inputPostfix: string; outputPrefix: string; outputPostfix: string; - newLineOperator: string; + newlineOperator: string; } export interface ICodePromptConfig { @@ -31,11 +31,11 @@ export interface ICodePromptConfig { descriptionCloseCommentOperator: string; commentOperator: string; closeCommentOperator?: string; - newLineOperator: string; + newlineOperator: string; } export interface IChatConfig { userName: string; botName: string; - newLineOperator: string; + newlineOperator: string; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..27c116a --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,12 @@ +export const dashesToCamelCase = input => +input + .split("-") + .reduce( + (res, word, i) => + i === 0 + ? word.toLowerCase() + : `${res}${word.charAt(0).toUpperCase()}${word + .substr(1) + .toLowerCase()}`, + "" +); \ No newline at end of file