| @@ -1991,7 +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 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong> |
| 1995 | </div> | 1995 | </div> |
| 1996 | </div> | 1996 | </div> |
| 1997 | <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"> |
| @@ -3661,10 +3661,13 @@ | |||
| 3661 | </h4> | 3661 | </h4> |
| 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."> | 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 | <option data-i18n="prompt_post_processing_none" value="">None</option> | 3663 | <option data-i18n="prompt_post_processing_none" value="">None</option> |
| 3664 | <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option> | 3664 | <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles (no tools)</option> |
| 3665 | <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option> | 3665 | <option data-i18n="prompt_post_processing_merge_tools" value="merge_tools">Merge consecutive roles (with tools)</option> |
| 3666 | <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option> | 3666 | <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles; no tools)</option> |
| 3667 | <option data-i18n="prompt_post_processing_single" value="single">Single user message</option> | 3667 | <option data-i18n="prompt_post_processing_semi_tools" value="semi_tools">Semi-strict (alternating roles; 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 | </select> | 3671 | </select> |
| 3669 | </div> | 3672 | </div> |
| 3670 | <div class="flex-container flex"> | 3673 | <div class="flex-container flex"> |
| @@ -207,8 +207,11 @@ export const custom_prompt_post_processing_types = { | |||
| 207 | /** @deprecated Use MERGE instead. */ | 207 | /** @deprecated Use MERGE instead. */ |
| 208 | CLAUDE: 'claude', | 208 | CLAUDE: 'claude', |
| 209 | MERGE: 'merge', | 209 | MERGE: 'merge', |
| 210 | MERGE_TOOLS: 'merge_tools', | ||
| 210 | SEMI: 'semi', | 211 | SEMI: 'semi', |
| 212 | SEMI_TOOLS: 'semi_tools', | ||
| 211 | STRICT: 'strict', | 213 | STRICT: 'strict', |
| 214 | STRICT_TOOLS: 'strict_tools', | ||
| 212 | SINGLE: 'single', | 215 | SINGLE: 'single', |
| 213 | }; | 216 | }; |
| 214 | 217 | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | import { DOMPurify } from '../lib.js'; | 1 | import { DOMPurify } from '../lib.js'; |
| 2 | 2 | ||
| 3 | import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js'; | 3 | import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js'; |
| 4 | import { chat_completion_sources, model_list, oai_settings } from './openai.js'; | 4 | import { chat_completion_sources, custom_prompt_post_processing_types, model_list, oai_settings } from './openai.js'; |
| 5 | import { Popup } from './popup.js'; | 5 | import { Popup } from './popup.js'; |
| 6 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 6 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 7 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | 7 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| @@ -592,7 +592,9 @@ export class ToolManager { | |||
| 592 | } | 592 | } |
| 593 | 593 | ||
| 594 | // Post-processing will forcefully remove past tool calls from the prompt, making them useless | 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 | return false; | 598 | return false; |
| 597 | } | 599 | } |
| 598 | 600 | ||
| @@ -26,12 +26,13 @@ import { | |||
| 26 | convertMistralMessages, | 26 | convertMistralMessages, |
| 27 | convertAI21Messages, | 27 | convertAI21Messages, |
| 28 | convertXAIMessages, | 28 | convertXAIMessages, |
| 29 | mergeMessages, | ||
| 30 | cachingAtDepthForOpenRouterClaude, | 29 | cachingAtDepthForOpenRouterClaude, |
| 31 | cachingAtDepthForClaude, | 30 | cachingAtDepthForClaude, |
| 32 | getPromptNames, | 31 | getPromptNames, |
| 33 | calculateClaudeBudgetTokens, | 32 | calculateClaudeBudgetTokens, |
| 34 | calculateGoogleBudgetTokens, | 33 | calculateGoogleBudgetTokens, |
| 34 | postProcessPrompt, | ||
| 35 | PROMPT_PROCESSING_TYPE, | ||
| 35 | } from '../../prompt-converters.js'; | 36 | } from '../../prompt-converters.js'; |
| 36 | 37 | ||
| 37 | import { readSecret, SECRET_KEYS } from '../secrets.js'; | 38 | import { readSecret, SECRET_KEYS } from '../secrets.js'; |
| @@ -63,34 +64,6 @@ const API_XAI = 'https://api.x.ai/v1'; | |||
| 63 | const API_POLLINATIONS = 'https://text.pollinations.ai/openai'; | 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 | * Gets OpenRouter transforms based on the request. | 67 | * Gets OpenRouter transforms based on the request. |
| 95 | * @param {import('express').Request} request Express request | 68 | * @param {import('express').Request} request Express request |
| 96 | * @returns {string[] | undefined} OpenRouter transforms | 69 | * @returns {string[] | undefined} OpenRouter transforms |
| @@ -893,7 +866,9 @@ async function sendDeepSeekRequest(request, response) { | |||
| 893 | }); | 866 | }); |
| 894 | } | 867 | } |
| 895 | 868 | ||
| 896 | const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; | 869 | const postProcessType = String(request.body.model).endsWith('-reasoner') |
| 870 | ? PROMPT_PROCESSING_TYPE.DEEPSEEK_REASONER | ||
| 871 | : PROMPT_PROCESSING_TYPE.DEEPSEEK; | ||
| 897 | const processedMessages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); | 872 | const processedMessages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); |
| 898 | 873 | ||
| 899 | const requestBody = { | 874 | const requestBody = { |
| @@ -1404,7 +1379,7 @@ router.post('/generate', function (request, response) { | |||
| 1404 | apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY); | 1379 | apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY); |
| 1405 | headers = {}; | 1380 | headers = {}; |
| 1406 | bodyParams = {}; | 1381 | bodyParams = {}; |
| 1407 | request.body.messages = postProcessPrompt(request.body.messages, 'strict', getPromptNames(request)); | 1382 | request.body.messages = postProcessPrompt(request.body.messages, PROMPT_PROCESSING_TYPE.STRICT, getPromptNames(request)); |
| 1408 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { | 1383 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { |
| 1409 | apiUrl = API_GROQ; | 1384 | apiUrl = API_GROQ; |
| 1410 | apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); | 1385 | apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); |
| @@ -12,6 +12,21 @@ const REASONING_EFFORT = { | |||
| 12 | max: 'max', | 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 | * @typedef {object} PromptNames | 31 | * @typedef {object} PromptNames |
| 17 | * @property {string} charName Character name | 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 | * Convert a prompt from the ChatML objects to the format used by Claude. | 104 | * Convert a prompt from the ChatML objects to the format used by Claude. |
| 41 | * Mainly deprecated. Only used for counting tokens. | 105 | * Mainly deprecated. Only used for counting tokens. |
| 42 | * @param {object[]} messages Array of messages | 106 | * @param {object[]} messages Array of messages |
| @@ -712,9 +776,10 @@ export function convertXAIMessages(messages, names) { | |||
| 712 | * @param {boolean} [options.strict] Enable strict mode: only allow one system message at the start, force user first message | 776 | * @param {boolean} [options.strict] Enable strict mode: only allow one system message at the start, force user first message |
| 713 | * @param {boolean} [options.placeholders] Add user placeholders to the messages in strict mode | 777 | * @param {boolean} [options.placeholders] Add user placeholders to the messages in strict mode |
| 714 | * @param {boolean} [options.single] Force every role to be user, merging all messages into one | 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 | * @returns {any[]} Merged messages | 780 | * @returns {any[]} Merged messages |
| 716 | */ | 781 | */ |
| 717 | export function mergeMessages(messages, names, { strict = false, placeholders = false, single = false } = {}) { | 782 | export function mergeMessages(messages, names, { strict = false, placeholders = false, single = false, tools = false } = {}) { |
| 718 | let mergedMessages = []; | 783 | let mergedMessages = []; |
| 719 | 784 | ||
| 720 | /** @type {Map<string,object>} */ | 785 | /** @type {Map<string,object>} */ |
| @@ -756,7 +821,7 @@ export function mergeMessages(messages, names, { strict = false, placeholders = | |||
| 756 | message.content = `${message.name}: ${message.content}`; | 821 | message.content = `${message.name}: ${message.content}`; |
| 757 | } | 822 | } |
| 758 | } | 823 | } |
| 759 | if (message.role === 'tool') { | 824 | if (message.role === 'tool' && !tools) { |
| 760 | message.role = 'user'; | 825 | message.role = 'user'; |
| 761 | } | 826 | } |
| 762 | if (single) { | 827 | if (single) { |
| @@ -774,13 +839,15 @@ export function mergeMessages(messages, names, { strict = false, placeholders = | |||
| 774 | message.role = 'user'; | 839 | message.role = 'user'; |
| 775 | } | 840 | } |
| 776 | delete message.name; | 841 | delete message.name; |
| 842 | if (!tools) { | ||
| 777 | delete message.tool_calls; | 843 | delete message.tool_calls; |
| 778 | delete message.tool_call_id; | 844 | delete message.tool_call_id; |
| 845 | } | ||
| 779 | }); | 846 | }); |
| 780 | 847 | ||
| 781 | // Squash consecutive messages with the same role | 848 | // Squash consecutive messages with the same role |
| 782 | messages.forEach((message) => { | 849 | messages.forEach((message) => { |
| 783 | if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role && message.content) { | 850 | if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role && message.content && message.role !== 'tool') { |
| 784 | mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content; | 851 | mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content; |
| 785 | } else { | 852 | } else { |
| 786 | mergedMessages.push(message); | 853 | mergedMessages.push(message); |
| @@ -836,7 +903,7 @@ export function mergeMessages(messages, names, { strict = false, placeholders = | |||
| 836 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); | 903 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); |
| 837 | } | 904 | } |
| 838 | } | 905 | } |
| 839 | return mergeMessages(mergedMessages, names, { strict: false, placeholders, single: false }); | 906 | return mergeMessages(mergedMessages, names, { strict: false, placeholders, single: false, tools }); |
| 840 | } | 907 | } |
| 841 | 908 | ||
| 842 | return mergedMessages; | 909 | return mergedMessages; |