DeepSeek Closes #3233

4c7d160d41872e73e5ded5a2a600a618f8097392

Cohee <18619528+Cohee1207@users.noreply.github.com>

10 files changed, +165 -5Ignore whitespace
public/index.html+23 -4
@@ -682,7 +682,7 @@
682682 </span>
683683 </div>
684684 </div>
685685 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">
686686 <div class="range-block-title" data-i18n="Temperature">
687687 Temperature
688688 </div>
@@ -695,7 +695,7 @@
695695 </div>
696696 </div>
697697 </div>
698698 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">
699699 <div class="range-block-title" data-i18n="Frequency Penalty">
700700 Frequency Penalty
701701 </div>
@@ -708,7 +708,7 @@
708708 </div>
709709 </div>
710710 </div>
711711 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">
712712 <div class="range-block-title" data-i18n="Presence Penalty">
713713 Presence Penalty
714714 </div>
@@ -734,7 +734,7 @@
734734 </div>
735735 </div>
736736 </div>
737737 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">
738738 <div class="range-block-title" data-i18n="Top P">
739739 Top P
740740 </div>
@@ -2679,6 +2679,7 @@
26792679 <option value="blockentropy">Block Entropy</option>
26802680 <option value="claude">Claude</option>
26812681 <option value="cohere">Cohere</option>
2682+ <option value="deepseek">DeepSeek</option>
26822683 <option value="groq">Groq</option>
26832684 <option value="makersuite">Google AI Studio</option>
26842685 <option value="mistralai">MistralAI</option>
@@ -3192,6 +3193,23 @@
31923193 </select>
31933194 </div>
31943195 </div>
3196+ <div id="deepseek_form" data-source="deepseek">
3197+ <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>
3198+ <div class="flex-container">
3199+ <input id="api_key_deepseek" name="api_key_deepseek" class="text_pole flex1" value="" type="text" autocomplete="off">
3200+ <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_nanogpt"></div>
3201+ </div>
3202+ <div data-for="api_key_deepseek" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
3203+ For privacy reasons, your API key will be hidden after you reload the page.
3204+ </div>
3205+ <div>
3206+ <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>
3207+ <select id="model_deepseek_select">
3208+ <option value="deepseek-chat">deepseek-chat</option>
3209+ <option value="deepseek-coder">deepseek-coder</option>
3210+ </select>
3211+ </div>
3212+ </div>
31953213 <div id="perplexity_form" data-source="perplexity">
31963214 <h4 data-i18n="Perplexity API Key">Perplexity API Key</h4>
31973215 <div class="flex-container">
@@ -3762,6 +3780,7 @@
37623780 <option value="17">Mistral Nemo</option>
37633781 <option value="8">Yi</option>
37643782 <option value="11">Claude 1/2</option>
3783+ <option value="18">DeepSeek V3</option>
37653784 <option value="6">API (WebUI / koboldcpp)</option>
37663785 </select>
37673786 </div>
public/scripts/RossAscends-mods.js+1 -0
@@ -390,6 +390,7 @@ function RA_autoconnect(PrevApi) {
390390 || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
391391 || (secret_state[SECRET_KEYS.BLOCKENTROPY] && oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY)
392392 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
393+ || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
393394 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
394395 ) {
395396 $('#api_button_openai').trigger('click');
public/scripts/openai.js+78 -1
@@ -183,6 +183,7 @@ export const chat_completion_sources = {
183183 ZEROONEAI: '01ai',
184184 BLOCKENTROPY: 'blockentropy',
185185 NANOGPT: 'nanogpt',
186+ DEEPSEEK: 'deepseek',
186187};
187188
188189const character_names_behavior = {
@@ -261,6 +262,7 @@ const default_settings = {
261262 nanogpt_model: 'gpt-4o-mini',
262263 zerooneai_model: 'yi-large',
263264 blockentropy_model: 'be-70b-base-llama3.1',
265+ deepseek_model: 'deepseek-chat',
264266 custom_model: '',
265267 custom_url: '',
266268 custom_include_body: '',
@@ -339,6 +341,7 @@ const oai_settings = {
339341 nanogpt_model: 'gpt-4o-mini',
340342 zerooneai_model: 'yi-large',
341343 blockentropy_model: 'be-70b-base-llama3.1',
344+ deepseek_model: 'deepseek-chat',
342345 custom_model: '',
343346 custom_url: '',
344347 custom_include_body: '',
@@ -1523,6 +1526,8 @@ function getChatCompletionModel() {
15231526 return oai_settings.blockentropy_model;
15241527 case chat_completion_sources.NANOGPT:
15251528 return oai_settings.nanogpt_model;
1529+ case chat_completion_sources.DEEPSEEK:
1530+ return oai_settings.deepseek_model;
15261531 default:
15271532 throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
15281533 }
@@ -1698,6 +1703,24 @@ function saveModelList(data) {
16981703
16991704 $('#model_nanogpt_select').val(oai_settings.nanogpt_model).trigger('change');
17001705 }
1706+
1707+ if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
1708+ $('#model_deepseek_select').empty();
1709+ model_list.forEach((model) => {
1710+ $('#model_deepseek_select').append(
1711+ $('<option>', {
1712+ value: model.id,
1713+ text: model.id,
1714+ }));
1715+ });
1716+
1717+ const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);
1718+ if (model_list.length > 0 && (!selectedModel || !oai_settings.deepseek_model)) {
1719+ oai_settings.deepseek_model = model_list[0].id;
1720+ }
1721+
1722+ $('#model_deepseek_select').val(oai_settings.deepseek_model).trigger('change');
1723+ }
17011724}
17021725
17031726function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@@ -1838,6 +1861,7 @@ async function sendOpenAIRequest(type, messages, signal) {
18381861 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
18391862 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
18401863 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
1864+ const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
18411865 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
18421866 const isQuiet = type === 'quiet';
18431867 const isImpersonate = type === 'impersonate';
@@ -1902,7 +1926,7 @@ async function sendOpenAIRequest(type, messages, signal) {
19021926 }
19031927
19041928 // Add logprobs request (currently OpenAI only, max 5 on their side)
19051929 if (useLogprobs && (isOAI || isCustom || isDeepSeek)) {
19061930 generate_data['logprobs'] = 5;
19071931 }
19081932
@@ -2000,6 +2024,11 @@ async function sendOpenAIRequest(type, messages, signal) {
20002024 delete generate_data.stop;
20012025 }
20022026
2027+ // https://api-docs.deepseek.com/api/create-chat-completion
2028+ if (isDeepSeek) {
2029+ generate_data.top_p = generate_data.top_p || Number.EPSILON;
2030+ }
2031+
20032032 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
20042033 generate_data['seed'] = oai_settings.seed;
20052034 }
@@ -2122,6 +2151,7 @@ function parseChatCompletionLogprobs(data) {
21222151
21232152 switch (oai_settings.chat_completion_source) {
21242153 case chat_completion_sources.OPENAI:
2154+ case chat_completion_sources.DEEPSEEK:
21252155 case chat_completion_sources.CUSTOM:
21262156 if (!data.choices?.length) {
21272157 return null;
@@ -3041,6 +3071,7 @@ function loadOpenAISettings(data, settings) {
30413071 oai_settings.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;
30423072 oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
30433073 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
3074+ oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
30443075 oai_settings.blockentropy_model = settings.blockentropy_model ?? default_settings.blockentropy_model;
30453076 oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
30463077 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
@@ -3122,6 +3153,8 @@ function loadOpenAISettings(data, settings) {
31223153 $(`#model_groq_select option[value="${oai_settings.groq_model}"`).attr('selected', true);
31233154 $('#model_nanogpt_select').val(oai_settings.nanogpt_model);
31243155 $(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).attr('selected', true);
3156+ $('#model_deepseek_select').val(oai_settings.deepseek_model);
3157+ $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
31253158 $('#model_01ai_select').val(oai_settings.zerooneai_model);
31263159 $('#model_blockentropy_select').val(oai_settings.blockentropy_model);
31273160 $('#custom_model_id').val(oai_settings.custom_model);
@@ -3861,6 +3894,7 @@ function onSettingsPresetChange() {
38613894 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false],
38623895 groq_model: ['#model_groq_select', 'groq_model', false],
38633896 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],
3897+ deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
38643898 zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
38653899 blockentropy_model: ['#model_blockentropy_select', 'blockentropy_model', false],
38663900 custom_model: ['#custom_model_id', 'custom_model', false],
@@ -4120,6 +4154,16 @@ async function onModelChange() {
41204154 oai_settings.nanogpt_model = value;
41214155 }
41224156
4157+ if ($(this).is('#model_deepseek_select')) {
4158+ if (!value) {
4159+ console.debug('Null DeepSeek model selected. Ignoring.');
4160+ return;
4161+ }
4162+
4163+ console.log('DeepSeek model changed to', value);
4164+ oai_settings.deepseek_model = value;
4165+ }
4166+
41234167 if (value && $(this).is('#model_01ai_select')) {
41244168 console.log('01.AI model changed to', value);
41254169 oai_settings.zerooneai_model = value;
@@ -4436,6 +4480,22 @@ async function onModelChange() {
44364480 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
44374481 }
44384482
4483+ if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
4484+ if (oai_settings.max_context_unlocked) {
4485+ $('#openai_max_context').attr('max', unlocked_max);
4486+ } else if (oai_settings.deepseek_model == 'deepseek-chat') {
4487+ $('#openai_max_context').attr('max', max_64k);
4488+ } else if (oai_settings.deepseek_model == 'deepseek-coder') {
4489+ $('#openai_max_context').attr('max', max_16k);
4490+ } else {
4491+ $('#openai_max_context').attr('max', max_64k);
4492+ }
4493+
4494+ oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
4495+ $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4496+ $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4497+ }
4498+
44394499 if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
44404500 oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);
44414501 $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');
@@ -4653,6 +4713,19 @@ async function onConnectButtonClick(e) {
46534713 }
46544714 }
46554715
4716+ if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
4717+ const api_key_deepseek = String($('#api_key_deepseek').val()).trim();
4718+
4719+ if (api_key_deepseek.length) {
4720+ await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek);
4721+ }
4722+
4723+ if (!secret_state[SECRET_KEYS.DEEPSEEK]) {
4724+ console.log('No secret key saved for DeepSeek');
4725+ return;
4726+ }
4727+ }
4728+
46564729 if (oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) {
46574730 const api_key_01ai = String($('#api_key_01ai').val()).trim();
46584731
@@ -4734,6 +4807,9 @@ function toggleChatCompletionForms() {
47344807 else if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
47354808 $('#model_blockentropy_select').trigger('change');
47364809 }
4810+ else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
4811+ $('#model_deepseek_select').trigger('change');
4812+ }
47374813 $('[data-source]').each(function () {
47384814 const validSources = $(this).data('source').split(',');
47394815 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
@@ -5447,6 +5523,7 @@ export function initOpenAI() {
54475523 $('#model_perplexity_select').on('change', onModelChange);
54485524 $('#model_groq_select').on('change', onModelChange);
54495525 $('#model_nanogpt_select').on('change', onModelChange);
5526+ $('#model_deepseek_select').on('change', onModelChange);
54505527 $('#model_01ai_select').on('change', onModelChange);
54515528 $('#model_blockentropy_select').on('change', onModelChange);
54525529 $('#model_custom_select').on('change', onModelChange);
public/scripts/secrets.js+2 -0
@@ -39,6 +39,7 @@ export const SECRET_KEYS = {
3939 TAVILY: 'api_key_tavily',
4040 BFL: 'api_key_bfl',
4141 GENERIC: 'api_key_generic',
42+ DEEPSEEK: 'api_key_deepseek',
4243};
4344
4445const INPUT_MAP = {
@@ -73,6 +74,7 @@ const INPUT_MAP = {
7374 [SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy',
7475 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
7576 [SECRET_KEYS.GENERIC]: '#api_key_generic',
77+ [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
7678};
7779
7880async function clearSecret() {
public/scripts/slash-commands.js+1 -0
@@ -3815,6 +3815,7 @@ function getModelOptions(quiet) {
38153815 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
38163816 { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
38173817 { id: 'model_blockentropy_select', api: 'openai', type: chat_completion_sources.BLOCKENTROPY },
3818+ { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
38183819 { id: 'model_novel_select', api: 'novel', type: null },
38193820 { id: 'horde_model', api: 'koboldhorde', type: null },
38203821 ];
public/scripts/tokenizers.js+15 -0
@@ -31,6 +31,7 @@ export const tokenizers = {
3131 QWEN2: 15,
3232 COMMAND_R: 16,
3333 NEMO: 17,
34+ DEEPSEEK: 18,
3435 BEST_MATCH: 99,
3536 MANUAL_SELECTION: 411,
3637};
@@ -46,6 +47,7 @@ export const ENCODE_TOKENIZERS = [
4647 tokenizers.QWEN2,
4748 tokenizers.COMMAND_R,
4849 tokenizers.NEMO,
50+ tokenizers.DEEPSEEK,
4951 // uncomment when NovelAI releases Kayra and Clio weights, lol
5052 //tokenizers.NERD,
5153 //tokenizers.NERD2,
@@ -133,6 +135,11 @@ const TOKENIZER_URLS = {
133135 decode: '/api/tokenizers/nemo/decode',
134136 count: '/api/tokenizers/nemo/encode',
135137 },
138+ [tokenizers.DEEPSEEK]: {
139+ encode: '/api/tokenizers/deepseek/encode',
140+ decode: '/api/tokenizers/deepseek/decode',
141+ count: '/api/tokenizers/deepseek/encode',
142+ },
136143 [tokenizers.API_TEXTGENERATIONWEBUI]: {
137144 encode: '/api/tokenizers/remote/textgenerationwebui/encode',
138145 count: '/api/tokenizers/remote/textgenerationwebui/encode',
@@ -551,12 +558,17 @@ export function getTokenizerModel() {
551558 const qwen2Tokenizer = 'qwen2';
552559 const commandRTokenizer = 'command-r';
553560 const nemoTokenizer = 'nemo';
561+ const deepseekTokenizer = 'deepseek';
554562
555563 // Assuming no one would use it for different models.. right?
556564 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
557565 return gpt4Tokenizer;
558566 }
559567
568+ if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
569+ return deepseekTokenizer;
570+ }
571+
560572 // Select correct tokenizer for WindowAI proxies
561573 if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && oai_settings.windowai_model) {
562574 if (oai_settings.windowai_model.includes('gpt-4')) {
@@ -619,6 +631,9 @@ export function getTokenizerModel() {
619631 else if (oai_settings.openrouter_model.includes('jamba')) {
620632 return jambaTokenizer;
621633 }
634+ else if (oai_settings.openrouter_model.includes('deepseek')) {
635+ return deepseekTokenizer;
636+ }
622637 }
623638
624639 if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) {
src/constants.js+1 -0
@@ -175,6 +175,7 @@ export const CHAT_COMPLETION_SOURCES = {
175175 ZEROONEAI: '01ai',
176176 BLOCKENTROPY: 'blockentropy',
177177 NANOGPT: 'nanogpt',
178+ DEEPSEEK: 'deepseek',
178179};
179180
180181/**
src/endpoints/backends/chat-completions.js+19 -0
@@ -51,6 +51,7 @@ const API_01AI = 'https://api.01.ai/v1';
5151const API_BLOCKENTROPY = 'https://api.blockentropy.ai/v1';
5252const API_AI21 = 'https://api.ai21.com/studio/v1';
5353const API_NANOGPT = 'https://nano-gpt.com/api/v1';
54+const API_DEEPSEEK = 'https://api.deepseek.com/beta';
5455
5556/**
5657 * Applies a post-processing step to the generated messages.
@@ -66,6 +67,8 @@ function postProcessPrompt(messages, type, names) {
6667 return mergeMessages(messages, names, false);
6768 case 'strict':
6869 return mergeMessages(messages, names, true);
70+ case 'deepseek':
71+ return (x => x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true) ? x : x)([...mergeMessages(messages, names, true)]);
6972 default:
7073 return messages;
7174 }
@@ -668,6 +671,10 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
668671 api_url = API_NANOGPT;
669672 api_key_openai = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
670673 headers = {};
674+ } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) {
675+ api_url = API_DEEPSEEK;
676+ api_key_openai = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
677+ headers = {};
671678 } else {
672679 console.log('This chat completion source is not supported yet.');
673680 return response_getstatus_openai.status(400).send({ error: true });
@@ -939,6 +946,18 @@ router.post('/generate', jsonParser, function (request, response) {
939946 apiKey = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY);
940947 headers = {};
941948 bodyParams = {};
949+ } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) {
950+ apiUrl = API_DEEPSEEK;
951+ apiKey = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
952+ headers = {};
953+ bodyParams = {};
954+
955+ if (request.body.logprobs > 0) {
956+ bodyParams['top_logprobs'] = request.body.logprobs;
957+ bodyParams['logprobs'] = true;
958+ }
959+
960+ request.body.messages = postProcessPrompt(request.body.messages, 'deepseek', getPromptNames(request));
942961 } else {
943962 console.log('This chat completion source is not supported yet.');
944963 return response.status(400).send({ error: true });
src/endpoints/secrets.js+1 -0
@@ -51,6 +51,7 @@ export const SECRET_KEYS = {
5151 NANOGPT: 'api_key_nanogpt',
5252 BFL: 'api_key_bfl',
5353 GENERIC: 'api_key_generic',
54+ DEEPSEEK: 'api_key_deepseek',
5455};
5556
5657// These are the keys that are safe to expose, even if allowKeysExposure is false
src/endpoints/tokenizers.js+24 -0
@@ -226,6 +226,7 @@ const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json');
226226const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json');
227227const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json');
228228const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json');
229+const deepseekTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/deepseek.json', 'src/tokenizers/llama3.json');
229230
230231export const sentencepieceTokenizers = [
231232 'llama',
@@ -407,6 +408,10 @@ export function getTokenizerModel(requestModel) {
407408 return 'yi';
408409 }
409410
411+ if (requestModel.includes('deepseek')) {
412+ return 'deepseek';
413+ }
414+
410415 if (requestModel.includes('gemma') || requestModel.includes('gemini')) {
411416 return 'gemma';
412417 }
@@ -655,6 +660,7 @@ router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llam
655660router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer));
656661router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer));
657662router.post('/nemo/encode', jsonParser, createWebTokenizerEncodingHandler(nemoTokenizer));
663+router.post('/deepseek/encode', jsonParser, createWebTokenizerEncodingHandler(deepseekTokenizer));
658664router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama));
659665router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd));
660666router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2));
@@ -668,6 +674,7 @@ router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llam
668674router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer));
669675router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer));
670676router.post('/nemo/decode', jsonParser, createWebTokenizerDecodingHandler(nemoTokenizer));
677+router.post('/deepseek/decode', jsonParser, createWebTokenizerDecodingHandler(deepseekTokenizer));
671678
672679router.post('/openai/encode', jsonParser, async function (req, res) {
673680 try {
@@ -723,6 +730,11 @@ router.post('/openai/encode', jsonParser, async function (req, res) {
723730 return handler(req, res);
724731 }
725732
733+ if (queryModel.includes('deepseek')) {
734+ const handler = createWebTokenizerEncodingHandler(deepseekTokenizer);
735+ return handler(req, res);
736+ }
737+
726738 const model = getTokenizerModel(queryModel);
727739 const handler = createTiktokenEncodingHandler(model);
728740 return handler(req, res);
@@ -786,6 +798,11 @@ router.post('/openai/decode', jsonParser, async function (req, res) {
786798 return handler(req, res);
787799 }
788800
801+ if (queryModel.includes('deepseek')) {
802+ const handler = createWebTokenizerDecodingHandler(deepseekTokenizer);
803+ return handler(req, res);
804+ }
805+
789806 const model = getTokenizerModel(queryModel);
790807 const handler = createTiktokenDecodingHandler(model);
791808 return handler(req, res);
@@ -863,6 +880,13 @@ router.post('/openai/count', jsonParser, async function (req, res) {
863880 return res.send({ 'token_count': num_tokens });
864881 }
865882
883+ if (model === 'deepseek') {
884+ const instance = await deepseekTokenizer.get();
885+ if (!instance) throw new Error('Failed to load the DeepSeek tokenizer');
886+ num_tokens = countWebTokenizerTokens(instance, req.body);
887+ return res.send({ 'token_count': num_tokens });
888+ }
889+
866890 const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1;
867891 const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3;
868892 const tokensPadding = 3;