2023-12-19 15:38:11 +01:00
import { characters , main _api , api _server , nai _settings , online _status , this _chid } from '../script.js' ;
2023-12-02 19:04:51 +01:00
import { power _user , registerDebugFunction } from './power-user.js' ;
import { chat _completion _sources , model _list , oai _settings } from './openai.js' ;
import { groups , selected _group } from './group-chats.js' ;
import { getStringHash } from './utils.js' ;
import { kai _flags } from './kai-settings.js' ;
2023-12-19 15:38:11 +01:00
import { textgen _types , textgenerationwebui _settings as textgen _settings , getTextGenServer } from './textgen-settings.js' ;
2023-12-03 16:03:19 +01:00
2023-12-18 21:38:28 +01:00
const { OOBA , TABBY , KOBOLDCPP , APHRODITE , LLAMACPP } = textgen _types ;
2023-08-23 01:38:43 +02:00
export const CHARACTERS _PER _TOKEN _RATIO = 3.35 ;
2023-08-24 20:23:35 +02:00
const TOKENIZER _WARNING _KEY = 'tokenizationWarningShown' ;
2023-08-23 01:38:43 +02:00
export const tokenizers = {
NONE : 0 ,
2023-08-27 17:27:34 +02:00
GPT2 : 1 ,
2023-11-05 21:45:37 +01:00
OPENAI : 2 ,
2023-08-23 01:38:43 +02:00
LLAMA : 3 ,
NERD : 4 ,
NERD2 : 5 ,
2023-12-10 14:56:38 +01:00
API _CURRENT : 6 ,
2023-11-06 00:26:13 +01:00
MISTRAL : 7 ,
2023-11-20 23:21:58 +01:00
YI : 8 ,
2023-12-10 02:20:53 +01:00
API _TEXTGENERATIONWEBUI : 9 ,
2023-12-10 14:56:38 +01:00
API _KOBOLD : 10 ,
2023-08-23 01:38:43 +02:00
BEST _MATCH : 99 ,
} ;
2023-11-21 00:04:27 +01:00
export const SENTENCEPIECE _TOKENIZERS = [
tokenizers . LLAMA ,
tokenizers . MISTRAL ,
tokenizers . YI ,
// uncomment when NovelAI releases Kayra and Clio weights, lol
//tokenizers.NERD,
//tokenizers.NERD2,
] ;
2024-01-05 15:17:06 +01:00
export const TEXTGEN _TOKENIZERS = [ OOBA , TABBY , KOBOLDCPP , LLAMACPP ] ;
2023-12-10 02:48:41 +01:00
const TOKENIZER _URLS = {
[ tokenizers . GPT2 ] : {
encode : '/api/tokenizers/gpt2/encode' ,
decode : '/api/tokenizers/gpt2/decode' ,
count : '/api/tokenizers/gpt2/encode' ,
} ,
[ tokenizers . OPENAI ] : {
encode : '/api/tokenizers/openai/encode' ,
decode : '/api/tokenizers/openai/decode' ,
count : '/api/tokenizers/openai/encode' ,
} ,
[ tokenizers . LLAMA ] : {
encode : '/api/tokenizers/llama/encode' ,
decode : '/api/tokenizers/llama/decode' ,
count : '/api/tokenizers/llama/encode' ,
} ,
[ tokenizers . NERD ] : {
encode : '/api/tokenizers/nerdstash/encode' ,
decode : '/api/tokenizers/nerdstash/decode' ,
count : '/api/tokenizers/nerdstash/encode' ,
} ,
[ tokenizers . NERD2 ] : {
encode : '/api/tokenizers/nerdstash_v2/encode' ,
decode : '/api/tokenizers/nerdstash_v2/decode' ,
count : '/api/tokenizers/nerdstash_v2/encode' ,
} ,
[ tokenizers . API _KOBOLD ] : {
count : '/api/tokenizers/remote/kobold/count' ,
2023-12-14 00:28:18 +01:00
encode : '/api/tokenizers/remote/kobold/count' ,
2023-12-10 02:48:41 +01:00
} ,
[ tokenizers . MISTRAL ] : {
encode : '/api/tokenizers/mistral/encode' ,
decode : '/api/tokenizers/mistral/decode' ,
count : '/api/tokenizers/mistral/encode' ,
} ,
[ tokenizers . YI ] : {
encode : '/api/tokenizers/yi/encode' ,
decode : '/api/tokenizers/yi/decode' ,
count : '/api/tokenizers/yi/encode' ,
} ,
[ tokenizers . API _TEXTGENERATIONWEBUI ] : {
encode : '/api/tokenizers/remote/textgenerationwebui/encode' ,
count : '/api/tokenizers/remote/textgenerationwebui/encode' ,
} ,
} ;
2023-12-02 19:04:51 +01:00
const objectStore = new localforage . createInstance ( { name : 'SillyTavern_ChatCompletions' } ) ;
2023-08-23 01:38:43 +02:00
let tokenCache = { } ;
2023-08-24 19:19:57 +02:00
/ * *
* Guesstimates the token count for a string .
* @ param { string } str String to tokenize .
* @ returns { number } Token count .
* /
export function guesstimate ( str ) {
return Math . ceil ( str . length / CHARACTERS _PER _TOKEN _RATIO ) ;
}
2023-08-23 01:38:43 +02:00
async function loadTokenCache ( ) {
try {
2023-12-02 20:11:06 +01:00
console . debug ( 'Chat Completions: loading token cache' ) ;
2023-08-23 01:38:43 +02:00
tokenCache = await objectStore . getItem ( 'tokenCache' ) || { } ;
} catch ( e ) {
console . log ( 'Chat Completions: unable to load token cache, using default value' , e ) ;
tokenCache = { } ;
}
}
export async function saveTokenCache ( ) {
try {
2023-12-02 20:11:06 +01:00
console . debug ( 'Chat Completions: saving token cache' ) ;
2023-08-23 01:38:43 +02:00
await objectStore . setItem ( 'tokenCache' , tokenCache ) ;
} catch ( e ) {
console . log ( 'Chat Completions: unable to save token cache' , e ) ;
}
}
async function resetTokenCache ( ) {
try {
console . debug ( 'Chat Completions: resetting token cache' ) ;
Object . keys ( tokenCache ) . forEach ( key => delete tokenCache [ key ] ) ;
await objectStore . removeItem ( 'tokenCache' ) ;
2023-08-27 22:20:43 +02:00
toastr . success ( 'Token cache cleared. Please reload the chat to re-tokenize it.' ) ;
2023-08-23 01:38:43 +02:00
} catch ( e ) {
console . log ( 'Chat Completions: unable to reset token cache' , e ) ;
}
}
2023-11-06 19:25:59 +01:00
/ * *
* Gets the friendly name of the current tokenizer .
* @ param { string } forApi API to get the tokenizer for . Defaults to the main API .
* @ returns { { tokenizerName : string , tokenizerId : number } } Tokenizer info
* /
export function getFriendlyTokenizerName ( forApi ) {
if ( ! forApi ) {
forApi = main _api ;
}
2023-12-02 19:04:51 +01:00
const tokenizerOption = $ ( '#tokenizer' ) . find ( ':selected' ) ;
2023-11-06 19:25:59 +01:00
let tokenizerId = Number ( tokenizerOption . val ( ) ) ;
let tokenizerName = tokenizerOption . text ( ) ;
if ( forApi !== 'openai' && tokenizerId === tokenizers . BEST _MATCH ) {
tokenizerId = getTokenizerBestMatch ( forApi ) ;
2023-12-10 15:03:25 +01:00
switch ( tokenizerId ) {
case tokenizers . API _KOBOLD :
tokenizerName = 'API (KoboldAI Classic)' ;
break ;
case tokenizers . API _TEXTGENERATIONWEBUI :
tokenizerName = 'API (Text Completion)' ;
break ;
default :
tokenizerName = $ ( ` #tokenizer option[value=" ${ tokenizerId } "] ` ) . text ( ) ;
break ;
}
2023-11-06 19:25:59 +01:00
}
tokenizerName = forApi == 'openai'
? getTokenizerModel ( )
: tokenizerName ;
tokenizerId = forApi == 'openai'
? tokenizers . OPENAI
: tokenizerId ;
return { tokenizerName , tokenizerId } ;
}
/ * *
* Gets the best tokenizer for the current API .
* @ param { string } forApi API to get the tokenizer for . Defaults to the main API .
* @ returns { number } Tokenizer type .
* /
export function getTokenizerBestMatch ( forApi ) {
if ( ! forApi ) {
forApi = main _api ;
}
if ( forApi === 'novel' ) {
2023-08-23 01:38:43 +02:00
if ( nai _settings . model _novel . includes ( 'clio' ) ) {
return tokenizers . NERD ;
}
if ( nai _settings . model _novel . includes ( 'kayra' ) ) {
return tokenizers . NERD2 ;
}
}
2023-11-06 19:25:59 +01:00
if ( forApi === 'kobold' || forApi === 'textgenerationwebui' || forApi === 'koboldhorde' ) {
2023-08-24 20:23:35 +02:00
// Try to use the API tokenizer if possible:
// - API must be connected
// - Kobold must pass a version check
// - Tokenizer haven't reported an error previously
2023-12-02 18:42:15 +01:00
const hasTokenizerError = sessionStorage . getItem ( TOKENIZER _WARNING _KEY ) ;
const isConnected = online _status !== 'no_connection' ;
2024-01-05 15:17:06 +01:00
const isTokenizerSupported = TEXTGEN _TOKENIZERS . includes ( textgen _settings . type ) ;
2023-12-02 18:42:15 +01:00
if ( ! hasTokenizerError && isConnected ) {
if ( forApi === 'kobold' && kai _flags . can _use _tokenization ) {
2023-12-10 02:20:53 +01:00
return tokenizers . API _KOBOLD ;
2023-12-02 18:42:15 +01:00
}
if ( forApi === 'textgenerationwebui' && isTokenizerSupported ) {
2023-12-10 02:20:53 +01:00
return tokenizers . API _TEXTGENERATIONWEBUI ;
2023-12-02 18:42:15 +01:00
}
2023-08-24 20:23:35 +02:00
}
2023-08-23 01:38:43 +02:00
return tokenizers . LLAMA ;
}
return tokenizers . NONE ;
}
2023-12-10 05:57:21 +01:00
// Get the current remote tokenizer API based on the current text generation API.
function currentRemoteTokenizerAPI ( ) {
switch ( main _api ) {
case 'kobold' :
return tokenizers . API _KOBOLD ;
case 'textgenerationwebui' :
return tokenizers . API _TEXTGENERATIONWEBUI ;
default :
return tokenizers . NONE ;
}
}
2023-08-27 17:27:34 +02:00
/ * *
* Calls the underlying tokenizer model to the token count for a string .
* @ param { number } type Tokenizer type .
* @ param { string } str String to tokenize .
* @ returns { number } Token count .
* /
2023-12-10 02:53:16 +01:00
function callTokenizer ( type , str ) {
if ( type === tokenizers . NONE ) return guesstimate ( str ) ;
2023-12-10 02:48:41 +01:00
2023-08-27 17:27:34 +02:00
switch ( type ) {
2023-12-10 05:57:21 +01:00
case tokenizers . API _CURRENT :
return callTokenizer ( currentRemoteTokenizerAPI ( ) , str ) ;
2023-12-10 02:20:53 +01:00
case tokenizers . API _KOBOLD :
2023-12-10 02:53:16 +01:00
return countTokensFromKoboldAPI ( str ) ;
2023-12-10 02:20:53 +01:00
case tokenizers . API _TEXTGENERATIONWEBUI :
2023-12-10 02:53:16 +01:00
return countTokensFromTextgenAPI ( str ) ;
2023-12-10 02:48:41 +01:00
default : {
const endpointUrl = TOKENIZER _URLS [ type ] ? . count ;
if ( ! endpointUrl ) {
console . warn ( 'Unknown tokenizer type' , type ) ;
2023-12-10 15:21:06 +01:00
return apiFailureTokenCount ( str ) ;
2023-12-10 02:48:41 +01:00
}
2023-12-10 02:53:16 +01:00
return countTokensFromServer ( endpointUrl , str ) ;
2023-12-10 02:48:41 +01:00
}
2023-08-27 17:27:34 +02:00
}
}
2023-08-23 01:38:43 +02:00
/ * *
* Gets the token count for a string using the current model tokenizer .
* @ param { string } str String to tokenize
* @ param { number | undefined } padding Optional padding tokens . Defaults to 0.
* @ returns { number } Token count .
* /
export function getTokenCount ( str , padding = undefined ) {
if ( typeof str !== 'string' || ! str ? . length ) {
return 0 ;
}
let tokenizerType = power _user . tokenizer ;
if ( main _api === 'openai' ) {
if ( padding === power _user . token _padding ) {
// For main "shadow" prompt building
tokenizerType = tokenizers . NONE ;
} else {
// For extensions and WI
return counterWrapperOpenAI ( str ) ;
}
}
if ( tokenizerType === tokenizers . BEST _MATCH ) {
2023-11-06 19:25:59 +01:00
tokenizerType = getTokenizerBestMatch ( main _api ) ;
2023-08-23 01:38:43 +02:00
}
if ( padding === undefined ) {
padding = 0 ;
}
const cacheObject = getTokenCacheObject ( ) ;
const hash = getStringHash ( str ) ;
2023-08-23 09:32:48 +02:00
const cacheKey = ` ${ tokenizerType } - ${ hash } + ${ padding } ` ;
2023-08-23 01:38:43 +02:00
if ( typeof cacheObject [ cacheKey ] === 'number' ) {
return cacheObject [ cacheKey ] ;
}
2023-12-10 02:53:16 +01:00
const result = callTokenizer ( tokenizerType , str ) + padding ;
2023-08-23 01:38:43 +02:00
if ( isNaN ( result ) ) {
2023-12-02 19:04:51 +01:00
console . warn ( 'Token count calculation returned NaN' ) ;
2023-08-23 01:38:43 +02:00
return 0 ;
}
cacheObject [ cacheKey ] = result ;
return result ;
}
/ * *
* Gets the token count for a string using the OpenAI tokenizer .
* @ param { string } text Text to tokenize .
* @ returns { number } Token count .
* /
function counterWrapperOpenAI ( text ) {
const message = { role : 'system' , content : text } ;
return countTokensOpenAI ( message , true ) ;
}
export function getTokenizerModel ( ) {
// OpenAI models always provide their own tokenizer
if ( oai _settings . chat _completion _source == chat _completion _sources . OPENAI ) {
return oai _settings . openai _model ;
}
2023-10-19 12:37:08 +02:00
const turbo0301Tokenizer = 'gpt-3.5-turbo-0301' ;
2023-08-23 01:38:43 +02:00
const turboTokenizer = 'gpt-3.5-turbo' ;
const gpt4Tokenizer = 'gpt-4' ;
const gpt2Tokenizer = 'gpt2' ;
const claudeTokenizer = 'claude' ;
2023-11-05 20:54:19 +01:00
const llamaTokenizer = 'llama' ;
2023-11-06 00:26:13 +01:00
const mistralTokenizer = 'mistral' ;
2023-11-20 23:21:58 +01:00
const yiTokenizer = 'yi' ;
2023-08-23 01:38:43 +02:00
// Assuming no one would use it for different models.. right?
if ( oai _settings . chat _completion _source == chat _completion _sources . SCALE ) {
return gpt4Tokenizer ;
}
// Select correct tokenizer for WindowAI proxies
if ( oai _settings . chat _completion _source == chat _completion _sources . WINDOWAI && oai _settings . windowai _model ) {
if ( oai _settings . windowai _model . includes ( 'gpt-4' ) ) {
return gpt4Tokenizer ;
}
2023-10-19 12:37:08 +02:00
else if ( oai _settings . windowai _model . includes ( 'gpt-3.5-turbo-0301' ) ) {
return turbo0301Tokenizer ;
}
2023-08-23 01:38:43 +02:00
else if ( oai _settings . windowai _model . includes ( 'gpt-3.5-turbo' ) ) {
return turboTokenizer ;
}
else if ( oai _settings . windowai _model . includes ( 'claude' ) ) {
return claudeTokenizer ;
}
else if ( oai _settings . windowai _model . includes ( 'GPT-NeoXT' ) ) {
return gpt2Tokenizer ;
}
}
// And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
if ( oai _settings . chat _completion _source == chat _completion _sources . OPENROUTER && oai _settings . openrouter _model ) {
2023-11-05 20:54:19 +01:00
const model = model _list . find ( x => x . id === oai _settings . openrouter _model ) ;
if ( model ? . architecture ? . tokenizer === 'Llama2' ) {
return llamaTokenizer ;
}
2023-11-06 00:26:13 +01:00
else if ( model ? . architecture ? . tokenizer === 'Mistral' ) {
return mistralTokenizer ;
}
2023-11-20 23:21:58 +01:00
else if ( model ? . architecture ? . tokenizer === 'Yi' ) {
return yiTokenizer ;
}
2023-11-05 20:54:19 +01:00
else if ( oai _settings . openrouter _model . includes ( 'gpt-4' ) ) {
2023-08-23 01:38:43 +02:00
return gpt4Tokenizer ;
}
2023-10-19 12:37:08 +02:00
else if ( oai _settings . openrouter _model . includes ( 'gpt-3.5-turbo-0301' ) ) {
return turbo0301Tokenizer ;
}
2023-08-23 01:38:43 +02:00
else if ( oai _settings . openrouter _model . includes ( 'gpt-3.5-turbo' ) ) {
return turboTokenizer ;
}
else if ( oai _settings . openrouter _model . includes ( 'claude' ) ) {
return claudeTokenizer ;
}
else if ( oai _settings . openrouter _model . includes ( 'GPT-NeoXT' ) ) {
return gpt2Tokenizer ;
}
}
2023-12-14 19:57:43 +01:00
if ( oai _settings . chat _completion _source == chat _completion _sources . MAKERSUITE ) {
2023-12-14 06:49:50 +01:00
return oai _settings . google _model ;
}
2023-08-23 01:38:43 +02:00
if ( oai _settings . chat _completion _source == chat _completion _sources . CLAUDE ) {
return claudeTokenizer ;
}
2023-12-15 23:47:51 +01:00
if ( oai _settings . chat _completion _source == chat _completion _sources . MISTRALAI ) {
return mistralTokenizer ;
}
2023-12-20 17:29:03 +01:00
if ( oai _settings . chat _completion _source == chat _completion _sources . CUSTOM ) {
return oai _settings . custom _model ;
}
2023-08-23 01:38:43 +02:00
// Default to Turbo 3.5
return turboTokenizer ;
}
/ * *
* @ param { any [ ] | Object } messages
* /
export function countTokensOpenAI ( messages , full = false ) {
const shouldTokenizeAI21 = oai _settings . chat _completion _source === chat _completion _sources . AI21 && oai _settings . use _ai21 _tokenizer ;
2023-12-14 07:31:08 +01:00
const shouldTokenizeGoogle = oai _settings . chat _completion _source === chat _completion _sources . MAKERSUITE && oai _settings . use _google _tokenizer ;
2023-12-14 06:49:50 +01:00
let tokenizerEndpoint = '' ;
2023-12-14 19:57:43 +01:00
if ( shouldTokenizeAI21 ) {
2023-12-14 06:49:50 +01:00
tokenizerEndpoint = '/api/tokenizers/ai21/count' ;
} else if ( shouldTokenizeGoogle ) {
tokenizerEndpoint = ` /api/tokenizers/google/count?model= ${ getTokenizerModel ( ) } ` ;
} else {
tokenizerEndpoint = ` /api/tokenizers/openai/count?model= ${ getTokenizerModel ( ) } ` ;
}
2023-08-23 01:38:43 +02:00
const cacheObject = getTokenCacheObject ( ) ;
if ( ! Array . isArray ( messages ) ) {
messages = [ messages ] ;
}
let token _count = - 1 ;
for ( const message of messages ) {
const model = getTokenizerModel ( ) ;
2023-12-14 06:49:50 +01:00
if ( model === 'claude' || shouldTokenizeAI21 || shouldTokenizeGoogle ) {
2023-08-23 01:38:43 +02:00
full = true ;
}
const hash = getStringHash ( JSON . stringify ( message ) ) ;
const cacheKey = ` ${ model } - ${ hash } ` ;
const cachedCount = cacheObject [ cacheKey ] ;
if ( typeof cachedCount === 'number' ) {
token _count += cachedCount ;
}
else {
jQuery . ajax ( {
async : false ,
type : 'POST' , //
2023-12-14 06:49:50 +01:00
url : tokenizerEndpoint ,
2023-08-23 01:38:43 +02:00
data : JSON . stringify ( [ message ] ) ,
2023-12-02 19:04:51 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
2023-08-23 01:38:43 +02:00
success : function ( data ) {
token _count += Number ( data . token _count ) ;
cacheObject [ cacheKey ] = Number ( data . token _count ) ;
2023-12-02 21:06:57 +01:00
} ,
2023-08-23 01:38:43 +02:00
} ) ;
}
}
if ( ! full ) token _count -= 2 ;
return token _count ;
}
/ * *
* Gets the token cache object for the current chat .
* @ returns { Object } Token cache object for the current chat .
* /
function getTokenCacheObject ( ) {
let chatId = 'undefined' ;
try {
if ( selected _group ) {
chatId = groups . find ( x => x . id == selected _group ) ? . chat _id ;
}
else if ( this _chid !== undefined ) {
chatId = characters [ this _chid ] . chat ;
}
} catch {
console . log ( 'No character / group selected. Using default cache item' ) ;
}
if ( typeof tokenCache [ chatId ] !== 'object' ) {
tokenCache [ chatId ] = { } ;
}
return tokenCache [ String ( chatId ) ] ;
}
2023-08-24 19:19:57 +02:00
/ * *
2023-12-10 02:08:48 +01:00
* Count tokens using the server API .
2023-08-24 19:19:57 +02:00
* @ param { string } endpoint API endpoint .
* @ param { string } str String to tokenize .
2023-12-10 02:53:16 +01:00
* @ returns { number } Token count .
2023-08-24 19:19:57 +02:00
* /
2023-12-10 02:53:16 +01:00
function countTokensFromServer ( endpoint , str ) {
2023-08-23 01:38:43 +02:00
let tokenCount = 0 ;
2023-08-24 19:19:57 +02:00
2023-08-23 01:38:43 +02:00
jQuery . ajax ( {
async : false ,
type : 'POST' ,
url : endpoint ,
2023-12-10 02:35:11 +01:00
data : JSON . stringify ( { text : str } ) ,
2023-12-02 19:04:51 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
2023-08-23 01:38:43 +02:00
success : function ( data ) {
2023-08-24 19:19:57 +02:00
if ( typeof data . count === 'number' ) {
tokenCount = data . count ;
} else {
2023-12-10 02:08:48 +01:00
tokenCount = apiFailureTokenCount ( str ) ;
}
} ,
} ) ;
2023-12-10 02:53:16 +01:00
return tokenCount ;
2023-12-10 02:08:48 +01:00
}
/ * *
* Count tokens using the AI provider ' s API .
* @ param { string } str String to tokenize .
2023-12-10 02:53:16 +01:00
* @ returns { number } Token count .
2023-12-10 02:08:48 +01:00
* /
2023-12-10 02:53:16 +01:00
function countTokensFromKoboldAPI ( str ) {
2023-12-10 02:20:53 +01:00
let tokenCount = 0 ;
jQuery . ajax ( {
async : false ,
type : 'POST' ,
2023-12-10 02:48:41 +01:00
url : TOKENIZER _URLS [ tokenizers . API _KOBOLD ] . count ,
2023-12-10 02:35:11 +01:00
data : JSON . stringify ( {
text : str ,
url : api _server ,
} ) ,
2023-12-10 02:20:53 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
success : function ( data ) {
if ( typeof data . count === 'number' ) {
tokenCount = data . count ;
} else {
tokenCount = apiFailureTokenCount ( str ) ;
}
} ,
} ) ;
2023-12-10 02:53:16 +01:00
return tokenCount ;
2023-12-10 02:20:53 +01:00
}
2023-12-10 02:35:11 +01:00
function getTextgenAPITokenizationParams ( str ) {
return {
text : str ,
api _type : textgen _settings . type ,
2023-12-19 15:38:11 +01:00
url : getTextGenServer ( ) ,
2023-12-18 21:38:28 +01:00
legacy _api : textgen _settings . legacy _api && ( textgen _settings . type === OOBA || textgen _settings . type === APHRODITE ) ,
2023-12-10 02:35:11 +01:00
} ;
}
2023-12-10 02:20:53 +01:00
/ * *
* Count tokens using the AI provider ' s API .
* @ param { string } str String to tokenize .
2023-12-10 02:53:16 +01:00
* @ returns { number } Token count .
2023-12-10 02:20:53 +01:00
* /
2023-12-10 02:53:16 +01:00
function countTokensFromTextgenAPI ( str ) {
2023-12-10 02:08:48 +01:00
let tokenCount = 0 ;
jQuery . ajax ( {
async : false ,
type : 'POST' ,
2023-12-10 02:48:41 +01:00
url : TOKENIZER _URLS [ tokenizers . API _TEXTGENERATIONWEBUI ] . count ,
2023-12-10 02:20:53 +01:00
data : JSON . stringify ( getTextgenAPITokenizationParams ( str ) ) ,
2023-12-10 02:08:48 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
success : function ( data ) {
if ( typeof data . count === 'number' ) {
tokenCount = data . count ;
} else {
tokenCount = apiFailureTokenCount ( str ) ;
2023-08-24 19:19:57 +02:00
}
2023-12-02 21:06:57 +01:00
} ,
2023-08-23 01:38:43 +02:00
} ) ;
2023-08-24 19:19:57 +02:00
2023-12-10 02:53:16 +01:00
return tokenCount ;
2023-08-23 01:38:43 +02:00
}
2023-12-10 02:08:48 +01:00
function apiFailureTokenCount ( str ) {
console . error ( 'Error counting tokens' ) ;
if ( ! sessionStorage . getItem ( TOKENIZER _WARNING _KEY ) ) {
toastr . warning (
'Your selected API doesn\'t support the tokenization endpoint. Using estimated counts.' ,
'Error counting tokens' ,
{ timeOut : 10000 , preventDuplicates : true } ,
) ;
sessionStorage . setItem ( TOKENIZER _WARNING _KEY , String ( true ) ) ;
}
return guesstimate ( str ) ;
}
2023-08-27 17:27:34 +02:00
/ * *
* Calls the underlying tokenizer model to encode a string to tokens .
* @ param { string } endpoint API endpoint .
* @ param { string } str String to tokenize .
* @ returns { number [ ] } Array of token ids .
* /
2023-12-10 02:48:41 +01:00
function getTextTokensFromServer ( endpoint , str ) {
2023-08-23 01:38:43 +02:00
let ids = [ ] ;
jQuery . ajax ( {
async : false ,
type : 'POST' ,
url : endpoint ,
2023-12-10 02:35:11 +01:00
data : JSON . stringify ( { text : str } ) ,
2023-12-02 19:04:51 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
2023-08-23 01:38:43 +02:00
success : function ( data ) {
ids = data . ids ;
2023-11-06 01:42:51 +01:00
// Don't want to break reverse compatibility, so sprinkle in some of the JS magic
if ( Array . isArray ( data . chunks ) ) {
Object . defineProperty ( ids , 'chunks' , { value : data . chunks } ) ;
}
2023-12-02 21:06:57 +01:00
} ,
2023-08-23 01:38:43 +02:00
} ) ;
return ids ;
}
2023-12-10 02:08:48 +01:00
/ * *
* Calls the AI provider ' s tokenize API to encode a string to tokens .
* @ param { string } str String to tokenize .
* @ returns { number [ ] } Array of token ids .
* /
2023-12-10 02:48:41 +01:00
function getTextTokensFromTextgenAPI ( str ) {
2023-12-10 02:08:48 +01:00
let ids = [ ] ;
jQuery . ajax ( {
async : false ,
type : 'POST' ,
2023-12-10 02:48:41 +01:00
url : TOKENIZER _URLS [ tokenizers . API _TEXTGENERATIONWEBUI ] . encode ,
2023-12-10 02:20:53 +01:00
data : JSON . stringify ( getTextgenAPITokenizationParams ( str ) ) ,
2023-12-10 02:08:48 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
success : function ( data ) {
ids = data . ids ;
} ,
} ) ;
return ids ;
}
2023-12-14 00:28:18 +01:00
/ * *
* Calls the AI provider ' s tokenize API to encode a string to tokens .
* @ param { string } str String to tokenize .
* @ returns { number [ ] } Array of token ids .
* /
function getTextTokensFromKoboldAPI ( str ) {
let ids = [ ] ;
jQuery . ajax ( {
async : false ,
type : 'POST' ,
url : TOKENIZER _URLS [ tokenizers . API _KOBOLD ] . encode ,
data : JSON . stringify ( {
text : str ,
url : api _server ,
} ) ,
dataType : 'json' ,
contentType : 'application/json' ,
success : function ( data ) {
ids = data . ids ;
} ,
} ) ;
return ids ;
}
2023-08-27 17:27:34 +02:00
/ * *
* Calls the underlying tokenizer model to decode token ids to text .
* @ param { string } endpoint API endpoint .
* @ param { number [ ] } ids Array of token ids
* /
2023-12-10 02:48:41 +01:00
function decodeTextTokensFromServer ( endpoint , ids ) {
2023-08-27 17:27:34 +02:00
let text = '' ;
jQuery . ajax ( {
async : false ,
type : 'POST' ,
url : endpoint ,
data : JSON . stringify ( { ids : ids } ) ,
2023-12-02 19:04:51 +01:00
dataType : 'json' ,
contentType : 'application/json' ,
2023-08-27 17:27:34 +02:00
success : function ( data ) {
text = data . text ;
2023-12-02 21:06:57 +01:00
} ,
2023-08-27 17:27:34 +02:00
} ) ;
return text ;
}
/ * *
2023-12-10 01:43:33 +01:00
* Encodes a string to tokens using the server API .
2023-08-27 17:27:34 +02:00
* @ param { number } tokenizerType Tokenizer type .
* @ param { string } str String to tokenize .
* @ returns { number [ ] } Array of token ids .
* /
2023-08-23 01:38:43 +02:00
export function getTextTokens ( tokenizerType , str ) {
switch ( tokenizerType ) {
2023-12-10 05:57:21 +01:00
case tokenizers . API _CURRENT :
2023-12-10 15:09:00 +01:00
return getTextTokens ( currentRemoteTokenizerAPI ( ) , str ) ;
2023-12-10 02:20:53 +01:00
case tokenizers . API _TEXTGENERATIONWEBUI :
2023-12-10 02:48:41 +01:00
return getTextTokensFromTextgenAPI ( str ) ;
2023-12-14 00:28:18 +01:00
case tokenizers . API _KOBOLD :
return getTextTokensFromKoboldAPI ( str ) ;
2023-12-10 02:48:41 +01:00
default : {
const tokenizerEndpoints = TOKENIZER _URLS [ tokenizerType ] ;
if ( ! tokenizerEndpoints ) {
2023-12-10 15:21:06 +01:00
apiFailureTokenCount ( str ) ;
2023-12-10 02:48:41 +01:00
console . warn ( 'Unknown tokenizer type' , tokenizerType ) ;
return [ ] ;
}
let endpointUrl = tokenizerEndpoints . encode ;
if ( ! endpointUrl ) {
2023-12-10 15:21:06 +01:00
apiFailureTokenCount ( str ) ;
2023-12-10 02:48:41 +01:00
console . warn ( 'This tokenizer type does not support encoding' , tokenizerType ) ;
return [ ] ;
}
if ( tokenizerType === tokenizers . OPENAI ) {
endpointUrl += ` ?model= ${ getTokenizerModel ( ) } ` ;
}
return getTextTokensFromServer ( endpointUrl , str ) ;
}
2023-08-23 01:38:43 +02:00
}
}
2023-08-27 17:27:34 +02:00
/ * *
2023-12-10 01:43:33 +01:00
* Decodes token ids to text using the server API .
2023-09-16 17:48:06 +02:00
* @ param { number } tokenizerType Tokenizer type .
2023-08-27 17:27:34 +02:00
* @ param { number [ ] } ids Array of token ids
* /
export function decodeTextTokens ( tokenizerType , ids ) {
2023-12-10 05:57:21 +01:00
// Currently, neither remote API can decode, but this may change in the future. Put this guard here to be safe
if ( tokenizerType === tokenizers . API _CURRENT ) {
2023-12-10 15:09:00 +01:00
return decodeTextTokens ( tokenizers . NONE , ids ) ;
2023-12-10 05:57:21 +01:00
}
2023-12-10 02:48:41 +01:00
const tokenizerEndpoints = TOKENIZER _URLS [ tokenizerType ] ;
if ( ! tokenizerEndpoints ) {
console . warn ( 'Unknown tokenizer type' , tokenizerType ) ;
return [ ] ;
}
let endpointUrl = tokenizerEndpoints . decode ;
if ( ! endpointUrl ) {
console . warn ( 'This tokenizer type does not support decoding' , tokenizerType ) ;
return [ ] ;
}
if ( tokenizerType === tokenizers . OPENAI ) {
endpointUrl += ` ?model= ${ getTokenizerModel ( ) } ` ;
2023-08-27 17:27:34 +02:00
}
2023-12-10 02:48:41 +01:00
return decodeTextTokensFromServer ( endpointUrl , ids ) ;
2023-08-27 17:27:34 +02:00
}
2023-10-24 23:32:49 +02:00
export async function initTokenizers ( ) {
2023-08-23 01:38:43 +02:00
await loadTokenCache ( ) ;
2023-08-27 22:20:43 +02:00
registerDebugFunction ( 'resetTokenCache' , 'Reset token cache' , 'Purges the calculated token counts. Use this if you want to force a full re-tokenization of all chats or suspect the token counts are wrong.' , resetTokenCache ) ;
2023-10-24 23:32:49 +02:00
}