OpenRouter: Support reasoning blocks
| @@ -1977,7 +1977,7 @@ | ||
| 1977 | 1977 | </span> |
| 1978 | 1978 | </div> |
| 1979 | 1979 | </div> |
| 1980 | 1980 | <div class="range-block" data-source="makersuite,deepseek,openrouter"> |
| 1981 | 1981 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 1982 | 1982 | <input id="openai_show_thoughts" type="checkbox" /> |
| 1983 | 1983 | <span> |
| @@ -170,7 +170,7 @@ import { | ||
| 170 | 170 | isElementInViewport, |
| 171 | 171 | copyText, |
| 172 | 172 | } from './scripts/utils.js'; |
| 173 | 173 | import { debounce_timeout, THINK_BREAK } from './scripts/constants.js'; |
| 174 | 174 | |
| 175 | 175 | import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; |
| 176 | 176 | import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js'; |
| @@ -5700,10 +5700,6 @@ function getTextContextFromData(data) { | ||
| 5700 | 5700 | function extractMessageFromData(data){ |
| 5701 | 5701 | const content = String(getTextContextFromData(data) ?? ''); |
| 5702 | 5702 | |
| 5703 | - if (content.includes(THINK_BREAK)) { | |
| 5704 | - return content.split(THINK_BREAK)[1]; | |
| 5705 | - } | |
| 5706 | - | |
| 5707 | 5703 | return content; |
| 5708 | 5704 | } |
| 5709 | 5705 | |
| @@ -5713,14 +5709,15 @@ function extractMessageFromData(data){ | ||
| 5713 | 5709 | * @returns {string} Extracted reasoning |
| 5714 | 5710 | */ |
| 5715 | 5711 | function extractReasoningFromData(data) { |
| 5716 | - const content = String(getTextContextFromData(data) ?? ''); | |
| 5712 | + if (main_api === 'openai' && oai_settings.show_thoughts) { | |
| 5717 | - | |
| 5713 | + switch (oai_settings.chat_completion_source) { | |
| 5718 | - if (content.includes(THINK_BREAK)) { | |
| 5714 | + case chat_completion_sources.DEEPSEEK: | |
| 5719 | - return content.split(THINK_BREAK)[0]; | |
| 5720 | - } | |
| 5721 | - | |
| 5722 | - if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) { | |
| 5723 | 5715 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; |
| 5716 | + case chat_completion_sources.OPENROUTER: | |
| 5717 | + return data?.choices?.[0]?.message?.reasoning ?? ''; | |
| 5718 | + case chat_completion_sources.MAKERSUITE: | |
| 5719 | + return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; | |
| 5720 | + } | |
| 5724 | 5721 | } |
| 5725 | 5722 | |
| 5726 | 5723 | return ''; |
| @@ -14,8 +14,3 @@ export const debounce_timeout = { | ||
| 14 | 14 | /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */ |
| 15 | 15 | extended: 5000, |
| 16 | 16 | }; |
| 17 | - | |
| 18 | -/** | |
| 19 | - * Custom boundary for splitting the text between the model's reasoning and the actual response. | |
| 20 | - */ | |
| 21 | -export const THINK_BREAK = '##�THINK_BREAK�##'; | |
| @@ -2161,6 +2161,11 @@ function getStreamingReply(data, state) { | ||
| 2161 | 2161 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); |
| 2162 | 2162 | } |
| 2163 | 2163 | return data.choices?.[0]?.delta?.content || ''; |
| 2164 | + } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) { | |
| 2165 | + if (oai_settings.show_thoughts) { | |
| 2166 | + state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); | |
| 2167 | + } | |
| 2168 | + return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | |
| 2164 | 2169 | } else { |
| 2165 | 2170 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 2166 | 2171 | } |
| @@ -235,6 +235,21 @@ async function* parseStreamData(json) { | ||
| 235 | 235 | } |
| 236 | 236 | return; |
| 237 | 237 | } |
| 238 | + else if (typeof json.choices[0].delta.reasoning === 'string' && json.choices[0].delta.reasoning.length > 0) { | |
| 239 | + for (let j = 0; j < json.choices[0].delta.reasoning.length; j++) { | |
| 240 | + const str = json.choices[0].delta.reasoning[j]; | |
| 241 | + const isLastSymbol = j === json.choices[0].delta.reasoning.length - 1; | |
| 242 | + const choiceClone = structuredClone(json.choices[0]); | |
| 243 | + choiceClone.delta.reasoning = str; | |
| 244 | + choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : ''; | |
| 245 | + const choices = [choiceClone]; | |
| 246 | + yield { | |
| 247 | + data: { ...json, choices }, | |
| 248 | + chunk: str, | |
| 249 | + }; | |
| 250 | + } | |
| 251 | + return; | |
| 252 | + } | |
| 238 | 253 | else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { |
| 239 | 254 | for (let j = 0; j < json.choices[0].delta.content.length; j++) { |
| 240 | 255 | const str = json.choices[0].delta.content[j]; |
| @@ -413,8 +413,3 @@ export const VLLM_KEYS = [ | ||
| 413 | 413 | 'guided_decoding_backend', |
| 414 | 414 | 'guided_whitespace_pattern', |
| 415 | 415 | ]; |
| 416 | - | |
| 417 | -/** | |
| 418 | - * Custom boundary for splitting the text between the model's reasoning and the actual response. | |
| 419 | - */ | |
| 420 | -export const THINK_BREAK = '##�THINK_BREAK�##'; | |
| @@ -7,7 +7,6 @@ import { | ||
| 7 | 7 | CHAT_COMPLETION_SOURCES, |
| 8 | 8 | GEMINI_SAFETY, |
| 9 | 9 | OPENROUTER_HEADERS, |
| 10 | - THINK_BREAK, | |
| 11 | 10 | } from '../../constants.js'; |
| 12 | 11 | import { |
| 13 | 12 | forwardFetchResponse, |
| @@ -392,11 +391,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 392 | 391 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 393 | 392 | console.log('Google AI Studio response:', responseContent); |
| 394 | 393 | |
| 395 | - if (Array.isArray(responseContent?.parts) && isThinking && !showThoughts) { | |
| 394 | + const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); | |
| 396 | - responseContent.parts = responseContent.parts.filter(part => !part.thought); | |
| 397 | - } | |
| 398 | - | |
| 399 | - const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join(THINK_BREAK); | |
| 400 | 395 | if (!responseText) { |
| 401 | 396 | let message = 'Google AI Studio Candidate text empty'; |
| 402 | 397 | console.log(message, generateResponseJson); |
| @@ -404,7 +399,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 404 | 399 | } |
| 405 | 400 | |
| 406 | 401 | // Wrap it back to OAI format |
| 407 | 402 | const reply = { choices: [{ 'message': { 'content': responseText } }], responseContent }; |
| 408 | 403 | return response.send(reply); |
| 409 | 404 | } |
| 410 | 405 | } catch (error) { |
| @@ -993,6 +988,10 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 993 | 988 | bodyParams['route'] = 'fallback'; |
| 994 | 989 | } |
| 995 | 990 | |
| 991 | + if (request.body.show_thoughts) { | |
| 992 | + bodyParams['include_reasoning'] = true; | |
| 993 | + } | |
| 994 | + | |
| 996 | 995 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1); |
| 997 | 996 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { |
| 998 | 997 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); |