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 | 1991 | <div class="flexBasis100p toggle-description justifyLeft"> |
| 1992 | 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 | 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 | 1995 | </div> |
| 1995 | 1996 | </div> |
| 1996 | 1997 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations"> |
| @@ -3535,13 +3536,21 @@ | ||
| 3535 | 3536 | </span> |
| 3536 | 3537 | </div> |
| 3537 | 3538 | </div> |
| 3538 | 3539 | <div id="prompt_post_porcessing_form" data-source="custom,openrouterprompt_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 | 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 | 3549 | <option data-i18n="prompt_post_processing_none" value="">None</option> |
| 3542 | 3550 | <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option> |
| 3543 | 3551 | <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option> |
| 3544 | 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 | 3554 | </select> |
| 3546 | 3555 | </div> |
| 3547 | 3556 | <div class="flex-container flex"> |
| @@ -203,13 +203,14 @@ const continue_postfix_types = { | ||
| 203 | 203 | DOUBLE_NEWLINE: '\n\n', |
| 204 | 204 | }; |
| 205 | 205 | |
| 206 | 206 | export const custom_prompt_post_processing_types = { |
| 207 | 207 | NONE: '', |
| 208 | 208 | /** @deprecated Use MERGE instead. */ |
| 209 | 209 | CLAUDE: 'claude', |
| 210 | 210 | MERGE: 'merge', |
| 211 | 211 | SEMI: 'semi', |
| 212 | 212 | STRICT: 'strict', |
| 213 | + SINGLE: 'single', | |
| 213 | 214 | }; |
| 214 | 215 | |
| 215 | 216 | const openrouter_middleout_types = { |
| @@ -575,6 +575,11 @@ export class ToolManager { | ||
| 575 | 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 | 583 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS && Array.isArray(model_list)) { |
| 579 | 584 | const currentModel = model_list.find(model => model.id === oai_settings.pollinations_model); |
| 580 | 585 | if (currentModel) { |
| @@ -72,15 +72,17 @@ function postProcessPrompt(messages, type, names) { | ||
| 72 | 72 | switch (type) { |
| 73 | 73 | case 'merge': |
| 74 | 74 | case 'claude': |
| 75 | 75 | return mergeMessages(messages, names, { strict: false, placeholders: false, single: false }); |
| 76 | 76 | case 'semi': |
| 77 | 77 | return mergeMessages(messages, names, { strict: true, placeholders: false, single: false }); |
| 78 | 78 | case 'strict': |
| 79 | 79 | return mergeMessages(messages, names, { strict: true, placeholders: true, single: false }); |
| 80 | 80 | case 'deepseek': |
| 81 | 81 | return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: false, single: false })); |
| 82 | 82 | case 'deepseek-reasoner': |
| 83 | 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 | 86 | default: |
| 85 | 87 | return messages; |
| 86 | 88 | } |
| @@ -1206,6 +1208,15 @@ router.post('/bias', async function (request, response) { | ||
| 1206 | 1208 | router.post('/generate', function (request, response) { |
| 1207 | 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 | 1220 | switch (request.body.chat_completion_source) { |
| 1210 | 1221 | case CHAT_COMPLETION_SOURCES.CLAUDE: return sendClaudeRequest(request, response); |
| 1211 | 1222 | case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response); |
| @@ -1224,15 +1235,6 @@ router.post('/generate', function (request, response) { | ||
| 1224 | 1235 | let bodyParams; |
| 1225 | 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 | 1238 | if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) { |
| 1237 | 1239 | apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString(); |
| 1238 | 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 | 695 | * Merge messages with the same consecutive role, removing names if they exist. |
| 696 | 696 | * @param {any[]} messages Messages to merge |
| 697 | 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 | 699 | * @param {boolean} placeholders[options.strict] AddEnable userstrict placeholdersmode: toonly allow one system message at the messagesstart, inforce strictuser modefirst 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 | 702 | * @returns {any[]} Merged messages |
| 701 | 703 | */ |
| 702 | 704 | export function mergeMessages(messages, names, { strict = false, placeholders = false, single = false } = {}) { |
| 703 | 705 | let mergedMessages = []; |
| 704 | 706 | |
| 705 | 707 | /** @type {Map<string,object>} */ |
| @@ -744,6 +746,20 @@ export function mergeMessages(messages, names, strict, placeholders) { | ||
| 744 | 746 | if (message.role === 'tool') { |
| 745 | 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 | 763 | delete message.name; |
| 748 | 764 | delete message.tool_calls; |
| 749 | 765 | delete message.tool_call_id; |
| @@ -807,7 +823,7 @@ export function mergeMessages(messages, names, strict, placeholders) { | ||
| 807 | 823 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); |
| 808 | 824 | } |
| 809 | 825 | } |
| 810 | 826 | return mergeMessages(mergedMessages, names, { strict: false, placeholders, single: false }); |
| 811 | 827 | } |
| 812 | 828 | |
| 813 | 829 | return mergedMessages; |