Claude: map Reasoning Effort to adaptive thinking config (#5219) Supersedes #5105
Signed| @@ -2182,6 +2182,9 @@ | |||
| 2182 | <div class="toggle-description justifyLeft marginBot5" data-i18n="Constrains the verbosity of the model's response."> | 2182 | <div class="toggle-description justifyLeft marginBot5" data-i18n="Constrains the verbosity of the model's response."> |
| 2183 | Constrains the verbosity of the model's response. | 2183 | Constrains the verbosity of the model's response. |
| 2184 | </div> | 2184 | </div> |
| 2185 | <strong class="toggle-description justifyLeft marginBot5" data-source="claude"> | ||
| 2186 | On Opus 4.6 / Sonnet 4.6, a non-automatic Reasoning Effort takes precedence over Verbosity. | ||
| 2187 | </strong> | ||
| 2185 | </div> | 2188 | </div> |
| 2186 | </div> | 2189 | </div> |
| 2187 | <div class="range-block" data-source="claude"> | 2190 | <div class="range-block" data-source="claude"> |
| @@ -228,6 +228,7 @@ async function sendClaudeRequest(request, response) { | |||
| 228 | const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5|opus-4-6|sonnet-4-6)/.test(request.body.model); | 228 | const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5|opus-4-6|sonnet-4-6)/.test(request.body.model); |
| 229 | const useVerbosity = /^claude-(opus-4-5|opus-4-6|sonnet-4-6)/.test(request.body.model); | 229 | const useVerbosity = /^claude-(opus-4-5|opus-4-6|sonnet-4-6)/.test(request.body.model); |
| 230 | const noPrefillModel = /^claude-(opus-4-6|sonnet-4-6)/.test(request.body.model); | 230 | const noPrefillModel = /^claude-(opus-4-6|sonnet-4-6)/.test(request.body.model); |
| 231 | const isAdaptiveModel = /^claude-(opus-4-6|sonnet-4-6)/.test(request.body.model); | ||
| 231 | let fixThinkingPrefill = false; | 232 | let fixThinkingPrefill = false; |
| 232 | // Add custom stop sequences | 233 | // Add custom stop sequences |
| 233 | const stopSequences = []; | 234 | const stopSequences = []; |
| @@ -305,10 +306,18 @@ async function sendClaudeRequest(request, response) { | |||
| 305 | } | 306 | } |
| 306 | 307 | ||
| 307 | const reasoningEffort = request.body.reasoning_effort; | 308 | const reasoningEffort = request.body.reasoning_effort; |
| 308 | const budgetTokens = calculateClaudeBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream); | 309 | const budgetTokens = calculateClaudeBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream, isAdaptiveModel); |
| 309 | 310 | ||
| 310 | if (useThinking && Number.isInteger(budgetTokens)) { | 311 | // Adaptive thinking: returns a string effort level (like Gemini 3) |
| 311 | // No prefill when thinking | 312 | if (useThinking && typeof budgetTokens === 'string') { |
| 313 | fixThinkingPrefill = true; | ||
| 314 | requestBody.thinking = { type: 'adaptive' }; | ||
| 315 | requestBody.output_config ??= {}; | ||
| 316 | requestBody.output_config.effort = budgetTokens; | ||
| 317 | // top_k is not allowed in adaptive mode | ||
| 318 | delete requestBody.top_k; | ||
| 319 | } else if (useThinking && Number.isInteger(budgetTokens)) { | ||
| 320 | // Traditional thinking: returns a numeric budget | ||
| 312 | fixThinkingPrefill = true; | 321 | fixThinkingPrefill = true; |
| 313 | const minThinkTokens = 1024; | 322 | const minThinkTokens = 1024; |
| 314 | if (requestBody.max_tokens <= minThinkTokens) { | 323 | if (requestBody.max_tokens <= minThinkTokens) { |
| @@ -332,8 +341,8 @@ async function sendClaudeRequest(request, response) { | |||
| 332 | convertedPrompt.messages[convertedPrompt.messages.length - 1].role = 'user'; | 341 | convertedPrompt.messages[convertedPrompt.messages.length - 1].role = 'user'; |
| 333 | } | 342 | } |
| 334 | 343 | ||
| 335 | // Verbosity = 'effort' (same values as OpenAI) | 344 | // Verbosity = 'effort' (same values as OpenAI) - only if not already set by adaptive thinking |
| 336 | if (useVerbosity && request.body.verbosity) { | 345 | if (useVerbosity && request.body.verbosity && !requestBody.output_config?.effort) { |
| 337 | betaHeaders.push('effort-2025-11-24'); | 346 | betaHeaders.push('effort-2025-11-24'); |
| 338 | requestBody.output_config ??= {}; | 347 | requestBody.output_config ??= {}; |
| 339 | requestBody.output_config.effort = request.body.verbosity; | 348 | requestBody.output_config.effort = request.body.verbosity; |
| @@ -1110,12 +1110,33 @@ export function cachingSystemPromptForOpenRouter(messages, ttl = undefined) { | |||
| 1110 | 1110 | ||
| 1111 | /** | 1111 | /** |
| 1112 | * Calculate the Claude budget tokens for a given reasoning effort. | 1112 | * Calculate the Claude budget tokens for a given reasoning effort. |
| 1113 | * Returns a string effort level for adaptive thinking (Opus 4.6+), a number for traditional thinking, or null for auto. | ||
| 1113 | * @param {number} maxTokens Maximum tokens | 1114 | * @param {number} maxTokens Maximum tokens |
| 1114 | * @param {string} reasoningEffort Reasoning effort | 1115 | * @param {string} reasoningEffort Reasoning effort |
| 1115 | * @param {boolean} stream If streaming is enabled | 1116 | * @param {boolean} stream If streaming is enabled |
| 1116 | * @returns {number?} Budget tokens | 1117 | * @param {boolean} isAdaptiveModel If the model supports adaptive thinking (Opus 4.6+) |
| 1118 | * @returns {number|string|null} Budget tokens, effort string, or null | ||
| 1117 | */ | 1119 | */ |
| 1118 | export function calculateClaudeBudgetTokens(maxTokens, reasoningEffort, stream) { | 1120 | export function calculateClaudeBudgetTokens(maxTokens, reasoningEffort, stream, isAdaptiveModel) { |
| 1121 | // Adaptive thinking for Opus 4.6+: return effort string (like Gemini 3) | ||
| 1122 | if (isAdaptiveModel) { | ||
| 1123 | switch (reasoningEffort) { | ||
| 1124 | case REASONING_EFFORT.auto: | ||
| 1125 | return null; | ||
| 1126 | case REASONING_EFFORT.min: | ||
| 1127 | return 'low'; | ||
| 1128 | case REASONING_EFFORT.low: | ||
| 1129 | return 'low'; | ||
| 1130 | case REASONING_EFFORT.medium: | ||
| 1131 | return 'medium'; | ||
| 1132 | case REASONING_EFFORT.high: | ||
| 1133 | return 'high'; | ||
| 1134 | case REASONING_EFFORT.max: | ||
| 1135 | return 'max'; | ||
| 1136 | } | ||
| 1137 | return null; | ||
| 1138 | } | ||
| 1139 | |||
| 1119 | let budgetTokens = 0; | 1140 | let budgetTokens = 0; |
| 1120 | 1141 | ||
| 1121 | switch (reasoningEffort) { | 1142 | switch (reasoningEffort) { |