UNPKG

@autobe/agent

Version:

AI backend server code generator

216 lines (201 loc) 6.79 kB
import { IAgenticaController } from "@agentica/core"; import { AutoBeOpenApi, AutoBeProgressEventBase, AutoBeTestPrepareFunction, AutoBeTestWriteEvent, } from "@autobe/interface"; import { AutoBeOpenApiTypeChecker } from "@autobe/utils"; import { IPointer, Singleton } from "tstl"; import typia, { ILlmApplication, IValidation } from "typia"; import { v7 } from "uuid"; import { AutoBeContext } from "../../context/AutoBeContext"; import { executeCachedBatch } from "../../utils/executeCachedBatch"; import { forceRetry } from "../../utils/forceRetry"; import { validateEmptyCode } from "../../utils/validateEmptyCode"; import { getTestArtifacts } from "./compile/getTestArtifacts"; import { transformTestGenerateWriteHistory } from "./histories/transformTestGenerationWriteHistory"; import { AutoBeTestGenerateProgrammer } from "./programmers/AutoBeTestGenerateProgrammer"; import { IAutoBeTestArtifacts } from "./structures/IAutoBeTestArtifacts"; import { IAutoBeTestGenerateProcedure } from "./structures/IAutoBeTestGenerateProcedure"; import { IAutoBeTestGenerationWriteApplication } from "./structures/IAutoBeTestGenerationWriteApplication"; export const orchestrateTestGenerateWrite = async ( ctx: AutoBeContext, props: { instruction: string; document: AutoBeOpenApi.IDocument; prepares: AutoBeTestPrepareFunction[]; progress: AutoBeProgressEventBase; }, ): Promise<IAutoBeTestGenerateProcedure[]> => { const result: Array<IAutoBeTestGenerateProcedure | null> = await executeCachedBatch( ctx, props.document.operations.map((operation) => async (promptCacheKey) => { if (operation.requestBody === null) return null; else if (operation.requestBody.typeName.endsWith(".ICreate") === false) return null; else if ( props.document.components.schemas[operation.requestBody.typeName] === undefined ) return null; else if ( AutoBeOpenApiTypeChecker.isObject( props.document.components.schemas[operation.requestBody.typeName], ) === false ) return null; const prepareFunction: AutoBeTestPrepareFunction | undefined = props.prepares.find( (pf) => pf.typeName === operation.requestBody?.typeName, ); if (prepareFunction === undefined) return null; const artifacts: IAutoBeTestArtifacts = await getTestArtifacts(ctx, { endpoint: { path: operation.path, method: operation.method, }, }); const counter = new Singleton(() => ++props.progress.completed); try { return await forceRetry(async () => { const event: AutoBeTestWriteEvent = await process(ctx, { prepare: prepareFunction, artifacts, operation, progress: props.progress, counter, promptCacheKey, instruction: props.instruction, }); if (event.function.type !== "generate") return null; ctx.dispatch(event); return { type: "generate", prepare: prepareFunction, artifacts, function: event.function, operation, } satisfies IAutoBeTestGenerateProcedure; }); } catch { counter.get(); return null; } }), ); return result.filter((r) => r !== null); }; async function process( ctx: AutoBeContext, props: { prepare: AutoBeTestPrepareFunction; artifacts: IAutoBeTestArtifacts; operation: AutoBeOpenApi.IOperation; progress: AutoBeProgressEventBase; counter: Singleton<number>; promptCacheKey: string; instruction: string; }, ): Promise<AutoBeTestWriteEvent> { const functionName: string = AutoBeTestGenerateProgrammer.getFunctionName( props.operation, ); const pointer: IPointer<IAutoBeTestGenerationWriteApplication.IProps | null> = { value: null, }; const { metric, tokenUsage } = await ctx.conversate({ source: "testWrite", controller: createController({ functionName, build: (next) => { pointer.value = next; }, }), enforceFunctionCall: true, promptCacheKey: props.promptCacheKey, ...(await transformTestGenerateWriteHistory(ctx, { instruction: props.instruction, prepare: props.prepare, operation: props.operation, artifacts: props.artifacts, })), }); if (pointer.value === null) { props.counter.get(); throw new Error("Failed to create generation function."); } const location: string = `test/generate/${functionName}.ts`; return { type: "testWrite", id: v7(), created_at: new Date().toISOString(), function: { type: "generate", endpoint: { method: props.operation.method, path: props.operation.path, }, actor: props.operation.authorizationActor, location, name: functionName, content: await AutoBeTestGenerateProgrammer.replaceImportStatements({ compiler: await ctx.compiler(), artifacts: props.artifacts, prepare: props.prepare, location, content: pointer.value.revise.final ?? pointer.value.draft, }), }, metric, tokenUsage, completed: props.counter.get(), total: props.progress.total, step: ctx.state().test?.step ?? 0, } satisfies AutoBeTestWriteEvent; } function createController(props: { functionName: string; build: (next: IAutoBeTestGenerationWriteApplication.IProps) => void; }): IAgenticaController.IClass { const validate: Validator = (input) => { const result: IValidation<IAutoBeTestGenerationWriteApplication.IProps> = typia.validate<IAutoBeTestGenerationWriteApplication.IProps>(input); if (result.success === false) return result; const errors: IValidation.IError[] = validateEmptyCode({ name: props.functionName, draft: result.data.draft, revise: result.data.revise, path: "$input", asynchronous: true, }); return errors.length ? { success: false, errors, data: result.data, } : result; }; const application: ILlmApplication = typia.llm.application<IAutoBeTestGenerationWriteApplication>({ validate: { generate: validate, }, }); return { protocol: "class", name: "testGenerationWrite", application, execute: { generate: (next) => { props.build(next); }, } satisfies IAutoBeTestGenerationWriteApplication, }; } type Validator = ( input: unknown, ) => IValidation<IAutoBeTestGenerationWriteApplication.IProps>;