From 09d72828cb22bb7fe5e189eff322b5632babc513 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Apr 2026 22:22:55 +0300 Subject: [PATCH] feat: add gemma 4 for AI studio (#5493) * feat: add gemma 4 for AI studio * fix: update max context return value for gemma-3n-e4b-it model * refactor: iterate array of [regex, number] * gemma4: enable tool calling and sysprompt Co-authored-by: Copilot --------- Co-authored-by: Copilot --- public/index.html | 2 + .../scripts/extensions/caption/settings.html | 5 + public/scripts/openai.js | 91 ++++++++++++++----- src/endpoints/backends/chat-completions.js | 9 +- 4 files changed, 81 insertions(+), 26 deletions(-) diff --git a/public/index.html b/public/index.html index dbae18ad4..9f541c7ae 100644 --- a/public/index.html +++ b/public/index.html @@ -3373,6 +3373,8 @@ + + diff --git a/public/scripts/extensions/caption/settings.html b/public/scripts/extensions/caption/settings.html index a53e3a0f4..011759a12 100644 --- a/public/scripts/extensions/caption/settings.html +++ b/public/scripts/extensions/caption/settings.html @@ -153,6 +153,11 @@ + + + + + diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 981fce9a1..a6ea2b84e 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -4933,6 +4933,65 @@ function getMaxContextOpenAI(value) { return max_128k; } +/** + * Get the maximum context size for Gemini models based on model identifier and optional model list. + * @param {string} model Model identifier + * @param {boolean} isUnlocked Whether context limits are unlocked + * @returns {number} Maximum context size in tokens + */ +function getGeminiMaxContext(model, isUnlocked) { + if (isUnlocked) { + return unlocked_max; + } + + if (Array.isArray(model_list) && model_list.length > 0) { + const contextLength = model_list.find((record) => record.id === model)?.inputTokenLimit; + if (Number.isFinite(contextLength) && contextLength > 0) { + return contextLength; + } + } + + /** @type {[RegExp, number][]} */ + const contextMap = [ + [/gemini-2\.5-flash-image/, max_32k], + [/gemini-3-pro-image/, max_64k], + [/gemini-(?:3[.\d]*|2\.(?:5|0))-(pro|flash)/, max_1mil], + [/(gemini-exp|learnlm-2\.0-flash|gemini-robotics)/, max_1mil], + [/gemma-3-27b-it/, max_128k], + [/gemma-3n-e4b-it/, max_8k], + [/gemma-3/, max_32k], + [/gemma-4/, max_256k], + ]; + + for (const [regex, max] of contextMap) { + if (regex.test(model)) { + return max; + } + } + + return max_128k; +} + +/** + * Get the maximum temperature for Gemini models based on model identifier and optional model list. + * @param {string} model Model identifier + * @returns {number} Maximum temperature for Gemini models + */ +function getGeminiMaxTemp(model) { + if (Array.isArray(model_list) && model_list.length > 0) { + const temp = model_list.find((record) => record.id === model)?.temperature; + if (Number.isFinite(temp) && temp > 0) { + return temp; + } + } + + if (/(vision|ultra|gemma)/.test(model)) { + return 1.0; + } + + return 2.0; +} + /** * Get the maximum context size for the Mistral model * @param {string} model Model identifier @@ -5443,28 +5502,11 @@ async function onModelChange() { } if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) { - if (oai_settings.max_context_unlocked) { - $('#openai_max_context').attr('max', max_2mil); - } else if (value.includes('gemini-2.5-flash-image')) { - $('#openai_max_context').attr('max', max_32k); - } else if (value.includes('gemini-3-pro-image')) { - $('#openai_max_context').attr('max', max_64k); - } else if (/gemini-3[.\d]*-(pro|flash)/.test(value) || /gemini-2.5-(pro|flash)/.test(value) || /gemini-2.0-(pro|flash)/.test(value)) { - $('#openai_max_context').attr('max', max_1mil); - } else if (value.includes('gemini-exp') || value.includes('learnlm-2.0-flash') || value.includes('gemini-robotics')) { - $('#openai_max_context').attr('max', max_1mil); - } else if (value.includes('gemma-3-27b-it')) { - $('#openai_max_context').attr('max', max_128k); - } else if (value.includes('gemma-3n-e4b-it')) { - $('#openai_max_context').attr('max', max_8k); - } else if (value.includes('gemma-3')) { - $('#openai_max_context').attr('max', max_32k); - } else { - $('#openai_max_context').attr('max', max_32k); - } - let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0; - oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai); - $('#temp_openai').attr('max', makersuite_max_temp).val(oai_settings.temp_openai).trigger('input'); + const contextSize = getGeminiMaxContext(value, oai_settings.max_context_unlocked); + const maxTemp = getGeminiMaxTemp(value); + $('#openai_max_context').attr('max', contextSize); + oai_settings.temp_openai = Math.min(maxTemp, oai_settings.temp_openai); + $('#temp_openai').attr('max', maxTemp).val(oai_settings.temp_openai).trigger('input'); oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); } @@ -6040,6 +6082,10 @@ export function isImageInliningSupported() { 'gemini-exp-1206', 'learnlm', 'gemini-robotics', + 'gemma-3-27b', + 'gemma-3-12b', + 'gemma-3-4b', + 'gemma-4', // MistralAI 'mistral-small-2503', 'mistral-small-2506', @@ -6144,6 +6190,7 @@ export function isVideoInliningSupported() { 'gemini-2.5', 'gemini-exp-1206', 'gemini-3', + 'gemma-4', // Z.AI (GLM) 'glm-4.5v', 'glm-4.6v', diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index 16972d578..e2ff3ae53 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -459,7 +459,7 @@ async function sendMakerSuiteRequest(request, response) { const includeReasoning = Boolean(request.body.include_reasoning); const aspectRatio = String(request.body.request_image_aspect_ratio); const imageSize = String(request.body.request_image_resolution); - const isGemma = model.includes('gemma'); + const isGemma3 = /gemma-3/.test(model); const isLearnLM = model.includes('learnlm'); const responseMimeType = request.body.responseMimeType ?? (request.body.json_schema ? 'application/json' : undefined); @@ -519,13 +519,13 @@ async function sendMakerSuiteRequest(request, response) { } } - const useSystemPrompt = !enableImageModality && !isGemma && request.body.use_sysprompt; + const useSystemPrompt = !enableImageModality && !isGemma3 && request.body.use_sysprompt; const tools = []; const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])]; - if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma) { + if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma3) { const functionDeclarations = []; const customTools = []; for (const tool of request.body.tools) { @@ -550,7 +550,7 @@ async function sendMakerSuiteRequest(request, response) { } } - if (enableWebSearch && !enableImageModality && !isGemma && !isLearnLM && !noSearchModels.includes(model)) { + if (enableWebSearch && !enableImageModality && !isGemma3 && !isLearnLM && !noSearchModels.includes(model)) { // Tool use with function calling is unsupported if (!tools.some(t => t.function_declarations)) { tools.push({ google_search: {} }); @@ -1832,6 +1832,7 @@ router.post('/status', async function (request, statusResponse) { const models = data.models ?.filter(model => model.supportedGenerationMethods?.includes('generateContent')) ?.map(model => ({ + ...model, id: model.name.replace('models/', ''), })) || [];