diff --git a/public/index.html b/public/index.html index 39f695090..f5b9adf22 100644 --- a/public/index.html +++ b/public/index.html @@ -1977,12 +1977,12 @@ -
+
@@ -3209,6 +3209,7 @@
diff --git a/public/script.js b/public/script.js index 22ab14db2..2bbc88c9e 100644 --- a/public/script.js +++ b/public/script.js @@ -5655,20 +5655,31 @@ function extractMessageFromData(data) { return data; } - switch (main_api) { - case 'kobold': - return data.results[0].text; - case 'koboldhorde': - return data.text; - case 'textgenerationwebui': - return data.choices?.[0]?.text ?? data.content ?? data.response ?? ''; - case 'novel': - return data.output; - case 'openai': - return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? ''; - default: - return ''; + function getTextContext() { + switch (main_api) { + case 'kobold': + return data.results[0].text; + case 'koboldhorde': + return data.text; + case 'textgenerationwebui': + return data.choices?.[0]?.text ?? data.content ?? data.response ?? ''; + case 'novel': + return data.output; + case 'openai': + return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? ''; + default: + return ''; + } } + + const content = getTextContext(); + + if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) { + const thoughts = data?.choices?.[0]?.message?.reasoning_content ?? ''; + return [thoughts, content].filter(x => x).join('\n\n'); + } + + return content; } /** diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 4ec0c7749..55a9858af 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -2030,6 +2030,16 @@ async function sendOpenAIRequest(type, messages, signal) { // https://api-docs.deepseek.com/api/create-chat-completion if (isDeepSeek) { generate_data.top_p = generate_data.top_p || Number.EPSILON; + + if (generate_data.model.endsWith('-reasoner')) { + delete generate_data.top_p; + delete generate_data.temperature; + delete generate_data.frequency_penalty; + delete generate_data.presence_penalty; + delete generate_data.top_logprobs; + delete generate_data.logprobs; + delete generate_data.logit_bias; + } } if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) { @@ -2085,6 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) { let text = ''; const swipes = []; const toolCalls = []; + const state = {}; while (true) { const { done, value } = await reader.read(); if (done) return; @@ -2095,9 +2106,9 @@ async function sendOpenAIRequest(type, messages, signal) { if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { const swipeIndex = parsed.choices[0].index - 1; - swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed); + swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state); } else { - text += getStreamingReply(parsed); + text += getStreamingReply(parsed, state); } ToolManager.parseToolCalls(toolCalls, parsed); @@ -2129,14 +2140,27 @@ async function sendOpenAIRequest(type, messages, signal) { } } -function getStreamingReply(data) { +/** + * Extracts the reply from the response data from a chat completions-like source + * @param {object} data Response data from the chat completions-like source + * @param {object} state Additional state to keep track of + * @returns {string} The reply extracted from the response data + */ +function getStreamingReply(data, state) { if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) { return data?.delta?.text || ''; } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) { return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || ''; } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || ''; - } else { + } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { + const hadThoughts = state.hadThoughts; + const thoughts = data.choices?.filter(x => oai_settings.show_thoughts || !x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''; + const content = data.choices?.[0]?.delta?.content || ''; + state.hadThoughts = !!thoughts; + const separator = hadThoughts && !thoughts ? '\n\n' : ''; + return [thoughts, separator, content].filter(x => x).join('\n\n'); + } else { return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; } } @@ -4488,7 +4512,7 @@ async function onModelChange() { if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { if (oai_settings.max_context_unlocked) { $('#openai_max_context').attr('max', unlocked_max); - } else if (oai_settings.deepseek_model == 'deepseek-chat') { + } else if (['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) { $('#openai_max_context').attr('max', max_64k); } else if (oai_settings.deepseek_model == 'deepseek-coder') { $('#openai_max_context').attr('max', max_16k); diff --git a/public/scripts/sse-stream.js b/public/scripts/sse-stream.js index c1c5f1bd6..3921e7d58 100644 --- a/public/scripts/sse-stream.js +++ b/public/scripts/sse-stream.js @@ -220,6 +220,21 @@ async function* parseStreamData(json) { } return; } + else if (typeof json.choices[0].delta.reasoning_content === 'string' && json.choices[0].delta.reasoning_content.length > 0) { + for (let j = 0; j < json.choices[0].delta.reasoning_content.length; j++) { + const str = json.choices[0].delta.reasoning_content[j]; + const isLastSymbol = j === json.choices[0].delta.reasoning_content.length - 1; + const choiceClone = structuredClone(json.choices[0]); + choiceClone.delta.reasoning_content = str; + choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : ''; + const choices = [choiceClone]; + yield { + data: { ...json, choices }, + chunk: str, + }; + } + return; + } else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { for (let j = 0; j < json.choices[0].delta.content.length; j++) { const str = json.choices[0].delta.content[j]; diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index 252e16d95..d67f309da 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -61,6 +61,7 @@ const API_DEEPSEEK = 'https://api.deepseek.com/beta'; * @returns */ function postProcessPrompt(messages, type, names) { + const addAssistantPrefix = x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x; switch (type) { case 'merge': case 'claude': @@ -70,7 +71,9 @@ function postProcessPrompt(messages, type, names) { case 'strict': return mergeMessages(messages, names, true, true); case 'deepseek': - return (x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x)(mergeMessages(messages, names, true, false)); + return addAssistantPrefix(mergeMessages(messages, names, true, false)); + case 'deepseek-reasoner': + return addAssistantPrefix(mergeMessages(messages, names, true, true)); default: return messages; } @@ -965,7 +968,8 @@ router.post('/generate', jsonParser, function (request, response) { bodyParams['logprobs'] = true; } - request.body.messages = postProcessPrompt(request.body.messages, 'deepseek', getPromptNames(request)); + const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; + request.body.messages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); } else { console.log('This chat completion source is not supported yet.'); return response.status(400).send({ error: true });