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>
Signed| @@ -3053,8 +3053,64 @@ class StreamingProcessor { | |||
| 3053 | } | 3053 | } |
| 3054 | 3054 | ||
| 3055 | /** | 3055 | /** |
| 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 | /** | ||
| 3056 | * Generates a message using the provided prompt. | 3111 | * 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: ''}, ...] | ||
| 3058 | * @param {string} api API to use. Main API is used if not specified. | 3114 | * @param {string} api API to use. Main API is used if not specified. |
| 3059 | * @param {boolean} instructOverride true to override instruct mode, false to use the default value | 3115 | * @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 | 3116 | * @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 | |||
| 3070 | 3126 | ||
| 3071 | const abortController = new AbortController(); | 3127 | const abortController = new AbortController(); |
| 3072 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; | 3128 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; |
| 3073 | const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride; | ||
| 3074 | const isQuiet = true; | ||
| 3075 | let eventHook = () => { }; | 3129 | let eventHook = () => { }; |
| 3076 | 3130 | ||
| 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'); | ||
| 3087 | 3133 | ||
| 3088 | try { | 3134 | try { |
| 3089 | if (responseLengthCustomized) { | 3135 | if (responseLengthCustomized) { |
| @@ -3100,7 +3146,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3100 | } else { | 3146 | } else { |
| 3101 | const isHorde = api === 'koboldhorde'; | 3147 | const isHorde = api === 'koboldhorde'; |
| 3102 | const koboldSettings = koboldai_settings[koboldai_setting_names[kai_settings.preset_settings]]; | 3148 | const koboldSettings = koboldai_settings[koboldai_setting_names[kai_settings.preset_settings]]; |
| 3103 | generateData = getKoboldGenerationData(prompt, koboldSettings, amount_gen, max_context, isHorde, 'quiet'); | 3149 | generateData = getKoboldGenerationData(prompt.toString(), koboldSettings, amount_gen, max_context, isHorde, 'quiet'); |
| 3104 | } | 3150 | } |
| 3105 | TempResponseLength.restore(api); | 3151 | TempResponseLength.restore(api); |
| 3106 | break; | 3152 | break; |
| @@ -3115,10 +3161,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3115 | TempResponseLength.restore(api); | 3161 | TempResponseLength.restore(api); |
| 3116 | break; | 3162 | break; |
| 3117 | case 'openai': { | 3163 | 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 | } | ||
| 3122 | eventHook = TempResponseLength.setupEventHook(api); | 3165 | eventHook = TempResponseLength.setupEventHook(api); |
| 3123 | } break; | 3166 | } break; |
| 3124 | } | 3167 | } |
| @@ -3126,7 +3169,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3126 | let data = {}; | 3169 | let data = {}; |
| 3127 | 3170 | ||
| 3128 | if (api === 'koboldhorde') { | 3171 | if (api === 'koboldhorde') { |
| 3129 | data = await generateHorde(prompt, generateData, abortController.signal, false); | 3172 | data = await generateHorde(prompt.toString(), generateData, abortController.signal, false); |
| 3130 | } else if (api === 'openai') { | 3173 | } else if (api === 'openai') { |
| 3131 | data = await sendOpenAIRequest('quiet', generateData, abortController.signal); | 3174 | data = await sendOpenAIRequest('quiet', generateData, abortController.signal); |
| 3132 | } else { | 3175 | } else { |