Ability to specify multiple messages and roles in generateRaw() (#4264) * adding capability for generateRaw() to specify roles of messages for chat completion * linting * additional comments * Apply review comments, fix type errors * Fix joiner for non-instruct prompts * Format joint prompt for novel, fix prefix for non-instruct * Fix role for string prompts * Do not trim sysprompt for instruct * Do not prefix names for CC * Append newline to systemPrompt in instruct --------- Co-authored-by: qvink <qvink@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

db318cd75c76b1f6d50c25a71a579863361aa49b

qvink <191186569+qvink@users.noreply.github.com>

Signed
1 files changed, +62 -19Ignore whitespace
public/script.js+62 -19
@@ -3053,8 +3053,64 @@ class StreamingProcessor {
30533053}
30543054
30553055/**
3056+ * Constructs a prompt to be used for either Text Completion or Chat Completion. Input is format-agnostic.
3057+ * @param {string | object[]} prompt Input prompt. Can be a string or an array of chat-style messages, i.e. [{role: '', content: ''}, ...]
3058+ * @param {string} api API to use.
3059+ * @param {boolean} instructOverride true to override instruct mode, false to use the default value
3060+ * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode
3061+ * @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI.
3062+ * @returns {string | object[]} Prompt ready for use in generation. If using TC, this will be a string. If using CC, this will be an array of chat-style messages.
3063+ */
3064+export function createRawPrompt(prompt, api, instructOverride, quietToLoud, systemPrompt) {
3065+ const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride;
3066+
3067+ // If the prompt was given as a string, convert to a message-style object assuming user role
3068+ if (typeof prompt === 'string') {
3069+ const message = api === 'openai'
3070+ ? { role: 'user', content: prompt.trim() }
3071+ : { role: 'system', content: prompt };
3072+ prompt = [message];
3073+ } else { // checks for message-style object
3074+ if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided');
3075+ }
3076+
3077+ // Format each message in the prompt, accounting for the provided roles
3078+ for (const message of prompt) {
3079+ let name = '';
3080+ if (message.role === 'user') name = message.name ?? name1;
3081+ if (message.role === 'assistant') name = message.name ?? name2;
3082+ if (message.role === 'system') name = message.name ?? '';
3083+ const prefix = isInstruct || api === 'openai' ? '' : (name ? `${name}: ` : '');
3084+ message.content = prefix + substituteParams(message.content ?? '');
3085+ if (isInstruct) { // instruct formatting for text completion
3086+ const isUser = message.role === 'user';
3087+ const isNarrator = message.role === 'system';
3088+ message.content = formatInstructModeChat(name, message.content, isUser, isNarrator, '', name1, name2, false);
3089+ }
3090+ }
3091+
3092+ // prepend system prompt, if provided
3093+ if (systemPrompt) {
3094+ systemPrompt = substituteParams(systemPrompt);
3095+ systemPrompt = isInstruct ? (formatInstructModeSystemPrompt(systemPrompt) + '\n') : systemPrompt.trim();
3096+ prompt.unshift({ role: 'system', content: systemPrompt });
3097+ }
3098+
3099+ // If text completion, convert to text prompt by concatenating all message contents
3100+ if (api !== 'openai') {
3101+ const joiner = isInstruct ? '' : '\n';
3102+ prompt = prompt.map(message => message.content).join(joiner);
3103+ prompt = api === 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt;
3104+ prompt = prompt + (isInstruct ? formatInstructModePrompt(name2, false, '', name1, name2, true, quietToLoud) : '\n'); // add last line
3105+ }
3106+
3107+ return prompt;
3108+}
3109+
3110+/**
30563111 * Generates a message using the provided prompt.
3057- * @param {string} prompt Prompt to generate a message from
3112+ * If the prompt is an array of chat-style messages and not using chat completion, it will be converted to a text prompt.
3113+ * @param {string | object[]} prompt Prompt to generate a message from. Can be a string or an array of chat-style messages, i.e. [{role: '', content: ''}, ...]
30583114 * @param {string} api API to use. Main API is used if not specified.
30593115 * @param {boolean} instructOverride true to override instruct mode, false to use the default value
30603116 * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode
@@ -3070,20 +3126,10 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
30703126
30713127 const abortController = new AbortController();
30723128 const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0;
3073- const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride;
3074- const isQuiet = true;
30753129 let eventHook = () => { };
30763130
3077- if (systemPrompt) {
3131+ // construct final prompt from the input. Can either be a string or an array of chat-style messages.
3078- systemPrompt = substituteParams(systemPrompt);
3132+ prompt = createRawPrompt(prompt, api, instructOverride, quietToLoud, systemPrompt);
3079- systemPrompt = isInstruct ? formatInstructModeSystemPrompt(systemPrompt) : systemPrompt;
3080- prompt = api === 'openai' ? prompt : `${systemPrompt}\n${prompt}`;
3081- }
3082-
3083- prompt = substituteParams(prompt);
3084- prompt = api == 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt;
3085- prompt = isInstruct ? formatInstructModeChat(name1, prompt, false, true, '', name1, name2, false) : prompt;
3086- prompt = isInstruct ? (prompt + formatInstructModePrompt(name2, false, '', name1, name2, isQuiet, quietToLoud)) : (prompt + '\n');
30873133
30883134 try {
30893135 if (responseLengthCustomized) {
@@ -3100,7 +3146,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
31003146 } else {
31013147 const isHorde = api === 'koboldhorde';
31023148 const koboldSettings = koboldai_settings[koboldai_setting_names[kai_settings.preset_settings]];
31033149 generateData = getKoboldGenerationData(prompt.toString(), koboldSettings, amount_gen, max_context, isHorde, 'quiet');
31043150 }
31053151 TempResponseLength.restore(api);
31063152 break;
@@ -3115,10 +3161,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
31153161 TempResponseLength.restore(api);
31163162 break;
31173163 case 'openai': {
3118- generateData = [{ role: 'user', content: prompt.trim() }];
3164+ generateData = prompt; // generateData is just the chat message object
3119- if (systemPrompt) {
3120- generateData.unshift({ role: 'system', content: systemPrompt.trim() });
3121- }
31223165 eventHook = TempResponseLength.setupEventHook(api);
31233166 } break;
31243167 }
@@ -3126,7 +3169,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
31263169 let data = {};
31273170
31283171 if (api === 'koboldhorde') {
31293172 data = await generateHorde(prompt.toString(), generateData, abortController.signal, false);
31303173 } else if (api === 'openai') {
31313174 data = await sendOpenAIRequest('quiet', generateData, abortController.signal);
31323175 } else {