UNPKG

@autobe/agent

Version:

AI backend server code generator

293 lines (278 loc) 9.54 kB
import { IAgenticaController } from "@agentica/core"; import { AutoBeEventSource, AutoBeInterfaceSchemaEvent, AutoBeOpenApi, AutoBeProgressEventBase, } from "@autobe/interface"; import { ILlmApplication, ILlmSchema, IValidation } from "@samchon/openapi"; import { OpenApiV3_1Emender } from "@samchon/openapi/lib/converters/OpenApiV3_1Emender"; import { IPointer } from "tstl"; import typia from "typia"; import { v7 } from "uuid"; import { AutoBeConfigConstant } from "../../constants/AutoBeConfigConstant"; import { AutoBeContext } from "../../context/AutoBeContext"; import { assertSchemaModel } from "../../context/assertSchemaModel"; import { divideArray } from "../../utils/divideArray"; import { executeCachedBatch } from "../../utils/executeCachedBatch"; import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController"; import { transformInterfaceSchemaHistory } from "./histories/transformInterfaceSchemaHistory"; import { IAutoBeInterfaceSchemaApplication } from "./structures/IAutoBeInterfaceSchemaApplication"; import { JsonSchemaFactory } from "./utils/JsonSchemaFactory"; import { JsonSchemaNamingConvention } from "./utils/JsonSchemaNamingConvention"; import { JsonSchemaValidator } from "./utils/JsonSchemaValidator"; import { fulfillJsonSchemaErrorMessages } from "./utils/fulfillJsonSchemaErrorMessages"; export async function orchestrateInterfaceSchema< Model extends ILlmSchema.Model, >( ctx: AutoBeContext<Model>, props: { operations: AutoBeOpenApi.IOperation[]; instruction: string; }, ): Promise<Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>> { // fix operation type names JsonSchemaNamingConvention.operations(props.operations); // gather type names const typeNames: Set<string> = new Set(); for (const op of props.operations) { if (op.requestBody !== null) typeNames.add(op.requestBody.typeName); if (op.responseBody !== null) typeNames.add(op.responseBody.typeName); } const presets: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = JsonSchemaFactory.presets(typeNames); // divide and conquer const matrix: string[][] = divideArray({ array: Array.from(typeNames), capacity: AutoBeConfigConstant.INTERFACE_CAPACITY, }); const progress: AutoBeProgressEventBase = { total: typeNames.size, completed: 0, }; const x: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = { ...presets, }; for (const y of await executeCachedBatch( ctx, matrix.map((it) => async (promptCacheKey) => { const operations: AutoBeOpenApi.IOperation[] = props.operations.filter( (op) => (op.requestBody && it.includes(op.requestBody.typeName)) || (op.responseBody && it.includes(op.responseBody.typeName)), ); const row: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = await divideAndConquer(ctx, { operations, progress, promptCacheKey, typeNames: it, instruction: props.instruction, }); return row; }), )) Object.assign(x, y); return x; } async function divideAndConquer<Model extends ILlmSchema.Model>( ctx: AutoBeContext<Model>, props: { operations: AutoBeOpenApi.IOperation[]; typeNames: string[]; progress: AutoBeProgressEventBase; promptCacheKey: string; instruction: string; }, ): Promise<Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>> { const remained: Set<string> = new Set(props.typeNames); const schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = {}; for (let i: number = 0; i < ctx.retry; ++i) { if (remained.size === 0) break; const newbie: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = await process(ctx, { instruction: props.instruction, operations: props.operations, promptCacheKey: props.promptCacheKey, progress: props.progress, oldbie: schemas, remained, }); for (const key of Object.keys(newbie)) { schemas[key] = newbie[key]; remained.delete(key); } } return schemas; } async function process<Model extends ILlmSchema.Model>( ctx: AutoBeContext<Model>, props: { operations: AutoBeOpenApi.IOperation[]; oldbie: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>; remained: Set<string>; progress: AutoBeProgressEventBase; promptCacheKey: string; instruction: string; }, ): Promise<Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>> { const already: string[] = Object.keys(props.oldbie); const preliminary: AutoBePreliminaryController< "analysisFiles" | "prismaSchemas" | "interfaceOperations" > = new AutoBePreliminaryController({ application: typia.json.application<IAutoBeInterfaceSchemaApplication>(), source: SOURCE, kinds: ["analysisFiles", "prismaSchemas", "interfaceOperations"], state: ctx.state(), }); return await preliminary.orchestrate(ctx, async (out) => { const pointer: IPointer<Record< string, AutoBeOpenApi.IJsonSchemaDescriptive > | null> = { value: null, }; const result: AutoBeContext.IResult<Model> = await ctx.conversate({ source: SOURCE, controller: createController({ model: ctx.model, build: async (next) => { pointer.value ??= {}; Object.assign(pointer.value, next); }, pointer, preliminary, }), enforceFunctionCall: true, promptCacheKey: props.promptCacheKey, ...transformInterfaceSchemaHistory({ preliminary, typeNames: Array.from( new Set([...props.remained, ...Object.keys(props.oldbie)]), ), operations: props.operations, instruction: props.instruction, remained: props.remained, already, }), }); if (pointer.value !== null) { const schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = (( OpenApiV3_1Emender.convertComponents({ schemas: pointer.value, }) as AutoBeOpenApi.IComponents ).schemas ?? {}) as Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>; ctx.dispatch({ type: SOURCE, id: v7(), schemas, metric: result.metric, tokenUsage: result.tokenUsage, completed: (props.progress.completed += Object.keys(schemas).length), total: (props.progress.total += Object.keys(schemas).filter( (k) => props.remained.has(k) === false, ).length), step: ctx.state().prisma?.step ?? 0, created_at: new Date().toISOString(), } satisfies AutoBeInterfaceSchemaEvent); return out(result)(schemas); } return out(result)(null); }); } function createController<Model extends ILlmSchema.Model>(props: { model: Model; build: ( next: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>, ) => Promise<void>; pointer: IPointer<Record< string, AutoBeOpenApi.IJsonSchemaDescriptive > | null>; preliminary: AutoBePreliminaryController< "analysisFiles" | "prismaSchemas" | "interfaceOperations" >; }): IAgenticaController.IClass<Model> { assertSchemaModel(props.model); const validate = ( next: unknown, ): IValidation<IAutoBeInterfaceSchemaApplication.IProps> => { if ( typia.is<{ request: { type: "complete"; schemas: object; }; }>(next) ) JsonSchemaFactory.fixPage("schemas", next.request); const result: IValidation<IAutoBeInterfaceSchemaApplication.IProps> = typia.validate<IAutoBeInterfaceSchemaApplication.IProps>(next); if (result.success === false) { fulfillJsonSchemaErrorMessages(result.errors); return result; } else if (result.data.request.type !== "complete") return props.preliminary.validate({ thinking: result.data.thinking, request: result.data.request, }); // Check all IAuthorized types const errors: IValidation.IError[] = []; JsonSchemaValidator.validateSchemas({ errors, schemas: result.data.request.schemas, path: "$input.request.schemas", }); if (errors.length !== 0) return { success: false, errors, data: next, }; return result; }; const application: ILlmApplication<Model> = collection[ props.model === "chatgpt" ? "chatgpt" : props.model === "gemini" ? "gemini" : "claude" ]( validate, ) satisfies ILlmApplication<any> as unknown as ILlmApplication<Model>; return { protocol: "class", name: SOURCE, application, execute: { process: async (next) => { if (next.request.type === "complete") await props.build(next.request.schemas); }, } satisfies IAutoBeInterfaceSchemaApplication, }; } const collection = { chatgpt: (validate: Validator) => typia.llm.application<IAutoBeInterfaceSchemaApplication, "chatgpt">({ validate: { process: validate, }, }), claude: (validate: Validator) => typia.llm.application<IAutoBeInterfaceSchemaApplication, "claude">({ validate: { process: validate, }, }), gemini: (validate: Validator) => typia.llm.application<IAutoBeInterfaceSchemaApplication, "gemini">({ validate: { process: validate, }, }), }; type Validator = ( input: unknown, ) => IValidation<IAutoBeInterfaceSchemaApplication.IProps>; const SOURCE = "interfaceSchema" satisfies AutoBeEventSource;