Merge branch 'staging' into sysprompt-divorce
| @@ -19,7 +19,7 @@ ENV NODE_ENV=production | ||
| 19 | 19 | COPY package*.json post-install.js ./ |
| 20 | 20 | RUN \ |
| 21 | 21 | echo "*** Install npm packages ***" && \ |
| 22 | 22 | npm i --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev && npm cache clean --force |
| 23 | 23 | |
| 24 | 24 | # Bundle app source |
| 25 | 25 | COPY . ./ |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | @echo off |
| 2 | 2 | pushd %~dp0 |
| 3 | 3 | set NODE_ENV=production |
| 4 | 4 | call npm install --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev |
| 5 | 5 | node server.js %* |
| 6 | 6 | pause |
| 7 | 7 | popd |
| @@ -12,7 +12,7 @@ if %errorlevel% neq 0 ( | ||
| 12 | 12 | ) |
| 13 | 13 | ) |
| 14 | 14 | set NODE_ENV=production |
| 15 | 15 | call npm install --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev |
| 16 | 16 | node server.js %* |
| 17 | 17 | pause |
| 18 | 18 | popd |
| @@ -95,7 +95,7 @@ if %errorlevel% neq 0 ( | ||
| 95 | 95 | |
| 96 | 96 | echo Installing npm packages and starting server |
| 97 | 97 | set NODE_ENV=production |
| 98 | 98 | call npm install --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev |
| 99 | 99 | node server.js %* |
| 100 | 100 | |
| 101 | 101 | :end |
| @@ -316,3 +316,15 @@ | ||
| 316 | 316 | margin-left: 0.5em; |
| 317 | 317 | } |
| 318 | 318 | } |
| 319 | + | |
| 320 | +.completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt:disabled) > div:first-child::after { | |
| 321 | + content: 'The content of this prompt is pulled from elsewhere and cannot be edited here.'; | |
| 322 | + display: block; | |
| 323 | + width: 100%; | |
| 324 | + font-weight: 600; | |
| 325 | + text-align: center; | |
| 326 | +} | |
| 327 | + | |
| 328 | +.completion_prompt_manager_popup_entry_form_control #completion_prompt_manager_popup_entry_form_prompt:disabled { | |
| 329 | + visibility: hidden; | |
| 330 | +} | |
| @@ -3297,7 +3297,8 @@ | ||
| 3297 | 3297 | <option value="16">Command-R</option> |
| 3298 | 3298 | <option value="4">NerdStash (NovelAI Clio)</option> |
| 3299 | 3299 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> |
| 3300 | 3300 | <option value="7">Mistral V1</option> |
| 3301 | + <option value="17">Mistral Nemo</option> | |
| 3301 | 3302 | <option value="8">Yi</option> |
| 3302 | 3303 | <option value="11">Claude 1/2</option> |
| 3303 | 3304 | <option value="6">API (WebUI / koboldcpp)</option> |
| @@ -6923,15 +6923,13 @@ export async function displayPastChats() { | ||
| 6923 | 6923 | } |
| 6924 | 6924 | // Check whether `text` {string} includes all of the `fragments` {string[]}. |
| 6925 | 6925 | function matchFragments(fragments, text) { |
| 6926 | 6926 | if (!text || !text.toLowerCase) {return false; |
| 6927 | - return false; | |
| 6927 | + return fragments.every(item => text.toLowerCase().includes(item)); | |
| 6928 | - } | |
| 6929 | - return fragments.every(item => text.includes(item)); | |
| 6930 | 6928 | } |
| 6931 | 6929 | const fragments = makeQueryFragments(searchQuery); |
| 6932 | 6930 | // At least one chat message must match *all* the fragments. |
| 6933 | 6931 | // Currently, this doesn't match if the fragment matches are distributed across several chat messages. |
| 6934 | 6932 | return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase())); |
| 6935 | 6933 | }); |
| 6936 | 6934 | |
| 6937 | 6935 | console.debug(filteredData); |
| @@ -427,12 +427,13 @@ class PromptManager { | ||
| 427 | 427 | |
| 428 | 428 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name; |
| 429 | 429 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system'; |
| 430 | 430 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content ?? ''; |
| 431 | 431 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0; |
| 432 | 432 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH; |
| 433 | 433 | document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; |
| 434 | 434 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false; |
| 435 | 435 | document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden'; |
| 436 | + document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').disabled = prompt.marker ?? false; | |
| 436 | 437 | |
| 437 | 438 | if (!this.systemPrompts.includes(promptId)) { |
| 438 | 439 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled'); |
| @@ -920,7 +921,15 @@ class PromptManager { | ||
| 920 | 921 | * @returns {boolean} True if the prompt can be edited, false otherwise. |
| 921 | 922 | */ |
| 922 | 923 | isPromptEditAllowed(prompt) { |
| 923 | - return !prompt.marker; | |
| 924 | + const forceEditPrompts = [ | |
| 925 | + 'charDescription', | |
| 926 | + 'charPersonality', | |
| 927 | + 'scenario', | |
| 928 | + 'personaDescription', | |
| 929 | + 'worldInfoBefore', | |
| 930 | + 'worldInfoAfter', | |
| 931 | + ]; | |
| 932 | + return forceEditPrompts.includes(prompt.identifier) || !prompt.marker; | |
| 924 | 933 | } |
| 925 | 934 | |
| 926 | 935 | /** |
| @@ -929,7 +938,17 @@ class PromptManager { | ||
| 929 | 938 | * @returns {boolean} True if the prompt can be deleted, false otherwise. |
| 930 | 939 | */ |
| 931 | 940 | isPromptToggleAllowed(prompt) { |
| 932 | - const forceTogglePrompts = ['charDescription', 'charPersonality', 'scenario', 'personaDescription', 'worldInfoBefore', 'worldInfoAfter', 'main', 'chatHistory', 'dialogueExamples']; | |
| 941 | + const forceTogglePrompts = [ | |
| 942 | + 'charDescription', | |
| 943 | + 'charPersonality', | |
| 944 | + 'scenario', | |
| 945 | + 'personaDescription', | |
| 946 | + 'worldInfoBefore', | |
| 947 | + 'worldInfoAfter', | |
| 948 | + 'main', | |
| 949 | + 'chatHistory', | |
| 950 | + 'dialogueExamples', | |
| 951 | + ]; | |
| 933 | 952 | return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier); |
| 934 | 953 | } |
| 935 | 954 | |
| @@ -1182,8 +1201,9 @@ class PromptManager { | ||
| 1182 | 1201 | const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block'); |
| 1183 | 1202 | |
| 1184 | 1203 | nameField.value = prompt.name ?? ''; |
| 1185 | 1204 | roleField.value = prompt.role ?? 'system'; |
| 1186 | 1205 | promptField.value = prompt.content ?? ''; |
| 1206 | + promptField.disabled = prompt.marker ?? false; | |
| 1187 | 1207 | injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE; |
| 1188 | 1208 | injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH; |
| 1189 | 1209 | injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; |
| @@ -1279,6 +1299,7 @@ class PromptManager { | ||
| 1279 | 1299 | nameField.value = ''; |
| 1280 | 1300 | roleField.selectedIndex = 0; |
| 1281 | 1301 | promptField.value = ''; |
| 1302 | + promptField.disabled = false; | |
| 1282 | 1303 | injectionPositionField.selectedIndex = 0; |
| 1283 | 1304 | injectionPositionField.removeAttribute('disabled'); |
| 1284 | 1305 | injectionDepthField.value = DEFAULT_DEPTH; |
| @@ -970,6 +970,12 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | ||
| 970 | 970 | } |
| 971 | 971 | |
| 972 | 972 | const prompt = prompts.get(source); |
| 973 | + | |
| 974 | + if (prompt.injection_position === INJECTION_POSITION.ABSOLUTE) { | |
| 975 | + promptManager.log(`Skipping prompt ${source} because it is an absolute prompt`); | |
| 976 | + return; | |
| 977 | + } | |
| 978 | + | |
| 973 | 979 | const index = target ? prompts.index(target) : prompts.index(source); |
| 974 | 980 | const collection = new MessageCollection(source); |
| 975 | 981 | collection.add(Message.fromPrompt(prompt)); |
| @@ -1014,8 +1020,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | ||
| 1014 | 1020 | acc.push(prompt.identifier); |
| 1015 | 1021 | return acc; |
| 1016 | 1022 | }, []); |
| 1017 | 1023 | const userAbsolutePromptsabsolutePrompts = prompts.collection |
| 1018 | 1024 | .filter((prompt) => false === prompt.system_prompt && prompt.injection_position === INJECTION_POSITION.ABSOLUTE) |
| 1019 | 1025 | .reduce((acc, prompt) => { |
| 1020 | 1026 | acc.push(prompt); |
| 1021 | 1027 | return acc; |
| @@ -1080,7 +1086,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | ||
| 1080 | 1086 | } |
| 1081 | 1087 | |
| 1082 | 1088 | // Add in-chat injections |
| 1083 | 1089 | messages = populationInjectionPrompts(userAbsolutePromptsabsolutePrompts, messages); |
| 1084 | 1090 | |
| 1085 | 1091 | // Decide whether dialogue examples should always be added |
| 1086 | 1092 | if (power_user.pin_examples) { |
| @@ -1217,6 +1223,18 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor | ||
| 1217 | 1223 | |
| 1218 | 1224 | // Merge system prompts with prompt manager prompts |
| 1219 | 1225 | systemPrompts.forEach(prompt => { |
| 1226 | + const collectionPrompt = prompts.get(prompt.identifier); | |
| 1227 | + | |
| 1228 | + // Apply system prompt role/depth overrides if they set in the prompt manager | |
| 1229 | + if (collectionPrompt) { | |
| 1230 | + // In-Chat / Relative | |
| 1231 | + prompt.injection_position = collectionPrompt.injection_position ?? prompt.injection_position; | |
| 1232 | + // Depth for In-Chat | |
| 1233 | + prompt.injection_depth = collectionPrompt.injection_depth ?? prompt.injection_depth; | |
| 1234 | + // Role (system, user, assistant) | |
| 1235 | + prompt.role = collectionPrompt.role ?? prompt.role; | |
| 1236 | + } | |
| 1237 | + | |
| 1220 | 1238 | const newPrompt = promptManager.preparePrompt(prompt); |
| 1221 | 1239 | const markerIndex = prompts.index(prompt.identifier); |
| 1222 | 1240 | |
| @@ -30,6 +30,7 @@ export const tokenizers = { | ||
| 30 | 30 | JAMBA: 14, |
| 31 | 31 | QWEN2: 15, |
| 32 | 32 | COMMAND_R: 16, |
| 33 | + NEMO: 17, | |
| 33 | 34 | BEST_MATCH: 99, |
| 34 | 35 | }; |
| 35 | 36 | |
| @@ -43,6 +44,7 @@ export const ENCODE_TOKENIZERS = [ | ||
| 43 | 44 | tokenizers.JAMBA, |
| 44 | 45 | tokenizers.QWEN2, |
| 45 | 46 | tokenizers.COMMAND_R, |
| 47 | + tokenizers.NEMO, | |
| 46 | 48 | // uncomment when NovelAI releases Kayra and Clio weights, lol |
| 47 | 49 | //tokenizers.NERD, |
| 48 | 50 | //tokenizers.NERD2, |
| @@ -121,6 +123,11 @@ const TOKENIZER_URLS = { | ||
| 121 | 123 | decode: '/api/tokenizers/command-r/decode', |
| 122 | 124 | count: '/api/tokenizers/command-r/encode', |
| 123 | 125 | }, |
| 126 | + [tokenizers.NEMO]: { | |
| 127 | + encode: '/api/tokenizers/nemo/encode', | |
| 128 | + decode: '/api/tokenizers/nemo/decode', | |
| 129 | + count: '/api/tokenizers/nemo/encode', | |
| 130 | + }, | |
| 124 | 131 | [tokenizers.API_TEXTGENERATIONWEBUI]: { |
| 125 | 132 | encode: '/api/tokenizers/remote/textgenerationwebui/encode', |
| 126 | 133 | count: '/api/tokenizers/remote/textgenerationwebui/encode', |
| @@ -535,6 +542,7 @@ export function getTokenizerModel() { | ||
| 535 | 542 | const jambaTokenizer = 'jamba'; |
| 536 | 543 | const qwen2Tokenizer = 'qwen2'; |
| 537 | 544 | const commandRTokenizer = 'command-r'; |
| 545 | + const nemoTokenizer = 'nemo'; | |
| 538 | 546 | |
| 539 | 547 | // Assuming no one would use it for different models.. right? |
| 540 | 548 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { |
| @@ -628,6 +636,9 @@ export function getTokenizerModel() { | ||
| 628 | 636 | } |
| 629 | 637 | |
| 630 | 638 | if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) { |
| 639 | + if (oai_settings.mistralai_model.includes('nemo') || oai_settings.mistralai_model.includes('pixtral')) { | |
| 640 | + return nemoTokenizer; | |
| 641 | + } | |
| 631 | 642 | return mistralTokenizer; |
| 632 | 643 | } |
| 633 | 644 | |
| @@ -4134,10 +4134,10 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4134 | 4134 | |
| 4135 | 4135 | switch (entry.position) { |
| 4136 | 4136 | case world_info_position.before: |
| 4137 | 4137 | WIBeforeEntries.unshift(substituteParams(content)); |
| 4138 | 4138 | break; |
| 4139 | 4139 | case world_info_position.after: |
| 4140 | 4140 | WIAfterEntries.unshift(substituteParams(content)); |
| 4141 | 4141 | break; |
| 4142 | 4142 | case world_info_position.EMTop: |
| 4143 | 4143 | EMEntries.unshift( |
| @@ -92,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) { | ||
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | 95 | const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n'); |
| 96 | - return chatContent; | |
| 97 | 96 | } |
| 98 | 97 | |
| 99 | 98 | /** |
| @@ -121,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) { | ||
| 121 | 120 | }); |
| 122 | 121 | } |
| 123 | 122 | |
| 124 | 123 | const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n'); |
| 125 | - return chatContent; | |
| 126 | 124 | } |
| 127 | 125 | |
| 128 | 126 | /** |
| @@ -159,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) { | ||
| 159 | 157 | return newChats; |
| 160 | 158 | } |
| 161 | 159 | |
| 160 | +/** | |
| 161 | + * Flattens `msg` and `swipes` data from Chub Chat format. | |
| 162 | + * Only changes enough to make it compatible with the standard chat serialization format. | |
| 163 | + * @param {string} userName User name | |
| 164 | + * @param {string} characterName Character name | |
| 165 | + * @param {string[]} lines serialised JSONL data | |
| 166 | + * @returns {string} Converted data | |
| 167 | + */ | |
| 168 | +function flattenChubChat(userName, characterName, lines) { | |
| 169 | + function flattenSwipe(swipe) { | |
| 170 | + return swipe.message ? swipe.message : swipe; | |
| 171 | + } | |
| 172 | + | |
| 173 | + function convert(line) { | |
| 174 | + const lineData = tryParse(line); | |
| 175 | + if (!lineData) return line; | |
| 176 | + | |
| 177 | + if (lineData.mes && lineData.mes.message) { | |
| 178 | + lineData.mes = lineData?.mes.message; | |
| 179 | + } | |
| 180 | + | |
| 181 | + if (lineData?.swipes && Array.isArray(lineData.swipes)) { | |
| 182 | + lineData.swipes = lineData.swipes.map(swipe => flattenSwipe(swipe)); | |
| 183 | + } | |
| 184 | + | |
| 185 | + return JSON.stringify(lineData); | |
| 186 | + } | |
| 187 | + | |
| 188 | + return (lines ?? []).map(convert).join('\n'); | |
| 189 | +} | |
| 190 | + | |
| 162 | 191 | const router = express.Router(); |
| 163 | 192 | |
| 164 | 193 | router.post('/save', jsonParser, function (request, response) { |
| @@ -273,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) { | ||
| 273 | 302 | } |
| 274 | 303 | try { |
| 275 | 304 | // Short path for JSONL files |
| 276 | 305 | if (request.body.format === 'jsonl') { |
| 277 | 306 | try { |
| 278 | 307 | const rawFile = fs.readFileSync(filename, 'utf8'); |
| 279 | 308 | const successMessage = { |
| @@ -283,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) { | ||
| 283 | 312 | |
| 284 | 313 | console.log(`Chat exported as ${exportfilename}`); |
| 285 | 314 | return response.status(200).json(successMessage); |
| 286 | - } | |
| 315 | + } catch (err) { | |
| 287 | - catch (err) { | |
| 288 | 316 | console.error(err); |
| 289 | 317 | const errorMessage = { |
| 290 | 318 | message: `Could not read JSONL file to export. Source chat file: ${filename}.`, |
| @@ -319,8 +347,7 @@ router.post('/export', jsonParser, async function (request, response) { | ||
| 319 | 347 | console.log(`Chat exported as ${exportfilename}`); |
| 320 | 348 | return response.status(200).json(successMessage); |
| 321 | 349 | }); |
| 322 | - } | |
| 350 | + } catch (err) { | |
| 323 | - catch (err) { | |
| 324 | 351 | console.log('chat export failed.'); |
| 325 | 352 | console.log(err); |
| 326 | 353 | return response.sendStatus(400); |
| @@ -396,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) { | ||
| 396 | 423 | } |
| 397 | 424 | |
| 398 | 425 | if (format === 'jsonl') { |
| 399 | 426 | constlet linelines = data.split('\n')[0]; |
| 427 | + const header = lines[0]; | |
| 428 | + | |
| 429 | + const jsonData = JSON.parse(header); | |
| 430 | + | |
| 431 | + if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) { | |
| 432 | + console.log('Incorrect chat format .jsonl'); | |
| 433 | + return response.send({ error: true }); | |
| 434 | + } | |
| 400 | 435 | |
| 401 | - const jsonData = JSON.parse(line); | |
| 436 | + // Do a tiny bit of work to import Chub Chat data | |
| 437 | + // Processing the entire file is so fast that it's not worth checking if it's a Chub chat first | |
| 438 | + let flattenedChat; | |
| 439 | + try { | |
| 440 | + // flattening is unlikely to break, but it's not worth failing to | |
| 441 | + // import normal chats in an attempt to import a Chub chat | |
| 442 | + flattenedChat = flattenChubChat(userName, characterName, lines); | |
| 443 | + } catch (error) { | |
| 444 | + console.warn('Failed to flatten Chub Chat data: ', error); | |
| 445 | + } | |
| 402 | 446 | |
| 403 | - if (jsonData.user_name !== undefined || jsonData.name !== undefined) { | |
| 404 | 447 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; |
| 405 | 448 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); |
| 449 | + if (flattenedChat !== data) { | |
| 450 | + writeFileAtomicSync(filePath, flattenedChat, 'utf8'); | |
| 451 | + } else { | |
| 406 | 452 | fs.copyFileSync(pathToUpload, filePath); |
| 453 | + } | |
| 407 | 454 | fs.unlinkSync(pathToUpload); |
| 408 | 455 | response.send({ res: true }); |
| 409 | - } else { | |
| 410 | - console.log('Incorrect chat format .jsonl'); | |
| 411 | - return response.send({ error: true }); | |
| 412 | - } | |
| 413 | 456 | } |
| 414 | 457 | } catch (error) { |
| 415 | 458 | console.error(error); |
| @@ -221,6 +221,7 @@ const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json'); | ||
| 221 | 221 | const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json'); |
| 222 | 222 | const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json'); |
| 223 | 223 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); |
| 224 | +const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json'); | |
| 224 | 225 | |
| 225 | 226 | const sentencepieceTokenizers = [ |
| 226 | 227 | 'llama', |
| @@ -418,6 +419,10 @@ function getTokenizerModel(requestModel) { | ||
| 418 | 419 | return 'command-r'; |
| 419 | 420 | } |
| 420 | 421 | |
| 422 | + if (requestModel.includes('nemo')) { | |
| 423 | + return 'nemo'; | |
| 424 | + } | |
| 425 | + | |
| 421 | 426 | // default |
| 422 | 427 | return 'gpt-3.5-turbo'; |
| 423 | 428 | } |
| @@ -645,6 +650,7 @@ router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(clau | ||
| 645 | 650 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); |
| 646 | 651 | router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer)); |
| 647 | 652 | router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer)); |
| 653 | +router.post('/nemo/encode', jsonParser, createWebTokenizerEncodingHandler(nemoTokenizer)); | |
| 648 | 654 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); |
| 649 | 655 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); |
| 650 | 656 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); |
| @@ -657,6 +663,7 @@ router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(clau | ||
| 657 | 663 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); |
| 658 | 664 | router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer)); |
| 659 | 665 | router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer)); |
| 666 | +router.post('/nemo/decode', jsonParser, createWebTokenizerDecodingHandler(nemoTokenizer)); | |
| 660 | 667 | |
| 661 | 668 | router.post('/openai/encode', jsonParser, async function (req, res) { |
| 662 | 669 | try { |
| @@ -707,6 +714,11 @@ router.post('/openai/encode', jsonParser, async function (req, res) { | ||
| 707 | 714 | return handler(req, res); |
| 708 | 715 | } |
| 709 | 716 | |
| 717 | + if (queryModel.includes('nemo')) { | |
| 718 | + const handler = createWebTokenizerEncodingHandler(nemoTokenizer); | |
| 719 | + return handler(req, res); | |
| 720 | + } | |
| 721 | + | |
| 710 | 722 | const model = getTokenizerModel(queryModel); |
| 711 | 723 | const handler = createTiktokenEncodingHandler(model); |
| 712 | 724 | return handler(req, res); |
| @@ -765,6 +777,11 @@ router.post('/openai/decode', jsonParser, async function (req, res) { | ||
| 765 | 777 | return handler(req, res); |
| 766 | 778 | } |
| 767 | 779 | |
| 780 | + if (queryModel.includes('nemo')) { | |
| 781 | + const handler = createWebTokenizerDecodingHandler(nemoTokenizer); | |
| 782 | + return handler(req, res); | |
| 783 | + } | |
| 784 | + | |
| 768 | 785 | const model = getTokenizerModel(queryModel); |
| 769 | 786 | const handler = createTiktokenDecodingHandler(model); |
| 770 | 787 | return handler(req, res); |
| @@ -835,6 +852,13 @@ router.post('/openai/count', jsonParser, async function (req, res) { | ||
| 835 | 852 | return res.send({ 'token_count': num_tokens }); |
| 836 | 853 | } |
| 837 | 854 | |
| 855 | + if (model === 'nemo') { | |
| 856 | + const instance = await nemoTokenizer.get(); | |
| 857 | + if (!instance) throw new Error('Failed to load the Nemo tokenizer'); | |
| 858 | + num_tokens = countWebTokenizerTokens(instance, req.body); | |
| 859 | + return res.send({ 'token_count': num_tokens }); | |
| 860 | + } | |
| 861 | + | |
| 838 | 862 | const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1; |
| 839 | 863 | const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3; |
| 840 | 864 | const tokensPadding = 3; |
| @@ -26,7 +26,7 @@ fi | ||
| 26 | 26 | |
| 27 | 27 | echo "Installing Node Modules..." |
| 28 | 28 | export NODE_ENV=production |
| 29 | 29 | npm i --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev |
| 30 | 30 | |
| 31 | 31 | echo "Entering SillyTavern..." |
| 32 | 32 | node "server.js" "$@" |