Add new Grok models (#4541) * Add new Grok models * Fix xAI request fields filtering * Fetch xAI model lists from API
Signed| @@ -3624,19 +3624,13 @@ | |||
| 3624 | <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> | 3624 | <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> |
| 3625 | For privacy reasons, your API key will be hidden after you click 'Connect'. | 3625 | For privacy reasons, your API key will be hidden after you click 'Connect'. |
| 3626 | </div> | 3626 | </div> |
| 3627 | <div> | ||
| 3627 | <h4 data-i18n="xAI Model">xAI Model</h4> | 3628 | <h4 data-i18n="xAI Model">xAI Model</h4> |
| 3628 | <select id="model_xai_select"> | 3629 | <select id="model_xai_select"> |
| 3629 | <option value="grok-4-0709">grok-4-0709</option> | 3630 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3630 | <option value="grok-3-beta">grok-3-beta</option> | ||
| 3631 | <option value="grok-3-fast-beta">grok-3-fast-beta</option> | ||
| 3632 | <option value="grok-3-mini-beta">grok-3-mini-beta</option> | ||
| 3633 | <option value="grok-3-mini-fast-beta">grok-3-mini-fast-beta</option> | ||
| 3634 | <option value="grok-2-vision-1212">grok-2-vision-1212</option> | ||
| 3635 | <option value="grok-2-1212">grok-2-1212</option> | ||
| 3636 | <option value="grok-vision-beta">grok-vision-beta</option> | ||
| 3637 | <option value="grok-beta">grok-beta</option> | ||
| 3638 | </select> | 3631 | </select> |
| 3639 | </div> | 3632 | </div> |
| 3633 | </div> | ||
| 3640 | <div id="aimlapi_form" data-source="aimlapi"> | 3634 | <div id="aimlapi_form" data-source="aimlapi"> |
| 3641 | <h4> | 3635 | <h4> |
| 3642 | <span data-i18n="AI/ML API Key">AI/ML API Key</span> | 3636 | <span data-i18n="AI/ML API Key">AI/ML API Key</span> |
| @@ -550,6 +550,7 @@ jQuery(async function () { | |||
| 550 | await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt'); | 550 | await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt'); |
| 551 | await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub'); | 551 | await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub'); |
| 552 | await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral'); | 552 | await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral'); |
| 553 | await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai'); | ||
| 553 | } | 554 | } |
| 554 | 555 | ||
| 555 | await addSettings(); | 556 | await addSettings(); |
| @@ -47,7 +47,7 @@ | |||
| 47 | </div> | 47 | </div> |
| 48 | </label> | 48 | </label> |
| 49 | <select id="caption_multimodal_model" class="flex1 text_pole"> | 49 | <select id="caption_multimodal_model" class="flex1 text_pole"> |
| 50 | <!-- AI/ML API, OpenRouter, Pollinations, NanoGPT, Mistral are added externally by JavaScript --> | 50 | <!-- AI/ML API, OpenRouter, Pollinations, NanoGPT, Mistral, xAI are added externally by JavaScript --> |
| 51 | <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option> | 51 | <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option> |
| 52 | <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option> | 52 | <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option> |
| 53 | <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option> | 53 | <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option> |
| @@ -168,9 +168,6 @@ | |||
| 168 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> | 168 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> |
| 169 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> | 169 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> |
| 170 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> | 170 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> |
| 171 | <option data-type="xai" value="grok-4-0709">grok-4-0709</option> | ||
| 172 | <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option> | ||
| 173 | <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option> | ||
| 174 | </select> | 171 | </select> |
| 175 | </div> | 172 | </div> |
| 176 | <div data-type="ollama"> | 173 | <div data-type="ollama"> |
| @@ -2028,6 +2028,24 @@ function saveModelList(data) { | |||
| 2028 | .append(new Option(modelId || 'None', modelId || '', true, true)) | 2028 | .append(new Option(modelId || 'None', modelId || '', true, true)) |
| 2029 | .trigger('change'); | 2029 | .trigger('change'); |
| 2030 | } | 2030 | } |
| 2031 | |||
| 2032 | if (oai_settings.chat_completion_source == chat_completion_sources.XAI) { | ||
| 2033 | $('#model_xai_select').empty(); | ||
| 2034 | model_list.forEach((model) => { | ||
| 2035 | $('#model_xai_select').append( | ||
| 2036 | $('<option>', { | ||
| 2037 | value: model.id, | ||
| 2038 | text: model.id, | ||
| 2039 | })); | ||
| 2040 | }); | ||
| 2041 | |||
| 2042 | const selectedModel = model_list.find(model => model.id === oai_settings.xai_model); | ||
| 2043 | if (model_list.length > 0 && (!selectedModel || !oai_settings.xai_model)) { | ||
| 2044 | oai_settings.xai_model = model_list[0].id; | ||
| 2045 | } | ||
| 2046 | |||
| 2047 | $('#model_xai_select').val(oai_settings.xai_model).trigger('change'); | ||
| 2048 | } | ||
| 2031 | } | 2049 | } |
| 2032 | 2050 | ||
| 2033 | function appendOpenRouterOptions(model_list, groupModels = false, sort = false) { | 2051 | function appendOpenRouterOptions(model_list, groupModels = false, sort = false) { |
| @@ -2452,19 +2470,24 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = | |||
| 2452 | } | 2470 | } |
| 2453 | 2471 | ||
| 2454 | if (isXAI) { | 2472 | if (isXAI) { |
| 2455 | if (generate_data.model.includes('grok-4')) { | 2473 | const model = generate_data.model; |
| 2474 | if (model.includes('grok-3-mini')) { | ||
| 2456 | delete generate_data.presence_penalty; | 2475 | delete generate_data.presence_penalty; |
| 2457 | delete generate_data.frequency_penalty; | 2476 | delete generate_data.frequency_penalty; |
| 2458 | delete generate_data.stop; | 2477 | delete generate_data.stop; |
| 2478 | } else { | ||
| 2479 | // As of 2025/09/21, only grok-3-mini accepts reasoning_effort | ||
| 2459 | delete generate_data.reasoning_effort; | 2480 | delete generate_data.reasoning_effort; |
| 2460 | } | 2481 | } |
| 2461 | if (generate_data.model.includes('grok-3-mini')) { | 2482 | |
| 2483 | if (model.includes('grok-4') || model.includes('grok-code')) { | ||
| 2462 | delete generate_data.presence_penalty; | 2484 | delete generate_data.presence_penalty; |
| 2463 | delete generate_data.frequency_penalty; | 2485 | delete generate_data.frequency_penalty; |
| 2486 | |||
| 2487 | // grok-4-fast-non-reasoning accepts stop | ||
| 2488 | if (!model.includes('grok-4-fast-non-reasoning')) { | ||
| 2489 | delete generate_data.stop; | ||
| 2464 | } | 2490 | } |
| 2465 | if (generate_data.model.includes('grok-vision')) { | ||
| 2466 | delete generate_data.tools; | ||
| 2467 | delete generate_data.tool_choice; | ||
| 2468 | } | 2491 | } |
| 2469 | } | 2492 | } |
| 2470 | 2493 | ||
| @@ -4916,6 +4939,10 @@ async function onModelChange() { | |||
| 4916 | } | 4939 | } |
| 4917 | 4940 | ||
| 4918 | if ($(this).is('#model_xai_select')) { | 4941 | if ($(this).is('#model_xai_select')) { |
| 4942 | if (!value) { | ||
| 4943 | console.debug('Null XAI model selected. Ignoring.'); | ||
| 4944 | return; | ||
| 4945 | } | ||
| 4919 | console.log('XAI model changed to', value); | 4946 | console.log('XAI model changed to', value); |
| 4920 | oai_settings.xai_model = value; | 4947 | oai_settings.xai_model = value; |
| 4921 | } | 4948 | } |
| @@ -5184,11 +5211,14 @@ async function onModelChange() { | |||
| 5184 | $('#openai_max_context').attr('max', unlocked_max); | 5211 | $('#openai_max_context').attr('max', unlocked_max); |
| 5185 | } else if (oai_settings.xai_model.includes('grok-2-vision')) { | 5212 | } else if (oai_settings.xai_model.includes('grok-2-vision')) { |
| 5186 | $('#openai_max_context').attr('max', max_32k); | 5213 | $('#openai_max_context').attr('max', max_32k); |
| 5187 | } else if (oai_settings.xai_model.includes('grok-vision')) { | 5214 | } else if (oai_settings.xai_model.includes('grok-4-fast')) { |
| 5188 | $('#openai_max_context').attr('max', max_8k); | 5215 | $('#openai_max_context').attr('max', max_2mil); |
| 5189 | } else if (oai_settings.xai_model.includes('grok-4')) { | 5216 | } else if (oai_settings.xai_model.includes('grok-4')) { |
| 5190 | $('#openai_max_context').attr('max', max_256k); | 5217 | $('#openai_max_context').attr('max', max_256k); |
| 5218 | } else if (oai_settings.xai_model.includes('grok-code')) { | ||
| 5219 | $('#openai_max_context').attr('max', max_256k); | ||
| 5191 | } else { | 5220 | } else { |
| 5221 | // grok 2 and grok 3 | ||
| 5192 | $('#openai_max_context').attr('max', max_128k); | 5222 | $('#openai_max_context').attr('max', max_128k); |
| 5193 | } | 5223 | } |
| 5194 | 5224 | ||
| @@ -5737,7 +5767,6 @@ export function isImageInliningSupported() { | |||
| 5737 | // xAI (Grok) | 5767 | // xAI (Grok) |
| 5738 | 'grok-4', | 5768 | 'grok-4', |
| 5739 | 'grok-2-vision', | 5769 | 'grok-2-vision', |
| 5740 | 'grok-vision', | ||
| 5741 | // Moonshot | 5770 | // Moonshot |
| 5742 | 'moonshot-v1-8k-vision-preview', | 5771 | 'moonshot-v1-8k-vision-preview', |
| 5743 | 'moonshot-v1-32k-vision-preview', | 5772 | 'moonshot-v1-32k-vision-preview', |
| @@ -5770,6 +5799,7 @@ export function isImageInliningSupported() { | |||
| 5770 | case chat_completion_sources.COHERE: | 5799 | case chat_completion_sources.COHERE: |
| 5771 | return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model)); | 5800 | return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model)); |
| 5772 | case chat_completion_sources.XAI: | 5801 | case chat_completion_sources.XAI: |
| 5802 | // TODO: xAI's /models endpoint doesn't return modality info | ||
| 5773 | return visionSupportedModels.some(model => oai_settings.xai_model.includes(model)); | 5803 | return visionSupportedModels.some(model => oai_settings.xai_model.includes(model)); |
| 5774 | case chat_completion_sources.AIMLAPI: | 5804 | case chat_completion_sources.AIMLAPI: |
| 5775 | return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision')); | 5805 | return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision')); |
| @@ -1012,7 +1012,7 @@ async function sendXaiRequest(request, response) { | |||
| 1012 | bodyParams['stop'] = request.body.stop; | 1012 | bodyParams['stop'] = request.body.stop; |
| 1013 | } | 1013 | } |
| 1014 | 1014 | ||
| 1015 | if (request.body.reasoning_effort && ['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) { | 1015 | if (request.body.reasoning_effort) { |
| 1016 | bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low'; | 1016 | bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low'; |
| 1017 | } | 1017 | } |
| 1018 | 1018 | ||
| @@ -2238,4 +2238,37 @@ multimodalModels.post('/mistral', async (req, res) => { | |||
| 2238 | } | 2238 | } |
| 2239 | }); | 2239 | }); |
| 2240 | 2240 | ||
| 2241 | multimodalModels.post('/xai', async (req, res) => { | ||
| 2242 | try { | ||
| 2243 | const key = readSecret(req.user.directories, SECRET_KEYS.XAI); | ||
| 2244 | |||
| 2245 | if (!key) { | ||
| 2246 | return res.json([]); | ||
| 2247 | } | ||
| 2248 | |||
| 2249 | // xAI's /models endpoint doesn't return modality info, so we must use /language-models instead | ||
| 2250 | const response = await fetch('https://api.x.ai/v1/language-models', { | ||
| 2251 | headers: { | ||
| 2252 | 'Authorization': `Bearer ${key}`, | ||
| 2253 | }, | ||
| 2254 | }); | ||
| 2255 | |||
| 2256 | if (!response.ok) { | ||
| 2257 | return res.json([]); | ||
| 2258 | } | ||
| 2259 | |||
| 2260 | /** @type {any} */ | ||
| 2261 | const data = await response.json(); | ||
| 2262 | const multimodalModels = data.models.filter(m => m.input_modalities?.includes('image')).map(m => m.id); | ||
| 2263 | if (!multimodalModels.includes('grok-4-0709')) { | ||
| 2264 | // The endpoint says it doesn't support images, but it does | ||
| 2265 | multimodalModels.push('grok-4-0709'); | ||
| 2266 | } | ||
| 2267 | return res.json(multimodalModels); | ||
| 2268 | } catch (error) { | ||
| 2269 | console.error(error); | ||
| 2270 | return res.sendStatus(500); | ||
| 2271 | } | ||
| 2272 | }); | ||
| 2273 | |||
| 2241 | router.use('/multimodal-models', multimodalModels); | 2274 | router.use('/multimodal-models', multimodalModels); |