UNPKG

@autobe/agent

Version:

AI backend server code generator

280 lines (262 loc) 8.85 kB
import { AutoBeEventSource, AutoBeInterfaceHistory, AutoBeOpenApi, AutoBeProgressEventBase, AutoBeRealizeCollectorPlan, AutoBeRealizePlanEvent, } from "@autobe/interface"; import { StringUtil } from "@autobe/utils"; import { IPointer, Singleton } from "tstl"; import typia, { ILlmApplication, ILlmController, IValidation } from "typia"; import { v4 } from "uuid"; import { AutoBeContext } from "../../context/AutoBeContext"; import { buildAnalysisContextSections } from "../../utils/RAGRetrieval"; import { executeCachedBatch } from "../../utils/executeCachedBatch"; import { forceRetry } from "../../utils/forceRetry"; import { getEmbedder } from "../../utils/getEmbedder"; import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController"; import { convertToSectionEntries } from "../common/internal/convertToSectionEntries"; import { IAnalysisSectionEntry } from "../common/structures/IAnalysisSectionEntry"; import { transformRealizeCollectorPlanHistory } from "./histories/transformRealizeCollectorPlanHistory"; import { AutoBeRealizeCollectorProgrammer } from "./programmers/AutoBeRealizeCollectorProgrammer"; import { IAutoBeRealizeCollectorPlanApplication } from "./structures/IAutoBeRealizeCollectorPlanApplication"; export async function orchestrateRealizeCollectorPlan( ctx: AutoBeContext, props: { progress: AutoBeProgressEventBase; }, ): Promise<AutoBeRealizeCollectorPlan[]> { const history: AutoBeInterfaceHistory | null = ctx.state().interface; if (history === null) throw new Error("Cannot realize collector plan without interface."); const document: AutoBeOpenApi.IDocument = history.document; const dtoTypeNames: string[] = Object.keys( document.components.schemas, ).filter(AutoBeRealizeCollectorProgrammer.filter); const prismaSchemaNames: Set<string> = new Set( ctx .state() .database!.result.data.files.map((f) => f.models) .flat() .map((m) => m.name), ); const result: AutoBeRealizeCollectorPlan[][] = await executeCachedBatch( ctx, Array.from(dtoTypeNames).map((it) => async (promptCacheKey) => { const counter = new Singleton(() => ++props.progress.completed); try { return await forceRetry(() => process(ctx, { document, dtoTypeName: it, prismaSchemaNames, promptCacheKey, progress: props.progress, counter, }), ); } catch (error) { counter.get(); throw error; } }), ); return result.flat(); } async function process( ctx: AutoBeContext, props: { document: AutoBeOpenApi.IDocument; dtoTypeName: string; prismaSchemaNames: Set<string>; promptCacheKey: string; progress: AutoBeProgressEventBase; counter: Singleton<number>; }, ): Promise<AutoBeRealizeCollectorPlan[]> { const allSections: IAnalysisSectionEntry[] = convertToSectionEntries( ctx.state().analyze?.files ?? [], ); const queryText: string = [ "collector", "plan", "dto", "prisma", props.dtoTypeName, ].join(" "); const ragSections: IAnalysisSectionEntry[] = await buildAnalysisContextSections( getEmbedder(), allSections, queryText, "TOPK", { log: false, logPrefix: "realizeCollectorPlan" }, ); const preliminary: AutoBePreliminaryController< | "analysisSections" | "databaseSchemas" | "interfaceSchemas" | "interfaceOperations" | "complete" > = new AutoBePreliminaryController({ dispatch: (e) => ctx.dispatch(e), state: ctx.state(), source: SOURCE, application: typia.json.application<IAutoBeRealizeCollectorPlanApplication>(), kinds: [ "analysisSections", "databaseSchemas", "interfaceSchemas", "interfaceOperations", "complete", ], local: { analysisSections: ragSections, interfaceOperations: props.document.operations.filter( (op) => op.requestBody?.typeName === props.dtoTypeName, ), interfaceSchemas: Object.fromEntries( Object.entries(props.document.components.schemas).filter( ([key]) => key === props.dtoTypeName, ), ), }, }); const event: AutoBeRealizePlanEvent = await preliminary.orchestrate( ctx, async (out) => { const pointer: IPointer<IAutoBeRealizeCollectorPlanApplication.IWrite | null> = { value: null, }; const result: AutoBeContext.IResult = await ctx.conversate({ source: "realizePlan", controller: createController({ prismaSchemaNames: props.prismaSchemaNames, dtoTypeName: props.dtoTypeName, build: (next) => { pointer.value = next; }, preliminary, }), enforceFunctionCall: true, promptCacheKey: props.promptCacheKey, ...transformRealizeCollectorPlanHistory({ state: ctx.state(), preliminary, dtoTypeName: props.dtoTypeName, }), }); if (pointer.value === null) return out(result)(null); const plans: AutoBeRealizeCollectorPlan[] = pointer.value.plans .filter((p) => p.databaseSchemaName !== null) .map((p) => ({ type: "collector", dtoTypeName: p.dtoTypeName, thinking: p.thinking, databaseSchemaName: p.databaseSchemaName!, references: p.references, })); const event: AutoBeRealizePlanEvent = { type: "realizePlan", id: v4(), plans, acquisition: preliminary.getAcquisition(), metric: result.metric, tokenUsage: result.tokenUsage, completed: props.counter.get(), total: props.progress.total, step: ctx.state().analyze?.step ?? 0, created_at: new Date().toISOString(), }; return out(result)(event); }, ); ctx.dispatch(event); return event.plans as AutoBeRealizeCollectorPlan[]; } function createController(props: { prismaSchemaNames: Set<string>; dtoTypeName: string; build: (next: IAutoBeRealizeCollectorPlanApplication.IWrite) => void; preliminary: AutoBePreliminaryController< | "analysisSections" | "databaseSchemas" | "interfaceSchemas" | "interfaceOperations" | "complete" >; }): ILlmController { const validate: Validator = (input) => { const result: IValidation<IAutoBeRealizeCollectorPlanApplication.IProps> = typia.validate<IAutoBeRealizeCollectorPlanApplication.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[] = []; result.data.request.plans.map((plan, i) => { if (props.dtoTypeName !== plan.dtoTypeName) errors.push({ path: `$input.request.plans[${i}].dtoTypeName`, value: plan.dtoTypeName, expected: JSON.stringify(props.dtoTypeName), description: StringUtil.trim` The DTO type name must be ${JSON.stringify(props.dtoTypeName)}. If you have planned other DTO type's collector, please entirely remake the plan with ONLY the DTO type ${JSON.stringify(props.dtoTypeName)}. `, }); if ( plan.databaseSchemaName !== null && props.prismaSchemaNames.has(plan.databaseSchemaName) === false ) errors.push({ path: `$input.request.plans[${i}].databaseSchemaName`, value: plan.databaseSchemaName, expected: Array.from(props.prismaSchemaNames) .map((s) => JSON.stringify(s)) .join(" | "), description: StringUtil.trim` The database schema name must be one of the available database schemas. ${Array.from(props.prismaSchemaNames) .map((s) => `- ${s}`) .join("\n")} `, }); }); return errors.length ? { success: false, errors, data: result.data, } : result; }; const application: ILlmApplication = props.preliminary.fixApplication( typia.llm.application<IAutoBeRealizeCollectorPlanApplication>({ validate: { process: validate, }, }), ); return { protocol: "class", name: SOURCE, application, execute: { process: (next) => { if (next.request.type === "write") props.build(next.request); }, } satisfies IAutoBeRealizeCollectorPlanApplication, }; } type Validator = ( input: unknown, ) => IValidation<IAutoBeRealizeCollectorPlanApplication.IProps>; const SOURCE = "realizePlan" satisfies AutoBeEventSource;