| @@ -1991,7 +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 | 1994 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong> |
| 1995 | 1995 | </div> |
| 1996 | 1996 | </div> |
| 1997 | 1997 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations"> |
| @@ -3661,10 +3661,13 @@ | ||
| 3661 | 3661 | </h4> |
| 3662 | 3662 | <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."> |
| 3663 | 3663 | <option data-i18n="prompt_post_processing_none" value="">None</option> |
| 3664 | 3664 | <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles (no tools)</option> |
| 3665 | 3665 | <option data-i18n="prompt_post_processing_semiprompt_post_processing_merge_tools" value="semimerge_tools">Semi-strictMerge (alternatingconsecutive roles (with tools)</option> |
| 3666 | 3666 | <option data-i18n="prompt_post_processing_strictprompt_post_processing_semi" value="strictsemi">StrictSemi-strict (user first, alternating roles; no tools)</option> |
| 3667 | 3667 | <option data-i18n="prompt_post_processing_singleprompt_post_processing_semi_tools" value="singlesemi_tools">SingleSemi-strict user(alternating messageroles; with tools)</option> |
| 3668 | + <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles; no tools)</option> | |
| 3669 | + <option data-i18n="prompt_post_processing_strict_tools" value="strict_tools">Strict (user first, alternating roles; with tools)</option> | |
| 3670 | + <option data-i18n="prompt_post_processing_single" value="single">Single user message (no tools)</option> | |
| 3668 | 3671 | </select> |
| 3669 | 3672 | </div> |
| 3670 | 3673 | <div class="flex-container flex"> |
| @@ -207,8 +207,11 @@ export const custom_prompt_post_processing_types = { | ||
| 207 | 207 | /** @deprecated Use MERGE instead. */ |
| 208 | 208 | CLAUDE: 'claude', |
| 209 | 209 | MERGE: 'merge', |
| 210 | + MERGE_TOOLS: 'merge_tools', | |
| 210 | 211 | SEMI: 'semi', |
| 212 | + SEMI_TOOLS: 'semi_tools', | |
| 211 | 213 | STRICT: 'strict', |
| 214 | + STRICT_TOOLS: 'strict_tools', | |
| 212 | 215 | SINGLE: 'single', |
| 213 | 216 | }; |
| 214 | 217 | |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import { DOMPurify } from '../lib.js'; |
| 2 | 2 | |
| 3 | 3 | import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js'; |
| 4 | 4 | import { chat_completion_sources, custom_prompt_post_processing_types, model_list, oai_settings } from './openai.js'; |
| 5 | 5 | import { Popup } from './popup.js'; |
| 6 | 6 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 7 | 7 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| @@ -592,7 +592,9 @@ export class ToolManager { | ||
| 592 | 592 | } |
| 593 | 593 | |
| 594 | 594 | // Post-processing will forcefully remove past tool calls from the prompt, making them useless |
| 595 | - if (oai_settings.custom_prompt_post_processing) { | |
| 595 | + const { NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS } = custom_prompt_post_processing_types; | |
| 596 | + const allowedPromptPostProcessing = [NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS]; | |
| 597 | + if (!allowedPromptPostProcessing.includes(oai_settings.custom_prompt_post_processing)) { | |
| 596 | 598 | return false; |
| 597 | 599 | } |
| 598 | 600 | |
| @@ -26,12 +26,13 @@ import { | ||
| 26 | 26 | convertMistralMessages, |
| 27 | 27 | convertAI21Messages, |
| 28 | 28 | convertXAIMessages, |
| 29 | - mergeMessages, | |
| 30 | 29 | cachingAtDepthForOpenRouterClaude, |
| 31 | 30 | cachingAtDepthForClaude, |
| 32 | 31 | getPromptNames, |
| 33 | 32 | calculateClaudeBudgetTokens, |
| 34 | 33 | calculateGoogleBudgetTokens, |
| 34 | + postProcessPrompt, | |
| 35 | + PROMPT_PROCESSING_TYPE, | |
| 35 | 36 | } from '../../prompt-converters.js'; |
| 36 | 37 | |
| 37 | 38 | import { readSecret, SECRET_KEYS } from '../secrets.js'; |
| @@ -63,34 +64,6 @@ const API_XAI = 'https://api.x.ai/v1'; | ||
| 63 | 64 | const API_POLLINATIONS = 'https://text.pollinations.ai/openai'; |
| 64 | 65 | |
| 65 | 66 | /** |
| 66 | - * Applies a post-processing step to the generated messages. | |
| 67 | - * @param {object[]} messages Messages to post-process | |
| 68 | - * @param {string} type Prompt conversion type | |
| 69 | - * @param {import('../../prompt-converters.js').PromptNames} names Prompt names | |
| 70 | - * @returns | |
| 71 | - */ | |
| 72 | -function postProcessPrompt(messages, type, names) { | |
| 73 | - const addAssistantPrefix = x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x; | |
| 74 | - switch (type) { | |
| 75 | - case 'merge': | |
| 76 | - case 'claude': | |
| 77 | - return mergeMessages(messages, names, { strict: false, placeholders: false, single: false }); | |
| 78 | - case 'semi': | |
| 79 | - return mergeMessages(messages, names, { strict: true, placeholders: false, single: false }); | |
| 80 | - case 'strict': | |
| 81 | - return mergeMessages(messages, names, { strict: true, placeholders: true, single: false }); | |
| 82 | - case 'deepseek': | |
| 83 | - return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: false, single: false })); | |
| 84 | - case 'deepseek-reasoner': | |
| 85 | - return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: true, single: false })); | |
| 86 | - case 'single': | |
| 87 | - return mergeMessages(messages, names, { strict: true, placeholders: false, single: true }); | |
| 88 | - default: | |
| 89 | - return messages; | |
| 90 | - } | |
| 91 | -} | |
| 92 | - | |
| 93 | -/** | |
| 94 | 67 | * Gets OpenRouter transforms based on the request. |
| 95 | 68 | * @param {import('express').Request} request Express request |
| 96 | 69 | * @returns {string[] | undefined} OpenRouter transforms |
| @@ -893,7 +866,9 @@ async function sendDeepSeekRequest(request, response) { | ||
| 893 | 866 | }); |
| 894 | 867 | } |
| 895 | 868 | |
| 896 | 869 | const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; |
| 870 | + ? PROMPT_PROCESSING_TYPE.DEEPSEEK_REASONER | |
| 871 | + : PROMPT_PROCESSING_TYPE.DEEPSEEK; | |
| 897 | 872 | const processedMessages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); |
| 898 | 873 | |
| 899 | 874 | const requestBody = { |
| @@ -1404,7 +1379,7 @@ router.post('/generate', function (request, response) { | ||
| 1404 | 1379 | apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY); |
| 1405 | 1380 | headers = {}; |
| 1406 | 1381 | bodyParams = {}; |
| 1407 | 1382 | request.body.messages = postProcessPrompt(request.body.messages, 'strict'PROMPT_PROCESSING_TYPE.STRICT, getPromptNames(request)); |
| 1408 | 1383 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { |
| 1409 | 1384 | apiUrl = API_GROQ; |
| 1410 | 1385 | apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); |
| @@ -12,6 +12,21 @@ const REASONING_EFFORT = { | ||
| 12 | 12 | max: 'max', |
| 13 | 13 | }; |
| 14 | 14 | |
| 15 | +export const PROMPT_PROCESSING_TYPE = { | |
| 16 | + NONE: '', | |
| 17 | + /** @deprecated Use MERGE instead. */ | |
| 18 | + CLAUDE: 'claude', | |
| 19 | + MERGE: 'merge', | |
| 20 | + MERGE_TOOLS: 'merge_tools', | |
| 21 | + SEMI: 'semi', | |
| 22 | + SEMI_TOOLS: 'semi_tools', | |
| 23 | + STRICT: 'strict', | |
| 24 | + STRICT_TOOLS: 'strict_tools', | |
| 25 | + SINGLE: 'single', | |
| 26 | + DEEPSEEK: 'deepseek', | |
| 27 | + DEEPSEEK_REASONER: 'deepseek-reasoner', | |
| 28 | +}; | |
| 29 | + | |
| 15 | 30 | /** |
| 16 | 31 | * @typedef {object} PromptNames |
| 17 | 32 | * @property {string} charName Character name |
| @@ -37,6 +52,55 @@ export function getPromptNames(request) { | ||
| 37 | 52 | } |
| 38 | 53 | |
| 39 | 54 | /** |
| 55 | + * Adds an assistant prefix to the last message. | |
| 56 | + * @param {any[]} prompt Prompt messages array | |
| 57 | + * @returns {any[]} Transformed messages array | |
| 58 | + */ | |
| 59 | +function addAssistantPrefix(prompt) { | |
| 60 | + if (!prompt.length) { | |
| 61 | + return prompt; | |
| 62 | + } | |
| 63 | + const hasAnyToolMessages = prompt.some(x => x.role === 'tool'); | |
| 64 | + if (!hasAnyToolMessages && prompt[prompt.length - 1].role === 'assistant') { | |
| 65 | + prompt[prompt.length - 1].prefix = true; | |
| 66 | + } | |
| 67 | + return prompt; | |
| 68 | +} | |
| 69 | + | |
| 70 | +/** | |
| 71 | + * Applies a post-processing step to the generated messages. | |
| 72 | + * @param {object[]} messages Messages to post-process | |
| 73 | + * @param {string} type Prompt conversion type | |
| 74 | + * @param {PromptNames} names Prompt names | |
| 75 | + * @returns | |
| 76 | + */ | |
| 77 | +export function postProcessPrompt(messages, type, names) { | |
| 78 | + switch (type) { | |
| 79 | + case PROMPT_PROCESSING_TYPE.MERGE: | |
| 80 | + case PROMPT_PROCESSING_TYPE.CLAUDE: | |
| 81 | + return mergeMessages(messages, names, { strict: false, placeholders: false, single: false, tools: false }); | |
| 82 | + case PROMPT_PROCESSING_TYPE.MERGE_TOOLS: | |
| 83 | + return mergeMessages(messages, names, { strict: false, placeholders: false, single: false, tools: true }); | |
| 84 | + case PROMPT_PROCESSING_TYPE.SEMI: | |
| 85 | + return mergeMessages(messages, names, { strict: true, placeholders: false, single: false, tools: false }); | |
| 86 | + case PROMPT_PROCESSING_TYPE.SEMI_TOOLS: | |
| 87 | + return mergeMessages(messages, names, { strict: true, placeholders: false, single: false, tools: true }); | |
| 88 | + case PROMPT_PROCESSING_TYPE.STRICT: | |
| 89 | + return mergeMessages(messages, names, { strict: true, placeholders: true, single: false, tools: false }); | |
| 90 | + case PROMPT_PROCESSING_TYPE.STRICT_TOOLS: | |
| 91 | + return mergeMessages(messages, names, { strict: true, placeholders: true, single: false, tools: true }); | |
| 92 | + case PROMPT_PROCESSING_TYPE.DEEPSEEK: | |
| 93 | + return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: false, single: false, tools: true })); | |
| 94 | + case PROMPT_PROCESSING_TYPE.DEEPSEEK_REASONER: | |
| 95 | + return addAssistantPrefix(mergeMessages(messages, names, { strict: true, placeholders: true, single: false, tools: true })); | |
| 96 | + case PROMPT_PROCESSING_TYPE.SINGLE: | |
| 97 | + return mergeMessages(messages, names, { strict: true, placeholders: false, single: true, tools: false }); | |
| 98 | + default: | |
| 99 | + return messages; | |
| 100 | + } | |
| 101 | +} | |
| 102 | + | |
| 103 | +/** | |
| 40 | 104 | * Convert a prompt from the ChatML objects to the format used by Claude. |
| 41 | 105 | * Mainly deprecated. Only used for counting tokens. |
| 42 | 106 | * @param {object[]} messages Array of messages |
| @@ -712,9 +776,10 @@ export function convertXAIMessages(messages, names) { | ||
| 712 | 776 | * @param {boolean} [options.strict] Enable strict mode: only allow one system message at the start, force user first message |
| 713 | 777 | * @param {boolean} [options.placeholders] Add user placeholders to the messages in strict mode |
| 714 | 778 | * @param {boolean} [options.single] Force every role to be user, merging all messages into one |
| 779 | + * @param {boolean} [options.tools] Allow tool calls in the prompt. If false, tool call messages are removed. | |
| 715 | 780 | * @returns {any[]} Merged messages |
| 716 | 781 | */ |
| 717 | 782 | export function mergeMessages(messages, names, { strict = false, placeholders = false, single = false, tools = false } = {}) { |
| 718 | 783 | let mergedMessages = []; |
| 719 | 784 | |
| 720 | 785 | /** @type {Map<string,object>} */ |
| @@ -756,7 +821,7 @@ export function mergeMessages(messages, names, { strict = false, placeholders = | ||
| 756 | 821 | message.content = `${message.name}: ${message.content}`; |
| 757 | 822 | } |
| 758 | 823 | } |
| 759 | 824 | if (message.role === 'tool' && !tools) { |
| 760 | 825 | message.role = 'user'; |
| 761 | 826 | } |
| 762 | 827 | if (single) { |
| @@ -774,13 +839,15 @@ export function mergeMessages(messages, names, { strict = false, placeholders = | ||
| 774 | 839 | message.role = 'user'; |
| 775 | 840 | } |
| 776 | 841 | delete message.name; |
| 777 | - delete message.tool_calls; | |
| 842 | + if (!tools) { | |
| 778 | 843 | delete message.tool_call_idtool_calls; |
| 844 | + delete message.tool_call_id; | |
| 845 | + } | |
| 779 | 846 | }); |
| 780 | 847 | |
| 781 | 848 | // Squash consecutive messages with the same role |
| 782 | 849 | messages.forEach((message) => { |
| 783 | 850 | if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role && message.content && message.role !== 'tool') { |
| 784 | 851 | mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content; |
| 785 | 852 | } else { |
| 786 | 853 | mergedMessages.push(message); |
| @@ -836,7 +903,7 @@ export function mergeMessages(messages, names, { strict = false, placeholders = | ||
| 836 | 903 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); |
| 837 | 904 | } |
| 838 | 905 | } |
| 839 | 906 | return mergeMessages(mergedMessages, names, { strict: false, placeholders, single: false, tools }); |
| 840 | 907 | } |
| 841 | 908 | |
| 842 | 909 | return mergedMessages; |