From 1c60ccf14add8bfb6c1a02694ed084c9adf86af8 Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Sat, 19 Sep 2020 21:34:29 -0700 Subject: [PATCH] Document Context Modifiers --- README.md | 9 +++ examples/contextModifiers/dontBeNegative.js | 7 ++ examples/contextModifiers/notes.md | 72 +++++++++++++++++++ .../reimplementAuthorsNote.js | 23 ++++++ 4 files changed, 111 insertions(+) create mode 100644 examples/contextModifiers/dontBeNegative.js create mode 100644 examples/contextModifiers/notes.md create mode 100644 examples/contextModifiers/reimplementAuthorsNote.js diff --git a/README.md b/README.md index ea92f88..2eaaf63 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ You can modify the quests property to change the quests of the adventure mid gam ### Input Modifier Called each time the player gives an input and has the opportunity to modify that input. +### Context Modifier +Called each time the AI model is about to receive input and has the opportunity to modify that input (by up to a 75% [edit distance](https://en.wikipedia.org/wiki/Levenshtein_distance) change). + ### Output Modifier Called each time the model generates an output and has the opportunity to modify that output. @@ -48,3 +51,9 @@ The `state` variable can be used to store information that's persistent across f ### Console `console.log("Some message")` will log messages that you can see in the scripting console + +### Info + +`info` contains some useful values, depending on which modifier you're in. +All modifiers have access to `info.actionCount`, the number of actions in the adventure so far. +When in a Context Modifier, `info.memoryLength` and `info.maxChars` are also set, indicating the length of the memory portion of text (if any), and the total allowed length of the context after which it will be truncated. \ No newline at end of file diff --git a/examples/contextModifiers/dontBeNegative.js b/examples/contextModifiers/dontBeNegative.js new file mode 100644 index 0000000..5861e98 --- /dev/null +++ b/examples/contextModifiers/dontBeNegative.js @@ -0,0 +1,7 @@ +const modifier = (text) => { + // This will always result in a shorter string, so no need to truncate it. + return { text: text.replace(/ not /gi, ' ') } +} + +// Don't modify this part +modifier(text) diff --git a/examples/contextModifiers/notes.md b/examples/contextModifiers/notes.md new file mode 100644 index 0000000..a0e62d2 --- /dev/null +++ b/examples/contextModifiers/notes.md @@ -0,0 +1,72 @@ +# Notes + +## Input Modifier + +```js +const modifier = (text) => { + state.notes = state.notes || [] + + if (text.match(/> You note:/i)) { + const note = text.replace(/> You note: ?/i, '').trim() + state.notes.push({ + pattern: history.map(({text}) => text).join('').split("\n").pop(), + note, + actionCount: info.actionCount, + }) + state.message = `Noted: ${note}` + text = '' + } else { + delete state.message + } + + return {text} +} + +// Don't modify this part +modifier(text) +``` + +Set a note by typing `note: ` when in Do mode. It will be tagged to whatever the most recent line of text is, appearing below it to the AI, but not visible to the user. + +## Content Modifier + +```js +// info.memoryLength is the length of the memory section of text. text.slice(0, info.memoryLength) will be the memory. +// info.maxChars is the maximum length that text can be. The server will truncate text to this length. +// info.actionCount is the number of actions in this adventure. + +const modifier = (text) => { + state.notes = state.notes || [] + + const memory = text.slice(0, info.memoryLength || 0) + let context = info.memoryLength ? text.slice(info.memoryLength + 1) : text + + // Assumes that the notes are sorted from oldest to newest. + state.notes = state.notes.filter(({ pattern, note, actionCount }) => { + if (actionCount > info.actionCount) { + // The user must have hit undo, removing this note. + return false + } + + const index = context.indexOf(pattern) + + if (index >- 1) { + context = [context.slice(0, index + pattern.length), "\n", note, context.slice(index + pattern.length)].join('') + return true + } else { + // Only keep ones that were found, otherwise they must have moved out of the history window. + return false + } + }) + + // Make sure the new context isn't too long, or it will get truncated by the server. + context = context.slice(-(info.maxChars - info.memoryLength)) + const finalText = [memory, context].join("\n") + return { text: finalText } +} + +// Don't modify this part +modifier(text) +``` + +You can debug this by viewing what the model received in the Scenario Script page. It's the little brain icon in the upper-right. \ No newline at end of file diff --git a/examples/contextModifiers/reimplementAuthorsNote.js b/examples/contextModifiers/reimplementAuthorsNote.js new file mode 100644 index 0000000..cab1868 --- /dev/null +++ b/examples/contextModifiers/reimplementAuthorsNote.js @@ -0,0 +1,23 @@ +// Checkout the repo examples to get an idea of other ways you can use scripting +// https://github.com/AIDungeon/Scripting/blob/master/examples + +// info.memoryLength is the length of the memory section of text. +// info.maxChars is the maximum length that text can be. The server will truncate the text you return to this length. + +// This modifier re-implements Author's Note as an example. +const modifier = (text) => { + const memory = info.memoryLength ? text.slice(0, info.memoryLength) : '' + const context = info.memoryLength ? text.slice(info.memoryLength + 1) : text + const lines = context.split("\n") + if (lines.length > 2) { + const authorsNote = "Everyone in this story is an AI programmer." + lines.splice(-3, 0, `[Author's note: ${authorsNote}]`) + } + // Make sure the new context isn't too long, or it will get truncated by the server. + const combinedLines = lines.join("\n").slice(-(info.maxChars - info.memoryLength)) + const finalText = [memory, combinedLines].join("") + return { text: finalText } +} + +// Don't modify this part +modifier(text) \ No newline at end of file