deepseek reasoner Closes #3322

d7bb92be540d17d28e4f1c0c0bdec95d2525045a

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

5 files changed, +63 -8Showing whitespace changes
public/index.html+3 -2
@@ -1977,12 +1977,12 @@
19771977 </span>
19781978 </div>
19791979 </div>
19801980 <div class="range-block" data-source="makersuite,deepseek">
19811981 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
19821982 <input id="openai_show_thoughts" type="checkbox" />
19831983 <span>
19841984 <span data-i18n="Show model thoughts">Show model thoughts</span>
19851985 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i>
19861986 </span>
19871987 </label>
19881988 <div class="toggle-description justifyLeft marginBot5">
@@ -3209,6 +3209,7 @@
32093209 <select id="model_deepseek_select">
32103210 <option value="deepseek-chat">deepseek-chat</option>
32113211 <option value="deepseek-coder">deepseek-coder</option>
3212+ <option value="deepseek-reasoner">deepseek-reasoner</option>
32123213 </select>
32133214 </div>
32143215 </div>
public/script.js+11 -0
@@ -5655,6 +5655,7 @@ function extractMessageFromData(data) {
56555655 return data;
56565656 }
56575657
5658+ function getTextContext() {
56585659 switch (main_api) {
56595660 case 'kobold':
56605661 return data.results[0].text;
@@ -5671,6 +5672,16 @@ function extractMessageFromData(data) {
56715672 }
56725673 }
56735674
5675+ const content = getTextContext();
5676+
5677+ if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) {
5678+ const thoughts = data?.choices?.[0]?.message?.reasoning_content ?? '';
5679+ return [thoughts, content].filter(x => x).join('\n\n');
5680+ }
5681+
5682+ return content;
5683+}
5684+
56745685/**
56755686 * Extracts multiswipe swipes from the response data.
56765687 * @param {Object} data Response data
public/scripts/openai.js+28 -4
@@ -2030,6 +2030,16 @@ async function sendOpenAIRequest(type, messages, signal) {
20302030 // https://api-docs.deepseek.com/api/create-chat-completion
20312031 if (isDeepSeek) {
20322032 generate_data.top_p = generate_data.top_p || Number.EPSILON;
2033+
2034+ if (generate_data.model.endsWith('-reasoner')) {
2035+ delete generate_data.top_p;
2036+ delete generate_data.temperature;
2037+ delete generate_data.frequency_penalty;
2038+ delete generate_data.presence_penalty;
2039+ delete generate_data.top_logprobs;
2040+ delete generate_data.logprobs;
2041+ delete generate_data.logit_bias;
2042+ }
20332043 }
20342044
20352045 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
@@ -2085,6 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20852095 let text = '';
20862096 const swipes = [];
20872097 const toolCalls = [];
2098+ const state = {};
20882099 while (true) {
20892100 const { done, value } = await reader.read();
20902101 if (done) return;
@@ -2095,9 +2106,9 @@ async function sendOpenAIRequest(type, messages, signal) {
20952106
20962107 if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
20972108 const swipeIndex = parsed.choices[0].index - 1;
20982109 swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state);
20992110 } else {
21002111 text += getStreamingReply(parsed, state);
21012112 }
21022113
21032114 ToolManager.parseToolCalls(toolCalls, parsed);
@@ -2129,13 +2140,26 @@ async function sendOpenAIRequest(type, messages, signal) {
21292140 }
21302141}
21312142
2132-function getStreamingReply(data) {
2143+/**
2144+ * Extracts the reply from the response data from a chat completions-like source
2145+ * @param {object} data Response data from the chat completions-like source
2146+ * @param {object} state Additional state to keep track of
2147+ * @returns {string} The reply extracted from the response data
2148+ */
2149+function getStreamingReply(data, state) {
21332150 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
21342151 return data?.delta?.text || '';
21352152 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
21362153 return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';
21372154 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
21382155 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
2156+ } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
2157+ const hadThoughts = state.hadThoughts;
2158+ const thoughts = data.choices?.filter(x => oai_settings.show_thoughts || !x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '';
2159+ const content = data.choices?.[0]?.delta?.content || '';
2160+ state.hadThoughts = !!thoughts;
2161+ const separator = hadThoughts && !thoughts ? '\n\n' : '';
2162+ return [thoughts, separator, content].filter(x => x).join('\n\n');
21392163 } else {
21402164 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
21412165 }
@@ -4488,7 +4512,7 @@ async function onModelChange() {
44884512 if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
44894513 if (oai_settings.max_context_unlocked) {
44904514 $('#openai_max_context').attr('max', unlocked_max);
44914515 } else if (oai_settings.deepseek_model ==['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) {
44924516 $('#openai_max_context').attr('max', max_64k);
44934517 } else if (oai_settings.deepseek_model == 'deepseek-coder') {
44944518 $('#openai_max_context').attr('max', max_16k);
public/scripts/sse-stream.js+15 -0
@@ -220,6 +220,21 @@ async function* parseStreamData(json) {
220220 }
221221 return;
222222 }
223+ else if (typeof json.choices[0].delta.reasoning_content === 'string' && json.choices[0].delta.reasoning_content.length > 0) {
224+ for (let j = 0; j < json.choices[0].delta.reasoning_content.length; j++) {
225+ const str = json.choices[0].delta.reasoning_content[j];
226+ const isLastSymbol = j === json.choices[0].delta.reasoning_content.length - 1;
227+ const choiceClone = structuredClone(json.choices[0]);
228+ choiceClone.delta.reasoning_content = str;
229+ choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : '';
230+ const choices = [choiceClone];
231+ yield {
232+ data: { ...json, choices },
233+ chunk: str,
234+ };
235+ }
236+ return;
237+ }
223238 else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
224239 for (let j = 0; j < json.choices[0].delta.content.length; j++) {
225240 const str = json.choices[0].delta.content[j];
src/endpoints/backends/chat-completions.js+6 -2
@@ -61,6 +61,7 @@ const API_DEEPSEEK = 'https://api.deepseek.com/beta';
6161 * @returns
6262 */
6363function postProcessPrompt(messages, type, names) {
64+ const addAssistantPrefix = x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x;
6465 switch (type) {
6566 case 'merge':
6667 case 'claude':
@@ -70,7 +71,9 @@ function postProcessPrompt(messages, type, names) {
7071 case 'strict':
7172 return mergeMessages(messages, names, true, true);
7273 case 'deepseek':
73- return (x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x)(mergeMessages(messages, names, true, false));
74+ return addAssistantPrefix(mergeMessages(messages, names, true, false));
75+ case 'deepseek-reasoner':
76+ return addAssistantPrefix(mergeMessages(messages, names, true, true));
7477 default:
7578 return messages;
7679 }
@@ -965,7 +968,8 @@ router.post('/generate', jsonParser, function (request, response) {
965968 bodyParams['logprobs'] = true;
966969 }
967970
968- request.body.messages = postProcessPrompt(request.body.messages, 'deepseek', getPromptNames(request));
971+ const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek';
972+ request.body.messages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request));
969973 } else {
970974 console.log('This chat completion source is not supported yet.');
971975 return response.status(400).send({ error: true });