Support camelCase Gemini params and validate vision

This commit is contained in:
user
2024-12-31 10:16:04 +00:00
parent c2f5d2fbf3
commit 372ad85283
3 changed files with 52 additions and 1 deletions
@@ -30,6 +30,7 @@ export const transformOutboundPayload: RequestPreprocessor = async (req) => {
}
applyMistralPromptFixes(req);
applyGoogleAIKeyTransforms(req);
// Native prompts are those which were already provided by the client in the
// target API format. We don't need to transform them.
@@ -87,3 +88,43 @@ function applyMistralPromptFixes(req: Request): void {
}
}
}
function toSnakeCase(str: string): string {
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
}
function transformKeysToSnakeCase(obj: any, hasTransformed = { value: false }): any {
if (Array.isArray(obj)) {
return obj.map(item => transformKeysToSnakeCase(item, hasTransformed));
}
if (obj !== null && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => {
const snakeKey = toSnakeCase(key);
if (snakeKey !== key) {
hasTransformed.value = true;
}
return [
snakeKey,
transformKeysToSnakeCase(value, hasTransformed)
];
})
);
}
return obj;
}
function applyGoogleAIKeyTransforms(req: Request): void {
// Google (Gemini) API in their infinite wisdom accepts both snake_case and camelCase
// even though in the docs they use snake_case. Some frontends (e.g. ST) use camelCase
// so we normalize all keys to snake_case here
if (req.outboundApi === "google-ai") {
const hasTransformed = { value: false };
req.body = transformKeysToSnakeCase(req.body, hasTransformed);
if (hasTransformed.value) {
req.log.info("Applied Gemini camelCase -> snake_case transform");
}
}
}
@@ -3,6 +3,7 @@ import { assertNever } from "../../../../shared/utils";
import { RequestPreprocessor } from "../index";
import { containsImageContent as containsImageContentOpenAI } from "../../../../shared/api-schemas/openai";
import { containsImageContent as containsImageContentAnthropic } from "../../../../shared/api-schemas/anthropic";
import { containsImageContent as containsImageContentGoogleAI } from "../../../../shared/api-schemas/google-ai";
import { ForbiddenError } from "../../../../shared/errors";
/**
@@ -25,8 +26,10 @@ export const validateVision: RequestPreprocessor = async (req) => {
case "anthropic-chat":
hasImage = containsImageContentAnthropic(req.body.messages);
break;
case "anthropic-text":
case "google-ai":
hasImage = containsImageContentGoogleAI(req.body.contents);
break;
case "anthropic-text":
case "mistral-ai":
case "mistral-text":
case "openai-image":