Don't use global state to build Chat Completion prompts

This commit is contained in:
Cohee
2023-11-21 14:38:15 +02:00
parent 01b629bd49
commit 73e081dd99
2 changed files with 78 additions and 50 deletions

View File

@ -3105,10 +3105,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
const storyString = renderStoryString(storyStringParams); const storyString = renderStoryString(storyStringParams);
let oaiMessages = [];
let oaiMessageExamples = [];
if (main_api === 'openai') { if (main_api === 'openai') {
message_already_generated = ''; message_already_generated = '';
setOpenAIMessages(coreChat); oaiMessages = setOpenAIMessages(coreChat);
setOpenAIMessageExamples(mesExamplesArray); oaiMessageExamples = setOpenAIMessageExamples(mesExamplesArray);
} }
// hack for regeneration of the first message // hack for regeneration of the first message
@ -3537,7 +3540,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
cyclePrompt: cyclePrompt, cyclePrompt: cyclePrompt,
systemPromptOverride: system, systemPromptOverride: system,
jailbreakPromptOverride: jailbreak, jailbreakPromptOverride: jailbreak,
personaDescription: persona personaDescription: persona,
messages: oaiMessages,
messageExamples: oaiMessageExamples,
}, dryRun); }, dryRun);
generate_data = { prompt: prompt }; generate_data = { prompt: prompt };

View File

@ -64,7 +64,6 @@ import {
} from "./instruct-mode.js"; } from "./instruct-mode.js";
export { export {
openai_msgs,
openai_messages_count, openai_messages_count,
oai_settings, oai_settings,
loadOpenAISettings, loadOpenAISettings,
@ -79,8 +78,6 @@ export {
MessageCollection MessageCollection
} }
let openai_msgs = [];
let openai_msgs_example = [];
let openai_messages_count = 0; let openai_messages_count = 0;
let openai_narrator_messages_count = 0; let openai_narrator_messages_count = 0;
@ -388,10 +385,15 @@ function convertChatCompletionToInstruct(messages, type) {
return prompt; return prompt;
} }
/**
* Formats chat messages into chat completion messages.
* @param {object[]} chat - Array containing all messages.
* @returns {object[]} - Array containing all messages formatted for chat completion.
*/
function setOpenAIMessages(chat) { function setOpenAIMessages(chat) {
let j = 0; let j = 0;
// clean openai msgs // clean openai msgs
openai_msgs = []; const messages = [];
openai_narrator_messages_count = 0; openai_narrator_messages_count = 0;
for (let i = chat.length - 1; i >= 0; i--) { for (let i = chat.length - 1; i >= 0; i--) {
let role = chat[j]['is_user'] ? 'user' : 'assistant'; let role = chat[j]['is_user'] ? 'user' : 'assistant';
@ -418,21 +420,29 @@ function setOpenAIMessages(chat) {
if (role == 'user' && oai_settings.wrap_in_quotes) content = `"${content}"`; if (role == 'user' && oai_settings.wrap_in_quotes) content = `"${content}"`;
const name = chat[j]['name']; const name = chat[j]['name'];
const image = chat[j]?.extra?.image; const image = chat[j]?.extra?.image;
openai_msgs[i] = { "role": role, "content": content, name: name, "image": image }; messages[i] = { "role": role, "content": content, name: name, "image": image };
j++; j++;
} }
return messages
} }
/**
* Formats chat messages into chat completion messages.
* @param {string[]} mesExamplesArray - Array containing all examples.
* @returns {object[]} - Array containing all examples formatted for chat completion.
*/
function setOpenAIMessageExamples(mesExamplesArray) { function setOpenAIMessageExamples(mesExamplesArray) {
// get a nice array of all blocks of all example messages = array of arrays (important!) // get a nice array of all blocks of all example messages = array of arrays (important!)
openai_msgs_example = []; const examples = [];
for (let item of mesExamplesArray) { for (let item of mesExamplesArray) {
// remove <START> {Example Dialogue:} and replace \r\n with just \n // remove <START> {Example Dialogue:} and replace \r\n with just \n
let replaced = item.replace(/<START>/i, "{Example Dialogue:}").replace(/\r/gm, ''); let replaced = item.replace(/<START>/i, "{Example Dialogue:}").replace(/\r/gm, '');
let parsed = parseExampleIntoIndividual(replaced); let parsed = parseExampleIntoIndividual(replaced);
// add to the example message blocks array // add to the example message blocks array
openai_msgs_example.push(parsed); examples.push(parsed);
} }
return examples;
} }
/** /**
@ -554,8 +564,9 @@ function formatWorldInfo(value) {
* This function populates the injections in the conversation. * This function populates the injections in the conversation.
* *
* @param {Prompt[]} prompts - Array containing injection prompts. * @param {Prompt[]} prompts - Array containing injection prompts.
* @param {Object[]} messages - Array containing all messages.
*/ */
function populationInjectionPrompts(prompts) { function populationInjectionPrompts(prompts, messages) {
let totalInsertedMessages = 0; let totalInsertedMessages = 0;
for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) { for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {
@ -581,12 +592,13 @@ function populationInjectionPrompts(prompts) {
if (roleMessages.length) { if (roleMessages.length) {
const injectIdx = i + totalInsertedMessages; const injectIdx = i + totalInsertedMessages;
openai_msgs.splice(injectIdx, 0, ...roleMessages); messages.splice(injectIdx, 0, ...roleMessages);
totalInsertedMessages += roleMessages.length; totalInsertedMessages += roleMessages.length;
} }
} }
openai_msgs = openai_msgs.reverse(); messages = messages.reverse();
return messages;
} }
export function isOpenRouterWithInstruct() { export function isOpenRouterWithInstruct() {
@ -595,13 +607,13 @@ export function isOpenRouterWithInstruct() {
/** /**
* Populates the chat history of the conversation. * Populates the chat history of the conversation.
* * @param {object[]} messages - Array containing all messages.
* @param {PromptCollection} prompts - Map object containing all prompts where the key is the prompt identifier and the value is the prompt object. * @param {PromptCollection} prompts - Map object containing all prompts where the key is the prompt identifier and the value is the prompt object.
* @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts. * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts.
* @param type * @param type
* @param cyclePrompt * @param cyclePrompt
*/ */
async function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt = null) { async function populateChatHistory(messages, prompts, chatCompletion, type = null, cyclePrompt = null) {
chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory')); chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory'));
let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || ''; let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || '';
@ -632,7 +644,7 @@ async function populateChatHistory(prompts, chatCompletion, type = null, cyclePr
chatCompletion.reserveBudget(continueMessage); chatCompletion.reserveBudget(continueMessage);
} }
const lastChatPrompt = openai_msgs[openai_msgs.length - 1]; const lastChatPrompt = messages[messages.length - 1];
const message = new Message('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement'); const message = new Message('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement');
if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) { if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) {
chatCompletion.insert(message, 'chatHistory'); chatCompletion.insert(message, 'chatHistory');
@ -641,13 +653,13 @@ async function populateChatHistory(prompts, chatCompletion, type = null, cyclePr
const imageInlining = isImageInliningSupported(); const imageInlining = isImageInliningSupported();
// Insert chat messages as long as there is budget available // Insert chat messages as long as there is budget available
const chatPool = [...openai_msgs].reverse(); const chatPool = [...messages].reverse();
for (let index = 0; index < chatPool.length; index++) { for (let index = 0; index < chatPool.length; index++) {
const chatPrompt = chatPool[index]; const chatPrompt = chatPool[index];
// We do not want to mutate the prompt // We do not want to mutate the prompt
const prompt = new Prompt(chatPrompt); const prompt = new Prompt(chatPrompt);
prompt.identifier = `chatHistory-${openai_msgs.length - index}`; prompt.identifier = `chatHistory-${messages.length - index}`;
const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt)); const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt));
if (true === promptManager.serviceSettings.names_in_completion && prompt.name) { if (true === promptManager.serviceSettings.names_in_completion && prompt.name) {
@ -688,12 +700,13 @@ async function populateChatHistory(prompts, chatCompletion, type = null, cyclePr
* *
* @param {PromptCollection} prompts - Map object containing all prompts where the key is the prompt identifier and the value is the prompt object. * @param {PromptCollection} prompts - Map object containing all prompts where the key is the prompt identifier and the value is the prompt object.
* @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts. * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts.
* @param {Object[]} messageExamples - Array containing all message examples.
*/ */
function populateDialogueExamples(prompts, chatCompletion) { function populateDialogueExamples(prompts, chatCompletion, messageExamples) {
chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples')); chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));
if (openai_msgs_example.length) { if (Array.isArray(messageExamples) && messageExamples.length) {
const newExampleChat = new Message('system', oai_settings.new_example_chat_prompt, 'newChat'); const newExampleChat = new Message('system', oai_settings.new_example_chat_prompt, 'newChat');
[...openai_msgs_example].forEach((dialogue, dialogueIndex) => { [...messageExamples].forEach((dialogue, dialogueIndex) => {
let examplesAdded = 0; let examplesAdded = 0;
if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples'); if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples');
@ -744,8 +757,12 @@ function getPromptPosition(position) {
* @param {string} options.quietPrompt - Instruction prompt for extras * @param {string} options.quietPrompt - Instruction prompt for extras
* @param {string} options.quietImage - Image prompt for extras * @param {string} options.quietImage - Image prompt for extras
* @param {string} options.type - The type of the chat, can be 'impersonate'. * @param {string} options.type - The type of the chat, can be 'impersonate'.
* @param {string} options.cyclePrompt - The last prompt in the conversation.
* @param {object[]} options.messages - Array containing all messages.
* @param {object[]} options.messageExamples - Array containing all message examples.
* @returns {Promise<void>}
*/ */
async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt } = {}) { async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples } = {}) {
// Helper function for preparing a prompt, that already exists within the prompt collection, for completion // Helper function for preparing a prompt, that already exists within the prompt collection, for completion
const addToChatCompletion = (source, target = null) => { const addToChatCompletion = (source, target = null) => {
// We need the prompts array to determine a position for the source. // We need the prompts array to determine a position for the source.
@ -852,15 +869,15 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
} }
// Add in-chat injections // Add in-chat injections
populationInjectionPrompts(userAbsolutePrompts); messages = populationInjectionPrompts(userAbsolutePrompts, messages);
// Decide whether dialogue examples should always be added // Decide whether dialogue examples should always be added
if (power_user.pin_examples) { if (power_user.pin_examples) {
populateDialogueExamples(prompts, chatCompletion); populateDialogueExamples(prompts, chatCompletion, messageExamples);
await populateChatHistory(prompts, chatCompletion, type, cyclePrompt); await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
} else { } else {
await populateChatHistory(prompts, chatCompletion, type, cyclePrompt); await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
populateDialogueExamples(prompts, chatCompletion); populateDialogueExamples(prompts, chatCompletion, messageExamples);
} }
chatCompletion.freeBudget(controlPrompts); chatCompletion.freeBudget(controlPrompts);
@ -998,6 +1015,8 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
* @param {string} content.quietPrompt - The quiet prompt to be used in the conversation. * @param {string} content.quietPrompt - The quiet prompt to be used in the conversation.
* @param {string} content.cyclePrompt - The last prompt used for chat message continuation. * @param {string} content.cyclePrompt - The last prompt used for chat message continuation.
* @param {Array} content.extensionPrompts - An array of additional prompts. * @param {Array} content.extensionPrompts - An array of additional prompts.
* @param {object[]} content.messages - An array of messages to be used as chat history.
* @param {string[]} content.messageExamples - An array of messages to be used as dialogue examples.
* @param dryRun - Whether this is a live call or not. * @param dryRun - Whether this is a live call or not.
* @returns {(*[]|boolean)[]} An array where the first element is the prepared chat and the second element is a boolean flag. * @returns {(*[]|boolean)[]} An array where the first element is the prepared chat and the second element is a boolean flag.
*/ */
@ -1016,7 +1035,9 @@ export async function prepareOpenAIMessages({
cyclePrompt, cyclePrompt,
systemPromptOverride, systemPromptOverride,
jailbreakPromptOverride, jailbreakPromptOverride,
personaDescription personaDescription,
messages,
messageExamples,
} = {}, dryRun) { } = {}, dryRun) {
// Without a character selected, there is no way to accurately calculate tokens // Without a character selected, there is no way to accurately calculate tokens
if (!promptManager.activeCharacter && dryRun) return [null, false]; if (!promptManager.activeCharacter && dryRun) return [null, false];
@ -1042,11 +1063,13 @@ export async function prepareOpenAIMessages({
extensionPrompts, extensionPrompts,
systemPromptOverride, systemPromptOverride,
jailbreakPromptOverride, jailbreakPromptOverride,
personaDescription personaDescription,
messages,
messageExamples,
}); });
// Fill the chat completion with as much context as the budget allows // Fill the chat completion with as much context as the budget allows
await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt }); await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
} catch (error) { } catch (error) {
if (error instanceof TokenBudgetExceededError) { if (error instanceof TokenBudgetExceededError) {
toastr.error('An error occurred while counting tokens: Token budget exceeded.') toastr.error('An error occurred while counting tokens: Token budget exceeded.')
@ -1117,7 +1140,7 @@ function checkQuotaError(data) {
} }
} }
async function sendWindowAIRequest(openai_msgs_tosend, signal, stream) { async function sendWindowAIRequest(messages, signal, stream) {
if (!('ai' in window)) { if (!('ai' in window)) {
return showWindowExtensionError(); return showWindowExtensionError();
} }
@ -1172,7 +1195,7 @@ async function sendWindowAIRequest(openai_msgs_tosend, signal, stream) {
const generatePromise = window.ai.generateText( const generatePromise = window.ai.generateText(
{ {
messages: openai_msgs_tosend, messages: messages,
}, },
{ {
temperature: temperature, temperature: temperature,
@ -1349,11 +1372,11 @@ function openRouterGroupByVendor(array) {
}, new Map()); }, new Map());
} }
async function sendAltScaleRequest(openai_msgs_tosend, logit_bias, signal, type) { async function sendAltScaleRequest(messages, logit_bias, signal, type) {
const generate_url = '/generate_altscale'; const generate_url = '/generate_altscale';
let firstSysMsgs = [] let firstSysMsgs = []
for (let msg of openai_msgs_tosend) { for (let msg of messages) {
if (msg.role === 'system') { if (msg.role === 'system') {
firstSysMsgs.push(substituteParams(msg.name ? msg.name + ": " + msg.content : msg.content)); firstSysMsgs.push(substituteParams(msg.name ? msg.name + ": " + msg.content : msg.content));
} else { } else {
@ -1361,20 +1384,20 @@ async function sendAltScaleRequest(openai_msgs_tosend, logit_bias, signal, type)
} }
} }
let subsequentMsgs = openai_msgs_tosend.slice(firstSysMsgs.length); let subsequentMsgs = messages.slice(firstSysMsgs.length);
const joinedSysMsgs = substituteParams(firstSysMsgs.join("\n")); const joinedSysMsgs = substituteParams(firstSysMsgs.join("\n"));
const joinedSubsequentMsgs = subsequentMsgs.reduce((acc, obj) => { const joinedSubsequentMsgs = subsequentMsgs.reduce((acc, obj) => {
return acc + obj.role + ": " + obj.content + "\n"; return acc + obj.role + ": " + obj.content + "\n";
}, ""); }, "");
openai_msgs_tosend = substituteParams(joinedSubsequentMsgs); messages = substituteParams(joinedSubsequentMsgs);
const messageId = getNextMessageId(type); const messageId = getNextMessageId(type);
replaceItemizedPromptText(messageId, openai_msgs_tosend); replaceItemizedPromptText(messageId, messages);
const generate_data = { const generate_data = {
sysprompt: joinedSysMsgs, sysprompt: joinedSysMsgs,
prompt: openai_msgs_tosend, prompt: messages,
temp: Number(oai_settings.temp_openai), temp: Number(oai_settings.temp_openai),
top_p: Number(oai_settings.top_p_openai), top_p: Number(oai_settings.top_p_openai),
max_tokens: Number(oai_settings.openai_max_tokens), max_tokens: Number(oai_settings.openai_max_tokens),
@ -1392,18 +1415,18 @@ async function sendAltScaleRequest(openai_msgs_tosend, logit_bias, signal, type)
return data.output; return data.output;
} }
async function sendOpenAIRequest(type, openai_msgs_tosend, signal) { async function sendOpenAIRequest(type, messages, signal) {
// Provide default abort signal // Provide default abort signal
if (!signal) { if (!signal) {
signal = new AbortController().signal; signal = new AbortController().signal;
} }
// HACK: Filter out null and non-object messages // HACK: Filter out null and non-object messages
if (!Array.isArray(openai_msgs_tosend)) { if (!Array.isArray(messages)) {
throw new Error('openai_msgs_tosend must be an array'); throw new Error('messages must be an array');
} }
openai_msgs_tosend = openai_msgs_tosend.filter(msg => msg && typeof msg === 'object'); messages = messages.filter(msg => msg && typeof msg === 'object');
let logit_bias = {}; let logit_bias = {};
const messageId = getNextMessageId(type); const messageId = getNextMessageId(type);
@ -1419,23 +1442,23 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21 && !isPalm; const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21 && !isPalm;
if (isTextCompletion && isOpenRouter) { if (isTextCompletion && isOpenRouter) {
openai_msgs_tosend = convertChatCompletionToInstruct(openai_msgs_tosend, type); messages = convertChatCompletionToInstruct(messages, type);
replaceItemizedPromptText(messageId, openai_msgs_tosend); replaceItemizedPromptText(messageId, messages);
} }
if (isAI21 || isPalm) { if (isAI21 || isPalm) {
const joinedMsgs = openai_msgs_tosend.reduce((acc, obj) => { const joinedMsgs = messages.reduce((acc, obj) => {
const prefix = prefixMap[obj.role]; const prefix = prefixMap[obj.role];
return acc + (prefix ? (selected_group ? "\n" : prefix + " ") : "") + obj.content + "\n"; return acc + (prefix ? (selected_group ? "\n" : prefix + " ") : "") + obj.content + "\n";
}, ""); }, "");
openai_msgs_tosend = substituteParams(joinedMsgs) + (isImpersonate ? `${name1}:` : `${name2}:`); messages = substituteParams(joinedMsgs) + (isImpersonate ? `${name1}:` : `${name2}:`);
replaceItemizedPromptText(messageId, openai_msgs_tosend); replaceItemizedPromptText(messageId, messages);
} }
// If we're using the window.ai extension, use that instead // If we're using the window.ai extension, use that instead
// Doesn't support logit bias yet // Doesn't support logit bias yet
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) { if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
return sendWindowAIRequest(openai_msgs_tosend, signal, stream); return sendWindowAIRequest(messages, signal, stream);
} }
const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.SCALE]; const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.SCALE];
@ -1448,12 +1471,12 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
} }
if (isScale && oai_settings.use_alt_scale) { if (isScale && oai_settings.use_alt_scale) {
return sendAltScaleRequest(openai_msgs_tosend, logit_bias, signal, type); return sendAltScaleRequest(messages, logit_bias, signal, type);
} }
const model = getChatCompletionModel(); const model = getChatCompletionModel();
const generate_data = { const generate_data = {
"messages": openai_msgs_tosend, "messages": messages,
"model": model, "model": model,
"temperature": Number(oai_settings.temp_openai), "temperature": Number(oai_settings.temp_openai),
"frequency_penalty": Number(oai_settings.freq_pen_openai), "frequency_penalty": Number(oai_settings.freq_pen_openai),