deepseek reasoner Closes #3322

d7bb92be540d17d28e4f1c0c0bdec95d2525045a

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

5 files changed, +77 -22Ignore whitespace
public/index.html+3 -2
@@ -1977,12 +1977,12 @@
1977 </span>1977 </span>
1978 </div>1978 </div>
1979 </div>1979 </div>
1980 <div class="range-block" data-source="makersuite">1980 <div class="range-block" data-source="makersuite,deepseek">
1981 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">1981 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
1982 <input id="openai_show_thoughts" type="checkbox" />1982 <input id="openai_show_thoughts" type="checkbox" />
1983 <span>1983 <span>
1984 <span data-i18n="Show model thoughts">Show model thoughts</span>1984 <span data-i18n="Show model thoughts">Show model thoughts</span>
1985 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking"></i>1985 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i>
1986 </span>1986 </span>
1987 </label>1987 </label>
1988 <div class="toggle-description justifyLeft marginBot5">1988 <div class="toggle-description justifyLeft marginBot5">
@@ -3209,6 +3209,7 @@
3209 <select id="model_deepseek_select">3209 <select id="model_deepseek_select">
3210 <option value="deepseek-chat">deepseek-chat</option>3210 <option value="deepseek-chat">deepseek-chat</option>
3211 <option value="deepseek-coder">deepseek-coder</option>3211 <option value="deepseek-coder">deepseek-coder</option>
3212 <option value="deepseek-reasoner">deepseek-reasoner</option>
3212 </select>3213 </select>
3213 </div>3214 </div>
3214 </div>3215 </div>
public/script.js+24 -13
@@ -5655,20 +5655,31 @@ function extractMessageFromData(data) {
5655 return data;5655 return data;
5656 }5656 }
56575657
5658 switch (main_api) {5658 function getTextContext() {
5659 case 'kobold':5659 switch (main_api) {
5660 return data.results[0].text;5660 case 'kobold':
5661 case 'koboldhorde':5661 return data.results[0].text;
5662 return data.text;5662 case 'koboldhorde':
5663 case 'textgenerationwebui':5663 return data.text;
5664 return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';5664 case 'textgenerationwebui':
5665 case 'novel':5665 return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';
5666 return data.output;5666 case 'novel':
5667 case 'openai':5667 return data.output;
5668 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';5668 case 'openai':
5669 default:5669 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
5670 return '';5670 default:
5671 return '';
5672 }
5671 }5673 }
5674
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;
5672}5683}
56735684
5674/**5685/**
public/scripts/openai.js+29 -5
@@ -2030,6 +2030,16 @@ async function sendOpenAIRequest(type, messages, signal) {
2030 // https://api-docs.deepseek.com/api/create-chat-completion2030 // https://api-docs.deepseek.com/api/create-chat-completion
2031 if (isDeepSeek) {2031 if (isDeepSeek) {
2032 generate_data.top_p = generate_data.top_p || Number.EPSILON;2032 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 }
2033 }2043 }
20342044
2035 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {2045 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
@@ -2085,6 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2085 let text = '';2095 let text = '';
2086 const swipes = [];2096 const swipes = [];
2087 const toolCalls = [];2097 const toolCalls = [];
2098 const state = {};
2088 while (true) {2099 while (true) {
2089 const { done, value } = await reader.read();2100 const { done, value } = await reader.read();
2090 if (done) return;2101 if (done) return;
@@ -2095,9 +2106,9 @@ async function sendOpenAIRequest(type, messages, signal) {
20952106
2096 if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {2107 if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
2097 const swipeIndex = parsed.choices[0].index - 1;2108 const swipeIndex = parsed.choices[0].index - 1;
2098 swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed);2109 swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state);
2099 } else {2110 } else {
2100 text += getStreamingReply(parsed);2111 text += getStreamingReply(parsed, state);
2101 }2112 }
21022113
2103 ToolManager.parseToolCalls(toolCalls, parsed);2114 ToolManager.parseToolCalls(toolCalls, parsed);
@@ -2129,14 +2140,27 @@ async function sendOpenAIRequest(type, messages, signal) {
2129 }2140 }
2130}2141}
21312142
2132function 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 */
2149function getStreamingReply(data, state) {
2133 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {2150 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
2134 return data?.delta?.text || '';2151 return data?.delta?.text || '';
2135 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {2152 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
2136 return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';2153 return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';
2137 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {2154 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
2138 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';2155 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
2139 } else {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');
2163 } else {
2140 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2164 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2141 }2165 }
2142}2166}
@@ -4488,7 +4512,7 @@ async function onModelChange() {
4488 if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {4512 if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
4489 if (oai_settings.max_context_unlocked) {4513 if (oai_settings.max_context_unlocked) {
4490 $('#openai_max_context').attr('max', unlocked_max);4514 $('#openai_max_context').attr('max', unlocked_max);
4491 } else if (oai_settings.deepseek_model == 'deepseek-chat') {4515 } else if (['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) {
4492 $('#openai_max_context').attr('max', max_64k);4516 $('#openai_max_context').attr('max', max_64k);
4493 } else if (oai_settings.deepseek_model == 'deepseek-coder') {4517 } else if (oai_settings.deepseek_model == 'deepseek-coder') {
4494 $('#openai_max_context').attr('max', max_16k);4518 $('#openai_max_context').attr('max', max_16k);
public/scripts/sse-stream.js+15 -0
@@ -220,6 +220,21 @@ async function* parseStreamData(json) {
220 }220 }
221 return;221 return;
222 }222 }
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 }
223 else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {238 else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
224 for (let j = 0; j < json.choices[0].delta.content.length; j++) {239 for (let j = 0; j < json.choices[0].delta.content.length; j++) {
225 const str = json.choices[0].delta.content[j];240 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';
61 * @returns61 * @returns
62 */62 */
63function postProcessPrompt(messages, type, names) {63function postProcessPrompt(messages, type, names) {
64 const addAssistantPrefix = x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x;
64 switch (type) {65 switch (type) {
65 case 'merge':66 case 'merge':
66 case 'claude':67 case 'claude':
@@ -70,7 +71,9 @@ function postProcessPrompt(messages, type, names) {
70 case 'strict':71 case 'strict':
71 return mergeMessages(messages, names, true, true);72 return mergeMessages(messages, names, true, true);
72 case 'deepseek':73 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));
74 default:77 default:
75 return messages;78 return messages;
76 }79 }
@@ -965,7 +968,8 @@ router.post('/generate', jsonParser, function (request, response) {
965 bodyParams['logprobs'] = true;968 bodyParams['logprobs'] = true;
966 }969 }
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));
969 } else {973 } else {
970 console.log('This chat completion source is not supported yet.');974 console.log('This chat completion source is not supported yet.');
971 return response.status(400).send({ error: true });975 return response.status(400).send({ error: true });