UNPKG

@matatbread/typia

Version:

Superfast runtime validators with only one line

277 lines (263 loc) 9.68 kB
import { ILlmApplication, ILlmSchema, IOpenApiSchemaError, IResult, OpenApi, } from "@samchon/openapi"; import { LlmSchemaComposer } from "@samchon/openapi/lib/composers/LlmSchemaComposer"; import { ILlmFunction } from "@samchon/openapi/lib/structures/ILlmFunction"; import { MetadataFactory } from "../../factories/MetadataFactory"; import { __IJsonApplication } from "../../schemas/json/__IJsonApplication"; import { Metadata } from "../../schemas/metadata/Metadata"; import { MetadataFunction } from "../../schemas/metadata/MetadataFunction"; import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; import { JsonApplicationProgrammer } from "../json/JsonApplicationProgrammer"; import { LlmSchemaProgrammer } from "./LlmSchemaProgrammer"; export namespace LlmApplicationProgrammer { export const validate = (model: ILlmSchema.Model) => { let top: Metadata | undefined; return ( metadata: Metadata, explore: MetadataFactory.IExplore, ): string[] => { top ??= metadata; if (explore.top === false) if ( explore.object === top?.objects[0]?.type && typeof explore.property === "string" && metadata.size() === 1 && metadata.nullable === false && metadata.isRequired() === true && metadata.functions.length === 1 ) return validateFunction(explore.property, metadata.functions[0]!); else return LlmSchemaProgrammer.validate(model)(metadata); const output: string[] = []; const valid: boolean = metadata.size() === 1 && metadata.objects.length === 1 && metadata.isRequired() === true && metadata.nullable === false; if (valid === false) output.push( "LLM application's generic arugment must be a class/interface type.", ); const object: MetadataObjectType | undefined = metadata.objects[0]?.type; if (object !== undefined) { if (object.properties.some((p) => p.key.isSoleLiteral() === false)) output.push( "LLM application does not allow dynamic keys on class/interface type.", ); let least: boolean = false; for (const p of object.properties) { const value: Metadata = p.value; if (value.functions.length) { least ||= true; if (valid === false) { if (value.functions.length !== 1 || value.size() !== 1) output.push( "LLM application's function type does not allow union type.", ); if (value.isRequired() === false) output.push( "LLM application's function type must be required.", ); if (value.nullable === true) output.push( "LLM application's function type must not be nullable.", ); } } } if (least === false) output.push( "LLM application's target type must have at least a function type.", ); } return output; }; }; const validateFunction = (name: string, func: MetadataFunction): string[] => { const output: string[] = []; const prefix: string = `LLM application's function (${JSON.stringify(name)})`; if (func.output.size() && func.output.isRequired() === false) output.push( `${prefix}'s return type must not be union type with undefined.`, ); if (func.parameters.length !== 1) output.push(`${prefix} must have a single parameter.`); if (func.parameters.length !== 0) { const type: Metadata = func.parameters[0]!.type; if (type.size() !== 1 || type.objects.length !== 1) output.push(`${prefix}'s parameter must be an object type.`); else { if ( type.objects[0]!.type.properties.some( (p) => p.key.isSoleLiteral() === false, ) ) output.push(`${prefix}'s parameter must not have dynamic keys.`); if (type.isRequired() === false) output.push( `${prefix}'s parameter must not be union type with undefined.`, ); if (type.nullable === true) output.push(`${prefix}'s parameter must not be nullable.`); } } return output; }; export const write = <Model extends ILlmSchema.Model>(props: { model: Model; metadata: Metadata; config?: Partial<ILlmSchema.ModelConfig[Model]>; }): ILlmApplication<Model> => { const errors: string[] = validate(props.model)(props.metadata, { top: true, object: null, property: null, parameter: null, nested: null, aliased: false, escaped: false, output: false, }); if (errors.length) throw new Error("Failed to write LLM application: " + errors.join("\n")); const errorMessages: string[] = []; const application: __IJsonApplication<"3.1"> = JsonApplicationProgrammer.write({ version: "3.1", metadata: props.metadata, }); const functions: Array<ILlmFunction<Model> | null> = application.functions.map((func) => writeFunction({ model: props.model, components: application.components, function: func, errors: errorMessages, }), ); if (functions.some((func) => func === null)) throw new Error( "Failed to write LLM application:\n\n" + errorMessages.map((str) => ` - ${str}`).join("\n"), ); return { model: props.model, options: { ...LlmSchemaComposer.defaultConfig(props.model), ...props.config, separate: null, }, functions: functions as ILlmFunction<Model>[], }; }; const writeFunction = <Model extends ILlmSchema.Model>(props: { model: Model; components: OpenApi.IComponents; function: __IJsonApplication.IFunction<OpenApi.IJsonSchema>; errors: string[]; }): ILlmFunction<Model> | null => { const parameters: ILlmSchema.ModelParameters[Model] | null = writeParameters({ ...props, accessor: `$input.${props.function.name}.parameters`, }); if (parameters === null) return null; const output: ILlmSchema.ModelSchema[Model] | null | undefined = writeOutput({ model: props.model, parameters, components: props.components, schema: props.function.output?.schema ?? null, errors: props.errors, accessor: `$input.${props.function.name}.output`, }); if (output === null) return null; else if ( output && output.description === undefined && !!props.function.output?.description?.length ) output.description = props.function.output.description; return { name: props.function.name, parameters, output: (output ?? undefined) as | ILlmSchema.ModelSchema[Model] | undefined, description: (() => { if ( !props.function.summary?.length || !props.function.description?.length ) return props.function.summary || props.function.description; const summary: string = props.function.summary.endsWith(".") ? props.function.summary.slice(0, -1) : props.function.summary; return props.function.description.startsWith(summary) ? props.function.description : summary + ".\n\n" + props.function.description; })(), deprecated: props.function.deprecated, tags: props.function.tags, strict: true, }; }; const writeParameters = <Model extends ILlmSchema.Model>(props: { model: Model; components: OpenApi.IComponents; function: __IJsonApplication.IFunction<OpenApi.IJsonSchema>; errors: string[]; accessor: string; }): ILlmSchema.ModelParameters[Model] | null => { const schema = props.function.parameters[0]?.schema; if (!schema) return null; const result: IResult< ILlmSchema.ModelParameters[Model], IOpenApiSchemaError > = LlmSchemaComposer.parameters(props.model)({ config: LlmSchemaComposer.defaultConfig(props.model) as any, components: props.components, schema: schema as | OpenApi.IJsonSchema.IObject | OpenApi.IJsonSchema.IReference, accessor: props.accessor, }) as IResult<ILlmSchema.ModelParameters[Model], IOpenApiSchemaError>; if (result.success === false) { props.errors.push( ...result.error.reasons.map((r) => ` - ${r.accessor}: ${r.message}`), ); return null; } return result.value; }; const writeOutput = <Model extends ILlmSchema.Model>(props: { model: Model; parameters: ILlmSchema.ModelParameters[Model]; components: OpenApi.IComponents; schema: OpenApi.IJsonSchema | null; errors: string[]; accessor: string; }): ILlmSchema.ModelSchema[Model] | null | undefined => { if (props.schema === null) return undefined; const result: IResult<ILlmSchema.ModelSchema[Model], IOpenApiSchemaError> = LlmSchemaComposer.schema(props.model)({ config: LlmSchemaComposer.defaultConfig(props.model) as any, components: props.components, schema: props.schema, $defs: (props.parameters as any).$defs, accessor: props.accessor, }) as IResult<ILlmSchema.ModelSchema[Model], IOpenApiSchemaError>; if (result.success === false) { props.errors.push( ...result.error.reasons.map((r) => ` - ${r.accessor}: ${r.message}`), ); return null; } return result.value; }; }