Vertex: Add Vertex AI-specific safety setting (#4770) Closes #4455
Signed| @@ -161,6 +161,29 @@ export const GEMINI_SAFETY = [ | |||
| 161 | }, | 161 | }, |
| 162 | ]; | 162 | ]; |
| 163 | 163 | ||
| 164 | export const VERTEX_SAFETY = [ | ||
| 165 | { | ||
| 166 | category: 'HARM_CATEGORY_IMAGE_HATE', | ||
| 167 | threshold: 'OFF', | ||
| 168 | }, | ||
| 169 | { | ||
| 170 | category: 'HARM_CATEGORY_IMAGE_DANGEROUS_CONTENT', | ||
| 171 | threshold: 'OFF', | ||
| 172 | }, | ||
| 173 | { | ||
| 174 | category: 'HARM_CATEGORY_IMAGE_HARASSMENT', | ||
| 175 | threshold: 'OFF', | ||
| 176 | }, | ||
| 177 | { | ||
| 178 | category: 'HARM_CATEGORY_IMAGE_SEXUALLY_EXPLICIT', | ||
| 179 | threshold: 'OFF', | ||
| 180 | }, | ||
| 181 | { | ||
| 182 | category: 'HARM_CATEGORY_JAILBREAK', | ||
| 183 | threshold: 'OFF', | ||
| 184 | }, | ||
| 185 | ]; | ||
| 186 | |||
| 164 | export const CHAT_COMPLETION_SOURCES = { | 187 | export const CHAT_COMPLETION_SOURCES = { |
| 165 | OPENAI: 'openai', | 188 | OPENAI: 'openai', |
| 166 | CLAUDE: 'claude', | 189 | CLAUDE: 'claude', |
| @@ -12,6 +12,7 @@ import { | |||
| 12 | OPENAI_REASONING_EFFORT_MAP, | 12 | OPENAI_REASONING_EFFORT_MAP, |
| 13 | OPENAI_REASONING_EFFORT_MODELS, | 13 | OPENAI_REASONING_EFFORT_MODELS, |
| 14 | OPENROUTER_HEADERS, | 14 | OPENROUTER_HEADERS, |
| 15 | VERTEX_SAFETY, | ||
| 15 | } from '../../constants.js'; | 16 | } from '../../constants.js'; |
| 16 | import { | 17 | import { |
| 17 | forwardFetchResponse, | 18 | forwardFetchResponse, |
| @@ -405,7 +406,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 405 | 406 | ||
| 406 | const tools = []; | 407 | const tools = []; |
| 407 | const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); | 408 | const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); |
| 408 | let safetySettings = GEMINI_SAFETY; | 409 | const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])]; |
| 409 | 410 | ||
| 410 | if (enableWebSearch && !enableImageModality && !isGemma && !isLearnLM && !noSearchModels.includes(model)) { | 411 | if (enableWebSearch && !enableImageModality && !isGemma && !isLearnLM && !noSearchModels.includes(model)) { |
| 411 | tools.push({ google_search: {} }); | 412 | tools.push({ google_search: {} }); |
| @@ -8,7 +8,7 @@ import urlJoin from 'url-join'; | |||
| 8 | import lodash from 'lodash'; | 8 | import lodash from 'lodash'; |
| 9 | 9 | ||
| 10 | import { readSecret, SECRET_KEYS } from './secrets.js'; | 10 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| 11 | import { GEMINI_SAFETY } from '../constants.js'; | 11 | import { GEMINI_SAFETY, VERTEX_SAFETY } from '../constants.js'; |
| 12 | import { delay, getConfigValue, trimTrailingSlash } from '../util.js'; | 12 | import { delay, getConfigValue, trimTrailingSlash } from '../util.js'; |
| 13 | 13 | ||
| 14 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; | 14 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; |
| @@ -159,12 +159,13 @@ export function getProjectIdFromServiceAccount(serviceAccount) { | |||
| 159 | * @param {express.Request} request Express request object | 159 | * @param {express.Request} request Express request object |
| 160 | * @param {string} model Model name to use | 160 | * @param {string} model Model name to use |
| 161 | * @param {string} endpoint API endpoint (default: 'generateContent') | 161 | * @param {string} endpoint API endpoint (default: 'generateContent') |
| 162 | * @returns {Promise<{url: string, headers: object, apiName: string, baseUrl: string}>} URL, headers, and API name | 162 | * @returns {Promise<{url: string, headers: object, apiName: string, baseUrl: string, safetySettings: object[]}>} URL, headers, and API name |
| 163 | */ | 163 | */ |
| 164 | export async function getGoogleApiConfig(request, model, endpoint = 'generateContent') { | 164 | export async function getGoogleApiConfig(request, model, endpoint = 'generateContent') { |
| 165 | const useVertexAi = request.body.api === 'vertexai'; | 165 | const useVertexAi = request.body.api === 'vertexai'; |
| 166 | const region = request.body.vertexai_region || 'us-central1'; | 166 | const region = request.body.vertexai_region || 'us-central1'; |
| 167 | const apiName = useVertexAi ? 'Google Vertex AI' : 'Google AI Studio'; | 167 | const apiName = useVertexAi ? 'Google Vertex AI' : 'Google AI Studio'; |
| 168 | const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])]; | ||
| 168 | 169 | ||
| 169 | let url; | 170 | let url; |
| 170 | let baseUrl; | 171 | let baseUrl; |
| @@ -225,7 +226,7 @@ export async function getGoogleApiConfig(request, model, endpoint = 'generateCon | |||
| 225 | headers['x-goog-api-key'] = apiKey; | 226 | headers['x-goog-api-key'] = apiKey; |
| 226 | } | 227 | } |
| 227 | 228 | ||
| 228 | return { url, headers, apiName, baseUrl }; | 229 | return { url, headers, apiName, baseUrl, safetySettings }; |
| 229 | } | 230 | } |
| 230 | 231 | ||
| 231 | export const router = express.Router(); | 232 | export const router = express.Router(); |
| @@ -235,7 +236,7 @@ router.post('/caption-image', async (request, response) => { | |||
| 235 | const mimeType = request.body.image.split(';')[0].split(':')[1]; | 236 | const mimeType = request.body.image.split(';')[0].split(':')[1]; |
| 236 | const base64Data = request.body.image.split(',')[1]; | 237 | const base64Data = request.body.image.split(',')[1]; |
| 237 | const model = request.body.model || 'gemini-2.0-flash'; | 238 | const model = request.body.model || 'gemini-2.0-flash'; |
| 238 | const { url, headers, apiName } = await getGoogleApiConfig(request, model); | 239 | const { url, headers, apiName, safetySettings } = await getGoogleApiConfig(request, model); |
| 239 | 240 | ||
| 240 | const body = { | 241 | const body = { |
| 241 | contents: [{ | 242 | contents: [{ |
| @@ -249,7 +250,7 @@ router.post('/caption-image', async (request, response) => { | |||
| 249 | }, | 250 | }, |
| 250 | }], | 251 | }], |
| 251 | }], | 252 | }], |
| 252 | safetySettings: GEMINI_SAFETY, | 253 | safetySettings: safetySettings, |
| 253 | }; | 254 | }; |
| 254 | 255 | ||
| 255 | console.debug(`${apiName} captioning request`, model, body); | 256 | console.debug(`${apiName} captioning request`, model, body); |
| @@ -355,7 +356,7 @@ router.post('/list-native-voices', async (_, response) => { | |||
| 355 | router.post('/generate-native-tts', async (request, response) => { | 356 | router.post('/generate-native-tts', async (request, response) => { |
| 356 | try { | 357 | try { |
| 357 | const { text, voice, model } = request.body; | 358 | const { text, voice, model } = request.body; |
| 358 | const { url, headers, apiName } = await getGoogleApiConfig(request, model); | 359 | const { url, headers, apiName, safetySettings } = await getGoogleApiConfig(request, model); |
| 359 | 360 | ||
| 360 | console.debug(`${apiName} TTS request`, { model, text, voice }); | 361 | console.debug(`${apiName} TTS request`, { model, text, voice }); |
| 361 | 362 | ||
| @@ -374,7 +375,7 @@ router.post('/generate-native-tts', async (request, response) => { | |||
| 374 | }, | 375 | }, |
| 375 | }, | 376 | }, |
| 376 | }, | 377 | }, |
| 377 | safetySettings: GEMINI_SAFETY, | 378 | safetySettings: safetySettings, |
| 378 | }; | 379 | }; |
| 379 | 380 | ||
| 380 | const result = await fetch(url, { | 381 | const result = await fetch(url, { |