Add Vertex AI express mode support (#3977) * Add Vertex AI express mode support Split Google AI Studio and Vertex AI * Add support for Vertex AI, including updating default models and related settings, modifying frontend HTML to include Vertex AI options, and adjusting request processing logic in the backend API. * Log API name in the console * Merge sysprompt toggles back * Use Gemma tokenizers for Vertex and LearnLM * AI Studio parity updates * Add link to express mode doc. Also technically it's not a form * Split title * Use array includes * Add support for Google Vertex AI in image captioning feature * Specify caption API name, add to compression list --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -15,6 +15,7 @@ | ||
| 15 | 15 | "custom_exclude_body": "", |
| 16 | 16 | "custom_include_headers": "", |
| 17 | 17 | "google_model": "gemini-pro", |
| 18 | + "vertexai_model": "gemini-2.0-flash-001", | |
| 18 | 19 | "temperature": 1, |
| 19 | 20 | "frequency_penalty": 0, |
| 20 | 21 | "presence_penalty": 0, |
| @@ -691,7 +691,7 @@ | ||
| 691 | 691 | </span> |
| 692 | 692 | </div> |
| 693 | 693 | </div> |
| 694 | 694 | <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai"> |
| 695 | 695 | <div class="range-block-title" data-i18n="Temperature"> |
| 696 | 696 | Temperature |
| 697 | 697 | </div> |
| @@ -730,7 +730,7 @@ | ||
| 730 | 730 | </div> |
| 731 | 731 | </div> |
| 732 | 732 | </div> |
| 733 | 733 | <div class="range-block" data-source="claude,openrouter,makersuite,vertexai,cohere,perplexity"> |
| 734 | 734 | <div class="range-block-title" data-i18n="Top K"> |
| 735 | 735 | Top K |
| 736 | 736 | </div> |
| @@ -743,7 +743,7 @@ | ||
| 743 | 743 | </div> |
| 744 | 744 | </div> |
| 745 | 745 | </div> |
| 746 | 746 | <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai"> |
| 747 | 747 | <div class="range-block-title" data-i18n="Top P"> |
| 748 | 748 | Top P |
| 749 | 749 | </div> |
| @@ -1969,7 +1969,7 @@ | ||
| 1969 | 1969 | </span> |
| 1970 | 1970 | </div> |
| 1971 | 1971 | </div> |
| 1972 | 1972 | <div class="range-block" data-source="makersuite,vertexai,openrouter,claude"> |
| 1973 | 1973 | <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand"> |
| 1974 | 1974 | <input id="openai_enable_web_search" type="checkbox" /> |
| 1975 | 1975 | <span data-i18n="Enable web search">Enable web search</span> |
| @@ -1983,7 +1983,7 @@ | ||
| 1983 | 1983 | </b> |
| 1984 | 1984 | </div> |
| 1985 | 1985 | </div> |
| 1986 | 1986 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations"> |
| 1987 | 1987 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1988 | 1988 | <input id="openai_function_calling" type="checkbox" /> |
| 1989 | 1989 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -1993,7 +1993,7 @@ | ||
| 1993 | 1993 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 1994 | 1994 | </div> |
| 1995 | 1995 | </div> |
| 1996 | 1996 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations"> |
| 1997 | 1997 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 1998 | 1998 | <input id="openai_image_inlining" type="checkbox" /> |
| 1999 | 1999 | <span data-i18n="Send inline images">Send inline images</span> |
| @@ -2018,7 +2018,7 @@ | ||
| 2018 | 2018 | </div> |
| 2019 | 2019 | </div> |
| 2020 | 2020 | </div> |
| 2021 | 2021 | <div class="range-block" data-source="makersuite,vertexai"> |
| 2022 | 2022 | <label for="openai_request_images" class="checkbox_label widthFreeExpand"> |
| 2023 | 2023 | <input id="openai_request_images" type="checkbox" /> |
| 2024 | 2024 | <span> |
| @@ -2030,12 +2030,12 @@ | ||
| 2030 | 2030 | <span data-i18n="Allows the model to return image attachments."> |
| 2031 | 2031 | Allows the model to return image attachments. |
| 2032 | 2032 | </span> |
| 2033 | 2033 | <em data-source="makersuite,vertexai" data-i18n="Request inline images_desc_2"> |
| 2034 | 2034 | Incompatible with the following features: function calling, web search, system prompt. |
| 2035 | 2035 | </em> |
| 2036 | 2036 | </div> |
| 2037 | 2037 | </div> |
| 2038 | 2038 | <div class="range-block" data-source="makersuite,vertexai"> |
| 2039 | 2039 | <label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand"> |
| 2040 | 2040 | <input id="use_makersuite_sysprompt" type="checkbox" /> |
| 2041 | 2041 | <span> |
| @@ -2060,12 +2060,12 @@ | ||
| 2060 | 2060 | </span> |
| 2061 | 2061 | </div> |
| 2062 | 2062 | </div> |
| 2063 | 2063 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,openrouter,pollinations"> |
| 2064 | 2064 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> |
| 2065 | 2065 | <label for="openai_reasoning_effort"> |
| 2066 | 2066 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| 2067 | 2067 | <i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level." data-i18n="[title]OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i> |
| 2068 | 2068 | <i data-source="claude,makersuite,vertexai" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent." data-i18n="[title]Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i> |
| 2069 | 2069 | </label> |
| 2070 | 2070 | <select id="openai_reasoning_effort"> |
| 2071 | 2071 | <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option> |
| @@ -2769,6 +2769,7 @@ | ||
| 2769 | 2769 | <option value="deepseek">DeepSeek</option> |
| 2770 | 2770 | <option value="groq">Groq</option> |
| 2771 | 2771 | <option value="makersuite">Google AI Studio</option> |
| 2772 | + <option value="vertexai">Google Vertex AI (Express mode)</option> | |
| 2772 | 2773 | <option value="mistralai">MistralAI</option> |
| 2773 | 2774 | <option value="nanogpt">NanoGPT</option> |
| 2774 | 2775 | <option value="openrouter">OpenRouter</option> |
| @@ -2779,7 +2780,7 @@ | ||
| 2779 | 2780 | <option value="xai">xAI (Grok)</option> |
| 2780 | 2781 | </optgroup> |
| 2781 | 2782 | </select> |
| 2782 | 2783 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai"> |
| 2783 | 2784 | <div class="inline-drawer-toggle inline-drawer-header"> |
| 2784 | 2785 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> |
| 2785 | 2786 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| @@ -2843,7 +2844,7 @@ | ||
| 2843 | 2844 | </div> |
| 2844 | 2845 | </div> |
| 2845 | 2846 | </div> |
| 2846 | 2847 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai"> |
| 2847 | 2848 | <div class="reverse_proxy_warning"> |
| 2848 | 2849 | <b> |
| 2849 | 2850 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> |
| @@ -3209,6 +3210,38 @@ | ||
| 3209 | 3210 | </select> |
| 3210 | 3211 | </div> |
| 3211 | 3212 | </form> |
| 3213 | + <div id="vertexai_form" data-source="vertexai"> | |
| 3214 | + <h4> | |
| 3215 | + <span data-i18n="Google Vertex AI API Key"> | |
| 3216 | + Google Vertex AI API Key | |
| 3217 | + </span> | |
| 3218 | + <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview" data-i18n="(Express mode keys only)" target="_blank" rel="noopener noreferrer"> | |
| 3219 | + (Express mode keys only) | |
| 3220 | + </a> | |
| 3221 | + </h4> | |
| 3222 | + <div class="flex-container"> | |
| 3223 | + <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off"> | |
| 3224 | + <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_vertexai"></div> | |
| 3225 | + </div> | |
| 3226 | + <div data-for="api_key_vertexai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page."> | |
| 3227 | + For privacy reasons, your API key will be hidden after you reload the page. | |
| 3228 | + </div> | |
| 3229 | + <div> | |
| 3230 | + <h4 data-i18n="Google Model">Google Model</h4> | |
| 3231 | + <select id="model_vertexai_select"> | |
| 3232 | + <optgroup label="Gemini 2.5"> | |
| 3233 | + <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option> | |
| 3234 | + <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> | |
| 3235 | + <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option> | |
| 3236 | + <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option> | |
| 3237 | + </optgroup> | |
| 3238 | + <optgroup label="Gemini 2.0"> | |
| 3239 | + <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> | |
| 3240 | + <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option> | |
| 3241 | + </optgroup> | |
| 3242 | + </select> | |
| 3243 | + </div> | |
| 3244 | + </div> | |
| 3212 | 3245 | <form id="mistralai_form" data-source="mistralai" action="javascript:void(null);" method="post" enctype="multipart/form-data"> |
| 3213 | 3246 | <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4> |
| 3214 | 3247 | <div class="flex-container"> |
| @@ -5941,6 +5941,7 @@ function extractImageFromData(data, { mainApi = null, chatCompletionSource = nul | ||
| 5941 | 5941 | switch (mainApi ?? main_api) { |
| 5942 | 5942 | case 'openai': { |
| 5943 | 5943 | switch (chatCompletionSource ?? oai_settings.chat_completion_source) { |
| 5944 | + case chat_completion_sources.VERTEXAI: | |
| 5944 | 5945 | case chat_completion_sources.MAKERSUITE: { |
| 5945 | 5946 | const inlineData = data?.responseContent?.parts?.find(x => x.inlineData)?.inlineData; |
| 5946 | 5947 | if (inlineData) { |
| @@ -402,6 +402,7 @@ function RA_autoconnect(PrevApi) { | ||
| 402 | 402 | || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) |
| 403 | 403 | || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21) |
| 404 | 404 | || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) |
| 405 | + || (secret_state[SECRET_KEYS.VERTEXAI] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) | |
| 405 | 406 | || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) |
| 406 | 407 | || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE) |
| 407 | 408 | || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY) |
| @@ -414,6 +414,7 @@ jQuery(async function () { | ||
| 414 | 414 | 'openai': SECRET_KEYS.OPENAI, |
| 415 | 415 | 'mistral': SECRET_KEYS.MISTRALAI, |
| 416 | 416 | 'google': SECRET_KEYS.MAKERSUITE, |
| 417 | + 'vertexai': SECRET_KEYS.VERTEXAI, | |
| 417 | 418 | 'anthropic': SECRET_KEYS.CLAUDE, |
| 418 | 419 | }; |
| 419 | 420 | |
| @@ -22,6 +22,7 @@ | ||
| 22 | 22 | <option value="cohere">Cohere</option> |
| 23 | 23 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> |
| 24 | 24 | <option value="google">Google AI Studio</option> |
| 25 | + <option value="vertexai">Google Vertex AI</option> | |
| 25 | 26 | <option value="groq">Groq</option> |
| 26 | 27 | <option value="koboldcpp">KoboldCpp</option> |
| 27 | 28 | <option value="llamacpp">llama.cpp</option> |
| @@ -111,6 +112,12 @@ | ||
| 111 | 112 | <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option> |
| 112 | 113 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 113 | 114 | <option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option> |
| 115 | + <option data-type="vertexai" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option> | |
| 116 | + <option data-type="vertexai" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> | |
| 117 | + <option data-type="vertexai" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option> | |
| 118 | + <option data-type="vertexai" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option> | |
| 119 | + <option data-type="vertexai" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> | |
| 120 | + <option data-type="vertexai" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option> | |
| 114 | 121 | <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option> |
| 115 | 122 | <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option> |
| 116 | 123 | <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option> |
| @@ -170,7 +177,7 @@ | ||
| 170 | 177 | <div data-type="ollama"> |
| 171 | 178 | The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>. |
| 172 | 179 | </div> |
| 173 | 180 | <label data-type="openai,anthropic,google,vertexai,mistral" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid."> |
| 174 | 181 | <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox"> |
| 175 | 182 | <span data-i18n="Allow reverse proxy">Allow reverse proxy</span> |
| 176 | 183 | </label> |
| @@ -15,7 +15,7 @@ import { createThumbnail, isValidUrl } from '../utils.js'; | ||
| 15 | 15 | */ |
| 16 | 16 | export async function getMultimodalCaption(base64Img, prompt) { |
| 17 | 17 | const useReverseProxy = |
| 18 | 18 | (['openai', 'anthropic', 'google', 'mistral', 'vertexai'].includes(extension_settings.caption.multimodal_api)) |
| 19 | 19 | && extension_settings.caption.allow_reverse_proxy |
| 20 | 20 | && oai_settings.reverse_proxy |
| 21 | 21 | && isValidUrl(oai_settings.reverse_proxy); |
| @@ -38,7 +38,8 @@ export async function getMultimodalCaption(base64Img, prompt) { | ||
| 38 | 38 | const isVllm = extension_settings.caption.multimodal_api === 'vllm'; |
| 39 | 39 | const base64Bytes = base64Img.length * 0.75; |
| 40 | 40 | const compressionLimit = 2 * 1024 * 1024; |
| 41 | 41 | ifconst ((thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {; |
| 42 | + if ((thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) { | |
| 42 | 43 | const maxSide = 1024; |
| 43 | 44 | base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg'); |
| 44 | 45 | } |
| @@ -94,6 +95,7 @@ export async function getMultimodalCaption(base64Img, prompt) { | ||
| 94 | 95 | function getEndpointUrl() { |
| 95 | 96 | switch (extension_settings.caption.multimodal_api) { |
| 96 | 97 | case 'google': |
| 98 | + case 'vertexai': | |
| 97 | 99 | return '/api/google/caption-image'; |
| 98 | 100 | case 'anthropic': |
| 99 | 101 | return '/api/anthropic/caption-image'; |
| @@ -143,6 +145,10 @@ function throwIfInvalidModel(useReverseProxy) { | ||
| 143 | 145 | throw new Error('Google AI Studio API key is not set.'); |
| 144 | 146 | } |
| 145 | 147 | |
| 148 | + if (extension_settings.caption.multimodal_api === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !useReverseProxy) { | |
| 149 | + throw new Error('Google Vertex AI API key is not set.'); | |
| 150 | + } | |
| 151 | + | |
| 146 | 152 | if (extension_settings.caption.multimodal_api === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) { |
| 147 | 153 | throw new Error('Mistral AI API key is not set.'); |
| 148 | 154 | } |
| @@ -176,6 +176,7 @@ export const chat_completion_sources = { | ||
| 176 | 176 | OPENROUTER: 'openrouter', |
| 177 | 177 | AI21: 'ai21', |
| 178 | 178 | MAKERSUITE: 'makersuite', |
| 179 | + VERTEXAI: 'vertexai', | |
| 179 | 180 | MISTRALAI: 'mistralai', |
| 180 | 181 | CUSTOM: 'custom', |
| 181 | 182 | COHERE: 'cohere', |
| @@ -277,6 +278,7 @@ export const settingsToUpdate = { | ||
| 277 | 278 | custom_include_headers: ['#custom_include_headers', 'custom_include_headers', false, true], |
| 278 | 279 | custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false, true], |
| 279 | 280 | google_model: ['#model_google_select', 'google_model', false, true], |
| 281 | + vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], | |
| 280 | 282 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], |
| 281 | 283 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], |
| 282 | 284 | wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false], |
| @@ -350,6 +352,7 @@ const default_settings = { | ||
| 350 | 352 | openai_model: 'gpt-4-turbo', |
| 351 | 353 | claude_model: 'claude-3-5-sonnet-20240620', |
| 352 | 354 | google_model: 'gemini-1.5-pro', |
| 355 | + vertexai_model: 'gemini-2.0-flash-001', | |
| 353 | 356 | ai21_model: 'jamba-1.6-large', |
| 354 | 357 | mistralai_model: 'mistral-large-latest', |
| 355 | 358 | cohere_model: 'command-r-plus', |
| @@ -433,6 +436,7 @@ const oai_settings = { | ||
| 433 | 436 | openai_model: 'gpt-4-turbo', |
| 434 | 437 | claude_model: 'claude-3-5-sonnet-20240620', |
| 435 | 438 | google_model: 'gemini-1.5-pro', |
| 439 | + vertexai_model: 'gemini-2.0-flash-001', | |
| 436 | 440 | ai21_model: 'jamba-1.6-large', |
| 437 | 441 | mistralai_model: 'mistral-large-latest', |
| 438 | 442 | cohere_model: 'command-r-plus', |
| @@ -1666,6 +1670,8 @@ export function getChatCompletionModel(source = null) { | ||
| 1666 | 1670 | return ''; |
| 1667 | 1671 | case chat_completion_sources.MAKERSUITE: |
| 1668 | 1672 | return oai_settings.google_model; |
| 1673 | + case chat_completion_sources.VERTEXAI: | |
| 1674 | + return oai_settings.vertexai_model; | |
| 1669 | 1675 | case chat_completion_sources.OPENROUTER: |
| 1670 | 1676 | return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null; |
| 1671 | 1677 | case chat_completion_sources.AI21: |
| @@ -2048,6 +2054,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2048 | 2054 | const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER; |
| 2049 | 2055 | const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE; |
| 2050 | 2056 | const isGoogle = oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE; |
| 2057 | + const isVertexAI = oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI; | |
| 2051 | 2058 | const isOAI = oai_settings.chat_completion_source == chat_completion_sources.OPENAI; |
| 2052 | 2059 | const isMistral = oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI; |
| 2053 | 2060 | const isCustom = oai_settings.chat_completion_source == chat_completion_sources.CUSTOM; |
| @@ -2123,8 +2130,8 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2123 | 2130 | delete generate_data.stop; |
| 2124 | 2131 | } |
| 2125 | 2132 | |
| 2126 | 2133 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite, and Vertex AI |
| 2127 | 2134 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) { |
| 2128 | 2135 | await validateReverseProxy(); |
| 2129 | 2136 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; |
| 2130 | 2137 | generate_data['proxy_password'] = oai_settings.proxy_password; |
| @@ -2175,7 +2182,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2175 | 2182 | generate_data['api_url_scale'] = oai_settings.api_url_scale; |
| 2176 | 2183 | } |
| 2177 | 2184 | |
| 2178 | 2185 | if (isGoogle || isVertexAI) { |
| 2179 | 2186 | const stopStringsLimit = 5; |
| 2180 | 2187 | generate_data['top_k'] = Number(oai_settings.top_k_openai); |
| 2181 | 2188 | generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16); |
| @@ -2382,7 +2389,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov | ||
| 2382 | 2389 | state.reasoning += data?.delta?.thinking || ''; |
| 2383 | 2390 | } |
| 2384 | 2391 | return data?.delta?.text || ''; |
| 2385 | 2392 | } else if (chat_completion_source === [chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(chat_completion_source)) { |
| 2386 | 2393 | const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData; |
| 2387 | 2394 | if (inlineData) { |
| 2388 | 2395 | state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`; |
| @@ -2772,7 +2779,13 @@ class Message { | ||
| 2772 | 2779 | * @returns {Promise<string>} Compressed image as a Data URL. |
| 2773 | 2780 | */ |
| 2774 | 2781 | async compressImage(image) { |
| 2775 | - if ([chat_completion_sources.OPENROUTER, chat_completion_sources.MAKERSUITE, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) { | |
| 2782 | + const compressImageSources = [ | |
| 2783 | + chat_completion_sources.OPENROUTER, | |
| 2784 | + chat_completion_sources.MAKERSUITE, | |
| 2785 | + chat_completion_sources.MISTRALAI, | |
| 2786 | + chat_completion_sources.VERTEXAI, | |
| 2787 | + ]; | |
| 2788 | + if (compressImageSources.includes(oai_settings.chat_completion_source)) { | |
| 2776 | 2789 | const sizeThreshold = 2 * 1024 * 1024; |
| 2777 | 2790 | const dataSize = image.length * 0.75; |
| 2778 | 2791 | const maxSide = 1024; |
| @@ -3368,6 +3381,7 @@ function loadOpenAISettings(data, settings) { | ||
| 3368 | 3381 | oai_settings.custom_include_headers = settings.custom_include_headers ?? default_settings.custom_include_headers; |
| 3369 | 3382 | oai_settings.custom_prompt_post_processing = settings.custom_prompt_post_processing ?? default_settings.custom_prompt_post_processing; |
| 3370 | 3383 | oai_settings.google_model = settings.google_model ?? default_settings.google_model; |
| 3384 | + oai_settings.vertexai_model = settings.vertexai_model ?? default_settings.vertexai_model; | |
| 3371 | 3385 | oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source; |
| 3372 | 3386 | oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale; |
| 3373 | 3387 | oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models; |
| @@ -3432,6 +3446,8 @@ function loadOpenAISettings(data, settings) { | ||
| 3432 | 3446 | $(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).prop('selected', true); |
| 3433 | 3447 | $('#model_google_select').val(oai_settings.google_model); |
| 3434 | 3448 | $(`#model_google_select option[value="${oai_settings.google_model}"`).prop('selected', true); |
| 3449 | + $('#model_vertexai_select').val(oai_settings.vertexai_model); | |
| 3450 | + $(`#model_vertexai_select option[value="${oai_settings.vertexai_model}"`).prop('selected', true); | |
| 3435 | 3451 | $('#model_ai21_select').val(oai_settings.ai21_model); |
| 3436 | 3452 | $(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).prop('selected', true); |
| 3437 | 3453 | $('#model_mistralai_select').val(oai_settings.mistralai_model); |
| @@ -3627,6 +3643,7 @@ async function getStatusOpen() { | ||
| 3627 | 3643 | chat_completion_sources.CLAUDE, |
| 3628 | 3644 | chat_completion_sources.AI21, |
| 3629 | 3645 | chat_completion_sources.MAKERSUITE, |
| 3646 | + chat_completion_sources.VERTEXAI, | |
| 3630 | 3647 | chat_completion_sources.PERPLEXITY, |
| 3631 | 3648 | chat_completion_sources.GROQ, |
| 3632 | 3649 | ]; |
| @@ -3648,7 +3665,16 @@ async function getStatusOpen() { | ||
| 3648 | 3665 | chat_completion_source: oai_settings.chat_completion_source, |
| 3649 | 3666 | }; |
| 3650 | 3667 | |
| 3651 | - if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) { | |
| 3668 | + const validateProxySources = [ | |
| 3669 | + chat_completion_sources.CLAUDE, | |
| 3670 | + chat_completion_sources.OPENAI, | |
| 3671 | + chat_completion_sources.MISTRALAI, | |
| 3672 | + chat_completion_sources.MAKERSUITE, | |
| 3673 | + chat_completion_sources.VERTEXAI, | |
| 3674 | + chat_completion_sources.DEEPSEEK, | |
| 3675 | + chat_completion_sources.XAI, | |
| 3676 | + ]; | |
| 3677 | + if (oai_settings.reverse_proxy && validateProxySources.includes(oai_settings.chat_completion_source)) { | |
| 3652 | 3678 | await validateReverseProxy(); |
| 3653 | 3679 | } |
| 3654 | 3680 | |
| @@ -3740,6 +3766,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { | ||
| 3740 | 3766 | custom_include_headers: settings.custom_include_headers, |
| 3741 | 3767 | custom_prompt_post_processing: settings.custom_prompt_post_processing, |
| 3742 | 3768 | google_model: settings.google_model, |
| 3769 | + vertexai_model: settings.vertexai_model, | |
| 3743 | 3770 | temperature: settings.temp_openai, |
| 3744 | 3771 | frequency_penalty: settings.freq_pen_openai, |
| 3745 | 3772 | presence_penalty: settings.pres_pen_openai, |
| @@ -4495,6 +4522,11 @@ async function onModelChange() { | ||
| 4495 | 4522 | oai_settings.google_model = value; |
| 4496 | 4523 | } |
| 4497 | 4524 | |
| 4525 | + if ($(this).is('#model_vertexai_select')) { | |
| 4526 | + console.log('Vertex AI model changed to', value); | |
| 4527 | + oai_settings.vertexai_model = value; | |
| 4528 | + } | |
| 4529 | + | |
| 4498 | 4530 | if ($(this).is('#model_mistralai_select')) { |
| 4499 | 4531 | // Upgrade old mistral models to new naming scheme |
| 4500 | 4532 | // would have done this in loadOpenAISettings, but it wasn't updating on preset change? |
| @@ -4575,7 +4607,7 @@ async function onModelChange() { | ||
| 4575 | 4607 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 4576 | 4608 | } |
| 4577 | 4609 | |
| 4578 | 4610 | if (oai_settings[chat_completion_sources.chat_completion_source ==MAKERSUITE, chat_completion_sources.MAKERSUITEVERTEXAI].includes(oai_settings.chat_completion_source)) { |
| 4579 | 4611 | if (oai_settings.max_context_unlocked) { |
| 4580 | 4612 | $('#openai_max_context').attr('max', max_2mil); |
| 4581 | 4613 | } else if (value.includes('gemini-1.5-pro')) { |
| @@ -4943,6 +4975,19 @@ async function onConnectButtonClick(e) { | ||
| 4943 | 4975 | } |
| 4944 | 4976 | } |
| 4945 | 4977 | |
| 4978 | + if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) { | |
| 4979 | + const api_key_vertexai = String($('#api_key_vertexai').val()).trim(); | |
| 4980 | + | |
| 4981 | + if (api_key_vertexai.length) { | |
| 4982 | + await writeSecret(SECRET_KEYS.VERTEXAI, api_key_vertexai); | |
| 4983 | + } | |
| 4984 | + | |
| 4985 | + if (!secret_state[SECRET_KEYS.VERTEXAI] && !oai_settings.reverse_proxy) { | |
| 4986 | + console.log('No secret key saved for Vertex AI'); | |
| 4987 | + return; | |
| 4988 | + } | |
| 4989 | + } | |
| 4990 | + | |
| 4946 | 4991 | if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) { |
| 4947 | 4992 | const api_key_claude = String($('#api_key_claude').val()).trim(); |
| 4948 | 4993 | |
| @@ -5120,6 +5165,9 @@ function toggleChatCompletionForms() { | ||
| 5120 | 5165 | else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { |
| 5121 | 5166 | $('#model_google_select').trigger('change'); |
| 5122 | 5167 | } |
| 5168 | + else if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) { | |
| 5169 | + $('#model_vertexai_select').trigger('change'); | |
| 5170 | + } | |
| 5123 | 5171 | else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) { |
| 5124 | 5172 | $('#model_openrouter_select').trigger('change'); |
| 5125 | 5173 | } |
| @@ -5281,6 +5329,8 @@ export function isImageInliningSupported() { | ||
| 5281 | 5329 | ); |
| 5282 | 5330 | case chat_completion_sources.MAKERSUITE: |
| 5283 | 5331 | return visionSupportedModels.some(model => oai_settings.google_model.includes(model)); |
| 5332 | + case chat_completion_sources.VERTEXAI: | |
| 5333 | + return visionSupportedModels.some(model => oai_settings.vertexai_model.includes(model)); | |
| 5284 | 5334 | case chat_completion_sources.CLAUDE: |
| 5285 | 5335 | return visionSupportedModels.some(model => oai_settings.claude_model.includes(model)); |
| 5286 | 5336 | case chat_completion_sources.OPENROUTER: |
| @@ -5887,6 +5937,7 @@ export function initOpenAI() { | ||
| 5887 | 5937 | $('#model_windowai_select').on('change', onModelChange); |
| 5888 | 5938 | $('#model_scale_select').on('change', onModelChange); |
| 5889 | 5939 | $('#model_google_select').on('change', onModelChange); |
| 5940 | + $('#model_vertexai_select').on('change', onModelChange); | |
| 5890 | 5941 | $('#model_openrouter_select').on('change', onModelChange); |
| 5891 | 5942 | $('#openrouter_group_models').on('change', onOpenrouterModelSortChange); |
| 5892 | 5943 | $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange); |
| @@ -114,6 +114,7 @@ export function extractReasoningFromData(data, { | ||
| 114 | 114 | case chat_completion_sources.OPENROUTER: |
| 115 | 115 | return data?.choices?.[0]?.message?.reasoning ?? ''; |
| 116 | 116 | case chat_completion_sources.MAKERSUITE: |
| 117 | + case chat_completion_sources.VERTEXAI: | |
| 117 | 118 | return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; |
| 118 | 119 | case chat_completion_sources.CLAUDE: |
| 119 | 120 | return data?.content?.find(part => part.type === 'thinking')?.thinking ?? ''; |
| @@ -16,6 +16,7 @@ export const SECRET_KEYS = { | ||
| 16 | 16 | AI21: 'api_key_ai21', |
| 17 | 17 | SCALE_COOKIE: 'scale_cookie', |
| 18 | 18 | MAKERSUITE: 'api_key_makersuite', |
| 19 | + VERTEXAI: 'api_key_vertexai', | |
| 19 | 20 | SERPAPI: 'api_key_serpapi', |
| 20 | 21 | MISTRALAI: 'api_key_mistralai', |
| 21 | 22 | TOGETHERAI: 'api_key_togetherai', |
| @@ -56,6 +57,7 @@ const INPUT_MAP = { | ||
| 56 | 57 | [SECRET_KEYS.AI21]: '#api_key_ai21', |
| 57 | 58 | [SECRET_KEYS.SCALE_COOKIE]: '#scale_cookie', |
| 58 | 59 | [SECRET_KEYS.MAKERSUITE]: '#api_key_makersuite', |
| 60 | + [SECRET_KEYS.VERTEXAI]: '#api_key_vertexai', | |
| 59 | 61 | [SECRET_KEYS.VLLM]: '#api_key_vllm', |
| 60 | 62 | [SECRET_KEYS.APHRODITE]: '#api_key_aphrodite', |
| 61 | 63 | [SECRET_KEYS.TABBY]: '#api_key_tabby', |
| @@ -4127,6 +4127,7 @@ function getModelOptions(quiet) { | ||
| 4127 | 4127 | { id: 'model_openrouter_select', api: 'openai', type: chat_completion_sources.OPENROUTER }, |
| 4128 | 4128 | { id: 'model_ai21_select', api: 'openai', type: chat_completion_sources.AI21 }, |
| 4129 | 4129 | { id: 'model_google_select', api: 'openai', type: chat_completion_sources.MAKERSUITE }, |
| 4130 | + { id: 'model_vertexai_select', api: 'openai', type: chat_completion_sources.VERTEXAI }, | |
| 4130 | 4131 | { id: 'model_mistralai_select', api: 'openai', type: chat_completion_sources.MISTRALAI }, |
| 4131 | 4132 | { id: 'custom_model_id', api: 'openai', type: chat_completion_sources.CUSTOM }, |
| 4132 | 4133 | { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE }, |
| @@ -676,6 +676,10 @@ export function getTokenizerModel() { | ||
| 676 | 676 | return gemmaTokenizer; |
| 677 | 677 | } |
| 678 | 678 | |
| 679 | + if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) { | |
| 680 | + return gemmaTokenizer; | |
| 681 | + } | |
| 682 | + | |
| 679 | 683 | if (oai_settings.chat_completion_source == chat_completion_sources.AI21) { |
| 680 | 684 | return jambaTokenizer; |
| 681 | 685 | } |
| @@ -592,6 +592,7 @@ export class ToolManager { | ||
| 592 | 592 | chat_completion_sources.COHERE, |
| 593 | 593 | chat_completion_sources.DEEPSEEK, |
| 594 | 594 | chat_completion_sources.MAKERSUITE, |
| 595 | + chat_completion_sources.VERTEXAI, | |
| 595 | 596 | chat_completion_sources.AI21, |
| 596 | 597 | chat_completion_sources.XAI, |
| 597 | 598 | chat_completion_sources.POLLINATIONS, |
| @@ -168,6 +168,7 @@ export const CHAT_COMPLETION_SOURCES = { | ||
| 168 | 168 | OPENROUTER: 'openrouter', |
| 169 | 169 | AI21: 'ai21', |
| 170 | 170 | MAKERSUITE: 'makersuite', |
| 171 | + VERTEXAI: 'vertexai', | |
| 171 | 172 | MISTRALAI: 'mistralai', |
| 172 | 173 | CUSTOM: 'custom', |
| 173 | 174 | COHERE: 'cohere', |
| @@ -52,6 +52,7 @@ const API_COHERE_V2 = 'https://api.cohere.ai/v2'; | ||
| 52 | 52 | const API_PERPLEXITY = 'https://api.perplexity.ai'; |
| 53 | 53 | const API_GROQ = 'https://api.groq.com/openai/v1'; |
| 54 | 54 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; |
| 55 | +const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com'; | |
| 55 | 56 | const API_01AI = 'https://api.lingyiwanwu.com/v1'; |
| 56 | 57 | const API_AI21 = 'https://api.ai21.com/studio/v1'; |
| 57 | 58 | const API_NANOGPT = 'https://nano-gpt.com/api/v1'; |
| @@ -337,13 +338,28 @@ async function sendScaleRequest(request, response) { | ||
| 337 | 338 | * @param {express.Response} response Express response |
| 338 | 339 | */ |
| 339 | 340 | async function sendMakerSuiteRequest(request, response) { |
| 340 | 341 | const apiUrluseVertexAi = new URL(request.body.reverse_proxychat_completion_source ||=== API_MAKERSUITE)CHAT_COMPLETION_SOURCES.VERTEXAI; |
| 341 | - const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); | |
| 342 | + const apiName = useVertexAi ? 'Google Vertex AI' : 'Google AI Studio'; | |
| 343 | + let apiUrl; | |
| 344 | + let apiKey; | |
| 345 | + | |
| 346 | + if (useVertexAi) { | |
| 347 | + apiUrl = new URL(request.body.reverse_proxy || API_VERTEX_AI); | |
| 348 | + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.VERTEXAI); | |
| 342 | 349 | |
| 343 | 350 | if (!request.body.reverse_proxy && !apiKey) { |
| 344 | 351 | console.warn('Google AI Studio`${apiName} API key is missing.'`); |
| 345 | 352 | return response.status(400).send({ error: true }); |
| 346 | 353 | } |
| 354 | + } else { | |
| 355 | + apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE); | |
| 356 | + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); | |
| 357 | + | |
| 358 | + if (!request.body.reverse_proxy && !apiKey) { | |
| 359 | + console.warn(`${apiName} API key is missing.`); | |
| 360 | + return response.status(400).send({ error: true }); | |
| 361 | + } | |
| 362 | + } | |
| 347 | 363 | |
| 348 | 364 | const model = String(request.body.model); |
| 349 | 365 | const stream = Boolean(request.body.stream); |
| @@ -465,7 +481,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 465 | 481 | } |
| 466 | 482 | |
| 467 | 483 | const body = getGeminiBody(); |
| 468 | 484 | console.debug('Google AI Studio`${apiName} request:'`, body); |
| 469 | 485 | |
| 470 | 486 | try { |
| 471 | 487 | const controller = new AbortController(); |
| @@ -477,7 +493,13 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 477 | 493 | const apiVersion = getConfigValue('gemini.apiVersion', 'v1beta'); |
| 478 | 494 | const responseType = (stream ? 'streamGenerateContent' : 'generateContent'); |
| 479 | 495 | |
| 480 | - const generateResponse = await fetch(`${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { | |
| 496 | + let url; | |
| 497 | + if (useVertexAi) { | |
| 498 | + url = `${apiUrl.toString().replace(/\/$/, '')}/v1/publishers/google/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`; | |
| 499 | + } else { | |
| 500 | + url = `${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`; | |
| 501 | + } | |
| 502 | + const generateResponse = await fetch(url, { | |
| 481 | 503 | body: JSON.stringify(body), |
| 482 | 504 | method: 'POST', |
| 483 | 505 | headers: { |
| @@ -498,7 +520,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 498 | 520 | } |
| 499 | 521 | } else { |
| 500 | 522 | if (!generateResponse.ok) { |
| 501 | 523 | console.warn(`Google AI Studio${apiName} API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); |
| 502 | 524 | return response.status(500).send({ error: true }); |
| 503 | 525 | } |
| 504 | 526 | |
| @@ -507,7 +529,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 507 | 529 | |
| 508 | 530 | const candidates = generateResponseJson?.candidates; |
| 509 | 531 | if (!candidates || candidates.length === 0) { |
| 510 | 532 | let message = 'Google AI Studio`${apiName} API returned no candidate'`; |
| 511 | 533 | console.warn(message, generateResponseJson); |
| 512 | 534 | if (generateResponseJson?.promptFeedback?.blockReason) { |
| 513 | 535 | message += `\nPrompt was blocked due to : ${generateResponseJson.promptFeedback.blockReason}`; |
| @@ -518,11 +540,11 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 518 | 540 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 519 | 541 | const functionCall = (candidates?.[0]?.content?.parts ?? []).some(part => part.functionCall); |
| 520 | 542 | const inlineData = (candidates?.[0]?.content?.parts ?? []).some(part => part.inlineData); |
| 521 | 543 | console.debug('Google AI Studio`${apiName} response:'`, util.inspect(generateResponseJson, { depth: 5, colors: true })); |
| 522 | 544 | |
| 523 | 545 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); |
| 524 | 546 | if (!responseText && !functionCall && !inlineData) { |
| 525 | 547 | let message = 'Google AI Studio`${apiName} Candidate text empty'`; |
| 526 | 548 | console.warn(message, generateResponseJson); |
| 527 | 549 | return response.send({ error: { message } }); |
| 528 | 550 | } |
| @@ -532,7 +554,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 532 | 554 | return response.send(reply); |
| 533 | 555 | } |
| 534 | 556 | } catch (error) { |
| 535 | 557 | console.error('`Error communicating with Google AI Studio${apiName} API: '`, error); |
| 536 | 558 | if (!response.headersSent) { |
| 537 | 559 | return response.status(500).send({ error: true }); |
| 538 | 560 | } |
| @@ -1189,6 +1211,7 @@ router.post('/generate', function (request, response) { | ||
| 1189 | 1211 | case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response); |
| 1190 | 1212 | case CHAT_COMPLETION_SOURCES.AI21: return sendAI21Request(request, response); |
| 1191 | 1213 | case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response); |
| 1214 | + case CHAT_COMPLETION_SOURCES.VERTEXAI: return sendMakerSuiteRequest(request, response); | |
| 1192 | 1215 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); |
| 1193 | 1216 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); |
| 1194 | 1217 | case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response); |
| @@ -7,6 +7,7 @@ import { readSecret, SECRET_KEYS } from './secrets.js'; | ||
| 7 | 7 | import { GEMINI_SAFETY } from '../constants.js'; |
| 8 | 8 | |
| 9 | 9 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; |
| 10 | +const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com'; | |
| 10 | 11 | |
| 11 | 12 | export const router = express.Router(); |
| 12 | 13 | |
| @@ -14,12 +15,27 @@ router.post('/caption-image', async (request, response) => { | ||
| 14 | 15 | try { |
| 15 | 16 | const mimeType = request.body.image.split(';')[0].split(':')[1]; |
| 16 | 17 | const base64Data = request.body.image.split(',')[1]; |
| 17 | - const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); | |
| 18 | + const useVertexAi = request.body.api === 'vertexai'; | |
| 18 | - const apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE); | |
| 19 | + const apiName = useVertexAi ? 'Google Vertex AI' : 'Google AI Studio'; | |
| 20 | + let apiKey; | |
| 21 | + let apiUrl; | |
| 22 | + if (useVertexAi) { | |
| 23 | + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.VERTEXAI); | |
| 24 | + apiUrl = new URL(request.body.reverse_proxy || API_VERTEX_AI); | |
| 25 | + } else { | |
| 26 | + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); | |
| 27 | + apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE); | |
| 28 | + } | |
| 19 | 29 | const model = request.body.model || 'gemini-2.0-flash'; |
| 20 | - const url = `${apiUrl.origin}/v1beta/models/${model}:generateContent?key=${apiKey}`; | |
| 30 | + let url; | |
| 31 | + if (useVertexAi) { | |
| 32 | + url = `${apiUrl.origin}/v1/publishers/google/models/${model}:generateContent?key=${apiKey}`; | |
| 33 | + } else { | |
| 34 | + url = `${apiUrl.origin}/v1beta/models/${model}:generateContent?key=${apiKey}`; | |
| 35 | + } | |
| 21 | 36 | const body = { |
| 22 | 37 | contents: [{ |
| 38 | + role: 'user', | |
| 23 | 39 | parts: [ |
| 24 | 40 | { text: request.body.prompt }, |
| 25 | 41 | { |
| @@ -32,7 +48,7 @@ router.post('/caption-image', async (request, response) => { | ||
| 32 | 48 | safetySettings: GEMINI_SAFETY, |
| 33 | 49 | }; |
| 34 | 50 | |
| 35 | 51 | console.debug('Multimodal`${apiName} captioning request'`, model, body); |
| 36 | 52 | |
| 37 | 53 | const result = await fetch(url, { |
| 38 | 54 | body: JSON.stringify(body), |
| @@ -44,13 +60,13 @@ router.post('/caption-image', async (request, response) => { | ||
| 44 | 60 | |
| 45 | 61 | if (!result.ok) { |
| 46 | 62 | const error = await result.json(); |
| 47 | 63 | console.error(`Google AI Studio${apiName} API returned error: ${result.status} ${result.statusText}`, error); |
| 48 | 64 | return response.status(500).send({ error: true }); |
| 49 | 65 | } |
| 50 | 66 | |
| 51 | 67 | /** @type {any} */ |
| 52 | 68 | const data = await result.json(); |
| 53 | 69 | console.info('Multimodal`${apiName} captioning response'`, data); |
| 54 | 70 | |
| 55 | 71 | const candidates = data?.candidates; |
| 56 | 72 | if (!candidates) { |
| @@ -26,6 +26,7 @@ export const SECRET_KEYS = { | ||
| 26 | 26 | ONERING_URL: 'oneringtranslator_url', |
| 27 | 27 | DEEPLX_URL: 'deeplx_url', |
| 28 | 28 | MAKERSUITE: 'api_key_makersuite', |
| 29 | + VERTEXAI: 'api_key_vertexai', | |
| 29 | 30 | SERPAPI: 'api_key_serpapi', |
| 30 | 31 | TOGETHERAI: 'api_key_togetherai', |
| 31 | 32 | MISTRALAI: 'api_key_mistralai', |
| @@ -463,7 +463,7 @@ export function getTokenizerModel(requestModel) { | ||
| 463 | 463 | return 'deepseek'; |
| 464 | 464 | } |
| 465 | 465 | |
| 466 | 466 | if (requestModel.includes('gemma') || requestModel.includes('gemini') || requestModel.includes('learnlm')) { |
| 467 | 467 | return 'gemma'; |
| 468 | 468 | } |
| 469 | 469 | |