UNPKG

@autobe/agent

Version:

AI backend server code generator

270 lines (253 loc) 8.44 kB
import { IAgenticaController } from "@agentica/core"; import { AutoBeDatabase, AutoBeDatabaseCorrectEvent, AutoBeEventSource, IAutoBeCompiler, IAutoBeDatabaseValidation, } from "@autobe/interface"; import { IPointer } from "tstl"; import typia, { ILlmApplication, IValidation } from "typia"; import { v7 } from "uuid"; import { AutoBeConfigConstant } from "../../constants/AutoBeConfigConstant"; import { AutoBeContext } from "../../context/AutoBeContext"; import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController"; import { transformDatabaseCorrectHistory } from "./histories/transformDatabaseCorrectHistory"; import { AutoBeDatabaseModelProgrammer } from "./programmers/AutoBeDatabaseModelProgrammer"; import { IAutoBeDatabaseCorrectApplication } from "./structures/IAutoBeDatabaseCorrectApplication"; export function orchestrateDatabaseCorrect( ctx: AutoBeContext, application: AutoBeDatabase.IApplication, ): Promise<IAutoBeDatabaseValidation> { const unique: Set<string> = new Set(); for (const file of application.files) file.models = file.models.filter((model) => { if (unique.has(model.name)) return false; unique.add(model.name); return true; }); application.files = application.files.filter((f) => f.models.length !== 0); return iterate(ctx, application, AutoBeConfigConstant.DATABASE_CORRECT_RETRY); } async function iterate( ctx: AutoBeContext, application: AutoBeDatabase.IApplication, life: number, ): Promise<IAutoBeDatabaseValidation> { const compiler: IAutoBeCompiler = await ctx.compiler(); const result: IAutoBeDatabaseValidation = await compiler.database.validate(application); if (result.success) return result; // SUCCESS else if (life < 0) return result; // FAILURE // VALIDATION FAILED const schemas: Record<string, string> = await compiler.database.writePrismaSchemas(application, "postgres"); ctx.dispatch({ type: "databaseValidate", id: v7(), result, schemas, compiled: await compiler.database.compilePrismaSchemas({ files: schemas, }), step: ctx.state().analyze?.step ?? 0, created_at: new Date().toISOString(), }); const data: AutoBeDatabase.IApplication = await (async () => { try { const next: IExecutionResult = await process(ctx, result); return next.correction; } catch (error) { console.log("prismaCorrect iterate failure", error); return result.data; } })(); return await iterate(ctx, data, life - 1); } async function process( ctx: AutoBeContext, failure: IAutoBeDatabaseValidation.IFailure, capacity: number = 8, ): Promise<IExecutionResult> { const count: number = getTableCount(failure); if (count <= capacity) return execute(ctx, failure); let correction: AutoBeDatabase.IApplication = failure.data; const volume: number = Math.ceil(count / capacity); const plannings: string[] = []; const models: Record<string, AutoBeDatabase.IModel> = {}; let i: number = 0; while (i++ < volume && failure.errors.length !== 0) { const next: IExecutionResult = await execute(ctx, { ...failure, errors: (() => { const unique: Set<string | null> = new Set(); const errors: IAutoBeDatabaseValidation.IError[] = []; for (const err of failure.errors) { unique.add(err.table ?? null); if (unique.size > capacity) break; else errors.push(err); } return errors; })(), }); plannings.push(next.planning); for (const m of next.models) models[m.name] = m; const compiler: IAutoBeCompiler = await ctx.compiler(); const result: IAutoBeDatabaseValidation = await compiler.database.validate( next.correction, ); correction = next.correction; if (result.success === true) break; else failure = result; } return { type: "write", planning: plannings.join("\n\n"), models: Object.values(models), correction, }; } async function execute( ctx: AutoBeContext, failure: IAutoBeDatabaseValidation.IFailure, ): Promise<IExecutionResult> { const preliminary: AutoBePreliminaryController< | "analysisSections" | "databaseSchemas" | "previousAnalysisSections" | "previousDatabaseSchemas" > = new AutoBePreliminaryController({ dispatch: (e) => ctx.dispatch(e), application: typia.json.application<IAutoBeDatabaseCorrectApplication>(), source: SOURCE, kinds: [ "analysisSections", "previousAnalysisSections", "databaseSchemas", "previousDatabaseSchemas", ], state: ctx.state(), all: { databaseSchemas: failure.data.files.map((f) => f.models).flat(), }, local: { databaseSchemas: Array.from( new Set(failure.errors.map((e) => e.table).filter((t) => t !== null)), ) .map((table: string): AutoBeDatabase.IModel | undefined => failure.data.files .map((f) => f.models) .flat() .find((m) => m.name === table), ) .filter((m) => m !== undefined), }, config: { database: "ast", }, }); const pointer: IPointer<IAutoBeDatabaseCorrectApplication.IWrite | null> = { value: null, }; const event: AutoBeDatabaseCorrectEvent = await preliminary.orchestrate( ctx, async (out) => { const result: AutoBeContext.IResult = await ctx.conversate({ source: SOURCE, controller: createController({ preliminary, build: (next) => { pointer.value = next; }, }), enforceFunctionCall: true, ...transformDatabaseCorrectHistory({ preliminary, result: failure, }), }); if (pointer.value === null) return out(result)(null); const correction: AutoBeDatabase.IApplication = { files: failure.data.files.map((file) => ({ filename: file.filename, namespace: file.namespace, models: file.models.map((model) => { AutoBeDatabaseModelProgrammer.emend(model); const newbie = pointer.value?.models.find( (m) => m.name === model.name, ); return newbie ?? model; }), })), }; return out(result)({ type: SOURCE, id: v7(), failure, planning: pointer.value.planning, correction: correction, acquisition: preliminary.getAcquisition(), metric: result.metric, tokenUsage: result.tokenUsage, step: ctx.state().analyze?.step ?? 0, created_at: new Date().toISOString(), } satisfies AutoBeDatabaseCorrectEvent); }, ); ctx.dispatch(event); return { ...pointer.value!, correction: event.correction, }; } interface IExecutionResult extends IAutoBeDatabaseCorrectApplication.IWrite { correction: AutoBeDatabase.IApplication; } const getTableCount = (failure: IAutoBeDatabaseValidation.IFailure): number => { const unique: Set<string | null> = new Set( failure.errors.map((error) => error.table ?? null), ); return unique.size; }; function createController(props: { preliminary: AutoBePreliminaryController< | "analysisSections" | "previousAnalysisSections" | "databaseSchemas" | "previousDatabaseSchemas" >; build: (next: IAutoBeDatabaseCorrectApplication.IWrite) => void; }): IAgenticaController.IClass { const validate: Validator = (input) => { const result = typia.validate<IAutoBeDatabaseCorrectApplication.IProps>(input); if (result.success === false || result.data.request.type === "write") return result; return props.preliminary.validate({ thinking: result.data.thinking, request: result.data.request, }); }; const application: ILlmApplication = props.preliminary.fixApplication( typia.llm.application<IAutoBeDatabaseCorrectApplication>({ validate: { process: validate, }, }), ); return { protocol: "class", name: SOURCE satisfies AutoBeEventSource, application, execute: { process: (next) => { if (next.request.type === "write") props.build(next.request); }, } satisfies IAutoBeDatabaseCorrectApplication, }; } type Validator = ( input: unknown, ) => IValidation<IAutoBeDatabaseCorrectApplication.IProps>; const SOURCE = "databaseCorrect" satisfies AutoBeEventSource;