mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-24 19:26:14 +01:00
🚀 fix: Resolve Google Client Issues, CDN Screenshots, Update Models (#5703)
* 🤖 refactor: streamline model selection logic for title model in GoogleClient
* refactor: add options for empty object schemas in convertJsonSchemaToZod
* refactor: add utility function to check for empty object schemas in convertJsonSchemaToZod
* fix: Google MCP Tool errors, and remove Object Unescaping as Google fixed this
* fix: google safetySettings
* feat: add safety settings exclusion via GOOGLE_EXCLUDE_SAFETY_SETTINGS environment variable
* fix: rename environment variable for console JSON string length
* fix: disable portal for dropdown in ExportModal component
* fix: screenshot functionality to use image placeholder for remote images
* feat: add visionMode property to BaseClient and initialize in GoogleClient to fix resendFiles issue
* fix: enhance formatMessages to include image URLs in message content for Vertex AI
* fix: safety settings for titleChatCompletion
* fix: remove deprecated model assignment in GoogleClient and streamline title model retrieval
* fix: remove unused image preloading logic in ScreenshotContext
* chore: update default google models to latest models shared by vertex ai and gen ai
* refactor: enhance Google error messaging
* fix: update token values and model limits for Gemini models
* ci: fix model matching
* chore: bump version of librechat-data-provider to 0.7.699
This commit is contained in:
parent
33e60c379b
commit
63afb317c6
19 changed files with 939 additions and 720 deletions
|
|
@ -699,18 +699,19 @@ export const defaultModels = {
|
|||
[EModelEndpoint.assistants]: ['chatgpt-4o-latest', ...sharedOpenAIModels],
|
||||
[EModelEndpoint.agents]: sharedOpenAIModels, // TODO: Add agent models (agentsModels)
|
||||
[EModelEndpoint.google]: [
|
||||
'gemini-pro',
|
||||
'gemini-pro-vision',
|
||||
'chat-bison',
|
||||
'chat-bison-32k',
|
||||
'codechat-bison',
|
||||
'codechat-bison-32k',
|
||||
'text-bison',
|
||||
'text-bison-32k',
|
||||
'text-unicorn',
|
||||
'code-gecko',
|
||||
'code-bison',
|
||||
'code-bison-32k',
|
||||
// Shared Google Models between Vertex AI & Gen AI
|
||||
// Gemini 2.0 Models
|
||||
'gemini-2.0-flash-001',
|
||||
'gemini-2.0-flash-exp',
|
||||
'gemini-2.0-flash-lite-preview-02-05',
|
||||
'gemini-2.0-pro-exp-02-05',
|
||||
// Gemini 1.5 Models
|
||||
'gemini-1.5-flash-001',
|
||||
'gemini-1.5-flash-002',
|
||||
'gemini-1.5-pro-001',
|
||||
'gemini-1.5-pro-002',
|
||||
// Gemini 1.0 Models
|
||||
'gemini-1.0-pro-001',
|
||||
],
|
||||
[EModelEndpoint.anthropic]: sharedAnthropicModels,
|
||||
[EModelEndpoint.openAI]: [
|
||||
|
|
@ -1019,6 +1020,10 @@ export enum ErrorTypes {
|
|||
* Invalid request error, API rejected request
|
||||
*/
|
||||
NO_SYSTEM_MESSAGES = 'no_system_messages',
|
||||
/**
|
||||
* Google provider returned an error
|
||||
*/
|
||||
GOOGLE_ERROR = 'google_error',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse('test')).toBe('test');
|
||||
expect(() => zodSchema.parse(123)).toThrow();
|
||||
expect(zodSchema?.parse('test')).toBe('test');
|
||||
expect(() => zodSchema?.parse(123)).toThrow();
|
||||
});
|
||||
|
||||
it('should convert string enum schema', () => {
|
||||
|
|
@ -24,8 +24,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse('foo')).toBe('foo');
|
||||
expect(() => zodSchema.parse('invalid')).toThrow();
|
||||
expect(zodSchema?.parse('foo')).toBe('foo');
|
||||
expect(() => zodSchema?.parse('invalid')).toThrow();
|
||||
});
|
||||
|
||||
it('should convert number schema', () => {
|
||||
|
|
@ -34,8 +34,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse(123)).toBe(123);
|
||||
expect(() => zodSchema.parse('123')).toThrow();
|
||||
expect(zodSchema?.parse(123)).toBe(123);
|
||||
expect(() => zodSchema?.parse('123')).toThrow();
|
||||
});
|
||||
|
||||
it('should convert boolean schema', () => {
|
||||
|
|
@ -44,8 +44,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse(true)).toBe(true);
|
||||
expect(() => zodSchema.parse('true')).toThrow();
|
||||
expect(zodSchema?.parse(true)).toBe(true);
|
||||
expect(() => zodSchema?.parse('true')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -57,8 +57,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse(['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
|
||||
expect(() => zodSchema.parse(['a', 123, 'c'])).toThrow();
|
||||
expect(zodSchema?.parse(['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
|
||||
expect(() => zodSchema?.parse(['a', 123, 'c'])).toThrow();
|
||||
});
|
||||
|
||||
it('should convert array of numbers schema', () => {
|
||||
|
|
@ -68,8 +68,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
||||
expect(() => zodSchema.parse([1, '2', 3])).toThrow();
|
||||
expect(zodSchema?.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
||||
expect(() => zodSchema?.parse([1, '2', 3])).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -84,8 +84,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse({ name: 'John', age: 30 })).toEqual({ name: 'John', age: 30 });
|
||||
expect(() => zodSchema.parse({ name: 123, age: 30 })).toThrow();
|
||||
expect(zodSchema?.parse({ name: 'John', age: 30 })).toEqual({ name: 'John', age: 30 });
|
||||
expect(() => zodSchema?.parse({ name: 123, age: 30 })).toThrow();
|
||||
});
|
||||
|
||||
it('should handle required fields', () => {
|
||||
|
|
@ -99,8 +99,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse({ name: 'John' })).toEqual({ name: 'John' });
|
||||
expect(() => zodSchema.parse({})).toThrow();
|
||||
expect(zodSchema?.parse({ name: 'John' })).toEqual({ name: 'John' });
|
||||
expect(() => zodSchema?.parse({})).toThrow();
|
||||
});
|
||||
|
||||
it('should handle nested objects', () => {
|
||||
|
|
@ -120,10 +120,10 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse({ user: { name: 'John', age: 30 } })).toEqual({
|
||||
expect(zodSchema?.parse({ user: { name: 'John', age: 30 } })).toEqual({
|
||||
user: { name: 'John', age: 30 },
|
||||
});
|
||||
expect(() => zodSchema.parse({ user: { age: 30 } })).toThrow();
|
||||
expect(() => zodSchema?.parse({ user: { age: 30 } })).toThrow();
|
||||
});
|
||||
|
||||
it('should handle objects with arrays', () => {
|
||||
|
|
@ -138,8 +138,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse({ names: ['John', 'Jane'] })).toEqual({ names: ['John', 'Jane'] });
|
||||
expect(() => zodSchema.parse({ names: ['John', 123] })).toThrow();
|
||||
expect(zodSchema?.parse({ names: ['John', 'Jane'] })).toEqual({ names: ['John', 'Jane'] });
|
||||
expect(() => zodSchema?.parse({ names: ['John', 123] })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse({})).toEqual({});
|
||||
expect(zodSchema?.parse({})).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle unknown types as unknown', () => {
|
||||
|
|
@ -160,8 +160,8 @@ describe('convertJsonSchemaToZod', () => {
|
|||
} as unknown as JsonSchemaType;
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse('anything')).toBe('anything');
|
||||
expect(zodSchema.parse(123)).toBe(123);
|
||||
expect(zodSchema?.parse('anything')).toBe('anything');
|
||||
expect(zodSchema?.parse(123)).toBe(123);
|
||||
});
|
||||
|
||||
it('should handle empty enum arrays as regular strings', () => {
|
||||
|
|
@ -171,7 +171,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(zodSchema.parse('test')).toBe('test');
|
||||
expect(zodSchema?.parse('test')).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -223,6 +223,9 @@ describe('convertJsonSchemaToZod', () => {
|
|||
],
|
||||
},
|
||||
};
|
||||
if (zodSchema == null) {
|
||||
throw new Error('Zod schema is null');
|
||||
}
|
||||
|
||||
expect(zodSchema.parse(validData)).toEqual(validData);
|
||||
expect(() =>
|
||||
|
|
@ -253,7 +256,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
},
|
||||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
expect(zodSchema.description).toBe('A test schema description');
|
||||
expect(zodSchema?.description).toBe('A test schema description');
|
||||
});
|
||||
|
||||
it('should preserve field descriptions', () => {
|
||||
|
|
@ -309,7 +312,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
|
||||
// Type assertions for better type safety
|
||||
const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
|
||||
expect(zodSchema.description).toBe('User record');
|
||||
expect(zodSchema?.description).toBe('User record');
|
||||
|
||||
if ('user' in shape) {
|
||||
expect(shape.user.description).toBe('User details');
|
||||
|
|
@ -436,7 +439,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
// Test top-level description
|
||||
expect(zodSchema.description).toBe('User profile configuration');
|
||||
expect(zodSchema?.description).toBe('User profile configuration');
|
||||
|
||||
const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
|
||||
|
||||
|
|
@ -464,4 +467,60 @@ describe('convertJsonSchemaToZod', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('empty object handling', () => {
|
||||
it('should return undefined for empty object schemas when allowEmptyObject is false', () => {
|
||||
const emptyObjectSchemas = [
|
||||
{ type: 'object' as const },
|
||||
{ type: 'object' as const, properties: {} },
|
||||
];
|
||||
|
||||
emptyObjectSchemas.forEach((schema) => {
|
||||
expect(convertJsonSchemaToZod(schema, { allowEmptyObject: false })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return zod schema for empty object schemas when allowEmptyObject is true', () => {
|
||||
const emptyObjectSchemas = [
|
||||
{ type: 'object' as const },
|
||||
{ type: 'object' as const, properties: {} },
|
||||
];
|
||||
|
||||
emptyObjectSchemas.forEach((schema) => {
|
||||
const result = convertJsonSchemaToZod(schema, { allowEmptyObject: true });
|
||||
expect(result).toBeDefined();
|
||||
expect(result instanceof z.ZodObject).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return zod schema for empty object schemas by default', () => {
|
||||
const emptyObjectSchemas = [
|
||||
{ type: 'object' as const },
|
||||
{ type: 'object' as const, properties: {} },
|
||||
];
|
||||
|
||||
emptyObjectSchemas.forEach((schema) => {
|
||||
const result = convertJsonSchemaToZod(schema);
|
||||
expect(result).toBeDefined();
|
||||
expect(result instanceof z.ZodObject).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should still convert non-empty object schemas regardless of allowEmptyObject setting', () => {
|
||||
const schema: JsonSchemaType = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
},
|
||||
};
|
||||
|
||||
const resultWithFlag = convertJsonSchemaToZod(schema, { allowEmptyObject: false });
|
||||
const resultWithoutFlag = convertJsonSchemaToZod(schema);
|
||||
|
||||
expect(resultWithFlag).toBeDefined();
|
||||
expect(resultWithoutFlag).toBeDefined();
|
||||
expect(resultWithFlag instanceof z.ZodObject).toBeTruthy();
|
||||
expect(resultWithoutFlag instanceof z.ZodObject).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,24 @@ export type JsonSchemaType = {
|
|||
description?: string;
|
||||
};
|
||||
|
||||
export function convertJsonSchemaToZod(schema: JsonSchemaType): z.ZodType {
|
||||
function isEmptyObjectSchema(jsonSchema?: JsonSchemaType): boolean {
|
||||
return (
|
||||
jsonSchema != null &&
|
||||
typeof jsonSchema === 'object' &&
|
||||
jsonSchema.type === 'object' &&
|
||||
(jsonSchema.properties == null || Object.keys(jsonSchema.properties).length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
export function convertJsonSchemaToZod(
|
||||
schema: JsonSchemaType,
|
||||
options: { allowEmptyObject?: boolean } = {},
|
||||
): z.ZodType | undefined {
|
||||
const { allowEmptyObject = true } = options;
|
||||
if (!allowEmptyObject && isEmptyObjectSchema(schema)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let zodSchema: z.ZodType;
|
||||
|
||||
// Handle primitive types
|
||||
|
|
@ -26,13 +43,16 @@ export function convertJsonSchemaToZod(schema: JsonSchemaType): z.ZodType {
|
|||
zodSchema = z.boolean();
|
||||
} else if (schema.type === 'array' && schema.items !== undefined) {
|
||||
const itemSchema = convertJsonSchemaToZod(schema.items);
|
||||
zodSchema = z.array(itemSchema);
|
||||
zodSchema = z.array(itemSchema as z.ZodType);
|
||||
} else if (schema.type === 'object') {
|
||||
const shape: Record<string, z.ZodType> = {};
|
||||
const properties = schema.properties ?? {};
|
||||
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
let fieldSchema = convertJsonSchemaToZod(value);
|
||||
if (!fieldSchema) {
|
||||
continue;
|
||||
}
|
||||
if (value.description != null && value.description !== '') {
|
||||
fieldSchema = fieldSchema.describe(value.description);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue