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 <copilot@github.com> --------- Co-authored-by: Copilot <copilot@github.com>
Signed| @@ -3373,6 +3373,8 @@ | |||
| 3373 | <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option> | 3373 | <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option> |
| 3374 | </optgroup> | 3374 | </optgroup> |
| 3375 | <optgroup label="Gemma"> | 3375 | <optgroup label="Gemma"> |
| 3376 | <option value="gemma-4-31b-it">gemma-4-31b-it</option> | ||
| 3377 | <option value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option> | ||
| 3376 | <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option> | 3378 | <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option> |
| 3377 | <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option> | 3379 | <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option> |
| 3378 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> | 3380 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> |
| @@ -153,6 +153,11 @@ | |||
| 153 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> | 153 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> |
| 154 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> | 154 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 155 | <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option> | 155 | <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option> |
| 156 | <option data-type="google" value="gemma-4-31b-it">gemma-4-31b-it</option> | ||
| 157 | <option data-type="google" value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option> | ||
| 158 | <option data-type="google" value="gemma-3-27b-it">gemma-3-27b-it</option> | ||
| 159 | <option data-type="google" value="gemma-3-12b-it">gemma-3-12b-it</option> | ||
| 160 | <option data-type="google" value="gemma-3-4b-it">gemma-3-4b-it</option> | ||
| 156 | <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option> | 161 | <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option> |
| 157 | <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option> | 162 | <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option> |
| 158 | <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option> | 163 | <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option> |
| @@ -4934,6 +4934,65 @@ function getMaxContextOpenAI(value) { | |||
| 4934 | } | 4934 | } |
| 4935 | 4935 | ||
| 4936 | /** | 4936 | /** |
| 4937 | * Get the maximum context size for Gemini models based on model identifier and optional model list. | ||
| 4938 | * @param {string} model Model identifier | ||
| 4939 | * @param {boolean} isUnlocked Whether context limits are unlocked | ||
| 4940 | * @returns {number} Maximum context size in tokens | ||
| 4941 | */ | ||
| 4942 | function getGeminiMaxContext(model, isUnlocked) { | ||
| 4943 | if (isUnlocked) { | ||
| 4944 | return unlocked_max; | ||
| 4945 | } | ||
| 4946 | |||
| 4947 | if (Array.isArray(model_list) && model_list.length > 0) { | ||
| 4948 | const contextLength = model_list.find((record) => record.id === model)?.inputTokenLimit; | ||
| 4949 | if (Number.isFinite(contextLength) && contextLength > 0) { | ||
| 4950 | return contextLength; | ||
| 4951 | } | ||
| 4952 | } | ||
| 4953 | |||
| 4954 | /** @type {[RegExp, number][]} */ | ||
| 4955 | const contextMap = [ | ||
| 4956 | [/gemini-2\.5-flash-image/, max_32k], | ||
| 4957 | [/gemini-3-pro-image/, max_64k], | ||
| 4958 | [/gemini-(?:3[.\d]*|2\.(?:5|0))-(pro|flash)/, max_1mil], | ||
| 4959 | [/(gemini-exp|learnlm-2\.0-flash|gemini-robotics)/, max_1mil], | ||
| 4960 | [/gemma-3-27b-it/, max_128k], | ||
| 4961 | [/gemma-3n-e4b-it/, max_8k], | ||
| 4962 | [/gemma-3/, max_32k], | ||
| 4963 | [/gemma-4/, max_256k], | ||
| 4964 | ]; | ||
| 4965 | |||
| 4966 | for (const [regex, max] of contextMap) { | ||
| 4967 | if (regex.test(model)) { | ||
| 4968 | return max; | ||
| 4969 | } | ||
| 4970 | } | ||
| 4971 | |||
| 4972 | return max_128k; | ||
| 4973 | } | ||
| 4974 | |||
| 4975 | /** | ||
| 4976 | * Get the maximum temperature for Gemini models based on model identifier and optional model list. | ||
| 4977 | * @param {string} model Model identifier | ||
| 4978 | * @returns {number} Maximum temperature for Gemini models | ||
| 4979 | */ | ||
| 4980 | function getGeminiMaxTemp(model) { | ||
| 4981 | if (Array.isArray(model_list) && model_list.length > 0) { | ||
| 4982 | const temp = model_list.find((record) => record.id === model)?.temperature; | ||
| 4983 | if (Number.isFinite(temp) && temp > 0) { | ||
| 4984 | return temp; | ||
| 4985 | } | ||
| 4986 | } | ||
| 4987 | |||
| 4988 | if (/(vision|ultra|gemma)/.test(model)) { | ||
| 4989 | return 1.0; | ||
| 4990 | } | ||
| 4991 | |||
| 4992 | return 2.0; | ||
| 4993 | } | ||
| 4994 | |||
| 4995 | /** | ||
| 4937 | * Get the maximum context size for the Mistral model | 4996 | * Get the maximum context size for the Mistral model |
| 4938 | * @param {string} model Model identifier | 4997 | * @param {string} model Model identifier |
| 4939 | * @param {boolean} isUnlocked Whether context limits are unlocked | 4998 | * @param {boolean} isUnlocked Whether context limits are unlocked |
| @@ -5443,28 +5502,11 @@ async function onModelChange() { | |||
| 5443 | } | 5502 | } |
| 5444 | 5503 | ||
| 5445 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) { | 5504 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) { |
| 5446 | if (oai_settings.max_context_unlocked) { | 5505 | const contextSize = getGeminiMaxContext(value, oai_settings.max_context_unlocked); |
| 5447 | $('#openai_max_context').attr('max', max_2mil); | 5506 | const maxTemp = getGeminiMaxTemp(value); |
| 5448 | } else if (value.includes('gemini-2.5-flash-image')) { | 5507 | $('#openai_max_context').attr('max', contextSize); |
| 5449 | $('#openai_max_context').attr('max', max_32k); | 5508 | oai_settings.temp_openai = Math.min(maxTemp, oai_settings.temp_openai); |
| 5450 | } else if (value.includes('gemini-3-pro-image')) { | 5509 | $('#temp_openai').attr('max', maxTemp).val(oai_settings.temp_openai).trigger('input'); |
| 5451 | $('#openai_max_context').attr('max', max_64k); | ||
| 5452 | } else if (/gemini-3[.\d]*-(pro|flash)/.test(value) || /gemini-2.5-(pro|flash)/.test(value) || /gemini-2.0-(pro|flash)/.test(value)) { | ||
| 5453 | $('#openai_max_context').attr('max', max_1mil); | ||
| 5454 | } else if (value.includes('gemini-exp') || value.includes('learnlm-2.0-flash') || value.includes('gemini-robotics')) { | ||
| 5455 | $('#openai_max_context').attr('max', max_1mil); | ||
| 5456 | } else if (value.includes('gemma-3-27b-it')) { | ||
| 5457 | $('#openai_max_context').attr('max', max_128k); | ||
| 5458 | } else if (value.includes('gemma-3n-e4b-it')) { | ||
| 5459 | $('#openai_max_context').attr('max', max_8k); | ||
| 5460 | } else if (value.includes('gemma-3')) { | ||
| 5461 | $('#openai_max_context').attr('max', max_32k); | ||
| 5462 | } else { | ||
| 5463 | $('#openai_max_context').attr('max', max_32k); | ||
| 5464 | } | ||
| 5465 | let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0; | ||
| 5466 | oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai); | ||
| 5467 | $('#temp_openai').attr('max', makersuite_max_temp).val(oai_settings.temp_openai).trigger('input'); | ||
| 5468 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | 5510 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 5469 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | 5511 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| 5470 | } | 5512 | } |
| @@ -6040,6 +6082,10 @@ export function isImageInliningSupported() { | |||
| 6040 | 'gemini-exp-1206', | 6082 | 'gemini-exp-1206', |
| 6041 | 'learnlm', | 6083 | 'learnlm', |
| 6042 | 'gemini-robotics', | 6084 | 'gemini-robotics', |
| 6085 | 'gemma-3-27b', | ||
| 6086 | 'gemma-3-12b', | ||
| 6087 | 'gemma-3-4b', | ||
| 6088 | 'gemma-4', | ||
| 6043 | // MistralAI | 6089 | // MistralAI |
| 6044 | 'mistral-small-2503', | 6090 | 'mistral-small-2503', |
| 6045 | 'mistral-small-2506', | 6091 | 'mistral-small-2506', |
| @@ -6144,6 +6190,7 @@ export function isVideoInliningSupported() { | |||
| 6144 | 'gemini-2.5', | 6190 | 'gemini-2.5', |
| 6145 | 'gemini-exp-1206', | 6191 | 'gemini-exp-1206', |
| 6146 | 'gemini-3', | 6192 | 'gemini-3', |
| 6193 | 'gemma-4', | ||
| 6147 | // Z.AI (GLM) | 6194 | // Z.AI (GLM) |
| 6148 | 'glm-4.5v', | 6195 | 'glm-4.5v', |
| 6149 | 'glm-4.6v', | 6196 | 'glm-4.6v', |
| @@ -459,7 +459,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 459 | const includeReasoning = Boolean(request.body.include_reasoning); | 459 | const includeReasoning = Boolean(request.body.include_reasoning); |
| 460 | const aspectRatio = String(request.body.request_image_aspect_ratio); | 460 | const aspectRatio = String(request.body.request_image_aspect_ratio); |
| 461 | const imageSize = String(request.body.request_image_resolution); | 461 | const imageSize = String(request.body.request_image_resolution); |
| 462 | const isGemma = model.includes('gemma'); | 462 | const isGemma3 = /gemma-3/.test(model); |
| 463 | const isLearnLM = model.includes('learnlm'); | 463 | const isLearnLM = model.includes('learnlm'); |
| 464 | 464 | ||
| 465 | const responseMimeType = request.body.responseMimeType ?? (request.body.json_schema ? 'application/json' : undefined); | 465 | const responseMimeType = request.body.responseMimeType ?? (request.body.json_schema ? 'application/json' : undefined); |
| @@ -519,13 +519,13 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 519 | } | 519 | } |
| 520 | } | 520 | } |
| 521 | 521 | ||
| 522 | const useSystemPrompt = !enableImageModality && !isGemma && request.body.use_sysprompt; | 522 | const useSystemPrompt = !enableImageModality && !isGemma3 && request.body.use_sysprompt; |
| 523 | 523 | ||
| 524 | const tools = []; | 524 | const tools = []; |
| 525 | const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); | 525 | const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); |
| 526 | const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])]; | 526 | const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])]; |
| 527 | 527 | ||
| 528 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma) { | 528 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma3) { |
| 529 | const functionDeclarations = []; | 529 | const functionDeclarations = []; |
| 530 | const customTools = []; | 530 | const customTools = []; |
| 531 | for (const tool of request.body.tools) { | 531 | for (const tool of request.body.tools) { |
| @@ -550,7 +550,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 550 | } | 550 | } |
| 551 | } | 551 | } |
| 552 | 552 | ||
| 553 | if (enableWebSearch && !enableImageModality && !isGemma && !isLearnLM && !noSearchModels.includes(model)) { | 553 | if (enableWebSearch && !enableImageModality && !isGemma3 && !isLearnLM && !noSearchModels.includes(model)) { |
| 554 | // Tool use with function calling is unsupported | 554 | // Tool use with function calling is unsupported |
| 555 | if (!tools.some(t => t.function_declarations)) { | 555 | if (!tools.some(t => t.function_declarations)) { |
| 556 | tools.push({ google_search: {} }); | 556 | tools.push({ google_search: {} }); |
| @@ -1832,6 +1832,7 @@ router.post('/status', async function (request, statusResponse) { | |||
| 1832 | const models = data.models | 1832 | const models = data.models |
| 1833 | ?.filter(model => model.supportedGenerationMethods?.includes('generateContent')) | 1833 | ?.filter(model => model.supportedGenerationMethods?.includes('generateContent')) |
| 1834 | ?.map(model => ({ | 1834 | ?.map(model => ({ |
| 1835 | ...model, | ||
| 1835 | id: model.name.replace('models/', ''), | 1836 | id: model.name.replace('models/', ''), |
| 1836 | })) || []; | 1837 | })) || []; |
| 1837 | 1838 | ||