LibreChat/packages/data-provider/src/generate.ts
Aman 9d8fd92dd3
🇬 refactor: Update default Google Models and Parameters (#2782)
* Update Google default model and parameters

* Update .env.example Vertex AI Models to reflect latest version and deprecate bison family

* Update Vertex AI model list in .env.example
2024-05-22 10:20:35 -04:00

582 lines
18 KiB
TypeScript

import { z, ZodArray, ZodError, ZodIssueCode } from 'zod';
import { tConversationSchema, googleSettings as google, openAISettings as openAI } from './schemas';
import type { ZodIssue } from 'zod';
import type { TConversation, TSetOption, TPreset } from './schemas';
export type GoogleSettings = Partial<typeof google>;
export type OpenAISettings = Partial<typeof google>;
export type ComponentType =
| 'input'
| 'textarea'
| 'slider'
| 'checkbox'
| 'switch'
| 'dropdown'
| 'tags';
export type OptionType = 'conversation' | 'model' | 'custom';
export enum ComponentTypes {
Input = 'input',
Textarea = 'textarea',
Slider = 'slider',
Checkbox = 'checkbox',
Switch = 'switch',
Dropdown = 'dropdown',
Tags = 'tags',
}
export enum SettingTypes {
Number = 'number',
Boolean = 'boolean',
String = 'string',
Enum = 'enum',
Array = 'array',
}
export enum OptionTypes {
Conversation = 'conversation',
Model = 'model',
Custom = 'custom',
}
export interface SettingDefinition {
key: string;
description?: string;
type: 'number' | 'boolean' | 'string' | 'enum' | 'array';
default?: number | boolean | string | string[];
showDefault?: boolean;
options?: string[];
range?: SettingRange;
enumMappings?: Record<string, number | boolean | string>;
component: ComponentType;
optionType?: OptionType;
columnSpan?: number;
columns?: number;
label?: string;
placeholder?: string;
labelCode?: boolean;
placeholderCode?: boolean;
descriptionCode?: boolean;
minText?: number;
maxText?: number;
minTags?: number; // Specific to tags component
maxTags?: number; // Specific to tags component
includeInput?: boolean; // Specific to slider component
descriptionSide?: 'top' | 'right' | 'bottom' | 'left';
}
export type DynamicSettingProps = Partial<SettingDefinition> & {
readonly?: boolean;
settingKey: string;
setOption: TSetOption;
conversation: TConversation | TPreset | null;
defaultValue?: number | boolean | string | string[];
className?: string;
inputClassName?: string;
};
const requiredSettingFields = ['key', 'type', 'component'];
export interface SettingRange {
min: number;
max: number;
step?: number;
}
export type SettingsConfiguration = SettingDefinition[];
export function generateDynamicSchema(settings: SettingsConfiguration) {
const schemaFields: { [key: string]: z.ZodTypeAny } = {};
for (const setting of settings) {
const {
key,
type,
default: defaultValue,
range,
options,
minText,
maxText,
minTags,
maxTags,
} = setting;
if (type === SettingTypes.Number) {
let schema = z.number();
if (range) {
schema = schema.min(range.min);
schema = schema.max(range.max);
}
if (typeof defaultValue === 'number') {
schemaFields[key] = schema.default(defaultValue);
} else {
schemaFields[key] = schema;
}
continue;
}
if (type === SettingTypes.Boolean) {
const schema = z.boolean();
if (typeof defaultValue === 'boolean') {
schemaFields[key] = schema.default(defaultValue);
} else {
schemaFields[key] = schema;
}
continue;
}
if (type === SettingTypes.String) {
let schema = z.string();
if (minText) {
schema = schema.min(minText);
}
if (maxText) {
schema = schema.max(maxText);
}
if (typeof defaultValue === 'string') {
schemaFields[key] = schema.default(defaultValue);
} else {
schemaFields[key] = schema;
}
continue;
}
if (type === SettingTypes.Enum) {
if (!options || options.length === 0) {
console.warn(`Missing or empty 'options' for enum setting '${key}'.`);
continue;
}
const schema = z.enum(options as [string, ...string[]]);
if (typeof defaultValue === 'string') {
schemaFields[key] = schema.default(defaultValue);
} else {
schemaFields[key] = schema;
}
continue;
}
if (type === SettingTypes.Array) {
let schema: z.ZodSchema = z.array(z.string().or(z.number()));
if (minTags && schema instanceof ZodArray) {
schema = schema.min(minTags);
}
if (maxTags && schema instanceof ZodArray) {
schema = schema.max(maxTags);
}
if (defaultValue && Array.isArray(defaultValue)) {
schema = schema.default(defaultValue);
}
schemaFields[key] = schema;
continue;
}
console.warn(`Unsupported setting type: ${type}`);
}
return z.object(schemaFields);
}
const ZodTypeToSettingType: Record<string, string | undefined> = {
ZodString: 'string',
ZodNumber: 'number',
ZodBoolean: 'boolean',
};
const minColumns = 1;
const maxColumns = 4;
const minSliderOptions = 2;
const minDropdownOptions = 2;
/**
* Validates the provided setting using the constraints unique to each component type.
* @throws {ZodError} Throws a ZodError if any validation fails.
*/
export function validateSettingDefinitions(settings: SettingsConfiguration): void {
const errors: ZodIssue[] = [];
// Validate columns
const columnsSet = new Set<number>();
for (const setting of settings) {
if (setting.columns !== undefined) {
if (setting.columns < minColumns || setting.columns > maxColumns) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid columns value for setting ${setting.key}. Must be between ${minColumns} and ${maxColumns}.`,
path: ['columns'],
});
} else {
columnsSet.add(setting.columns);
}
}
}
const columns = columnsSet.size === 1 ? columnsSet.values().next().value : 2;
for (const setting of settings) {
for (const field of requiredSettingFields) {
if (setting[field as keyof SettingDefinition] === undefined) {
errors.push({
code: ZodIssueCode.custom,
message: `Missing required field ${field} for setting ${setting.key}.`,
path: [field],
});
}
}
// check accepted types
const settingTypes = Object.values(SettingTypes);
if (!settingTypes.includes(setting.type as SettingTypes)) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid type for setting ${setting.key}. Must be one of ${settingTypes.join(
', ',
)}.`,
path: ['type'],
});
}
// Predefined constraints based on components
if (
(setting.component === ComponentTypes.Tags && setting.type !== SettingTypes.Array) ||
(setting.component !== ComponentTypes.Tags && setting.type === SettingTypes.Array)
) {
errors.push({
code: ZodIssueCode.custom,
message: `Tags component for setting ${setting.key} must have type array.`,
path: ['type'],
});
}
if (setting.component === ComponentTypes.Tags) {
if (setting.minTags !== undefined && setting.minTags < 0) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid minTags value for setting ${setting.key}. Must be non-negative.`,
path: ['minTags'],
});
}
if (setting.maxTags !== undefined && setting.maxTags < 0) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid maxTags value for setting ${setting.key}. Must be non-negative.`,
path: ['maxTags'],
});
}
if (setting.default && !Array.isArray(setting.default)) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must be an array.`,
path: ['default'],
});
}
if (setting.default && setting.maxTags && (setting.default as []).length > setting.maxTags) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must have at most ${setting.maxTags} tags.`,
path: ['default'],
});
}
if (setting.default && setting.minTags && (setting.default as []).length < setting.minTags) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must have at least ${setting.minTags} tags.`,
path: ['default'],
});
}
if (!setting.default) {
setting.default = [];
}
}
if (
setting.component === ComponentTypes.Input ||
setting.component === ComponentTypes.Textarea
) {
if (setting.type === SettingTypes.Number && setting.component === ComponentTypes.Textarea) {
errors.push({
code: ZodIssueCode.custom,
message: `Textarea component for setting ${setting.key} must have type string.`,
path: ['type'],
});
// continue;
}
if (
setting.minText !== undefined &&
setting.maxText !== undefined &&
setting.minText > setting.maxText
) {
errors.push({
code: ZodIssueCode.custom,
message: `For setting ${setting.key}, minText cannot be greater than maxText.`,
path: [setting.key, 'minText', 'maxText'],
});
// continue;
}
if (!setting.placeholder) {
setting.placeholder = '';
} // Default placeholder
}
if (setting.component === ComponentTypes.Slider) {
if (setting.type === SettingTypes.Number && !setting.range) {
errors.push({
code: ZodIssueCode.custom,
message: `Slider component for setting ${setting.key} must have a range if type is number.`,
path: ['range'],
});
// continue;
}
if (
setting.type === SettingTypes.Enum &&
(!setting.options || setting.options.length < minSliderOptions)
) {
errors.push({
code: ZodIssueCode.custom,
message: `Slider component for setting ${setting.key} requires at least ${minSliderOptions} options for enum type.`,
path: ['options'],
});
// continue;
}
setting.includeInput =
setting.type === SettingTypes.Number ? setting.includeInput ?? true : false; // Default to true if type is number
}
if (setting.component === ComponentTypes.Slider && setting.type === SettingTypes.Number) {
if (setting.default === undefined && setting.range) {
// Set default to the middle of the range if unspecified
setting.default = Math.round((setting.range.min + setting.range.max) / 2);
}
}
if (
setting.component === ComponentTypes.Checkbox ||
setting.component === ComponentTypes.Switch
) {
if (setting.options && setting.options.length > 2) {
errors.push({
code: ZodIssueCode.custom,
message: `Checkbox/Switch component for setting ${setting.key} must have 1-2 options.`,
path: ['options'],
});
// continue;
}
if (!setting.default && setting.type === SettingTypes.Boolean) {
setting.default = false; // Default to false if type is boolean
}
}
if (setting.component === ComponentTypes.Dropdown) {
if (!setting.options || setting.options.length < minDropdownOptions) {
errors.push({
code: ZodIssueCode.custom,
message: `Dropdown component for setting ${setting.key} requires at least ${minDropdownOptions} options.`,
path: ['options'],
});
// continue;
}
if (!setting.default && setting.options && setting.options.length > 0) {
setting.default = setting.options[0]; // Default to first option if not specified
}
}
// Default columnSpan
if (!setting.columnSpan) {
setting.columnSpan = Math.floor(columns / 2);
}
// Default label to key
if (!setting.label) {
setting.label = setting.key;
}
// Validate minText and maxText for input/textarea
if (
setting.component === ComponentTypes.Input ||
setting.component === ComponentTypes.Textarea
) {
if (setting.minText !== undefined && setting.minText < 0) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid minText value for setting ${setting.key}. Must be non-negative.`,
path: ['minText'],
});
}
if (setting.maxText !== undefined && setting.maxText < 0) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid maxText value for setting ${setting.key}. Must be non-negative.`,
path: ['maxText'],
});
}
}
// Validate optionType and conversation schema
if (setting.optionType !== OptionTypes.Custom) {
const conversationSchema = tConversationSchema.shape[setting.key as keyof TConversation];
if (!conversationSchema) {
errors.push({
code: ZodIssueCode.custom,
message: `Setting ${setting.key} with optionType "${setting.optionType}" must be defined in tConversationSchema.`,
path: ['optionType'],
});
} else {
const zodType = conversationSchema._def.typeName;
const settingTypeEquivalent = ZodTypeToSettingType[zodType] || null;
if (settingTypeEquivalent !== setting.type) {
errors.push({
code: ZodIssueCode.custom,
message: `Setting ${setting.key} with optionType "${setting.optionType}" must match the type defined in tConversationSchema.`,
path: ['optionType'],
});
}
}
}
/* Default value checks */
if (setting.type === SettingTypes.Number && isNaN(setting.default as number)) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must be a number.`,
path: ['default'],
});
}
if (setting.type === SettingTypes.Boolean && typeof setting.default !== 'boolean') {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must be a boolean.`,
path: ['default'],
});
}
if (
(setting.type === SettingTypes.String || setting.type === SettingTypes.Enum) &&
typeof setting.default !== 'string'
) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must be a string.`,
path: ['default'],
});
}
if (
setting.type === SettingTypes.Enum &&
setting.options &&
!setting.options.includes(setting.default as string)
) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${
setting.key
}. Must be one of the options: [${setting.options.join(', ')}].`,
path: ['default'],
});
}
if (
setting.type === SettingTypes.Number &&
setting.range &&
typeof setting.default === 'number' &&
(setting.default < setting.range.min || setting.default > setting.range.max)
) {
errors.push({
code: ZodIssueCode.custom,
message: `Invalid default value for setting ${setting.key}. Must be within the range [${setting.range.min}, ${setting.range.max}].`,
path: ['default'],
});
}
}
if (errors.length > 0) {
throw new ZodError(errors);
}
}
export const generateOpenAISchema = (customOpenAI: OpenAISettings) => {
const defaults = { ...openAI, ...customOpenAI };
return tConversationSchema
.pick({
model: true,
chatGptLabel: true,
promptPrefix: true,
temperature: true,
top_p: true,
presence_penalty: true,
frequency_penalty: true,
resendFiles: true,
imageDetail: true,
maxContextTokens: true,
})
.transform((obj) => ({
...obj,
model: obj.model ?? defaults.model.default,
chatGptLabel: obj.chatGptLabel ?? null,
promptPrefix: obj.promptPrefix ?? null,
temperature: obj.temperature ?? defaults.temperature.default,
top_p: obj.top_p ?? defaults.top_p.default,
presence_penalty: obj.presence_penalty ?? defaults.presence_penalty.default,
frequency_penalty: obj.frequency_penalty ?? defaults.frequency_penalty.default,
resendFiles:
typeof obj.resendFiles === 'boolean' ? obj.resendFiles : defaults.resendFiles.default,
imageDetail: obj.imageDetail ?? defaults.imageDetail.default,
maxContextTokens: obj.maxContextTokens ?? undefined,
}))
.catch(() => ({
model: defaults.model.default,
chatGptLabel: null,
promptPrefix: null,
temperature: defaults.temperature.default,
top_p: defaults.top_p.default,
presence_penalty: defaults.presence_penalty.default,
frequency_penalty: defaults.frequency_penalty.default,
resendFiles: defaults.resendFiles.default,
imageDetail: defaults.imageDetail.default,
maxContextTokens: undefined,
}));
};
export const generateGoogleSchema = (customGoogle: GoogleSettings) => {
const defaults = { ...google, ...customGoogle };
return tConversationSchema
.pick({
model: true,
modelLabel: true,
promptPrefix: true,
examples: true,
temperature: true,
maxOutputTokens: true,
topP: true,
topK: true,
maxContextTokens: true,
})
.transform((obj) => {
return {
...obj,
model: obj.model ?? defaults.model.default,
modelLabel: obj.modelLabel ?? null,
promptPrefix: obj.promptPrefix ?? null,
examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }],
temperature: obj.temperature ?? defaults.temperature.default,
maxOutputTokens: obj.maxOutputTokens ?? defaults.maxOutputTokens.default,
topP: obj.topP ?? defaults.topP.default,
topK: obj.topK ?? defaults.topK.default,
maxContextTokens: obj.maxContextTokens ?? undefined,
};
})
.catch(() => ({
model: defaults.model.default,
modelLabel: null,
promptPrefix: null,
examples: [{ input: { content: '' }, output: { content: '' } }],
temperature: defaults.temperature.default,
maxOutputTokens: defaults.maxOutputTokens.default,
topP: defaults.topP.default,
topK: defaults.topK.default,
maxContextTokens: undefined,
}));
};