UNPKG

@autobe/agent

Version:

AI backend server code generator

269 lines (254 loc) 8.72 kB
import { IAgenticaController } from "@agentica/core"; import { AutoBeOpenApi, AutoBeProgressEventBase, AutoBeTestPrepareFunction, AutoBeTestWriteEvent, } from "@autobe/interface"; import { AutoBeFunctionCallingMetricFactory, 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 { AutoBeTokenUsageComponent } from "../../context/AutoBeTokenUsageComponent"; import { executeCachedBatch } from "../../utils/executeCachedBatch"; import { forceRetry } from "../../utils/forceRetry"; import { transformTestPrepareWriteHistory } from "./histories/transformTestPrepareWriteHistory"; import { AutoBeTestPrepareProgrammer } from "./programmers/AutoBeTestPrepareProgrammer"; import { IAutoBeTestPrepareProcedure } from "./structures/IAutoBeTestPrepareProcedure"; import { IAutoBeTestPrepareWriteApplication } from "./structures/IAutoBeTestPrepareWriteApplication"; /** * Orchestrates the generation of test data preparation functions. * * This orchestrator analyzes all ICreate DTOs from OpenAPI operations and * generates intelligent test data preparation functions that: * * - Create mock data respecting validation constraints * - Exclude sensitive/system-managed properties from input parameters * - Generate realistic test data using @nestia/e2e utilities * - Support partial input overrides for test customization * * The prepare functions enable consistent, maintainable test data generation * across the entire E2E test suite. * * @param ctx AutoBE context containing OpenAPI document and LLM access * @param instruction User instructions for test data generation context * @returns Array of generated prepare function definitions */ export const orchestrateTestPrepareWrite = async ( ctx: AutoBeContext, props: { instruction: string; document: AutoBeOpenApi.IDocument; progress: AutoBeProgressEventBase; }, ): Promise<IAutoBeTestPrepareProcedure[]> => { interface ICreateType { key: string; value: AutoBeOpenApi.IJsonSchema.IObject; } const createTypes: ICreateType[] = []; for (const [key, value] of Object.entries(props.document.components.schemas)) if ( key.endsWith(".ICreate") && AutoBeOpenApiTypeChecker.isObject(value) === true ) createTypes.push({ key, value, }); // Generate prepare functions using LLM in parallel with prompt caching const result: Array<IAutoBeTestPrepareProcedure | null> = await executeCachedBatch( ctx, createTypes.map((entry) => async (promptCacheKey) => { const counter = new Singleton(() => ++props.progress.completed); try { const event: AutoBeTestWriteEvent<AutoBeTestPrepareFunction> = await forceRetry(() => process(ctx, { document: props.document, typeName: entry.key, schema: entry.value, instruction: props.instruction, promptCacheKey, progress: props.progress, counter, }), ); ctx.dispatch(event); return { type: "prepare", typeName: entry.key, schema: entry.value, function: event.function, }; } catch { counter.get(); return null; } }), ); // Filter out null results and return successful generations return result.filter((r) => r !== null); }; /** Processes the generation of a single prepare function using LLM. */ async function process( ctx: AutoBeContext, props: { document: AutoBeOpenApi.IDocument; typeName: string; schema: AutoBeOpenApi.IJsonSchema.IObject; promptCacheKey: string; progress: AutoBeProgressEventBase; counter: Singleton<number>; instruction: string; }, ): Promise<AutoBeTestWriteEvent<AutoBeTestPrepareFunction>> { if ( !!props.schema.additionalProperties === false && Object.keys(props.schema.properties).length === 0 ) { const functionName: string = AutoBeTestPrepareProgrammer.getFunctionName( props.typeName, ); return { id: v7(), type: "testWrite", function: { type: "prepare", location: `test/prepare/${functionName}.ts`, content: await AutoBeTestPrepareProgrammer.replaceImportStatements({ compiler: await ctx.compiler(), typeName: props.typeName, schemas: props.document.components.schemas, content: AutoBeTestPrepareProgrammer.writeNonPropertyCode({ typeName: props.typeName, schema: props.schema, }), }), typeName: props.typeName, name: functionName, }, completed: props.counter.get(), total: props.progress.total, step: ctx.state().interface?.step ?? 0, tokenUsage: new AutoBeTokenUsageComponent(), metric: AutoBeFunctionCallingMetricFactory.create(), created_at: new Date().toISOString(), }; } const pointer: IPointer<IAutoBeTestPrepareWriteApplication.IProps | null> = { value: null, }; // Execute LLM conversation with function calling const { metric, tokenUsage } = await ctx.conversate({ source: "testWrite", controller: createController({ dtoTypeName: props.typeName, schema: props.schema, build: (app) => { pointer.value = app; }, }), enforceFunctionCall: true, promptCacheKey: props.promptCacheKey, ...(await transformTestPrepareWriteHistory(ctx, props)), }); // Validate LLM response if (pointer.value === null) { props.counter.get(); throw new Error( `Failed to generate prepare function for ${props.typeName}`, ); } const functionName: string = AutoBeTestPrepareProgrammer.getFunctionName( props.typeName, ); const event: AutoBeTestWriteEvent<AutoBeTestPrepareFunction> = { id: v7(), type: "testWrite", function: { type: "prepare", location: `test/prepare/${functionName}.ts`, content: await AutoBeTestPrepareProgrammer.replaceImportStatements({ compiler: await ctx.compiler(), typeName: props.typeName, schemas: props.document.components.schemas, content: pointer.value.revise.final ?? pointer.value.draft, }), typeName: props.typeName, name: functionName, }, completed: props.counter.get(), total: props.progress.total, step: ctx.state().interface?.step ?? 0, tokenUsage, metric, created_at: new Date().toISOString(), }; return event; } /** Creates LLM controller for function calling. */ function createController(props: { dtoTypeName: string; schema: AutoBeOpenApi.IJsonSchema.IObject; build: (app: IAutoBeTestPrepareWriteApplication.IProps) => void; }): IAgenticaController.IClass { const validate = ( input: unknown, ): IValidation<IAutoBeTestPrepareWriteApplication.IProps> => { // Basic typia validation const result: IValidation<IAutoBeTestPrepareWriteApplication.IProps> = typia.validate<IAutoBeTestPrepareWriteApplication.IProps>(input); if (result.success === false) return result; // Custom business logic validation const errors: IValidation.IError[] = AutoBeTestPrepareProgrammer.validate({ typeName: props.dtoTypeName, schema: props.schema, mappings: result.data.mappings, draft: result.data.draft, revise: result.data.revise, }); // // Incorrect template literal syntax validation // const backtickRegex: RegExp = /`/g; // const count: number = ( // (result.data.revise.final ?? result.data.draft).match(backtickRegex) ?? [] // ).length; // if (count % 2 !== 0) // errors.push({ // path: result.data.revise.final // ? "$input.request.revise.final" // : "$input.request.draft", // expected: "even number of backticks", // value: count, // description: "Unmatched backtick in template literal", // }); return errors.length > 0 ? { success: false, errors, data: result.data, } : result; }; const application: ILlmApplication = typia.llm.application<IAutoBeTestPrepareWriteApplication>({ validate: { write: validate, }, }); return { protocol: "class", name: "testPrepareWrite", application, execute: { write: (next) => { props.build(next); }, } satisfies IAutoBeTestPrepareWriteApplication, }; }