Merge pull request #2794 from SillyTavern/more-tokenizers Implement downloadable tokenizers
Signed| @@ -95,6 +95,9 @@ requestOverrides: [] | |||
| 95 | enableExtensions: true | 95 | enableExtensions: true |
| 96 | # Automatically update extensions when a release version changes | 96 | # Automatically update extensions when a release version changes |
| 97 | enableExtensionsAutoUpdate: true | 97 | enableExtensionsAutoUpdate: true |
| 98 | # Additional model tokenizers can be downloaded on demand. | ||
| 99 | # Disabling will fallback to another locally available tokenizer. | ||
| 100 | enableDownloadableTokenizers: true | ||
| 98 | # Extension settings | 101 | # Extension settings |
| 99 | extras: | 102 | extras: |
| 100 | # Disables automatic model download from HuggingFace | 103 | # Disables automatic model download from HuggingFace |
| @@ -3289,6 +3289,8 @@ | |||
| 3289 | <option value="12">Llama 3</option> | 3289 | <option value="12">Llama 3</option> |
| 3290 | <option value="13">Gemma / Gemini</option> | 3290 | <option value="13">Gemma / Gemini</option> |
| 3291 | <option value="14">Jamba</option> | 3291 | <option value="14">Jamba</option> |
| 3292 | <option value="15">Qwen2</option> | ||
| 3293 | <option value="16">Command-R</option> | ||
| 3292 | <option value="4">NerdStash (NovelAI Clio)</option> | 3294 | <option value="4">NerdStash (NovelAI Clio)</option> |
| 3293 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> | 3295 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> |
| 3294 | <option value="7">Mistral</option> | 3296 | <option value="7">Mistral</option> |
| @@ -590,6 +590,9 @@ function calculateOpenRouterCost() { | |||
| 590 | export function getCurrentOpenRouterModelTokenizer() { | 590 | export function getCurrentOpenRouterModelTokenizer() { |
| 591 | const modelId = textgen_settings.openrouter_model; | 591 | const modelId = textgen_settings.openrouter_model; |
| 592 | const model = openRouterModels.find(x => x.id === modelId); | 592 | const model = openRouterModels.find(x => x.id === modelId); |
| 593 | if (modelId?.includes('jamba')) { | ||
| 594 | return tokenizers.JAMBA; | ||
| 595 | } | ||
| 593 | switch (model?.architecture?.tokenizer) { | 596 | switch (model?.architecture?.tokenizer) { |
| 594 | case 'Llama2': | 597 | case 'Llama2': |
| 595 | return tokenizers.LLAMA; | 598 | return tokenizers.LLAMA; |
| @@ -603,6 +606,10 @@ export function getCurrentOpenRouterModelTokenizer() { | |||
| 603 | return tokenizers.GEMMA; | 606 | return tokenizers.GEMMA; |
| 604 | case 'Claude': | 607 | case 'Claude': |
| 605 | return tokenizers.CLAUDE; | 608 | return tokenizers.CLAUDE; |
| 609 | case 'Cohere': | ||
| 610 | return tokenizers.COMMAND_R; | ||
| 611 | case 'Qwen': | ||
| 612 | return tokenizers.QWEN2; | ||
| 606 | default: | 613 | default: |
| 607 | return tokenizers.OPENAI; | 614 | return tokenizers.OPENAI; |
| 608 | } | 615 | } |
| @@ -28,6 +28,8 @@ export const tokenizers = { | |||
| 28 | LLAMA3: 12, | 28 | LLAMA3: 12, |
| 29 | GEMMA: 13, | 29 | GEMMA: 13, |
| 30 | JAMBA: 14, | 30 | JAMBA: 14, |
| 31 | QWEN2: 15, | ||
| 32 | COMMAND_R: 16, | ||
| 31 | BEST_MATCH: 99, | 33 | BEST_MATCH: 99, |
| 32 | }; | 34 | }; |
| 33 | 35 | ||
| @@ -105,6 +107,16 @@ const TOKENIZER_URLS = { | |||
| 105 | decode: '/api/tokenizers/jamba/decode', | 107 | decode: '/api/tokenizers/jamba/decode', |
| 106 | count: '/api/tokenizers/jamba/encode', | 108 | count: '/api/tokenizers/jamba/encode', |
| 107 | }, | 109 | }, |
| 110 | [tokenizers.QWEN2]: { | ||
| 111 | encode: '/api/tokenizers/qwen2/encode', | ||
| 112 | decode: '/api/tokenizers/qwen2/decode', | ||
| 113 | count: '/api/tokenizers/qwen2/encode', | ||
| 114 | }, | ||
| 115 | [tokenizers.COMMAND_R]: { | ||
| 116 | encode: '/api/tokenizers/command-r/encode', | ||
| 117 | decode: '/api/tokenizers/command-r/decode', | ||
| 118 | count: '/api/tokenizers/command-r/encode', | ||
| 119 | }, | ||
| 108 | [tokenizers.API_TEXTGENERATIONWEBUI]: { | 120 | [tokenizers.API_TEXTGENERATIONWEBUI]: { |
| 109 | encode: '/api/tokenizers/remote/textgenerationwebui/encode', | 121 | encode: '/api/tokenizers/remote/textgenerationwebui/encode', |
| 110 | count: '/api/tokenizers/remote/textgenerationwebui/encode', | 122 | count: '/api/tokenizers/remote/textgenerationwebui/encode', |
| @@ -293,6 +305,12 @@ export function getTokenizerBestMatch(forApi) { | |||
| 293 | if (model.includes('jamba')) { | 305 | if (model.includes('jamba')) { |
| 294 | return tokenizers.JAMBA; | 306 | return tokenizers.JAMBA; |
| 295 | } | 307 | } |
| 308 | if (model.includes('command-r')) { | ||
| 309 | return tokenizers.COMMAND_R; | ||
| 310 | } | ||
| 311 | if (model.includes('qwen2')) { | ||
| 312 | return tokenizers.QWEN2; | ||
| 313 | } | ||
| 296 | } | 314 | } |
| 297 | 315 | ||
| 298 | return tokenizers.LLAMA; | 316 | return tokenizers.LLAMA; |
| @@ -511,6 +529,8 @@ export function getTokenizerModel() { | |||
| 511 | const yiTokenizer = 'yi'; | 529 | const yiTokenizer = 'yi'; |
| 512 | const gemmaTokenizer = 'gemma'; | 530 | const gemmaTokenizer = 'gemma'; |
| 513 | const jambaTokenizer = 'jamba'; | 531 | const jambaTokenizer = 'jamba'; |
| 532 | const qwen2Tokenizer = 'qwen2'; | ||
| 533 | const commandRTokenizer = 'command-r'; | ||
| 514 | 534 | ||
| 515 | // Assuming no one would use it for different models.. right? | 535 | // Assuming no one would use it for different models.. right? |
| 516 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { | 536 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { |
| @@ -558,6 +578,12 @@ export function getTokenizerModel() { | |||
| 558 | else if (model?.architecture?.tokenizer === 'Gemini') { | 578 | else if (model?.architecture?.tokenizer === 'Gemini') { |
| 559 | return gemmaTokenizer; | 579 | return gemmaTokenizer; |
| 560 | } | 580 | } |
| 581 | else if (model?.architecture?.tokenizer === 'Qwen') { | ||
| 582 | return qwen2Tokenizer; | ||
| 583 | } | ||
| 584 | else if (model?.architecture?.tokenizer === 'Cohere') { | ||
| 585 | return commandRTokenizer; | ||
| 586 | } | ||
| 561 | else if (oai_settings.openrouter_model.includes('gpt-4o')) { | 587 | else if (oai_settings.openrouter_model.includes('gpt-4o')) { |
| 562 | return gpt4oTokenizer; | 588 | return gpt4oTokenizer; |
| 563 | } | 589 | } |
| @@ -581,6 +607,10 @@ export function getTokenizerModel() { | |||
| 581 | } | 607 | } |
| 582 | } | 608 | } |
| 583 | 609 | ||
| 610 | if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) { | ||
| 611 | return commandRTokenizer; | ||
| 612 | } | ||
| 613 | |||
| 584 | if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { | 614 | if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { |
| 585 | return gemmaTokenizer; | 615 | return gemmaTokenizer; |
| 586 | } | 616 | } |
| @@ -4,13 +4,12 @@ const express = require('express'); | |||
| 4 | const { SentencePieceProcessor } = require('@agnai/sentencepiece-js'); | 4 | const { SentencePieceProcessor } = require('@agnai/sentencepiece-js'); |
| 5 | const tiktoken = require('tiktoken'); | 5 | const tiktoken = require('tiktoken'); |
| 6 | const { Tokenizer } = require('@agnai/web-tokenizers'); | 6 | const { Tokenizer } = require('@agnai/web-tokenizers'); |
| 7 | const { convertClaudePrompt, convertGooglePrompt } = require('../prompt-converters'); | 7 | const { convertClaudePrompt } = require('../prompt-converters'); |
| 8 | const { readSecret, SECRET_KEYS } = require('./secrets'); | ||
| 9 | const { TEXTGEN_TYPES } = require('../constants'); | 8 | const { TEXTGEN_TYPES } = require('../constants'); |
| 10 | const { jsonParser } = require('../express-common'); | 9 | const { jsonParser } = require('../express-common'); |
| 11 | const { setAdditionalHeaders } = require('../additional-headers'); | 10 | const { setAdditionalHeaders } = require('../additional-headers'); |
| 12 | 11 | const { getConfigValue, isValidUrl } = require('../util'); | |
| 13 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; | 12 | const writeFileAtomicSync = require('write-file-atomic').sync; |
| 14 | 13 | ||
| 15 | /** | 14 | /** |
| 16 | * @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler | 15 | * @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler |
| @@ -53,6 +52,65 @@ const TEXT_COMPLETION_MODELS = [ | |||
| 53 | ]; | 52 | ]; |
| 54 | 53 | ||
| 55 | const CHARS_PER_TOKEN = 3.35; | 54 | const CHARS_PER_TOKEN = 3.35; |
| 55 | const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Gets a path to the tokenizer model. Downloads the model if it's a URL. | ||
| 59 | * @param {string} model Model URL or path | ||
| 60 | * @param {string|undefined} fallbackModel Fallback model path\ | ||
| 61 | * @returns {Promise<string>} Path to the tokenizer model | ||
| 62 | */ | ||
| 63 | async function getPathToTokenizer(model, fallbackModel) { | ||
| 64 | if (!isValidUrl(model)) { | ||
| 65 | return model; | ||
| 66 | } | ||
| 67 | |||
| 68 | try { | ||
| 69 | const url = new URL(model); | ||
| 70 | |||
| 71 | if (!['https:', 'http:'].includes(url.protocol)) { | ||
| 72 | throw new Error('Invalid URL protocol'); | ||
| 73 | } | ||
| 74 | |||
| 75 | const fileName = url.pathname.split('/').pop(); | ||
| 76 | |||
| 77 | if (!fileName) { | ||
| 78 | throw new Error('Failed to extract the file name from the URL'); | ||
| 79 | } | ||
| 80 | |||
| 81 | const CACHE_PATH = path.join(global.DATA_ROOT, '_cache'); | ||
| 82 | if (!fs.existsSync(CACHE_PATH)) { | ||
| 83 | fs.mkdirSync(CACHE_PATH, { recursive: true }); | ||
| 84 | } | ||
| 85 | |||
| 86 | const cachedFile = path.join(CACHE_PATH, fileName); | ||
| 87 | if (fs.existsSync(cachedFile)) { | ||
| 88 | return cachedFile; | ||
| 89 | } | ||
| 90 | |||
| 91 | if (!IS_DOWNLOAD_ALLOWED) { | ||
| 92 | throw new Error('Downloading tokenizers is disabled, the model is not cached'); | ||
| 93 | } | ||
| 94 | |||
| 95 | console.log('Downloading tokenizer model:', model); | ||
| 96 | const response = await fetch(model); | ||
| 97 | if (!response.ok) { | ||
| 98 | throw new Error(`Failed to fetch the model: ${response.status} ${response.statusText}`); | ||
| 99 | } | ||
| 100 | |||
| 101 | const arrayBuffer = await response.arrayBuffer(); | ||
| 102 | writeFileAtomicSync(cachedFile, Buffer.from(arrayBuffer)); | ||
| 103 | return cachedFile; | ||
| 104 | } catch (error) { | ||
| 105 | const getLastSegment = str => str?.split('/')?.pop() || ''; | ||
| 106 | if (fallbackModel) { | ||
| 107 | console.log(`Could not get a tokenizer from ${getLastSegment(model)}. Reason: ${error.message}. Using a fallback model: ${getLastSegment(fallbackModel)}.`); | ||
| 108 | return fallbackModel; | ||
| 109 | } | ||
| 110 | |||
| 111 | throw new Error(`Failed to instantiate a tokenizer and fallback is not provided. Reason: ${error.message}`); | ||
| 112 | } | ||
| 113 | } | ||
| 56 | 114 | ||
| 57 | /** | 115 | /** |
| 58 | * Sentencepiece tokenizer for tokenizing text. | 116 | * Sentencepiece tokenizer for tokenizing text. |
| @@ -66,13 +124,19 @@ class SentencePieceTokenizer { | |||
| 66 | * @type {string} Path to the tokenizer model | 124 | * @type {string} Path to the tokenizer model |
| 67 | */ | 125 | */ |
| 68 | #model; | 126 | #model; |
| 127 | /** | ||
| 128 | * @type {string|undefined} Path to the fallback model | ||
| 129 | */ | ||
| 130 | #fallbackModel; | ||
| 69 | 131 | ||
| 70 | /** | 132 | /** |
| 71 | * Creates a new Sentencepiece tokenizer. | 133 | * Creates a new Sentencepiece tokenizer. |
| 72 | * @param {string} model Path to the tokenizer model | 134 | * @param {string} model Path to the tokenizer model |
| 135 | * @param {string} [fallbackModel] Path to the fallback model | ||
| 73 | */ | 136 | */ |
| 74 | constructor(model) { | 137 | constructor(model, fallbackModel) { |
| 75 | this.#model = model; | 138 | this.#model = model; |
| 139 | this.#fallbackModel = fallbackModel; | ||
| 76 | } | 140 | } |
| 77 | 141 | ||
| 78 | /** | 142 | /** |
| @@ -85,9 +149,10 @@ class SentencePieceTokenizer { | |||
| 85 | } | 149 | } |
| 86 | 150 | ||
| 87 | try { | 151 | try { |
| 152 | const pathToModel = await getPathToTokenizer(this.#model, this.#fallbackModel); | ||
| 88 | this.#instance = new SentencePieceProcessor(); | 153 | this.#instance = new SentencePieceProcessor(); |
| 89 | await this.#instance.load(this.#model); | 154 | await this.#instance.load(pathToModel); |
| 90 | console.log('Instantiated the tokenizer for', path.parse(this.#model).name); | 155 | console.log('Instantiated the tokenizer for', path.parse(pathToModel).name); |
| 91 | return this.#instance; | 156 | return this.#instance; |
| 92 | } catch (error) { | 157 | } catch (error) { |
| 93 | console.error('Sentencepiece tokenizer failed to load: ' + this.#model, error); | 158 | console.error('Sentencepiece tokenizer failed to load: ' + this.#model, error); |
| @@ -108,13 +173,19 @@ class WebTokenizer { | |||
| 108 | * @type {string} Path to the tokenizer model | 173 | * @type {string} Path to the tokenizer model |
| 109 | */ | 174 | */ |
| 110 | #model; | 175 | #model; |
| 176 | /** | ||
| 177 | * @type {string|undefined} Path to the fallback model | ||
| 178 | */ | ||
| 179 | #fallbackModel; | ||
| 111 | 180 | ||
| 112 | /** | 181 | /** |
| 113 | * Creates a new Web tokenizer. | 182 | * Creates a new Web tokenizer. |
| 114 | * @param {string} model Path to the tokenizer model | 183 | * @param {string} model Path to the tokenizer model |
| 184 | * @param {string} [fallbackModel] Path to the fallback model | ||
| 115 | */ | 185 | */ |
| 116 | constructor(model) { | 186 | constructor(model, fallbackModel) { |
| 117 | this.#model = model; | 187 | this.#model = model; |
| 188 | this.#fallbackModel = fallbackModel; | ||
| 118 | } | 189 | } |
| 119 | 190 | ||
| 120 | /** | 191 | /** |
| @@ -127,9 +198,10 @@ class WebTokenizer { | |||
| 127 | } | 198 | } |
| 128 | 199 | ||
| 129 | try { | 200 | try { |
| 130 | const arrayBuffer = fs.readFileSync(this.#model).buffer; | 201 | const pathToModel = await getPathToTokenizer(this.#model, this.#fallbackModel); |
| 202 | const arrayBuffer = fs.readFileSync(pathToModel).buffer; | ||
| 131 | this.#instance = await Tokenizer.fromJSON(arrayBuffer); | 203 | this.#instance = await Tokenizer.fromJSON(arrayBuffer); |
| 132 | console.log('Instantiated the tokenizer for', path.parse(this.#model).name); | 204 | console.log('Instantiated the tokenizer for', path.parse(pathToModel).name); |
| 133 | return this.#instance; | 205 | return this.#instance; |
| 134 | } catch (error) { | 206 | } catch (error) { |
| 135 | console.error('Web tokenizer failed to load: ' + this.#model, error); | 207 | console.error('Web tokenizer failed to load: ' + this.#model, error); |
| @@ -147,6 +219,8 @@ const spp_gemma = new SentencePieceTokenizer('src/tokenizers/gemma.model'); | |||
| 147 | const spp_jamba = new SentencePieceTokenizer('src/tokenizers/jamba.model'); | 219 | const spp_jamba = new SentencePieceTokenizer('src/tokenizers/jamba.model'); |
| 148 | const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json'); | 220 | const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json'); |
| 149 | const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json'); | 221 | const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json'); |
| 222 | const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json'); | ||
| 223 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); | ||
| 150 | 224 | ||
| 151 | const sentencepieceTokenizers = [ | 225 | const sentencepieceTokenizers = [ |
| 152 | 'llama', | 226 | 'llama', |
| @@ -332,6 +406,14 @@ function getTokenizerModel(requestModel) { | |||
| 332 | return 'jamba'; | 406 | return 'jamba'; |
| 333 | } | 407 | } |
| 334 | 408 | ||
| 409 | if (requestModel.includes('qwen2')) { | ||
| 410 | return 'qwen2'; | ||
| 411 | } | ||
| 412 | |||
| 413 | if (requestModel.includes('command-r')) { | ||
| 414 | return 'command-r'; | ||
| 415 | } | ||
| 416 | |||
| 335 | // default | 417 | // default |
| 336 | return 'gpt-3.5-turbo'; | 418 | return 'gpt-3.5-turbo'; |
| 337 | } | 419 | } |
| @@ -557,6 +639,8 @@ router.post('/jamba/encode', jsonParser, createSentencepieceEncodingHandler(spp_ | |||
| 557 | router.post('/gpt2/encode', jsonParser, createTiktokenEncodingHandler('gpt2')); | 639 | router.post('/gpt2/encode', jsonParser, createTiktokenEncodingHandler('gpt2')); |
| 558 | router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(claude_tokenizer)); | 640 | router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(claude_tokenizer)); |
| 559 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); | 641 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); |
| 642 | router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer)); | ||
| 643 | router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer)); | ||
| 560 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); | 644 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); |
| 561 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); | 645 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); |
| 562 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); | 646 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); |
| @@ -567,6 +651,8 @@ router.post('/jamba/decode', jsonParser, createSentencepieceDecodingHandler(spp_ | |||
| 567 | router.post('/gpt2/decode', jsonParser, createTiktokenDecodingHandler('gpt2')); | 651 | router.post('/gpt2/decode', jsonParser, createTiktokenDecodingHandler('gpt2')); |
| 568 | router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(claude_tokenizer)); | 652 | router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(claude_tokenizer)); |
| 569 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); | 653 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); |
| 654 | router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer)); | ||
| 655 | router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer)); | ||
| 570 | 656 | ||
| 571 | router.post('/openai/encode', jsonParser, async function (req, res) { | 657 | router.post('/openai/encode', jsonParser, async function (req, res) { |
| 572 | try { | 658 | try { |
| @@ -607,6 +693,16 @@ router.post('/openai/encode', jsonParser, async function (req, res) { | |||
| 607 | return handler(req, res); | 693 | return handler(req, res); |
| 608 | } | 694 | } |
| 609 | 695 | ||
| 696 | if (queryModel.includes('qwen2')) { | ||
| 697 | const handler = createWebTokenizerEncodingHandler(qwen2Tokenizer); | ||
| 698 | return handler(req, res); | ||
| 699 | } | ||
| 700 | |||
| 701 | if (queryModel.includes('command-r')) { | ||
| 702 | const handler = createWebTokenizerEncodingHandler(commandTokenizer); | ||
| 703 | return handler(req, res); | ||
| 704 | } | ||
| 705 | |||
| 610 | const model = getTokenizerModel(queryModel); | 706 | const model = getTokenizerModel(queryModel); |
| 611 | const handler = createTiktokenEncodingHandler(model); | 707 | const handler = createTiktokenEncodingHandler(model); |
| 612 | return handler(req, res); | 708 | return handler(req, res); |
| @@ -655,6 +751,16 @@ router.post('/openai/decode', jsonParser, async function (req, res) { | |||
| 655 | return handler(req, res); | 751 | return handler(req, res); |
| 656 | } | 752 | } |
| 657 | 753 | ||
| 754 | if (queryModel.includes('qwen2')) { | ||
| 755 | const handler = createWebTokenizerDecodingHandler(qwen2Tokenizer); | ||
| 756 | return handler(req, res); | ||
| 757 | } | ||
| 758 | |||
| 759 | if (queryModel.includes('command-r')) { | ||
| 760 | const handler = createWebTokenizerDecodingHandler(commandTokenizer); | ||
| 761 | return handler(req, res); | ||
| 762 | } | ||
| 763 | |||
| 658 | const model = getTokenizerModel(queryModel); | 764 | const model = getTokenizerModel(queryModel); |
| 659 | const handler = createTiktokenDecodingHandler(model); | 765 | const handler = createTiktokenDecodingHandler(model); |
| 660 | return handler(req, res); | 766 | return handler(req, res); |
| @@ -711,6 +817,20 @@ router.post('/openai/count', jsonParser, async function (req, res) { | |||
| 711 | return res.send({ 'token_count': num_tokens }); | 817 | return res.send({ 'token_count': num_tokens }); |
| 712 | } | 818 | } |
| 713 | 819 | ||
| 820 | if (model === 'qwen2') { | ||
| 821 | const instance = await qwen2Tokenizer.get(); | ||
| 822 | if (!instance) throw new Error('Failed to load the Qwen2 tokenizer'); | ||
| 823 | num_tokens = countWebTokenizerTokens(instance, req.body); | ||
| 824 | return res.send({ 'token_count': num_tokens }); | ||
| 825 | } | ||
| 826 | |||
| 827 | if (model === 'command-r') { | ||
| 828 | const instance = await commandTokenizer.get(); | ||
| 829 | if (!instance) throw new Error('Failed to load the Command-R tokenizer'); | ||
| 830 | num_tokens = countWebTokenizerTokens(instance, req.body); | ||
| 831 | return res.send({ 'token_count': num_tokens }); | ||
| 832 | } | ||
| 833 | |||
| 714 | const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1; | 834 | const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1; |
| 715 | const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3; | 835 | const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3; |
| 716 | const tokensPadding = 3; | 836 | const tokensPadding = 3; |
| @@ -647,6 +647,20 @@ function getSeparator(n) { | |||
| 647 | return '='.repeat(n); | 647 | return '='.repeat(n); |
| 648 | } | 648 | } |
| 649 | 649 | ||
| 650 | /** | ||
| 651 | * Checks if the string is a valid URL. | ||
| 652 | * @param {string} url String to check | ||
| 653 | * @returns {boolean} If the URL is valid | ||
| 654 | */ | ||
| 655 | function isValidUrl(url) { | ||
| 656 | try { | ||
| 657 | new URL(url); | ||
| 658 | return true; | ||
| 659 | } catch (error) { | ||
| 660 | return false; | ||
| 661 | } | ||
| 662 | } | ||
| 663 | |||
| 650 | module.exports = { | 664 | module.exports = { |
| 651 | getConfig, | 665 | getConfig, |
| 652 | getConfigValue, | 666 | getConfigValue, |
| @@ -676,4 +690,5 @@ module.exports = { | |||
| 676 | makeHttp2Request, | 690 | makeHttp2Request, |
| 677 | removeColorFormatting, | 691 | removeColorFormatting, |
| 678 | getSeparator, | 692 | getSeparator, |
| 693 | isValidUrl, | ||
| 679 | }; | 694 | }; |