Add verbosity control (#4837) * Add verbosity control * Remove for Azure OpenAI
Signed| @@ -2078,6 +2078,22 @@ | |||
| 2078 | </div> | 2078 | </div> |
| 2079 | </div> | 2079 | </div> |
| 2080 | </div> | 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 | <div class="range-block" data-source="claude"> | 2097 | <div class="range-block" data-source="claude"> |
| 2082 | <div class="wide100p"> | 2098 | <div class="wide100p"> |
| 2083 | <div class="flex-container alignItemsCenter"> | 2099 | <div class="flex-container alignItemsCenter"> |
| @@ -239,6 +239,13 @@ export const reasoning_effort_types = { | |||
| 239 | max: 'max', | 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 | export const ZAI_ENDPOINT = { | 249 | export const ZAI_ENDPOINT = { |
| 243 | COMMON: 'common', | 250 | COMMON: 'common', |
| 244 | CODING: 'coding', | 251 | CODING: 'coding', |
| @@ -342,6 +349,7 @@ export const settingsToUpdate = { | |||
| 342 | function_calling: ['#openai_function_calling', 'function_calling', true, false], | 349 | function_calling: ['#openai_function_calling', 'function_calling', true, false], |
| 343 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false], | 350 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false], |
| 344 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false], | 351 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false], |
| 352 | verbosity: ['#openai_verbosity', 'verbosity', false, false], | ||
| 345 | enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true, false], | 353 | enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true, false], |
| 346 | seed: ['#seed_openai', 'seed', false, false], | 354 | seed: ['#seed_openai', 'seed', false, false], |
| 347 | n: ['#n_openai', 'n', false, false], | 355 | n: ['#n_openai', 'n', false, false], |
| @@ -442,6 +450,7 @@ const default_settings = { | |||
| 442 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 450 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 443 | show_thoughts: true, | 451 | show_thoughts: true, |
| 444 | reasoning_effort: reasoning_effort_types.auto, | 452 | reasoning_effort: reasoning_effort_types.auto, |
| 453 | verbosity: verbosity_levels.auto, | ||
| 445 | enable_web_search: false, | 454 | enable_web_search: false, |
| 446 | request_images: false, | 455 | request_images: false, |
| 447 | seed: -1, | 456 | seed: -1, |
| @@ -2226,6 +2235,15 @@ function getReasoningEffort() { | |||
| 2226 | return reasoningEffort; | 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 | * Send a chat completion request to backend | 2248 | * Send a chat completion request to backend |
| 2231 | * @param {string} type (impersonate, quiet, continue, etc) | 2249 | * @param {string} type (impersonate, quiet, continue, etc) |
| @@ -2313,6 +2331,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = | |||
| 2313 | 'enable_web_search': Boolean(oai_settings.enable_web_search), | 2331 | 'enable_web_search': Boolean(oai_settings.enable_web_search), |
| 2314 | 'request_images': Boolean(oai_settings.request_images), | 2332 | 'request_images': Boolean(oai_settings.request_images), |
| 2315 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, | 2333 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, |
| 2334 | 'verbosity': getVerbosity(), | ||
| 2316 | }; | 2335 | }; |
| 2317 | 2336 | ||
| 2318 | if (isAzureOpenAI) { | 2337 | if (isAzureOpenAI) { |
| @@ -6296,6 +6315,11 @@ export function initOpenAI() { | |||
| 6296 | saveSettingsDebounced(); | 6315 | saveSettingsDebounced(); |
| 6297 | }); | 6316 | }); |
| 6298 | 6317 | ||
| 6318 | $('#openai_verbosity').on('input', function () { | ||
| 6319 | oai_settings.verbosity = String($(this).val()); | ||
| 6320 | saveSettingsDebounced(); | ||
| 6321 | }); | ||
| 6322 | |||
| 6299 | $('#openai_enable_web_search').on('input', function () { | 6323 | $('#openai_enable_web_search').on('input', function () { |
| 6300 | oai_settings.enable_web_search = !!$(this).prop('checked'); | 6324 | oai_settings.enable_web_search = !!$(this).prop('checked'); |
| 6301 | calculateOpenRouterCost(); | 6325 | calculateOpenRouterCost(); |
| @@ -453,6 +453,8 @@ export const AZURE_OPENAI_KEYS = [ | |||
| 453 | 'reasoning_effort', | 453 | 'reasoning_effort', |
| 454 | ]; | 454 | ]; |
| 455 | 455 | ||
| 456 | export const OPENAI_VERBOSITY_MODELS = /^gpt-5/; | ||
| 457 | |||
| 456 | export const OPENAI_REASONING_EFFORT_MODELS = [ | 458 | export const OPENAI_REASONING_EFFORT_MODELS = [ |
| 457 | 'o1', | 459 | 'o1', |
| 458 | 'o3-mini', | 460 | 'o3-mini', |
| @@ -11,6 +11,7 @@ import { | |||
| 11 | GEMINI_SAFETY, | 11 | GEMINI_SAFETY, |
| 12 | OPENAI_REASONING_EFFORT_MAP, | 12 | OPENAI_REASONING_EFFORT_MAP, |
| 13 | OPENAI_REASONING_EFFORT_MODELS, | 13 | OPENAI_REASONING_EFFORT_MODELS, |
| 14 | OPENAI_VERBOSITY_MODELS, | ||
| 14 | OPENROUTER_HEADERS, | 15 | OPENROUTER_HEADERS, |
| 15 | VERTEX_SAFETY, | 16 | VERTEX_SAFETY, |
| 16 | ZAI_ENDPOINT, | 17 | ZAI_ENDPOINT, |
| @@ -163,6 +164,7 @@ async function sendClaudeRequest(request, response) { | |||
| 163 | const useThinking = /^claude-(3-7|opus-4|sonnet-4|haiku-4-5|opus-4-5)/.test(request.body.model); | 164 | const useThinking = /^claude-(3-7|opus-4|sonnet-4|haiku-4-5|opus-4-5)/.test(request.body.model); |
| 164 | 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 | 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 | const isLimitedSampling = /^claude-(opus-4-1|sonnet-4-5|haiku-4-5|opus-4-5)/.test(request.body.model); | 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 | const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; | 168 | const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m'; |
| 167 | let fixThinkingPrefill = false; | 169 | let fixThinkingPrefill = false; |
| 168 | // Add custom stop sequences | 170 | // Add custom stop sequences |
| @@ -268,6 +270,13 @@ async function sendClaudeRequest(request, response) { | |||
| 268 | convertedPrompt.messages[convertedPrompt.messages.length - 1].role = 'user'; | 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 | if (betaHeaders.length) { | 280 | if (betaHeaders.length) { |
| 272 | additionalHeaders['anthropic-beta'] = betaHeaders.join(','); | 281 | additionalHeaders['anthropic-beta'] = betaHeaders.join(','); |
| 273 | } | 282 | } |
| @@ -1835,6 +1844,10 @@ router.post('/generate', function (request, response) { | |||
| 1835 | bodyParams['reasoning'] = { effort: request.body.reasoning_effort }; | 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 | if (request.body.json_schema) { | 1851 | if (request.body.json_schema) { |
| 1839 | bodyParams['response_format'] = { | 1852 | bodyParams['response_format'] = { |
| 1840 | type: 'json_schema', | 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 | if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { | 2039 | if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { |
| 2021 | console.warn('OpenAI API key is missing.'); | 2040 | console.warn('OpenAI API key is missing.'); |
| 2022 | return response.status(400).send({ error: true }); | 2041 | return response.status(400).send({ error: true }); |