Add verbosity control (#4837) * Add verbosity control * Remove for Azure OpenAI
Signed| @@ -2078,6 +2078,22 @@ | ||
| 2078 | 2078 | </div> |
| 2079 | 2079 | </div> |
| 2080 | 2080 | </div> |
| 2081 | + <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,openrouter,claude"> | |
| 2082 | + <div class="flex-container oneline-dropdown"> | |
| 2083 | + <label for="openai_verbosity"> | |
| 2084 | + <span data-i18n="Verbosity">Verbosity</span> | |
| 2085 | + </label> | |
| 2086 | + <select id="openai_verbosity"> | |
| 2087 | + <option data-i18n="openai_verbosity_auto" value="auto">Auto</option> | |
| 2088 | + <option data-i18n="openai_verbosity_low" value="low">Low</option> | |
| 2089 | + <option data-i18n="openai_verbosity_medium" value="medium">Medium</option> | |
| 2090 | + <option data-i18n="openai_verbosity_high" value="high">High</option> | |
| 2091 | + </select> | |
| 2092 | + <div class="toggle-description justifyLeft marginBot5" data-i18n="Constrains the verbosity of the model's response."> | |
| 2093 | + Constrains the verbosity of the model's response. | |
| 2094 | + </div> | |
| 2095 | + </div> | |
| 2096 | + </div> | |
| 2081 | 2097 | <div class="range-block" data-source="claude"> |
| 2082 | 2098 | <div class="wide100p"> |
| 2083 | 2099 | <div class="flex-container alignItemsCenter"> |
| @@ -239,6 +239,13 @@ export const reasoning_effort_types = { | ||
| 239 | 239 | max: 'max', |
| 240 | 240 | }; |
| 241 | 241 | |
| 242 | +export const verbosity_levels = { | |
| 243 | + auto: 'auto', | |
| 244 | + low: 'low', | |
| 245 | + medium: 'medium', | |
| 246 | + high: 'high', | |
| 247 | +}; | |
| 248 | + | |
| 242 | 249 | export const ZAI_ENDPOINT = { |
| 243 | 250 | COMMON: 'common', |
| 244 | 251 | CODING: 'coding', |
| @@ -342,6 +349,7 @@ export const settingsToUpdate = { | ||
| 342 | 349 | function_calling: ['#openai_function_calling', 'function_calling', true, false], |
| 343 | 350 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false], |
| 344 | 351 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false], |
| 352 | + verbosity: ['#openai_verbosity', 'verbosity', false, false], | |
| 345 | 353 | enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true, false], |
| 346 | 354 | seed: ['#seed_openai', 'seed', false, false], |
| 347 | 355 | n: ['#n_openai', 'n', false, false], |
| @@ -442,6 +450,7 @@ const default_settings = { | ||
| 442 | 450 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 443 | 451 | show_thoughts: true, |
| 444 | 452 | reasoning_effort: reasoning_effort_types.auto, |
| 453 | + verbosity: verbosity_levels.auto, | |
| 445 | 454 | enable_web_search: false, |
| 446 | 455 | request_images: false, |
| 447 | 456 | seed: -1, |
| @@ -2226,6 +2235,15 @@ function getReasoningEffort() { | ||
| 2226 | 2235 | return reasoningEffort; |
| 2227 | 2236 | } |
| 2228 | 2237 | |
| 2238 | +function getVerbosity() { | |
| 2239 | + if (oai_settings.verbosity === verbosity_levels.auto) { | |
| 2240 | + return undefined; | |
| 2241 | + } | |
| 2242 | + | |
| 2243 | + // TODO: Adjust verbosity based on model capabilities | |
| 2244 | + return oai_settings.verbosity; | |
| 2245 | +} | |
| 2246 | + | |
| 2229 | 2247 | /** |
| 2230 | 2248 | * Send a chat completion request to backend |
| 2231 | 2249 | * @param {string} type (impersonate, quiet, continue, etc) |
| @@ -2313,6 +2331,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = | ||
| 2313 | 2331 | 'enable_web_search': Boolean(oai_settings.enable_web_search), |
| 2314 | 2332 | 'request_images': Boolean(oai_settings.request_images), |
| 2315 | 2333 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, |
| 2334 | + 'verbosity': getVerbosity(), | |
| 2316 | 2335 | }; |
| 2317 | 2336 | |
| 2318 | 2337 | if (isAzureOpenAI) { |
| @@ -6296,6 +6315,11 @@ export function initOpenAI() { | ||
| 6296 | 6315 | saveSettingsDebounced(); |
| 6297 | 6316 | }); |
| 6298 | 6317 | |
| 6318 | + $('#openai_verbosity').on('input', function () { | |
| 6319 | + oai_settings.verbosity = String($(this).val()); | |
| 6320 | + saveSettingsDebounced(); | |
| 6321 | + }); | |
| 6322 | + | |
| 6299 | 6323 | $('#openai_enable_web_search').on('input', function () { |
| 6300 | 6324 | oai_settings.enable_web_search = !!$(this).prop('checked'); |
| 6301 | 6325 | calculateOpenRouterCost(); |
| @@ -453,6 +453,8 @@ export const AZURE_OPENAI_KEYS = [ | ||
| 453 | 453 | 'reasoning_effort', |
| 454 | 454 | ]; |
| 455 | 455 | |
| 456 | +export const OPENAI_VERBOSITY_MODELS = /^gpt-5/; | |
| 457 | + | |
| 456 | 458 | export const OPENAI_REASONING_EFFORT_MODELS = [ |
| 457 | 459 | 'o1', |
| 458 | 460 | 'o3-mini', |
| @@ -11,6 +11,7 @@ import { | ||
| 11 | 11 | GEMINI_SAFETY, |
| 12 | 12 | OPENAI_REASONING_EFFORT_MAP, |
| 13 | 13 | OPENAI_REASONING_EFFORT_MODELS, |
| 14 | + OPENAI_VERBOSITY_MODELS, | |
| 14 | 15 | OPENROUTER_HEADERS, |
| 15 | 16 | VERTEX_SAFETY, |
| 16 | 17 | ZAI_ENDPOINT, |
| @@ -163,6 +164,7 @@ async function sendClaudeRequest(request, response) { | ||
| 163 | 164 | const useThinking = /^claude-(3-7|opus-4|sonnet-4|haiku-4-5|opus-4-5)/.test(request.body.model); |
| 164 | 165 | const useWebSearch = /^claude-(3-5|3-7|opus-4|sonnet-4|haiku-4-5|opus-4-5)/.test(request.body.model) && Boolean(request.body.enable_web_search); |
| 165 | 166 | const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5)/.test(request.body.model); |
| 167 | + const useVerbosity = /^claude-(opus-4-5)/.test(request.body.model); | |
| 166 | 168 | const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; |
| 167 | 169 | let fixThinkingPrefill = false; |
| 168 | 170 | // Add custom stop sequences |
| @@ -268,6 +270,13 @@ async function sendClaudeRequest(request, response) { | ||
| 268 | 270 | convertedPrompt.messages[convertedPrompt.messages.length - 1].role = 'user'; |
| 269 | 271 | } |
| 270 | 272 | |
| 273 | + // Verbosity = 'effort' (same values as OpenAI) | |
| 274 | + if (useVerbosity && request.body.verbosity) { | |
| 275 | + betaHeaders.push('effort-2025-11-24'); | |
| 276 | + requestBody.output_config ??= {}; | |
| 277 | + requestBody.output_config.effort = request.body.verbosity; | |
| 278 | + } | |
| 279 | + | |
| 271 | 280 | if (betaHeaders.length) { |
| 272 | 281 | additionalHeaders['anthropic-beta'] = betaHeaders.join(','); |
| 273 | 282 | } |
| @@ -1835,6 +1844,10 @@ router.post('/generate', function (request, response) { | ||
| 1835 | 1844 | bodyParams['reasoning'] = { effort: request.body.reasoning_effort }; |
| 1836 | 1845 | } |
| 1837 | 1846 | |
| 1847 | + if (request.body.verbosity) { | |
| 1848 | + bodyParams['verbosity'] = request.body.verbosity; | |
| 1849 | + } | |
| 1850 | + | |
| 1838 | 1851 | if (request.body.json_schema) { |
| 1839 | 1852 | bodyParams['response_format'] = { |
| 1840 | 1853 | type: 'json_schema', |
| @@ -2017,6 +2030,12 @@ router.post('/generate', function (request, response) { | ||
| 2017 | 2030 | } |
| 2018 | 2031 | } |
| 2019 | 2032 | |
| 2033 | + if (request.body.verbosity && [CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) { | |
| 2034 | + if (OPENAI_VERBOSITY_MODELS.test(request.body.model)) { | |
| 2035 | + bodyParams['verbosity'] = request.body.verbosity; | |
| 2036 | + } | |
| 2037 | + } | |
| 2038 | + | |
| 2020 | 2039 | if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { |
| 2021 | 2040 | console.warn('OpenAI API key is missing.'); |
| 2022 | 2041 | return response.status(400).send({ error: true }); |