Allow prompt post-processing for all sources. Add 'single user msg' processing (#4009) * Allow prompt post-processing for all sources. Add 'single user msg' PPP type * Fix copilot comments * Fix typo in element id * Remove redundant conditions * Lint fix * Add link to PPP docs
Signed| @@ -1991,6 +1991,7 @@ | |||
| 1991 | <div class="flexBasis100p toggle-description justifyLeft"> | 1991 | <div class="flexBasis100p toggle-description justifyLeft"> |
| 1992 | <span data-i18n="enable_functions_desc_1">Allows using </span><a href="https://platform.openai.com/docs/guides/function-calling" target="_blank" data-i18n="enable_functions_desc_2">function tools</a>. | 1992 | <span data-i18n="enable_functions_desc_1">Allows using </span><a href="https://platform.openai.com/docs/guides/function-calling" target="_blank" data-i18n="enable_functions_desc_2">function tools</a>. |
| 1993 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> | 1993 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 1994 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing is used!</strong> | ||
| 1994 | </div> | 1995 | </div> |
| 1995 | </div> | 1996 | </div> |
| 1996 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations"> | 1997 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations"> |
| @@ -3535,13 +3536,21 @@ | |||
| 3535 | </span> | 3536 | </span> |
| 3536 | </div> | 3537 | </div> |
| 3537 | </div> | 3538 | </div> |
| 3538 | <div id="prompt_post_porcessing_form" data-source="custom,openrouter"> | 3539 | <div id="prompt_post_processing_form"> |
| 3539 | <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4> | 3540 | <h4> |
| 3541 | <span data-i18n="Prompt Post-Processing"> | ||
| 3542 | Prompt Post-Processing | ||
| 3543 | </span> | ||
| 3544 | <a href="https://docs.sillytavern.app/usage/api-connections/openai/#prompt-post-processing" class="notes-link" target="_blank"> | ||
| 3545 | <span class="fa-solid fa-circle-question note-link-span"></span> | ||
| 3546 | </a> | ||
| 3547 | </h4> | ||
| 3540 | <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API."> | 3548 | <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API."> |
| 3541 | <option data-i18n="prompt_post_processing_none" value="">None</option> | 3549 | <option data-i18n="prompt_post_processing_none" value="">None</option> |
| 3542 | <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option> | 3550 | <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option> |
| 3543 | <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option> | 3551 | <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option> |
| 3544 | <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option> | 3552 | <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option> |
| 3553 | <option data-i18n="prompt_post_processing_single" value="single">Single user message</option> | ||
| 3545 | </select> | 3554 | </select> |
| 3546 | </div> | 3555 | </div> |
| 3547 | <div class="flex-container flex"> | 3556 | <div class="flex-container flex"> |
| @@ -203,13 +203,14 @@ const continue_postfix_types = { | |||
| 203 | DOUBLE_NEWLINE: '\n\n', | 203 | DOUBLE_NEWLINE: '\n\n', |
| 204 | }; | 204 | }; |
| 205 | 205 | ||
| 206 | const custom_prompt_post_processing_types = { | 206 | export const custom_prompt_post_processing_types = { |
| 207 | NONE: '', | 207 | NONE: '', |
| 208 | /** @deprecated Use MERGE instead. */ | 208 | /** @deprecated Use MERGE instead. */ |
| 209 | CLAUDE: 'claude', | 209 | CLAUDE: 'claude', |
| 210 | MERGE: 'merge', | 210 | MERGE: 'merge', |
| 211 | SEMI: 'semi', | 211 | SEMI: 'semi', |
| 212 | STRICT: 'strict', | 212 | STRICT: 'strict', |
| 213 | SINGLE: 'single', | ||
| 213 | }; | 214 | }; |
| 214 | 215 | ||
| 215 | const openrouter_middleout_types = { | 216 | const openrouter_middleout_types = { |
| @@ -575,6 +575,11 @@ export class ToolManager { | |||
| 575 | return false; | 575 | return false; |
| 576 | } | 576 | } |
| 577 | 577 | ||
| 578 | // Post-processing will forcefully remove past tool calls from the prompt, making them useless | ||
| 579 | if (oai_settings.custom_prompt_post_processing) { | ||
| 580 | return false; | ||
| 581 | } | ||
| 582 | |||
| 578 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS && Array.isArray(model_list)) { | 583 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS && Array.isArray(model_list)) { |
| 579 | const currentModel = model_list.find(model => model.id === oai_settings.pollinations_model); | 584 | const currentModel = model_list.find(model => model.id === oai_settings.pollinations_model); |
| 580 | if (currentModel) { | 585 | if (currentModel) { |
| @@ -72,15 +72,17 @@ function postProcessPrompt(messages, type, names) { | |||
| 72 | switch (type) { | 72 | switch (type) { |
| 73 | case 'merge': | 73 | case 'merge': |
| 74 | case 'claude': | 74 | case 'claude': |
| 75 | return mergeMessages(messages, names, false, false); | 75 | return mergeMessages(messages, names, { strict: false, placeholders: false, single: false }); |
| 76 | case 'semi': | 76 | case 'semi': |
| 77 | return mergeMessages(messages, names, true, false); | 77 | return mergeMessages(messages, names, { strict: true, placeholders: false, single: false }); |
| 78 | case 'strict': | 78 | case 'strict': |
| 79 | return mergeMessages(messages, names, true, true); | 79 | return mergeMessages(messages, names, { strict: true, placeholders: true, single: false }); |
| 80 | case 'deepseek': | 80 | case 'deepseek': |
| 81 | return addAssistantPrefix(mergeMessages(messages, names, true, false)); | 81 | return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: false, single: false })); |
| 82 | case 'deepseek-reasoner': | 82 | case 'deepseek-reasoner': |
| 83 | return addAssistantPrefix(mergeMessages(messages, names, true, true)); | 83 | return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: true, single: false })); |
| 84 | case 'single': | ||
| 85 | return mergeMessages(messages, names, { strict: true, placeholders: false, single: true }); | ||
| 84 | default: | 86 | default: |
| 85 | return messages; | 87 | return messages; |
| 86 | } | 88 | } |
| @@ -383,7 +385,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 383 | 385 | ||
| 384 | function getGeminiBody() { | 386 | function getGeminiBody() { |
| 385 | // #region UGLY MODEL LISTS AREA | 387 | // #region UGLY MODEL LISTS AREA |
| 386 | const imageGenerationModels = [ | 388 | const imageGenerationModels = [ |
| 387 | 'gemini-2.0-flash-exp', | 389 | 'gemini-2.0-flash-exp', |
| 388 | 'gemini-2.0-flash-exp-image-generation', | 390 | 'gemini-2.0-flash-exp-image-generation', |
| 389 | ]; | 391 | ]; |
| @@ -1206,6 +1208,15 @@ router.post('/bias', async function (request, response) { | |||
| 1206 | router.post('/generate', function (request, response) { | 1208 | router.post('/generate', function (request, response) { |
| 1207 | if (!request.body) return response.status(400).send({ error: true }); | 1209 | if (!request.body) return response.status(400).send({ error: true }); |
| 1208 | 1210 | ||
| 1211 | const postProcessingType = request.body.custom_prompt_post_processing; | ||
| 1212 | if (Array.isArray(request.body.messages) && postProcessingType) { | ||
| 1213 | console.info('Applying custom prompt post-processing of type', postProcessingType); | ||
| 1214 | request.body.messages = postProcessPrompt( | ||
| 1215 | request.body.messages, | ||
| 1216 | postProcessingType, | ||
| 1217 | getPromptNames(request)); | ||
| 1218 | } | ||
| 1219 | |||
| 1209 | switch (request.body.chat_completion_source) { | 1220 | switch (request.body.chat_completion_source) { |
| 1210 | case CHAT_COMPLETION_SOURCES.CLAUDE: return sendClaudeRequest(request, response); | 1221 | case CHAT_COMPLETION_SOURCES.CLAUDE: return sendClaudeRequest(request, response); |
| 1211 | case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response); | 1222 | case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response); |
| @@ -1224,15 +1235,6 @@ router.post('/generate', function (request, response) { | |||
| 1224 | let bodyParams; | 1235 | let bodyParams; |
| 1225 | const isTextCompletion = Boolean(request.body.model && TEXT_COMPLETION_MODELS.includes(request.body.model)) || typeof request.body.messages === 'string'; | 1236 | const isTextCompletion = Boolean(request.body.model && TEXT_COMPLETION_MODELS.includes(request.body.model)) || typeof request.body.messages === 'string'; |
| 1226 | 1237 | ||
| 1227 | const postProcessTypes = [CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENROUTER]; | ||
| 1228 | if (Array.isArray(request.body.messages) && postProcessTypes.includes(request.body.chat_completion_source) && request.body.custom_prompt_post_processing) { | ||
| 1229 | console.info('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing); | ||
| 1230 | request.body.messages = postProcessPrompt( | ||
| 1231 | request.body.messages, | ||
| 1232 | request.body.custom_prompt_post_processing, | ||
| 1233 | getPromptNames(request)); | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) { | 1238 | if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) { |
| 1237 | apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString(); | 1239 | apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString(); |
| 1238 | apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI); | 1240 | apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI); |
| @@ -695,11 +695,13 @@ export function convertXAIMessages(messages, names) { | |||
| 695 | * Merge messages with the same consecutive role, removing names if they exist. | 695 | * Merge messages with the same consecutive role, removing names if they exist. |
| 696 | * @param {any[]} messages Messages to merge | 696 | * @param {any[]} messages Messages to merge |
| 697 | * @param {PromptNames} names Prompt names | 697 | * @param {PromptNames} names Prompt names |
| 698 | * @param {boolean} strict Enable strict mode: only allow one system message at the start, force user first message | 698 | * @param {object} options Options for merging |
| 699 | * @param {boolean} placeholders Add user placeholders to the messages in strict mode | 699 | * @param {boolean} [options.strict] Enable strict mode: only allow one system message at the start, force user first message |
| 700 | * @param {boolean} [options.placeholders] Add user placeholders to the messages in strict mode | ||
| 701 | * @param {boolean} [options.single] Force every role to be user, merging all messages into one | ||
| 700 | * @returns {any[]} Merged messages | 702 | * @returns {any[]} Merged messages |
| 701 | */ | 703 | */ |
| 702 | export function mergeMessages(messages, names, strict, placeholders) { | 704 | export function mergeMessages(messages, names, { strict = false, placeholders = false, single = false } = {}) { |
| 703 | let mergedMessages = []; | 705 | let mergedMessages = []; |
| 704 | 706 | ||
| 705 | /** @type {Map<string,object>} */ | 707 | /** @type {Map<string,object>} */ |
| @@ -744,6 +746,20 @@ export function mergeMessages(messages, names, strict, placeholders) { | |||
| 744 | if (message.role === 'tool') { | 746 | if (message.role === 'tool') { |
| 745 | message.role = 'user'; | 747 | message.role = 'user'; |
| 746 | } | 748 | } |
| 749 | if (single) { | ||
| 750 | if (message.role === 'assistant') { | ||
| 751 | if (names.charName && !message.content.startsWith(`${names.charName}: `) && !names.startsWithGroupName(message.content)) { | ||
| 752 | message.content = `${names.charName}: ${message.content}`; | ||
| 753 | } | ||
| 754 | } | ||
| 755 | if (message.role === 'user') { | ||
| 756 | if (names.userName && !message.content.startsWith(`${names.userName}: `)) { | ||
| 757 | message.content = `${names.userName}: ${message.content}`; | ||
| 758 | } | ||
| 759 | } | ||
| 760 | |||
| 761 | message.role = 'user'; | ||
| 762 | } | ||
| 747 | delete message.name; | 763 | delete message.name; |
| 748 | delete message.tool_calls; | 764 | delete message.tool_calls; |
| 749 | delete message.tool_call_id; | 765 | delete message.tool_call_id; |
| @@ -807,7 +823,7 @@ export function mergeMessages(messages, names, strict, placeholders) { | |||
| 807 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); | 823 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); |
| 808 | } | 824 | } |
| 809 | } | 825 | } |
| 810 | return mergeMessages(mergedMessages, names, false, placeholders); | 826 | return mergeMessages(mergedMessages, names, { strict: false, placeholders, single: false }); |
| 811 | } | 827 | } |
| 812 | 828 | ||
| 813 | return mergedMessages; | 829 | return mergedMessages; |