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' ;
2024-10-05 15:09:39 +02:00
const PROMPT _PLACEHOLDER = getConfigValue ( 'promptPlaceholder' , 'Let\'s get started.' ) ;
2023-09-16 17:48:06 +02:00
/ * *
* 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 .
2023-09-16 17:48:06 +02:00
* @ param { object [ ] } messages Array of messages
2023-12-13 20:19:26 +01:00
* @ 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 .
2023-12-13 20:19:26 +01:00
* @ param { boolean } useSystemPrompt Indicates if the system prompt format should be used .
2023-12-22 16:04:58 +01:00
* @ param { boolean } excludePrefixes Exlude Human / Assistant prefixes .
2023-12-13 20:19:26 +01:00
* @ param { string } addSysHumanMsg Add Human message between system prompt and assistant .
2023-09-16 17:48:06 +02:00
* @ 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 ) {
2024-10-04 14:24:10 +02:00
messages . forEach ( ( m ) => {
if ( ! m . content ) {
m . content = '' ;
}
if ( m . tool _calls ) {
m . content += JSON . stringify ( m . tool _calls ) ;
}
} ) ;
2023-12-22 16:04:58 +01:00
if ( excludePrefixes ) {
2023-12-22 23:25:48 +01:00
messages . slice ( 0 , - 1 ) . forEach ( message => message . role = 'system' ) ;
2023-12-22 16:04:58 +01:00
} 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' ;
2023-12-18 01:25:17 +01:00
if ( firstAssistantIndex > 0 && addSysHumanMsg && ! hasUser ) {
2023-12-15 12:10:53 +01:00
messages . splice ( firstAssistantIndex , 0 , {
role : 'user' ,
2023-12-18 01:25:17 +01:00
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.
2023-12-13 20:19:26 +01:00
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: ' ,
2023-12-18 01:25:17 +01:00
} [ 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 } ` ;
2023-09-16 17:48:06 +02:00
} ) . 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
2024-03-04 19:40:19 +01:00
* @ param { string } prefillString User determined prefill string
* @ param { boolean } useSysPrompt See if we want to use a system prompt
2024-10-06 22:02:38 +02:00
* @ param { boolean } useTools See if we want to use tools
2024-03-04 19:40:19 +01:00
* @ param { string } humanMsgFix Add Human message between system prompt and assistant .
2024-03-08 07:31:36 +01:00
* @ 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 = '' ) {
2024-10-05 17:04:08 +02:00
let systemPrompt = [ ] ;
2024-03-04 19:40:19 +01:00
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 ;
2024-03-04 20:01:36 +01:00
for ( i = 0 ; i < messages . length ; i ++ ) {
if ( messages [ i ] . role !== 'system' ) {
2024-03-04 19:40:19 +01:00
break ;
}
2024-03-27 22:37:28 +01:00
// Append example names if not already done by the frontend (e.g. for group chats).
2024-03-08 07:31:36 +01:00
if ( userName && messages [ i ] . name === 'example_user' ) {
2024-03-27 22:37:28 +01:00
if ( ! messages [ i ] . content . startsWith ( ` ${ userName } : ` ) ) {
messages [ i ] . content = ` ${ userName } : ${ messages [ i ] . content } ` ;
}
2024-03-08 07:31:36 +01:00
}
if ( charName && messages [ i ] . name === 'example_assistant' ) {
2024-03-27 22:37:28 +01:00
if ( ! messages [ i ] . content . startsWith ( ` ${ charName } : ` ) ) {
messages [ i ] . content = ` ${ charName } : ${ messages [ i ] . content } ` ;
}
2024-03-08 07:31:36 +01:00
}
2024-10-05 17:04:08 +02:00
systemPrompt . push ( { type : 'text' , text : messages [ i ] . content } ) ;
2023-12-30 20:04:37 +01:00
}
2024-03-04 20:01:36 +01:00
messages . splice ( 0 , i ) ;
2023-12-30 20:04:37 +01:00
2024-03-04 19:40:19 +01:00
// Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.
2024-03-12 00:17:35 +01:00
// Also prevents erroring out if the messages array is empty.
if ( messages . length === 0 || ( messages . length > 0 && messages [ 0 ] . role !== 'user' ) ) {
2024-03-04 20:01:36 +01:00
messages . unshift ( {
2024-03-04 19:40:19 +01:00
role : 'user' ,
2024-10-05 15:09:39 +02:00
content : humanMsgFix || PROMPT _PLACEHOLDER ,
2024-03-04 19:40:19 +01:00
} ) ;
}
2023-12-30 20:04:37 +01:00
}
2024-10-04 02:41:25 +02:00
2024-03-04 19:40:19 +01:00
// Now replace all further messages that have the role 'system' with the role 'user'. (or all if we're not using one)
2024-10-06 21:22:19 +02:00
const parse = ( str ) => typeof str === 'string' ? JSON . parse ( str ) : str ;
2024-03-04 20:01:36 +01:00
messages . forEach ( ( message ) => {
2024-10-04 02:41:25 +02:00
if ( message . role === 'assistant' && message . tool _calls ) {
message . content = message . tool _calls . map ( ( tc ) => ( {
type : 'tool_use' ,
id : tc . id ,
name : tc . function . name ,
2024-10-06 21:22:19 +02:00
input : parse ( tc . function . arguments ) ,
2024-10-04 02:41:25 +02:00
} ) ) ;
}
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' ) {
2024-03-09 20:10:12 +01:00
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' ;
2024-10-04 02:41:25 +02:00
// Delete name here so it doesn't get added later
delete message . name ;
2023-12-30 20:04:37 +01:00
}
2024-10-04 02:41:25 +02: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
} ) ;
2024-10-04 02:41:25 +02: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' ,
2024-10-04 02:41:25 +02:00
// 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
} ) ;
}
2024-03-04 20:01:36 +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.
2024-03-04 20:01:36 +01:00
let mergedMessages = [ ] ;
messages . forEach ( ( message ) => {
if ( mergedMessages . length > 0 && mergedMessages [ mergedMessages . length - 1 ] . role === message . role ) {
2024-10-04 02:41:25 +02:00
mergedMessages [ mergedMessages . length - 1 ] . content . push ( ... message . content ) ;
2024-03-04 20:01:36 +01:00
} else {
mergedMessages . push ( message ) ;
}
} ) ;
2024-03-04 20:28:19 +01:00
2024-10-06 22:02:38 +02: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 ;
}
} ) ;
} ) ;
}
2024-10-05 17:04:08 +02:00
return { messages : mergedMessages , systemPrompt : systemPrompt } ;
2023-12-30 20:04:37 +01:00
}
2024-04-01 23:20:17 +02: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
2024-10-08 22:50:09 +02:00
* @ returns { { chatHistory : object [ ] } } Prompt for Cohere
2024-04-01 23:20:17 +02:00
* /
2024-10-10 21:37:22 +02:00
export function convertCohereMessages ( messages , charName = '' , userName = '' ) {
2024-04-01 23:20:17 +02:00
if ( messages . length === 0 ) {
messages . unshift ( {
role : 'user' ,
2024-10-05 15:09:39 +02:00
content : PROMPT _PLACEHOLDER ,
2024-04-01 23:20:17 +02:00
} ) ;
}
2024-10-08 22:50:09 +02:00
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 ( ', ' ) } ` ;
2024-10-08 22:50:09 +02:00
}
}
// 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 ;
}
2024-04-01 23:20:17 +02:00
} ) ;
2024-10-08 22:50:09 +02:00
// 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' ;
2024-10-08 22:50:09 +02:00
}
return { chatHistory : messages } ;
2024-04-01 23:20:17 +02:00
}
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
2024-04-11 18:45:50 +02:00
* @ 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 = '' ) {
2023-12-14 20:21:37 +01:00
// 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' ,
2024-09-24 19:58:56 +02:00
'gemini-1.5-flash-002' ,
2024-08-28 01:22:06 +02:00
'gemini-1.5-flash-exp-0827' ,
2024-10-04 13:15:16 +02:00
'gemini-1.5-flash-8b' ,
2024-08-28 01:22:06 +02:00
'gemini-1.5-flash-8b-exp-0827' ,
2024-09-24 19:58:56 +02:00
'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' ,
2024-09-24 19:58:56 +02:00
'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' ) {
2024-04-11 18:45:50 +02:00
// 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 ( ) ;
}
}
2024-04-11 18:45:50 +02:00
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 ) {
2024-09-08 09:48:28 +02:00
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 : {
2024-09-08 09:48:28 +02:00
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 } ;
2023-12-14 06:49:50 +01:00
}
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' ,
2024-10-05 15:09:39 +02:00
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 [ ] ;
}
2024-07-25 20:04:57 +02:00
// Make the last assistant message a prefill
2024-07-27 18:57:40 +02:00
const prefixEnabled = getConfigValue ( 'mistral.enablePrefix' , false ) ;
2024-05-03 19:22:03 +02:00
const lastMsg = messages [ messages . length - 1 ] ;
2024-07-27 18:57:40 +02:00
if ( prefixEnabled && messages . length > 0 && lastMsg ? . role === 'assistant' ) {
2024-07-25 20:04:57 +02:00
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
2024-07-25 20:04:57 +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' ) {
2024-07-25 20:04:57 +02:00
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' ) {
2024-07-25 20:04:57 +02:00
if ( userName && ! msg . content . startsWith ( ` ${ userName } : ` ) ) {
2024-05-03 19:22:03 +02:00
msg . content = ` ${ userName } : ${ msg . content } ` ;
}
delete msg . name ;
}
2024-07-25 20:04:57 +02:00
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-07-25 20:04:57 +02:00
} ) ;
2024-05-03 19:22:03 +02:00
2024-10-06 13:28:52 +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
} ;
2024-10-06 13:28:52 +02:00
fixToolMessages ( ) ;
2024-07-25 20:04:57 +02:00
// 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-07-25 20:04:57 +02:00
}
2024-05-03 19:22:03 +02:00
return messages ;
}
2024-10-05 15:09:39 +02:00
/ * *
* 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 ) {
2024-10-05 15:09:39 +02:00
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 ;
}
2023-12-14 15:00:17 +01:00
/ * *
* 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 ) {
2023-12-14 15:00:17 +01:00
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:' ;
}