Claude 3.7 think mode
| @@ -2000,7 +2000,7 @@ | ||
| 2000 | 2000 | </span> |
| 2001 | 2001 | </div> |
| 2002 | 2002 | </div> |
| 2003 | 2003 | <div class="range-block" data-source="deepseek,openrouter,custom,claude"> |
| 2004 | 2004 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2005 | 2005 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2006 | 2006 | <span> |
| @@ -2014,10 +2014,11 @@ | ||
| 2014 | 2014 | </span> |
| 2015 | 2015 | </div> |
| 2016 | 2016 | </div> |
| 2017 | 2017 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude"> |
| 2018 | 2018 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> |
| 2019 | 2019 | <label for="openai_reasoning_effort" data-i18n="Reasoning Effort"> |
| 2020 | - Reasoning Effort | |
| 2020 | + <span data-i18n="Reasoning Effort">Reasoning Effort</span> | |
| 2021 | + <i data-source="claude" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%)."></i> | |
| 2021 | 2022 | </label> |
| 2022 | 2023 | <select id="openai_reasoning_effort"> |
| 2023 | 2024 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> |
| @@ -5725,7 +5725,7 @@ function extractMessageFromData(data) { | ||
| 5725 | 5725 | case 'novel': |
| 5726 | 5726 | return data.output; |
| 5727 | 5727 | case 'openai': |
| 5728 | 5728 | return data?.content?.find(p => p.type === 'text')?.text ?? data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? ''; |
| 5729 | 5729 | default: |
| 5730 | 5730 | return ''; |
| 5731 | 5731 | } |
| @@ -2149,6 +2149,9 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2149 | 2149 | */ |
| 2150 | 2150 | function getStreamingReply(data, state) { |
| 2151 | 2151 | if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) { |
| 2152 | + if (oai_settings.show_thoughts) { | |
| 2153 | + state.reasoning += data?.delta?.thinking || ''; | |
| 2154 | + } | |
| 2152 | 2155 | return data?.delta?.text || ''; |
| 2153 | 2156 | } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) { |
| 2154 | 2157 | if (oai_settings.show_thoughts) { |
| @@ -76,6 +76,8 @@ export function extractReasoningFromData(data) { | ||
| 76 | 76 | return data?.choices?.[0]?.message?.reasoning ?? ''; |
| 77 | 77 | case chat_completion_sources.MAKERSUITE: |
| 78 | 78 | return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; |
| 79 | + case chat_completion_sources.CLAUDE: | |
| 80 | + return data?.content?.find(part => part.type === 'thinking')?.thinking ?? ''; | |
| 79 | 81 | case chat_completion_sources.CUSTOM: { |
| 80 | 82 | return data?.choices?.[0]?.message?.reasoning_content |
| 81 | 83 | ?? data?.choices?.[0]?.message?.reasoning |
| @@ -28,6 +28,7 @@ import { | ||
| 28 | 28 | cachingAtDepthForOpenRouterClaude, |
| 29 | 29 | cachingAtDepthForClaude, |
| 30 | 30 | getPromptNames, |
| 31 | + calculateBudgetTokens, | |
| 31 | 32 | } from '../../prompt-converters.js'; |
| 32 | 33 | |
| 33 | 34 | import { readSecret, SECRET_KEYS } from '../secrets.js'; |
| @@ -129,6 +130,8 @@ async function sendClaudeRequest(request, response) { | ||
| 129 | 130 | const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0; |
| 130 | 131 | const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt; |
| 131 | 132 | const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, getPromptNames(request)); |
| 133 | + const useThinking = request.body.model.startsWith('claude-3-7') && Boolean(request.body.include_reasoning); | |
| 134 | + let voidPrefill = false; | |
| 132 | 135 | // Add custom stop sequences |
| 133 | 136 | const stopSequences = []; |
| 134 | 137 | if (Array.isArray(request.body.stop)) { |
| @@ -163,9 +166,9 @@ async function sendClaudeRequest(request, response) { | ||
| 163 | 166 | .map(tool => tool.function) |
| 164 | 167 | .map(fn => ({ name: fn.name, description: fn.description, input_schema: fn.parameters })); |
| 165 | 168 | |
| 166 | - // Claude doesn't do prefills on function calls, and doesn't allow empty messages | |
| 169 | + if (requestBody.tools.length) { | |
| 167 | - if (requestBody.tools.length && convertedPrompt.messages.length && convertedPrompt.messages[convertedPrompt.messages.length - 1].role === 'assistant') { | |
| 170 | + // No prefill when using tools | |
| 168 | - convertedPrompt.messages.push({ role: 'user', content: [{ type: 'text', text: '\u200b' }] }); | |
| 171 | + voidPrefill = true; | |
| 169 | 172 | } |
| 170 | 173 | if (enableSystemPromptCache && requestBody.tools.length) { |
| 171 | 174 | requestBody.tools[requestBody.tools.length - 1]['cache_control'] = { type: 'ephemeral' }; |
| @@ -180,6 +183,33 @@ async function sendClaudeRequest(request, response) { | ||
| 180 | 183 | betaHeaders.push('prompt-caching-2024-07-31'); |
| 181 | 184 | } |
| 182 | 185 | |
| 186 | + if (useThinking) { | |
| 187 | + // No prefill when thinking | |
| 188 | + voidPrefill = true; | |
| 189 | + const reasoningEffort = request.body.reasoning_effort; | |
| 190 | + const budgetTokens = calculateBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream); | |
| 191 | + const minThinkTokens = 1024; | |
| 192 | + if (requestBody.max_tokens <= minThinkTokens) { | |
| 193 | + const newValue = requestBody.max_tokens + minThinkTokens; | |
| 194 | + console.warn(color.yellow(`Claude thinking requires a minimum of ${minThinkTokens} response tokens.`)); | |
| 195 | + console.info(color.blue(`Increasing response length to ${newValue}.`)); | |
| 196 | + requestBody.max_tokens = newValue; | |
| 197 | + } | |
| 198 | + requestBody.thinking = { | |
| 199 | + type: 'enabled', | |
| 200 | + budget_tokens: budgetTokens, | |
| 201 | + }; | |
| 202 | + | |
| 203 | + // NO I CAN'T SILENTLY IGNORE THE TEMPERATURE. | |
| 204 | + delete requestBody.temperature; | |
| 205 | + delete requestBody.top_p; | |
| 206 | + delete requestBody.top_k; | |
| 207 | + } | |
| 208 | + | |
| 209 | + if (voidPrefill && convertedPrompt.messages.length && convertedPrompt.messages[convertedPrompt.messages.length - 1].role === 'assistant') { | |
| 210 | + convertedPrompt.messages.push({ role: 'user', content: [{ type: 'text', text: '\u200b' }] }); | |
| 211 | + } | |
| 212 | + | |
| 183 | 213 | if (betaHeaders.length) { |
| 184 | 214 | additionalHeaders['anthropic-beta'] = betaHeaders.join(','); |
| 185 | 215 | } |
| @@ -862,3 +862,34 @@ export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth) { | ||
| 862 | 862 | } |
| 863 | 863 | } |
| 864 | 864 | } |
| 865 | + | |
| 866 | +/** | |
| 867 | + * Calculate the budget tokens for a given reasoning effort. | |
| 868 | + * @param {number} maxTokens Maximum tokens | |
| 869 | + * @param {string} reasoningEffort Reasoning effort | |
| 870 | + * @param {boolean} stream If streaming is enabled | |
| 871 | + * @returns {number} Budget tokens | |
| 872 | + */ | |
| 873 | +export function calculateBudgetTokens(maxTokens, reasoningEffort, stream) { | |
| 874 | + let budgetTokens = 0; | |
| 875 | + | |
| 876 | + switch (reasoningEffort) { | |
| 877 | + case 'low': | |
| 878 | + budgetTokens = Math.floor(maxTokens * 0.1); | |
| 879 | + break; | |
| 880 | + case 'medium': | |
| 881 | + budgetTokens = Math.floor(maxTokens * 0.25); | |
| 882 | + break; | |
| 883 | + case 'high': | |
| 884 | + budgetTokens = Math.floor(maxTokens * 0.5); | |
| 885 | + break; | |
| 886 | + } | |
| 887 | + | |
| 888 | + budgetTokens = Math.max(budgetTokens, 1024); | |
| 889 | + | |
| 890 | + if (!stream) { | |
| 891 | + budgetTokens = Math.min(budgetTokens, 21333); | |
| 892 | + } | |
| 893 | + | |
| 894 | + return budgetTokens; | |
| 895 | +} | |