UNPKG

@autobe/agent

Version:

AI backend server code generator

226 lines (212 loc) 7 kB
import { IAgenticaController } from "@agentica/core"; import { AutoBeDatabaseComponent, AutoBeDatabaseComponentEvent, AutoBeDatabaseGroup, AutoBeEventSource, AutoBeProgressEventBase, } from "@autobe/interface"; import { IPointer, Singleton } from "tstl"; import typia, { ILlmApplication, IValidation } from "typia"; import { v7 } from "uuid"; import { AutoBeContext } from "../../context/AutoBeContext"; import { buildAnalysisContextSections } from "../../utils/RAGRetrieval"; import { executeCachedBatch } from "../../utils/executeCachedBatch"; import { getEmbedder } from "../../utils/getEmbedder"; import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController"; import { convertToSectionEntries } from "../common/internal/convertToSectionEntries"; import { IAnalysisSectionEntry } from "../common/structures/IAnalysisSectionEntry"; import { transformDatabaseComponentsHistory } from "./histories/transformDatabaseComponentsHistory"; import { AutoBeDatabaseComponentProgrammer } from "./programmers/AutoBeDatabaseComponentProgrammer"; import { IAutoBeDatabaseComponentApplication } from "./structures/IAutoBeDatabaseComponentApplication"; export async function orchestrateDatabaseComponent( ctx: AutoBeContext, props: { instruction: string; groups: AutoBeDatabaseGroup[]; }, ): Promise<AutoBeDatabaseComponent[]> { // Filter to only process domain groups - authorization groups are handled // by orchestratePrismaAuthorization separately const domainGroups = props.groups.filter((g) => g.kind === "domain"); if (domainGroups.length === 0) return []; const prefix: string | null = ctx.state().analyze?.prefix ?? null; const progress: AutoBeProgressEventBase = { completed: 0, total: domainGroups.length, }; const components: AutoBeDatabaseComponent[] = await executeCachedBatch( ctx, domainGroups.map((group) => async (promptCacheKey) => { const counter = new Singleton(() => ++progress.completed); try { const component: AutoBeDatabaseComponent = await process(ctx, { group, instruction: props.instruction, prefix, progress, counter, promptCacheKey, }); return component; } catch (error) { counter.get(); throw error; } }), ); return AutoBeDatabaseComponentProgrammer.removeDuplicatedTable(components); } async function process( ctx: AutoBeContext, props: { group: AutoBeDatabaseGroup; instruction: string; prefix: string | null; progress: AutoBeProgressEventBase; counter: Singleton<number>; promptCacheKey: string; }, ): Promise<AutoBeDatabaseComponent> { const allSections: IAnalysisSectionEntry[] = convertToSectionEntries( ctx.state().analyze?.files ?? [], ); const queryText: string = [ "prisma", "schema", "component", props.group.filename, props.group.namespace, ].join(" "); const ragSections: IAnalysisSectionEntry[] = await buildAnalysisContextSections( getEmbedder(), allSections, queryText, "TOPK", { log: false, logPrefix: "prismaComponent" }, ); const preliminary: AutoBePreliminaryController< | "analysisSections" | "previousAnalysisSections" | "previousDatabaseSchemas" | "complete" > = new AutoBePreliminaryController({ dispatch: (e) => ctx.dispatch(e), application: typia.json.application<IAutoBeDatabaseComponentApplication>(), source: SOURCE, kinds: [ "analysisSections", "previousAnalysisSections", "previousDatabaseSchemas", "complete", ], state: ctx.state(), local: { analysisSections: ragSections, }, }); const event: AutoBeDatabaseComponentEvent = await preliminary.orchestrate( ctx, async (out) => { const pointer: IPointer<IAutoBeDatabaseComponentApplication.IWrite | null> = { value: null, }; const result: AutoBeContext.IResult = await ctx.conversate({ source: SOURCE, controller: createController({ pointer, preliminary, prefix: props.prefix, }), enforceFunctionCall: true, promptCacheKey: props.promptCacheKey, ...transformDatabaseComponentsHistory(ctx.state(), { instruction: props.instruction, prefix: props.prefix, preliminary, group: props.group, }), }); if (pointer.value === null) return out(result)(null); // Build complete component from group skeleton + tables const component: AutoBeDatabaseComponent = { ...props.group, tables: pointer.value.tables, }; return out(result)({ type: SOURCE, id: v7(), created_at: new Date().toISOString(), analysis: pointer.value.analysis, rationale: pointer.value.rationale, component, acquisition: preliminary.getAcquisition(), metric: result.metric, tokenUsage: result.tokenUsage, step: ctx.state().analyze?.step ?? 0, total: props.progress.total, completed: props.counter.get(), }); }, ); ctx.dispatch(event); return event.component; } function createController(props: { pointer: IPointer<IAutoBeDatabaseComponentApplication.IWrite | null>; preliminary: AutoBePreliminaryController< | "analysisSections" | "previousAnalysisSections" | "previousDatabaseSchemas" | "complete" >; prefix: string | null; }): IAgenticaController.IClass { const validate: Validator = (input) => { const result: IValidation<IAutoBeDatabaseComponentApplication.IProps> = typia.validate<IAutoBeDatabaseComponentApplication.IProps>(input); if (result.success === false) return result; else if (result.data.request.type !== "write") return props.preliminary.validate({ thinking: result.data.thinking, request: result.data.request, }); const errors: IValidation.IError[] = []; AutoBeDatabaseComponentProgrammer.validate({ errors, prefix: props.prefix, tables: result.data.request.tables, path: "$input.request.tables", }); if (errors.length > 0) return { success: false, data: result.data, errors, }; return result; }; const application: ILlmApplication = props.preliminary.fixApplication( typia.llm.application<IAutoBeDatabaseComponentApplication>({ validate: { process: validate, }, }), ); return { protocol: "class", name: SOURCE, application, execute: { process: (input) => { if (input.request.type === "write") props.pointer.value = input.request; }, } satisfies IAutoBeDatabaseComponentApplication, }; } type Validator = ( input: unknown, ) => IValidation<IAutoBeDatabaseComponentApplication.IProps>; const SOURCE = "databaseComponent" satisfies AutoBeEventSource;