Split Custom OAI prompt post-processing modes
| @@ -121,6 +121,8 @@ extras: | |||
| 121 | speechToTextModel: Xenova/whisper-small | 121 | speechToTextModel: Xenova/whisper-small |
| 122 | textToSpeechModel: Xenova/speecht5_tts | 122 | textToSpeechModel: Xenova/speecht5_tts |
| 123 | # -- OPENAI CONFIGURATION -- | 123 | # -- OPENAI CONFIGURATION -- |
| 124 | # A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message | ||
| 125 | promptPlaceholder: "[Start a new chat]" | ||
| 124 | openai: | 126 | openai: |
| 125 | # Will send a random user ID to OpenAI completion API | 127 | # Will send a random user ID to OpenAI completion API |
| 126 | randomizeUserId: false | 128 | randomizeUserId: false |
| @@ -3102,7 +3102,8 @@ | |||
| 3102 | <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4> | 3102 | <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4> |
| 3103 | <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."> | 3103 | <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."> |
| 3104 | <option data-i18n="prompt_post_processing_none" value="">None</option> | 3104 | <option data-i18n="prompt_post_processing_none" value="">None</option> |
| 3105 | <option value="claude">Claude</option> | 3105 | <option value="merge">Merge consecutive roles</option> |
| 3106 | <option value="strict">Strict (user first, alternating roles)</option> | ||
| 3106 | </select> | 3107 | </select> |
| 3107 | </form> | 3108 | </form> |
| 3108 | <div id="01ai_form" data-source="01ai"> | 3109 | <div id="01ai_form" data-source="01ai"> |
| @@ -199,7 +199,10 @@ const continue_postfix_types = { | |||
| 199 | 199 | ||
| 200 | const custom_prompt_post_processing_types = { | 200 | const custom_prompt_post_processing_types = { |
| 201 | NONE: '', | 201 | NONE: '', |
| 202 | /** @deprecated Use MERGE instead. */ | ||
| 202 | CLAUDE: 'claude', | 203 | CLAUDE: 'claude', |
| 204 | MERGE: 'merge', | ||
| 205 | STRICT: 'strict', | ||
| 203 | }; | 206 | }; |
| 204 | 207 | ||
| 205 | const sensitiveFields = [ | 208 | const sensitiveFields = [ |
| @@ -3043,6 +3046,10 @@ function loadOpenAISettings(data, settings) { | |||
| 3043 | setNamesBehaviorControls(); | 3046 | setNamesBehaviorControls(); |
| 3044 | setContinuePostfixControls(); | 3047 | setContinuePostfixControls(); |
| 3045 | 3048 | ||
| 3049 | if (oai_settings.custom_prompt_post_processing === custom_prompt_post_processing_types.CLAUDE) { | ||
| 3050 | oai_settings.custom_prompt_post_processing = custom_prompt_post_processing_types.MERGE; | ||
| 3051 | } | ||
| 3052 | |||
| 3046 | $('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change'); | 3053 | $('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change'); |
| 3047 | $('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked); | 3054 | $('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked); |
| 3048 | $('#custom_prompt_post_processing').val(oai_settings.custom_prompt_post_processing); | 3055 | $('#custom_prompt_post_processing').val(oai_settings.custom_prompt_post_processing); |
| @@ -4,7 +4,7 @@ const fetch = require('node-fetch').default; | |||
| 4 | const { jsonParser } = require('../../express-common'); | 4 | const { jsonParser } = require('../../express-common'); |
| 5 | const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants'); | 5 | const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants'); |
| 6 | const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util'); | 6 | const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util'); |
| 7 | const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages } = require('../../prompt-converters'); | 7 | const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages, mergeMessages } = require('../../prompt-converters'); |
| 8 | const CohereStream = require('../../cohere-stream'); | 8 | const CohereStream = require('../../cohere-stream'); |
| 9 | 9 | ||
| 10 | const { readSecret, SECRET_KEYS } = require('../secrets'); | 10 | const { readSecret, SECRET_KEYS } = require('../secrets'); |
| @@ -31,8 +31,11 @@ const API_AI21 = 'https://api.ai21.com/studio/v1'; | |||
| 31 | */ | 31 | */ |
| 32 | function postProcessPrompt(messages, type, charName, userName) { | 32 | function postProcessPrompt(messages, type, charName, userName) { |
| 33 | switch (type) { | 33 | switch (type) { |
| 34 | case 'merge': | ||
| 34 | case 'claude': | 35 | case 'claude': |
| 35 | return convertClaudeMessages(messages, '', false, '', charName, userName).messages; | 36 | return mergeMessages(messages, charName, userName, false); |
| 37 | case 'strict': | ||
| 38 | return mergeMessages(messages, charName, userName, true); | ||
| 36 | default: | 39 | default: |
| 37 | return messages; | 40 | return messages; |
| 38 | } | 41 | } |
| @@ -902,7 +905,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 902 | apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY); | 905 | apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY); |
| 903 | headers = {}; | 906 | headers = {}; |
| 904 | bodyParams = {}; | 907 | bodyParams = {}; |
| 905 | request.body.messages = postProcessPrompt(request.body.messages, 'claude', request.body.char_name, request.body.user_name); | 908 | request.body.messages = postProcessPrompt(request.body.messages, 'strict', request.body.char_name, request.body.user_name); |
| 906 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { | 909 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { |
| 907 | apiUrl = API_GROQ; | 910 | apiUrl = API_GROQ; |
| 908 | apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); | 911 | apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); |
| @@ -1,6 +1,8 @@ | |||
| 1 | require('./polyfill.js'); | 1 | require('./polyfill.js'); |
| 2 | const { getConfigValue } = require('./util.js'); | 2 | const { getConfigValue } = require('./util.js'); |
| 3 | 3 | ||
| 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); | ||
| 5 | |||
| 4 | /** | 6 | /** |
| 5 | * Convert a prompt from the ChatML objects to the format used by Claude. | 7 | * Convert a prompt from the ChatML objects to the format used by Claude. |
| 6 | * Mainly deprecated. Only used for counting tokens. | 8 | * Mainly deprecated. Only used for counting tokens. |
| @@ -122,7 +124,7 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, humanMsgFi | |||
| 122 | if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) { | 124 | if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) { |
| 123 | messages.unshift({ | 125 | messages.unshift({ |
| 124 | role: 'user', | 126 | role: 'user', |
| 125 | content: humanMsgFix || '[Start a new chat]', | 127 | content: humanMsgFix || PROMPT_PLACEHOLDER, |
| 126 | }); | 128 | }); |
| 127 | } | 129 | } |
| 128 | } | 130 | } |
| @@ -260,7 +262,6 @@ function convertCohereMessages(messages, charName = '', userName = '') { | |||
| 260 | 'user': 'USER', | 262 | 'user': 'USER', |
| 261 | 'assistant': 'CHATBOT', | 263 | 'assistant': 'CHATBOT', |
| 262 | }; | 264 | }; |
| 263 | const placeholder = '[Start a new chat]'; | ||
| 264 | let systemPrompt = ''; | 265 | let systemPrompt = ''; |
| 265 | 266 | ||
| 266 | // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array. | 267 | // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array. |
| @@ -288,12 +289,12 @@ function convertCohereMessages(messages, charName = '', userName = '') { | |||
| 288 | if (messages.length === 0) { | 289 | if (messages.length === 0) { |
| 289 | messages.unshift({ | 290 | messages.unshift({ |
| 290 | role: 'user', | 291 | role: 'user', |
| 291 | content: placeholder, | 292 | content: PROMPT_PLACEHOLDER, |
| 292 | }); | 293 | }); |
| 293 | } | 294 | } |
| 294 | 295 | ||
| 295 | const lastNonSystemMessageIndex = messages.findLastIndex(msg => msg.role === 'user' || msg.role === 'assistant'); | 296 | const lastNonSystemMessageIndex = messages.findLastIndex(msg => msg.role === 'user' || msg.role === 'assistant'); |
| 296 | const userPrompt = messages.slice(lastNonSystemMessageIndex).map(msg => msg.content).join('\n\n') || placeholder; | 297 | const userPrompt = messages.slice(lastNonSystemMessageIndex).map(msg => msg.content).join('\n\n') || PROMPT_PLACEHOLDER; |
| 297 | 298 | ||
| 298 | const chatHistory = messages.slice(0, lastNonSystemMessageIndex).map(msg => { | 299 | const chatHistory = messages.slice(0, lastNonSystemMessageIndex).map(msg => { |
| 299 | return { | 300 | return { |
| @@ -469,7 +470,7 @@ function convertAI21Messages(messages, charName = '', userName = '') { | |||
| 469 | if (messages.length === 0) { | 470 | if (messages.length === 0) { |
| 470 | messages.unshift({ | 471 | messages.unshift({ |
| 471 | role: 'user', | 472 | role: 'user', |
| 472 | content: '[Start a new chat]', | 473 | content: PROMPT_PLACEHOLDER, |
| 473 | }); | 474 | }); |
| 474 | } | 475 | } |
| 475 | 476 | ||
| @@ -554,6 +555,83 @@ function convertMistralMessages(messages, charName = '', userName = '') { | |||
| 554 | } | 555 | } |
| 555 | 556 | ||
| 556 | /** | 557 | /** |
| 558 | * Merge messages with the same consecutive role, removing names if they exist. | ||
| 559 | * @param {any[]} messages Messages to merge | ||
| 560 | * @param {string} charName Character name | ||
| 561 | * @param {string} userName User name | ||
| 562 | * @param {boolean} strict Enable strict mode: only allow one system message at the start, force user first message | ||
| 563 | * @returns {any[]} Merged messages | ||
| 564 | */ | ||
| 565 | function mergeMessages(messages, charName, userName, strict) { | ||
| 566 | let mergedMessages = []; | ||
| 567 | |||
| 568 | // Remove names from the messages | ||
| 569 | messages.forEach((message) => { | ||
| 570 | if (!message.content) { | ||
| 571 | message.content = ''; | ||
| 572 | } | ||
| 573 | if (message.role === 'system' && message.name === 'example_assistant') { | ||
| 574 | if (charName && !message.content.startsWith(`${charName}: `)) { | ||
| 575 | message.content = `${charName}: ${message.content}`; | ||
| 576 | } | ||
| 577 | } | ||
| 578 | if (message.role === 'system' && message.name === 'example_user') { | ||
| 579 | if (userName && !message.content.startsWith(`${userName}: `)) { | ||
| 580 | message.content = `${userName}: ${message.content}`; | ||
| 581 | } | ||
| 582 | } | ||
| 583 | if (message.name && message.role !== 'system') { | ||
| 584 | if (!message.content.startsWith(`${message.name}: `)) { | ||
| 585 | message.content = `${message.name}: ${message.content}`; | ||
| 586 | } | ||
| 587 | } | ||
| 588 | if (message.role === 'tool') { | ||
| 589 | message.role = 'user'; | ||
| 590 | } | ||
| 591 | delete message.name; | ||
| 592 | delete message.tool_calls; | ||
| 593 | delete message.tool_call_id; | ||
| 594 | }); | ||
| 595 | |||
| 596 | // Squash consecutive messages with the same role | ||
| 597 | messages.forEach((message) => { | ||
| 598 | if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role && message.content) { | ||
| 599 | mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content; | ||
| 600 | } else { | ||
| 601 | mergedMessages.push(message); | ||
| 602 | } | ||
| 603 | }); | ||
| 604 | |||
| 605 | // Prevent erroring out if the messages array is empty. | ||
| 606 | if (messages.length === 0) { | ||
| 607 | messages.unshift({ | ||
| 608 | role: 'user', | ||
| 609 | content: PROMPT_PLACEHOLDER, | ||
| 610 | }); | ||
| 611 | } | ||
| 612 | |||
| 613 | if (strict) { | ||
| 614 | for (let i = 0; i < mergedMessages.length; i++) { | ||
| 615 | // Force mid-prompt system messages to be user messages | ||
| 616 | if (i > 0 && mergedMessages[i].role === 'system') { | ||
| 617 | mergedMessages[i].role = 'user'; | ||
| 618 | } | ||
| 619 | } | ||
| 620 | if (mergedMessages.length) { | ||
| 621 | if (mergedMessages[0].role === 'system' && (mergedMessages.length === 1 || mergedMessages[1].role !== 'user')) { | ||
| 622 | mergedMessages.splice(1, 0, { role: 'user', content: PROMPT_PLACEHOLDER }); | ||
| 623 | } | ||
| 624 | else if (mergedMessages[0].role !== 'system' && mergedMessages[0].role !== 'user') { | ||
| 625 | mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER }); | ||
| 626 | } | ||
| 627 | } | ||
| 628 | return mergeMessages(mergedMessages, charName, userName, false); | ||
| 629 | } | ||
| 630 | |||
| 631 | return mergedMessages; | ||
| 632 | } | ||
| 633 | |||
| 634 | /** | ||
| 557 | * Convert a prompt from the ChatML objects to the format used by Text Completion API. | 635 | * Convert a prompt from the ChatML objects to the format used by Text Completion API. |
| 558 | * @param {object[]} messages Array of messages | 636 | * @param {object[]} messages Array of messages |
| 559 | * @returns {string} Prompt for Text Completion API | 637 | * @returns {string} Prompt for Text Completion API |
| @@ -586,4 +664,5 @@ module.exports = { | |||
| 586 | convertCohereMessages, | 664 | convertCohereMessages, |
| 587 | convertMistralMessages, | 665 | convertMistralMessages, |
| 588 | convertAI21Messages, | 666 | convertAI21Messages, |
| 667 | mergeMessages, | ||
| 589 | }; | 668 | }; |