Support reasoning for OpenRouter text completion

753a99faf93ed4a6c0f36db4f2bf63d471ab5f0e

pcpthm <pcpthm@gmail.com>

6 files changed, +35 -14Ignore whitespace
public/index.html+4 -0
@@ -1587,6 +1587,10 @@
1587 <input type="checkbox" id="skip_special_tokens_textgenerationwebui" />1587 <input type="checkbox" id="skip_special_tokens_textgenerationwebui" />
1588 <small data-i18n="Skip Special Tokens">Skip Special Tokens</small>1588 <small data-i18n="Skip Special Tokens">Skip Special Tokens</small>
1589 </label>1589 </label>
1590 <label data-tg-type="openrouter" class="checkbox_label flexGrow flexShrink" for="include_reasoning_textgenerationwebui">
1591 <input type="checkbox" id="include_reasoning_textgenerationwebui" />
1592 <small data-i18n="Request Model Reasoning">Request Model Reasoning</small>
1593 </label>
1590 <label data-tg-type="ooba, aphrodite, tabby" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui">1594 <label data-tg-type="ooba, aphrodite, tabby" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui">
1591 <input type="checkbox" id="temperature_last_textgenerationwebui" />1595 <input type="checkbox" id="temperature_last_textgenerationwebui" />
1592 <label>1596 <label>
public/script.js+20 -9
@@ -5704,15 +5704,26 @@ function extractMessageFromData(data) {
5704 * @returns {string} Extracted reasoning5704 * @returns {string} Extracted reasoning
5705 */5705 */
5706function extractReasoningFromData(data) {5706function extractReasoningFromData(data) {
5707 if (main_api === 'openai' && oai_settings.show_thoughts) {5707 switch (main_api) {
5708 switch (oai_settings.chat_completion_source) {5708 case 'textgenerationwebui':
5709 case chat_completion_sources.DEEPSEEK:5709 switch (textgen_settings.type) {
5710 return data?.choices?.[0]?.message?.reasoning_content ?? '';5710 case textgen_types.OPENROUTER:
5711 case chat_completion_sources.OPENROUTER:5711 return data?.choices?.[0]?.reasoning ?? '';
5712 return data?.choices?.[0]?.message?.reasoning ?? '';5712 }
5713 case chat_completion_sources.MAKERSUITE:5713 break;
5714 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';5714
5715 }5715 case 'openai':
5716 if (!oai_settings.show_thoughts) break;
5717
5718 switch (oai_settings.chat_completion_source) {
5719 case chat_completion_sources.DEEPSEEK:
5720 return data?.choices?.[0]?.message?.reasoning_content ?? '';
5721 case chat_completion_sources.OPENROUTER:
5722 return data?.choices?.[0]?.message?.reasoning ?? '';
5723 case chat_completion_sources.MAKERSUITE:
5724 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';
5725 }
5726 break;
5716 }5727 }
57175728
5718 return '';5729 return '';
public/scripts/openai.js+3 -3
@@ -1913,7 +1913,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1913 'user_name': name1,1913 'user_name': name1,
1914 'char_name': name2,1914 'char_name': name2,
1915 'group_names': getGroupNames(),1915 'group_names': getGroupNames(),
1916 'show_thoughts': Boolean(oai_settings.show_thoughts),1916 'include_reasoning': Boolean(oai_settings.show_thoughts),
1917 };1917 };
19181918
1919 // Empty array will produce a validation error1919 // Empty array will produce a validation error
@@ -2151,7 +2151,7 @@ function getStreamingReply(data, state) {
2151 return data?.delta?.text || '';2151 return data?.delta?.text || '';
2152 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {2152 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
2153 if (oai_settings.show_thoughts) {2153 if (oai_settings.show_thoughts) {
2154 state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');2154 state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');
2155 }2155 }
2156 return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';2156 return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';
2157 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {2157 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
@@ -2166,7 +2166,7 @@ function getStreamingReply(data, state) {
2166 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');2166 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
2167 }2167 }
2168 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2168 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2169 } else {2169 } else {
2170 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2170 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2171 }2171 }
2172}2172}
public/scripts/textgen-settings.js+6 -1
@@ -172,6 +172,7 @@ const settings = {
172 //truncation_length: 2048,172 //truncation_length: 2048,
173 ban_eos_token: false,173 ban_eos_token: false,
174 skip_special_tokens: true,174 skip_special_tokens: true,
175 include_reasoning: true,
175 streaming: false,176 streaming: false,
176 mirostat_mode: 0,177 mirostat_mode: 0,
177 mirostat_tau: 5,178 mirostat_tau: 5,
@@ -263,6 +264,7 @@ export const setting_names = [
263 'add_bos_token',264 'add_bos_token',
264 'ban_eos_token',265 'ban_eos_token',
265 'skip_special_tokens',266 'skip_special_tokens',
267 'include_reasoning',
266 'streaming',268 'streaming',
267 'mirostat_mode',269 'mirostat_mode',
268 'mirostat_tau',270 'mirostat_tau',
@@ -740,6 +742,7 @@ jQuery(function () {
740 'add_bos_token_textgenerationwebui': true,742 'add_bos_token_textgenerationwebui': true,
741 'temperature_last_textgenerationwebui': true,743 'temperature_last_textgenerationwebui': true,
742 'skip_special_tokens_textgenerationwebui': true,744 'skip_special_tokens_textgenerationwebui': true,
745 'include_reasoning_textgenerationwebui': true,
743 'top_a_textgenerationwebui': 0,746 'top_a_textgenerationwebui': 0,
744 'top_a_counter_textgenerationwebui': 0,747 'top_a_counter_textgenerationwebui': 0,
745 'mirostat_mode_textgenerationwebui': 0,748 'mirostat_mode_textgenerationwebui': 0,
@@ -986,7 +989,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
986 let logprobs = null;989 let logprobs = null;
987 const swipes = [];990 const swipes = [];
988 const toolCalls = [];991 const toolCalls = [];
989 const state = {};992 const state = { reasoning: '' };
990 while (true) {993 while (true) {
991 const { done, value } = await reader.read();994 const { done, value } = await reader.read();
992 if (done) return;995 if (done) return;
@@ -1003,6 +1006,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
1003 const newText = data?.choices?.[0]?.text || data?.content || '';1006 const newText = data?.choices?.[0]?.text || data?.content || '';
1004 text += newText;1007 text += newText;
1005 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);1008 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);
1009 state.reasoning += data?.choices?.[0]?.reasoning ?? '';
1006 }1010 }
10071011
1008 yield { text, swipes, logprobs, toolCalls, state };1012 yield { text, swipes, logprobs, toolCalls, state };
@@ -1266,6 +1270,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
1266 'truncation_length': max_context,1270 'truncation_length': max_context,
1267 'ban_eos_token': settings.ban_eos_token,1271 'ban_eos_token': settings.ban_eos_token,
1268 'skip_special_tokens': settings.skip_special_tokens,1272 'skip_special_tokens': settings.skip_special_tokens,
1273 'include_reasoning': settings.include_reasoning,
1269 'top_a': settings.top_a,1274 'top_a': settings.top_a,
1270 'tfs': settings.tfs,1275 'tfs': settings.tfs,
1271 'epsilon_cutoff': [OOBA, MANCER].includes(settings.type) ? settings.epsilon_cutoff : undefined,1276 'epsilon_cutoff': [OOBA, MANCER].includes(settings.type) ? settings.epsilon_cutoff : undefined,
src/constants.js+1 -0
@@ -369,6 +369,7 @@ export const OPENROUTER_KEYS = [
369 'prompt',369 'prompt',
370 'stop',370 'stop',
371 'provider',371 'provider',
372 'include_reasoning',
372];373];
373374
374// https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220375// https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220
src/endpoints/backends/chat-completions.js+1 -1
@@ -998,7 +998,7 @@ router.post('/generate', jsonParser, function (request, response) {
998 bodyParams['route'] = 'fallback';998 bodyParams['route'] = 'fallback';
999 }999 }
10001000
1001 if (request.body.show_thoughts) {1001 if (request.body.include_reasoning) {
1002 bodyParams['include_reasoning'] = true;1002 bodyParams['include_reasoning'] = true;
1003 }1003 }
10041004