2023-10-30 23:44:03 +01:00
const { v4 } = require ( "uuid" ) ;
const { chatPrompt } = require ( "../../chats" ) ;
class AnthropicLLM {
constructor ( embedder = null ) {
if ( ! process . env . ANTHROPIC _API _KEY )
throw new Error ( "No Anthropic API key was set." ) ;
// Docs: https://www.npmjs.com/package/@anthropic-ai/sdk
const AnthropicAI = require ( "@anthropic-ai/sdk" ) ;
const anthropic = new AnthropicAI ( {
apiKey : process . env . ANTHROPIC _API _KEY ,
} ) ;
this . anthropic = anthropic ;
2023-11-14 00:17:22 +01:00
this . model = process . env . ANTHROPIC _MODEL _PREF || "claude-2" ;
2023-11-06 22:13:53 +01:00
this . limits = {
history : this . promptWindowLimit ( ) * 0.15 ,
system : this . promptWindowLimit ( ) * 0.15 ,
user : this . promptWindowLimit ( ) * 0.7 ,
} ;
2023-10-30 23:44:03 +01:00
if ( ! embedder )
throw new Error (
"INVALID ANTHROPIC SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use Anthropic as your LLM."
) ;
this . embedder = embedder ;
this . answerKey = v4 ( ) . split ( "-" ) [ 0 ] ;
}
2023-11-14 00:07:30 +01:00
streamingEnabled ( ) {
return "streamChat" in this && "streamGetChatCompletion" in this ;
}
2023-11-06 22:13:53 +01:00
promptWindowLimit ( ) {
switch ( this . model ) {
case "claude-instant-1" :
return 72_000 ;
case "claude-2" :
return 100_000 ;
default :
return 72_000 ; // assume a claude-instant-1 model
}
}
isValidChatCompletionModel ( modelName = "" ) {
const validModels = [ "claude-2" , "claude-instant-1" ] ;
2023-10-30 23:44:03 +01:00
return validModels . includes ( modelName ) ;
}
// Moderation can be done with Anthropic, but its not really "exact" so we skip it
// https://docs.anthropic.com/claude/docs/content-moderation
async isSafe ( _input = "" ) {
// Not implemented so must be stubbed
return { safe : true , reasons : [ ] } ;
}
constructPrompt ( {
systemPrompt = "" ,
contextTexts = [ ] ,
chatHistory = [ ] ,
userPrompt = "" ,
} ) {
return ` \n \n Human: Please read question supplied within the <question> tags. Using all information generate an answer to the question and output it within < ${
this . answerKey
} > tags . Previous conversations can be used within the < history > tags and can be used to influence the output . Content between the < system > tag is additional information and instruction that will impact how answers are formatted or responded to . Additional contextual information retrieved to help answer the users specific query is available to use for answering and can be found between < context > tags . When no < context > tags may are present use the knowledge available and in the conversation to answer . When one or more < context > tags are available you will use those to help answer the question or augment pre - existing knowledge . You should never say "Based on the provided context" or other phrasing that is not related to the user question .
< system > $ { systemPrompt } < / s y s t e m >
$ { contextTexts
. map ( ( text , i ) => {
return ` <context> ${ text } </context> \n ` ;
} )
. join ( "" ) }
< history > $ { chatHistory . map ( ( history ) => {
switch ( history . role ) {
case "assistant" :
return ` \n \n Assistant: ${ history . content } ` ;
case "user" :
return ` \n \n Human: ${ history . content } ` ;
default :
return "\n" ;
}
} ) } < / h i s t o r y >
< question > $ { userPrompt } < / q u e s t i o n >
\ n \ nAssistant : ` ;
}
2023-11-06 22:13:53 +01:00
async sendChat ( chatHistory = [ ] , prompt , workspace = { } , rawHistory = [ ] ) {
if ( ! this . isValidChatCompletionModel ( this . model ) )
2023-10-30 23:44:03 +01:00
throw new Error (
2023-11-06 22:13:53 +01:00
` Anthropic chat: ${ this . model } is not valid for chat completion! `
2023-10-30 23:44:03 +01:00
) ;
2023-11-06 22:13:53 +01:00
const compressedPrompt = await this . compressMessages (
{
systemPrompt : chatPrompt ( workspace ) ,
userPrompt : prompt ,
chatHistory ,
} ,
rawHistory
) ;
2023-10-30 23:44:03 +01:00
const { content , error } = await this . anthropic . completions
. create ( {
2023-11-06 22:13:53 +01:00
model : this . model ,
2023-10-30 23:44:03 +01:00
max _tokens _to _sample : 300 ,
2023-11-06 22:13:53 +01:00
prompt : compressedPrompt ,
2023-10-30 23:44:03 +01:00
} )
. then ( ( res ) => {
const { completion } = res ;
const re = new RegExp (
"(?:<" + this . answerKey + ">)([\\s\\S]*)(?:</" + this . answerKey + ">)"
) ;
const response = completion . match ( re ) ? . [ 1 ] ? . trim ( ) ;
if ( ! response )
throw new Error ( "Anthropic: No response could be parsed." ) ;
return { content : response , error : null } ;
} )
. catch ( ( e ) => {
return { content : null , error : e . message } ;
} ) ;
if ( error ) throw new Error ( error ) ;
return content ;
}
async getChatCompletion ( prompt = "" , _opts = { } ) {
2023-11-06 22:13:53 +01:00
if ( ! this . isValidChatCompletionModel ( this . model ) )
2023-10-30 23:44:03 +01:00
throw new Error (
2023-11-06 22:13:53 +01:00
` Anthropic chat: ${ this . model } is not valid for chat completion! `
2023-10-30 23:44:03 +01:00
) ;
const { content , error } = await this . anthropic . completions
. create ( {
2023-11-06 22:13:53 +01:00
model : this . model ,
2023-10-30 23:44:03 +01:00
max _tokens _to _sample : 300 ,
prompt ,
} )
. then ( ( res ) => {
const { completion } = res ;
const re = new RegExp (
"(?:<" + this . answerKey + ">)([\\s\\S]*)(?:</" + this . answerKey + ">)"
) ;
const response = completion . match ( re ) ? . [ 1 ] ? . trim ( ) ;
if ( ! response )
throw new Error ( "Anthropic: No response could be parsed." ) ;
return { content : response , error : null } ;
} )
. catch ( ( e ) => {
return { content : null , error : e . message } ;
} ) ;
if ( error ) throw new Error ( error ) ;
return content ;
}
2023-11-06 22:13:53 +01:00
async compressMessages ( promptArgs = { } , rawHistory = [ ] ) {
const { messageStringCompressor } = require ( "../../helpers/chat" ) ;
const compressedPrompt = await messageStringCompressor (
this ,
promptArgs ,
rawHistory
) ;
return compressedPrompt ;
}
2023-10-30 23:44:03 +01:00
// Simple wrapper for dynamic embedder & normalize interface for all LLM implementations
async embedTextInput ( textInput ) {
return await this . embedder . embedTextInput ( textInput ) ;
}
async embedChunks ( textChunks = [ ] ) {
return await this . embedder . embedChunks ( textChunks ) ;
}
}
module . exports = {
AnthropicLLM ,
} ;