SillyTavern/src/prompt-converters.js

707 lines
28 KiB
JavaScript
Raw Normal View History

2024-10-10 23:28:17 +02:00
import crypto from 'node:crypto';
2024-10-10 21:37:22 +02:00
import { getConfigValue } from './util.js';
const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.');
/**
* Convert a prompt from the ChatML objects to the format used by Claude.
2024-05-03 22:59:39 +02:00
* Mainly deprecated. Only used for counting tokens.
* @param {object[]} messages Array of messages
* @param {boolean} addAssistantPostfix Add Assistant postfix.
* @param {string} addAssistantPrefill Add Assistant prefill after the assistant postfix.
2023-12-19 18:44:52 +01:00
* @param {boolean} withSysPromptSupport Indicates if the Claude model supports the system prompt format.
* @param {boolean} useSystemPrompt Indicates if the system prompt format should be used.
* @param {boolean} excludePrefixes Exlude Human/Assistant prefixes.
* @param {string} addSysHumanMsg Add Human message between system prompt and assistant.
* @returns {string} Prompt for Claude
* @copyright Prompt Conversion script taken from RisuAI by kwaroran (GPLv3).
*/
2024-10-10 21:37:22 +02:00
export function convertClaudePrompt(messages, addAssistantPostfix, addAssistantPrefill, withSysPromptSupport, useSystemPrompt, addSysHumanMsg, excludePrefixes) {
2023-11-24 21:58:20 +01:00
2023-12-15 19:15:48 +01:00
//Prepare messages for claude.
2023-12-22 23:37:28 +01:00
//When 'Exclude Human/Assistant prefixes' checked, setting messages role to the 'system'(last message is exception).
2023-12-15 12:10:53 +01:00
if (messages.length > 0) {
messages.forEach((m) => {
if (!m.content) {
m.content = '';
}
if (m.tool_calls) {
m.content += JSON.stringify(m.tool_calls);
}
});
if (excludePrefixes) {
2023-12-22 23:25:48 +01:00
messages.slice(0, -1).forEach(message => message.role = 'system');
} else {
messages[0].role = 'system';
}
2023-12-15 19:15:48 +01:00
//Add the assistant's message to the end of messages.
2023-12-15 12:10:53 +01:00
if (addAssistantPostfix) {
messages.push({
2023-12-15 19:15:48 +01:00
role: 'assistant',
2023-12-16 13:12:06 +01:00
content: addAssistantPrefill || '',
2023-12-15 12:10:53 +01:00
});
2023-11-21 21:11:26 +01:00
}
2023-12-15 19:15:48 +01:00
// Find the index of the first message with an assistant role and check for a "'user' role/Human:" before it.
2023-12-15 12:10:53 +01:00
let hasUser = false;
const firstAssistantIndex = messages.findIndex((message, i) => {
2023-12-16 13:12:06 +01:00
if (i >= 0 && (message.role === 'user' || message.content.includes('\n\nHuman: '))) {
2023-12-15 12:10:53 +01:00
hasUser = true;
}
return message.role === 'assistant' && i > 0;
});
2023-12-22 23:25:48 +01:00
// When 2.1+ and 'Use system prompt' checked, switches to the system prompt format by setting the first message's role to the 'system'.
2023-12-16 13:12:06 +01:00
// Inserts the human's message before the first the assistant one, if there are no such message or prefix found.
2023-12-19 18:44:52 +01:00
if (withSysPromptSupport && useSystemPrompt) {
2023-12-15 12:10:53 +01:00
messages[0].role = 'system';
if (firstAssistantIndex > 0 && addSysHumanMsg && !hasUser) {
2023-12-15 12:10:53 +01:00
messages.splice(firstAssistantIndex, 0, {
role: 'user',
content: addSysHumanMsg,
2023-12-15 12:10:53 +01:00
});
}
} else {
2023-12-15 19:15:48 +01:00
// Otherwise, use the default message format by setting the first message's role to 'user'(compatible with all claude models including 2.1.)
2023-12-15 12:10:53 +01:00
messages[0].role = 'user';
2023-12-15 19:58:03 +01:00
// Fix messages order for default message format when(messages > Context Size) by merging two messages with "\n\nHuman: " prefixes into one, before the first Assistant's message.
2023-12-22 23:37:28 +01:00
if (firstAssistantIndex > 0 && !excludePrefixes) {
2023-12-15 19:58:03 +01:00
messages[firstAssistantIndex - 1].role = firstAssistantIndex - 1 !== 0 && messages[firstAssistantIndex - 1].role === 'user' ? 'FixHumMsg' : messages[firstAssistantIndex - 1].role;
2023-12-15 12:10:53 +01:00
}
}
}
2023-12-18 01:32:25 +01:00
// Convert messages to the prompt.
let requestPrompt = messages.map((v, i) => {
2023-12-26 12:45:39 +01:00
// Set prefix according to the role. Also, when "Exclude Human/Assistant prefixes" is checked, names are added via the system prefix.
2023-12-15 19:58:03 +01:00
let prefix = {
2023-12-15 19:15:48 +01:00
'assistant': '\n\nAssistant: ',
'user': '\n\nHuman: ',
2023-12-22 23:25:48 +01:00
'system': i === 0 ? '' : v.name === 'example_assistant' ? '\n\nA: ' : v.name === 'example_user' ? '\n\nH: ' : excludePrefixes && v.name ? `\n\n${v.name}: ` : '\n\n',
2023-12-15 19:58:03 +01:00
'FixHumMsg': '\n\nFirst message: ',
}[v.role] ?? '';
// Claude doesn't support message names, so we'll just add them to the message content.
return `${prefix}${v.name && v.role !== 'system' ? `${v.name}: ` : ''}${v.content}`;
}).join('');
return requestPrompt;
}
2023-12-30 20:04:37 +01:00
/**
* Convert ChatML objects into working with Anthropic's new Messaging API.
* @param {object[]} messages Array of messages
* @param {string} prefillString User determined prefill string
* @param {boolean} useSysPrompt See if we want to use a system prompt
* @param {boolean} useTools See if we want to use tools
* @param {string} humanMsgFix Add Human message between system prompt and assistant.
* @param {string} charName Character name
* @param {string} userName User name
2023-12-30 20:04:37 +01:00
*/
2024-10-10 21:37:22 +02:00
export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') {
let systemPrompt = [];
if (useSysPrompt) {
// Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.
let i;
for (i = 0; i < messages.length; i++) {
if (messages[i].role !== 'system') {
break;
}
// Append example names if not already done by the frontend (e.g. for group chats).
if (userName && messages[i].name === 'example_user') {
if (!messages[i].content.startsWith(`${userName}: `)) {
messages[i].content = `${userName}: ${messages[i].content}`;
}
}
if (charName && messages[i].name === 'example_assistant') {
if (!messages[i].content.startsWith(`${charName}: `)) {
messages[i].content = `${charName}: ${messages[i].content}`;
}
}
systemPrompt.push({ type: 'text', text: messages[i].content });
2023-12-30 20:04:37 +01:00
}
messages.splice(0, i);
2023-12-30 20:04:37 +01:00
// Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.
// Also prevents erroring out if the messages array is empty.
if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) {
messages.unshift({
role: 'user',
content: humanMsgFix || PROMPT_PLACEHOLDER,
});
}
2023-12-30 20:04:37 +01:00
}
// Now replace all further messages that have the role 'system' with the role 'user'. (or all if we're not using one)
const parse = (str) => typeof str === 'string' ? JSON.parse(str) : str;
messages.forEach((message) => {
if (message.role === 'assistant' && message.tool_calls) {
message.content = message.tool_calls.map((tc) => ({
type: 'tool_use',
id: tc.id,
name: tc.function.name,
input: parse(tc.function.arguments),
}));
}
if (message.role === 'tool') {
message.role = 'user';
message.content = [{
type: 'tool_result',
tool_use_id: message.tool_call_id,
content: message.content,
}];
}
2023-12-30 20:04:37 +01:00
if (message.role === 'system') {
if (userName && message.name === 'example_user') {
message.content = `${userName}: ${message.content}`;
}
if (charName && message.name === 'example_assistant') {
message.content = `${charName}: ${message.content}`;
}
2023-12-30 20:04:37 +01:00
message.role = 'user';
// Delete name here so it doesn't get added later
delete message.name;
2023-12-30 20:04:37 +01:00
}
// Convert everything to an array of it would be easier to work with
if (typeof message.content === 'string') {
// Take care of name properties since claude messages don't support them
if (message.name) {
message.content = `${message.name}: ${message.content}`;
}
message.content = [{ type: 'text', text: message.content }];
} else if (Array.isArray(message.content)) {
message.content = message.content.map((content) => {
if (content.type === 'image_url') {
const imageEntry = content?.image_url;
const imageData = imageEntry?.url;
const mimeType = imageData?.split(';')?.[0].split(':')?.[1];
const base64Data = imageData?.split(',')?.[1];
return {
type: 'image',
source: {
type: 'base64',
media_type: mimeType,
data: base64Data,
},
};
}
if (content.type === 'text') {
if (message.name) {
content.text = `${message.name}: ${content.text}`;
}
return content;
}
return content;
});
}
// Remove offending properties
delete message.name;
delete message.tool_calls;
delete message.tool_call_id;
2023-12-30 20:04:37 +01:00
});
// Images in assistant messages should be moved to the next user message
for (let i = 0; i < messages.length; i++) {
if (messages[i].role === 'assistant' && messages[i].content.some(c => c.type === 'image')) {
// Find the next user message
let j = i + 1;
while (j < messages.length && messages[j].role !== 'user') {
j++;
}
// Move the images
if (j >= messages.length) {
// If there is no user message after the assistant message, add a new one
messages.splice(i + 1, 0, { role: 'user', content: [] });
}
messages[j].content.push(...messages[i].content.filter(c => c.type === 'image'));
messages[i].content = messages[i].content.filter(c => c.type !== 'image');
}
}
2024-03-14 13:51:56 +01:00
// Shouldn't be conditional anymore, messages api expects the last role to be user unless we're explicitly prefilling
if (prefillString) {
messages.push({
role: 'assistant',
// Dangling whitespace are not allowed for prefilling
2024-10-04 20:48:35 +02:00
content: [{ type: 'text', text: prefillString.trimEnd() }],
2024-03-14 13:51:56 +01:00
});
}
// Since the messaging endpoint only supports user assistant roles in turns, we have to merge messages with the same role if they follow eachother
2024-03-04 23:41:57 +01:00
// Also handle multi-modality, holy slop.
let mergedMessages = [];
messages.forEach((message) => {
if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role) {
mergedMessages[mergedMessages.length - 1].content.push(...message.content);
} else {
mergedMessages.push(message);
}
});
2024-03-04 20:28:19 +01:00
if (!useTools) {
mergedMessages.forEach((message) => {
message.content.forEach((content) => {
if (content.type === 'tool_use') {
content.type = 'text';
content.text = JSON.stringify(content.input);
delete content.id;
delete content.name;
delete content.input;
}
if (content.type === 'tool_result') {
content.type = 'text';
content.text = content.content;
delete content.tool_use_id;
delete content.content;
}
});
});
}
return { messages: mergedMessages, systemPrompt: systemPrompt };
2023-12-30 20:04:37 +01:00
}
/**
* Convert a prompt from the ChatML objects to the format used by Cohere.
* @param {object[]} messages Array of messages
* @param {string} charName Character name
* @param {string} userName User name
* @returns {{chatHistory: object[]}} Prompt for Cohere
*/
2024-10-10 21:37:22 +02:00
export function convertCohereMessages(messages, charName = '', userName = '') {
if (messages.length === 0) {
messages.unshift({
role: 'user',
content: PROMPT_PLACEHOLDER,
});
}
messages.forEach((msg, index) => {
// Tool calls require an assistent primer
if (Array.isArray(msg.tool_calls)) {
if (index > 0 && messages[index - 1].role === 'assistant') {
msg.content = messages[index - 1].content;
messages.splice(index - 1, 1);
} else {
2024-10-09 09:23:49 +02:00
msg.content = `I'm going to call a tool for that: ${msg.tool_calls.map(tc => tc?.function?.name).join(', ')}`;
}
}
// No names support (who would've thought)
if (msg.name) {
if (msg.role == 'system' && msg.name == 'example_assistant') {
if (charName && !msg.content.startsWith(`${charName}: `)) {
msg.content = `${charName}: ${msg.content}`;
}
}
if (msg.role == 'system' && msg.name == 'example_user') {
if (userName && !msg.content.startsWith(`${userName}: `)) {
msg.content = `${userName}: ${msg.content}`;
}
}
if (msg.role !== 'system' && !msg.content.startsWith(`${msg.name}: `)) {
msg.content = `${msg.name}: ${msg.content}`;
}
delete msg.name;
}
});
// A prompt should end with a user/tool message
2024-10-09 09:23:49 +02:00
if (messages.length && !['user', 'tool'].includes(messages[messages.length - 1].role)) {
messages[messages.length - 1].role = 'user';
}
return { chatHistory: messages };
}
2023-12-14 19:05:27 +01:00
/**
* Convert a prompt from the ChatML objects to the format used by Google MakerSuite models.
* @param {object[]} messages Array of messages
* @param {string} model Model name
2024-04-11 08:38:20 +02:00
* @param {boolean} useSysPrompt Use system prompt
* @param {string} charName Character name
* @param {string} userName User name
2024-04-11 08:38:20 +02:00
* @returns {{contents: *[], system_instruction: {parts: {text: string}}}} Prompt for Google MakerSuite models
2023-12-14 19:05:27 +01:00
*/
2024-10-10 21:37:22 +02:00
export function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '', userName = '') {
// This is a 1x1 transparent PNG
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
2023-12-14 16:28:54 +01:00
2024-03-27 04:52:51 +01:00
const visionSupportedModels = [
2024-08-02 20:18:41 +02:00
'gemini-1.5-flash',
2024-05-21 15:14:21 +02:00
'gemini-1.5-flash-latest',
2024-08-02 20:18:41 +02:00
'gemini-1.5-flash-001',
'gemini-1.5-flash-002',
2024-08-28 01:22:06 +02:00
'gemini-1.5-flash-exp-0827',
'gemini-1.5-flash-8b',
2024-08-28 01:22:06 +02:00
'gemini-1.5-flash-8b-exp-0827',
'gemini-1.5-flash-8b-exp-0924',
2024-08-02 20:18:41 +02:00
'gemini-1.5-pro',
2024-03-27 04:52:51 +01:00
'gemini-1.5-pro-latest',
2024-08-02 20:18:41 +02:00
'gemini-1.5-pro-001',
'gemini-1.5-pro-002',
2024-08-02 20:18:41 +02:00
'gemini-1.5-pro-exp-0801',
2024-08-28 01:22:06 +02:00
'gemini-1.5-pro-exp-0827',
2024-05-21 15:14:21 +02:00
'gemini-1.0-pro-vision-latest',
'gemini-pro-vision',
];
const dummyRequiredModels = [
'gemini-1.0-pro-vision-latest',
2024-03-27 04:52:51 +01:00
'gemini-pro-vision',
];
const isMultimodal = visionSupportedModels.includes(model);
2024-03-27 06:48:26 +01:00
let hasImage = false;
2023-12-14 16:28:54 +01:00
2024-04-11 08:38:20 +02:00
let sys_prompt = '';
if (useSysPrompt) {
while (messages.length > 1 && messages[0].role === 'system') {
// Append example names if not already done by the frontend (e.g. for group chats).
if (userName && messages[0].name === 'example_user') {
if (!messages[0].content.startsWith(`${userName}: `)) {
messages[0].content = `${userName}: ${messages[0].content}`;
}
}
if (charName && messages[0].name === 'example_assistant') {
if (!messages[0].content.startsWith(`${charName}: `)) {
messages[0].content = `${charName}: ${messages[0].content}`;
}
}
2024-04-11 08:38:20 +02:00
sys_prompt += `${messages[0].content}\n\n`;
messages.shift();
}
}
const system_instruction = { parts: { text: sys_prompt.trim() } };
2024-04-11 08:38:20 +02:00
2024-03-27 06:48:26 +01:00
const contents = [];
messages.forEach((message, index) => {
// fix the roles
if (message.role === 'system') {
message.role = 'user';
} else if (message.role === 'assistant') {
message.role = 'model';
}
2023-12-14 16:28:54 +01:00
2024-03-27 06:48:26 +01:00
// similar story as claude
if (message.name) {
if (Array.isArray(message.content)) {
message.content[0].text = `${message.name}: ${message.content[0].text}`;
2023-12-14 16:28:54 +01:00
} else {
2024-03-27 06:48:26 +01:00
message.content = `${message.name}: ${message.content}`;
}
delete message.name;
}
//create the prompt parts
const parts = [];
if (typeof message.content === 'string') {
parts.push({ text: message.content });
} else if (Array.isArray(message.content)) {
message.content.forEach((part) => {
if (part.type === 'text') {
parts.push({ text: part.text });
} else if (part.type === 'image_url' && isMultimodal) {
const mimeType = part.image_url.url.split(';')[0].split(':')[1];
const base64Data = part.image_url.url.split(',')[1];
2024-03-27 06:48:26 +01:00
parts.push({
inlineData: {
mimeType: mimeType,
data: base64Data,
2024-03-27 06:48:26 +01:00
},
2023-12-14 16:28:54 +01:00
});
2024-03-27 06:48:26 +01:00
hasImage = true;
2023-12-14 16:28:54 +01:00
}
2024-03-27 06:48:26 +01:00
});
}
// merge consecutive messages with the same role
if (index > 0 && message.role === contents[contents.length - 1].role) {
contents[contents.length - 1].parts[0].text += '\n\n' + parts[0].text;
} else {
contents.push({
role: message.role,
parts: parts,
});
}
});
// pro 1.5 doesn't require a dummy image to be attached, other vision models do
2024-05-21 15:14:21 +02:00
if (isMultimodal && dummyRequiredModels.includes(model) && !hasImage) {
2024-03-27 06:48:26 +01:00
contents[0].parts.push({
inlineData: {
mimeType: 'image/png',
data: PNG_PIXEL,
},
2023-12-14 16:28:54 +01:00
});
}
2024-04-11 08:38:20 +02:00
return { contents: contents, system_instruction: system_instruction };
}
2024-08-26 11:07:36 +02:00
/**
* Convert AI21 prompt. Classic: system message squash, user/assistant message merge.
* @param {object[]} messages Array of messages
* @param {string} charName Character name
* @param {string} userName User name
*/
2024-10-10 21:37:22 +02:00
export function convertAI21Messages(messages, charName = '', userName = '') {
2024-08-26 11:07:36 +02:00
if (!Array.isArray(messages)) {
return [];
}
// Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.
let i = 0, systemPrompt = '';
for (i = 0; i < messages.length; i++) {
if (messages[i].role !== 'system') {
break;
}
// Append example names if not already done by the frontend (e.g. for group chats).
if (userName && messages[i].name === 'example_user') {
if (!messages[i].content.startsWith(`${userName}: `)) {
messages[i].content = `${userName}: ${messages[i].content}`;
}
}
if (charName && messages[i].name === 'example_assistant') {
if (!messages[i].content.startsWith(`${charName}: `)) {
messages[i].content = `${charName}: ${messages[i].content}`;
}
}
systemPrompt += `${messages[i].content}\n\n`;
}
messages.splice(0, i);
2024-08-26 16:16:02 +02:00
// Prevent erroring out if the messages array is empty.
if (messages.length === 0) {
2024-08-26 11:07:36 +02:00
messages.unshift({
role: 'user',
content: PROMPT_PLACEHOLDER,
2024-08-26 11:07:36 +02:00
});
}
if (systemPrompt) {
messages.unshift({
role: 'system',
content: systemPrompt.trim(),
});
}
// Doesn't support completion names, so prepend if not already done by the frontend (e.g. for group chats).
messages.forEach(msg => {
if ('name' in msg) {
if (msg.role !== 'system' && !msg.content.startsWith(`${msg.name}: `)) {
msg.content = `${msg.name}: ${msg.content}`;
}
delete msg.name;
}
});
// Since the messaging endpoint only supports alternating turns, we have to merge messages with the same role if they follow each other
let mergedMessages = [];
messages.forEach((message) => {
if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role) {
mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content;
} else {
mergedMessages.push(message);
}
});
return mergedMessages;
}
2024-05-03 19:22:03 +02:00
/**
* Convert a prompt from the ChatML objects to the format used by MistralAI.
* @param {object[]} messages Array of messages
* @param {string} charName Character name
* @param {string} userName User name
*/
2024-10-10 21:37:22 +02:00
export function convertMistralMessages(messages, charName = '', userName = '') {
2024-05-03 19:22:03 +02:00
if (!Array.isArray(messages)) {
return [];
}
// Make the last assistant message a prefill
const prefixEnabled = getConfigValue('mistral.enablePrefix', false);
2024-05-03 19:22:03 +02:00
const lastMsg = messages[messages.length - 1];
if (prefixEnabled && messages.length > 0 && lastMsg?.role === 'assistant') {
lastMsg.prefix = true;
2024-05-03 19:22:03 +02:00
}
2024-10-06 22:58:10 +02:00
const sanitizeToolId = (id) => crypto.createHash('sha512').update(id).digest('hex').slice(0, 9);
2024-10-06 19:07:43 +02:00
// Doesn't support completion names, so prepend if not already done by the frontend (e.g. for group chats).
2024-05-03 19:22:03 +02:00
messages.forEach(msg => {
2024-10-06 19:07:43 +02:00
if ('tool_calls' in msg && Array.isArray(msg.tool_calls)) {
msg.tool_calls.forEach(tool => {
tool.id = sanitizeToolId(tool.id);
});
}
if ('tool_call_id' in msg && msg.role === 'tool') {
msg.tool_call_id = sanitizeToolId(msg.tool_call_id);
}
2024-05-03 19:22:03 +02:00
if (msg.role === 'system' && msg.name === 'example_assistant') {
if (charName && !msg.content.startsWith(`${charName}: `)) {
2024-05-03 19:22:03 +02:00
msg.content = `${charName}: ${msg.content}`;
}
delete msg.name;
}
if (msg.role === 'system' && msg.name === 'example_user') {
if (userName && !msg.content.startsWith(`${userName}: `)) {
2024-05-03 19:22:03 +02:00
msg.content = `${userName}: ${msg.content}`;
}
delete msg.name;
}
if (msg.name && msg.role !== 'system' && !msg.content.startsWith(`${msg.name}: `)) {
2024-05-03 19:22:03 +02:00
msg.content = `${msg.name}: ${msg.content}`;
delete msg.name;
}
});
2024-05-03 19:22:03 +02:00
// If user role message immediately follows a tool message, append it to the last user message
const fixToolMessages = () => {
let rerun = true;
while (rerun) {
rerun = false;
messages.forEach((message, i) => {
if (i === messages.length - 1) {
return;
}
if (message.role === 'tool' && messages[i + 1].role === 'user') {
const lastUserMessage = messages.slice(0, i).findLastIndex(m => m.role === 'user' && m.content);
if (lastUserMessage !== -1) {
messages[lastUserMessage].content += '\n\n' + messages[i + 1].content;
messages.splice(i + 1, 1);
rerun = true;
}
}
});
}
2024-10-06 18:12:28 +02:00
};
fixToolMessages();
// If system role message immediately follows an assistant message, change its role to user
for (let i = 0; i < messages.length - 1; i++) {
if (messages[i].role === 'assistant' && messages[i + 1].role === 'system') {
messages[i + 1].role = 'user';
2024-05-03 19:22:03 +02:00
}
}
2024-05-03 19:22:03 +02:00
return messages;
}
/**
* Merge messages with the same consecutive role, removing names if they exist.
* @param {any[]} messages Messages to merge
* @param {string} charName Character name
* @param {string} userName User name
* @param {boolean} strict Enable strict mode: only allow one system message at the start, force user first message
* @returns {any[]} Merged messages
*/
2024-10-10 21:37:22 +02:00
export function mergeMessages(messages, charName, userName, strict) {
let mergedMessages = [];
// Remove names from the messages
messages.forEach((message) => {
if (!message.content) {
message.content = '';
}
if (message.role === 'system' && message.name === 'example_assistant') {
if (charName && !message.content.startsWith(`${charName}: `)) {
message.content = `${charName}: ${message.content}`;
}
}
if (message.role === 'system' && message.name === 'example_user') {
if (userName && !message.content.startsWith(`${userName}: `)) {
message.content = `${userName}: ${message.content}`;
}
}
if (message.name && message.role !== 'system') {
if (!message.content.startsWith(`${message.name}: `)) {
message.content = `${message.name}: ${message.content}`;
}
}
if (message.role === 'tool') {
message.role = 'user';
}
delete message.name;
delete message.tool_calls;
delete message.tool_call_id;
});
// Squash consecutive messages with the same role
messages.forEach((message) => {
if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role && message.content) {
mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content;
} else {
mergedMessages.push(message);
}
});
// Prevent erroring out if the messages array is empty.
if (messages.length === 0) {
messages.unshift({
role: 'user',
content: PROMPT_PLACEHOLDER,
});
}
if (strict) {
for (let i = 0; i < mergedMessages.length; i++) {
// Force mid-prompt system messages to be user messages
if (i > 0 && mergedMessages[i].role === 'system') {
mergedMessages[i].role = 'user';
}
}
if (mergedMessages.length) {
if (mergedMessages[0].role === 'system' && (mergedMessages.length === 1 || mergedMessages[1].role !== 'user')) {
mergedMessages.splice(1, 0, { role: 'user', content: PROMPT_PLACEHOLDER });
}
else if (mergedMessages[0].role !== 'system' && mergedMessages[0].role !== 'user') {
mergedMessages.unshift({ role: 'user', content: PROMPT_PLACEHOLDER });
}
}
return mergeMessages(mergedMessages, charName, userName, false);
}
return mergedMessages;
}
/**
* Convert a prompt from the ChatML objects to the format used by Text Completion API.
* @param {object[]} messages Array of messages
* @returns {string} Prompt for Text Completion API
*/
2024-10-10 21:37:22 +02:00
export function convertTextCompletionPrompt(messages) {
if (typeof messages === 'string') {
return messages;
}
const messageStrings = [];
messages.forEach(m => {
if (m.role === 'system' && m.name === undefined) {
messageStrings.push('System: ' + m.content);
}
else if (m.role === 'system' && m.name !== undefined) {
messageStrings.push(m.name + ': ' + m.content);
}
else {
messageStrings.push(m.role + ': ' + m.content);
}
});
return messageStrings.join('\n') + '\nassistant:';
}