2023-09-16 17:48:06 +02:00
/ * *
* Convert a prompt from the ChatML objects to the format used by Claude .
* @ 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 ) .
* /
2023-12-22 16:04:58 +01:00
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 ) {
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
* @ param { string } humanMsgFix Add Human message between system prompt and assistant .
2023-12-30 20:04:37 +01:00
* /
2024-03-04 19:40:19 +01:00
function convertClaudeMessages ( messages , prefillString , useSysPrompt , humanMsgFix ) {
2023-12-30 20:04:37 +01: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-04 20:01:36 +01:00
systemPrompt += ` ${ messages [ i ] . content } \n \n ` ;
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-04 20:01:36 +01:00
if ( messages . length > 0 && messages [ 0 ] . role !== 'user' ) {
messages . unshift ( {
2024-03-04 19:40:19 +01:00
role : 'user' ,
content : humanMsgFix || '' ,
} ) ;
}
2023-12-30 20:04:37 +01: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-03-04 20:01:36 +01:00
messages . forEach ( ( message ) => {
2023-12-30 20:04:37 +01:00
if ( message . role === 'system' ) {
message . role = 'user' ;
}
} ) ;
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
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 ) ;
}
} ) ;
2024-03-04 19:40:19 +01:00
// Shouldn't be conditional anymore, messages api expects the last role to be user unless we're explicitly prefilling
if ( prefillString ) {
mergedMessages . push ( {
2023-12-30 20:04:37 +01:00
role : 'assistant' ,
2024-03-04 19:40:19 +01:00
content : prefillString ,
2023-12-30 20:04:37 +01:00
} ) ;
}
2024-03-04 19:40:19 +01:00
return { messages : mergedMessages , systemPrompt : systemPrompt . trim ( ) } ;
2023-12-30 20:04:37 +01: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
* @ returns { object [ ] } Prompt for Google MakerSuite models
* /
2023-12-14 17:01:42 +01:00
function convertGooglePrompt ( messages , model ) {
2023-12-14 20:21:37 +01:00
// This is a 1x1 transparent PNG
const PNG _PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' ;
2023-12-14 06:49:50 +01:00
const contents = [ ] ;
let lastRole = '' ;
let currentText = '' ;
2023-12-14 16:28:54 +01:00
2023-12-14 17:01:42 +01:00
const isMultimodal = model === 'gemini-pro-vision' ;
2023-12-14 16:28:54 +01:00
if ( isMultimodal ) {
const combinedText = messages . map ( ( message ) => {
const role = message . role === 'assistant' ? 'MODEL: ' : 'USER: ' ;
return role + message . content ;
} ) . join ( '\n\n' ) . trim ( ) ;
2023-12-14 20:21:37 +01:00
const imageEntry = messages . find ( ( message ) => message . content ? . [ 1 ] ? . image _url ) ;
const imageData = imageEntry ? . content ? . [ 1 ] ? . image _url ? . data ? ? PNG _PIXEL ;
2023-12-14 16:28:54 +01:00
contents . push ( {
parts : [
{ text : combinedText } ,
{
inlineData : {
mimeType : 'image/png' ,
2023-12-14 20:21:37 +01:00
data : imageData ,
2023-12-14 16:28:54 +01:00
} ,
} ,
] ,
role : 'user' ,
} ) ;
} else {
messages . forEach ( ( message , index ) => {
const role = message . role === 'assistant' ? 'model' : 'user' ;
if ( lastRole === role ) {
currentText += '\n\n' + message . content ;
} else {
if ( currentText !== '' ) {
contents . push ( {
parts : [ { text : currentText . trim ( ) } ] ,
role : lastRole ,
} ) ;
}
currentText = message . content ;
lastRole = role ;
}
if ( index === messages . length - 1 ) {
2023-12-14 06:49:50 +01:00
contents . push ( {
parts : [ { text : currentText . trim ( ) } ] ,
role : lastRole ,
} ) ;
}
2023-12-14 16:28:54 +01:00
} ) ;
}
2023-12-14 06:49:50 +01:00
return contents ;
}
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
* /
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:' ;
}
2023-09-16 17:48:06 +02:00
module . exports = {
convertClaudePrompt ,
2023-12-30 20:04:37 +01:00
convertClaudeMessages ,
2023-12-14 06:49:50 +01:00
convertGooglePrompt ,
2023-12-14 15:00:17 +01:00
convertTextCompletionPrompt ,
2023-12-02 20:11:06 +01:00
} ;