UNPKG

@autobe/agent

Version:

AI backend server code generator

351 lines (330 loc) 11 kB
import { IAgenticaController } from "@agentica/core"; import { AutoBeEventSource, AutoBeOpenApi, AutoBeProgressEventBase, } from "@autobe/interface"; import { AutoBeInterfacePrerequisite } from "@autobe/interface/src/histories/contents/AutoBeInterfacePrerequisite"; import { AutoBeOpenApiEndpointComparator } from "@autobe/utils"; import { ILlmApplication, ILlmSchema, IValidation } from "@samchon/openapi"; import { HashMap, IPointer, Pair } from "tstl"; import typia from "typia"; import { v7 } from "uuid"; import { AutoBeConfigConstant } from "../../constants/AutoBeConfigConstant"; import { AutoBeContext } from "../../context/AutoBeContext"; import { assertSchemaModel } from "../../context/assertSchemaModel"; import { divideArray } from "../../utils/divideArray"; import { executeCachedBatch } from "../../utils/executeCachedBatch"; import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController"; import { transformInterfacePrerequisiteHistory } from "./histories/transformInterfacePrerequisiteHistory"; import { IAutoBeInterfacePrerequisiteApplication } from "./structures/IAutoBeInterfacePrerequisiteApplication"; export async function orchestrateInterfacePrerequisite< Model extends ILlmSchema.Model, >( ctx: AutoBeContext<Model>, document: AutoBeOpenApi.IDocument, ): Promise<AutoBeInterfacePrerequisite[]> { const operations: AutoBeOpenApi.IOperation[] = document.operations.filter((op) => op.authorizationType === null) ?? []; const progress: AutoBeProgressEventBase = { total: operations.length, completed: 0, }; const prerequisiteOperations: AutoBeOpenApi.IOperation[] = document.operations.filter( (op) => op.authorizationType === null && op.method === "post", ); const dict: HashMap<AutoBeOpenApi.IEndpoint, AutoBeOpenApi.IOperation> = new HashMap<AutoBeOpenApi.IEndpoint, AutoBeOpenApi.IOperation>( prerequisiteOperations.map( (op) => new Pair({ path: op.path, method: op.method }, op), ), AutoBeOpenApiEndpointComparator.hashCode, AutoBeOpenApiEndpointComparator.equals, ); const prerequisitesNotFound: string = [ `You have to select one of the endpoints below`, "", " method | path ", "--------|------", ...prerequisiteOperations .map((op) => `\`${op.method}\` | \`${op.path}\``) .join("\n"), ].join("\n"); const exclude: AutoBeInterfacePrerequisite[] = []; let include: AutoBeOpenApi.IOperation[] = [...operations]; let trial: number = 0; do { const matrix: AutoBeOpenApi.IOperation[][] = divideArray({ array: include, capacity: AutoBeConfigConstant.INTERFACE_CAPACITY, }); await executeCachedBatch( ctx, matrix.map((ops) => async (promptCacheKey) => { const row: AutoBeInterfacePrerequisite[] = await divideAndConquer(ctx, { dict: dict, document, includes: ops, progress, promptCacheKey, prerequisitesNotFound, }); exclude.push(...row); return row; }), ); include = include.filter((op) => { if ( exclude.some( (el) => el.endpoint.method === op.method && el.endpoint.path === op.path, ) ) { return false; } return true; }); } while (include.length > 0 && ++trial < ctx.retry); return exclude; } async function divideAndConquer<Model extends ILlmSchema.Model>( ctx: AutoBeContext<Model>, props: { dict: HashMap<AutoBeOpenApi.IEndpoint, AutoBeOpenApi.IOperation>; document: AutoBeOpenApi.IDocument; includes: AutoBeOpenApi.IOperation[]; progress: AutoBeProgressEventBase; promptCacheKey: string; prerequisitesNotFound: string; }, ): Promise<AutoBeInterfacePrerequisite[]> { try { return await process(ctx, props); } catch { props.progress.completed += props.includes.length; return []; } } async function process<Model extends ILlmSchema.Model>( ctx: AutoBeContext<Model>, props: { dict: HashMap<AutoBeOpenApi.IEndpoint, AutoBeOpenApi.IOperation>; document: AutoBeOpenApi.IDocument; includes: AutoBeOpenApi.IOperation[]; progress: AutoBeProgressEventBase; promptCacheKey: string; prerequisitesNotFound: string; }, ): Promise<AutoBeInterfacePrerequisite[]> { const preliminary: AutoBePreliminaryController< | "analysisFiles" | "prismaSchemas" | "interfaceOperations" | "interfaceSchemas" > = new AutoBePreliminaryController({ application: typia.json.application<IAutoBeInterfacePrerequisiteApplication>(), source: SOURCE, kinds: [ "analysisFiles", "prismaSchemas", "interfaceOperations", "interfaceSchemas", ], state: ctx.state(), all: { interfaceOperations: props.document.operations, interfaceSchemas: props.document.components.schemas, }, local: { interfaceOperations: props.includes, }, }); return await preliminary.orchestrate(ctx, async (out) => { const pointer: IPointer<AutoBeInterfacePrerequisite[] | null> = { value: null, }; const result: AutoBeContext.IResult<Model> = await ctx.conversate({ source: SOURCE, controller: createController({ model: ctx.model, document: props.document, dict: props.dict, includes: props.includes, prerequisitesNotFound: props.prerequisitesNotFound, preliminary, build: (next) => { pointer.value = next; }, }), enforceFunctionCall: true, promptCacheKey: props.promptCacheKey, ...transformInterfacePrerequisiteHistory({ document: props.document, includes: props.includes, preliminary, }), }); if (pointer.value !== null) { props.progress.completed += pointer.value.length; ctx.dispatch({ type: SOURCE, id: v7(), created_at: new Date().toISOString(), metric: result.metric, tokenUsage: result.tokenUsage, operations: pointer.value, total: props.progress.total, completed: props.progress.completed, step: ctx.state().prisma?.step ?? 0, }); return out(result)(pointer.value); } return out(result)(null); }); } function createController<Model extends ILlmSchema.Model>(props: { model: Model; document: AutoBeOpenApi.IDocument; dict: HashMap<AutoBeOpenApi.IEndpoint, AutoBeOpenApi.IOperation>; includes: AutoBeOpenApi.IOperation[]; prerequisitesNotFound: string; preliminary: AutoBePreliminaryController< | "analysisFiles" | "prismaSchemas" | "interfaceOperations" | "interfaceSchemas" >; build: (next: AutoBeInterfacePrerequisite[]) => void; }): IAgenticaController.IClass<Model> { assertSchemaModel(props.model); const validate = ( next: unknown, ): IValidation<IAutoBeInterfacePrerequisiteApplication.IProps> => { const result: IValidation<IAutoBeInterfacePrerequisiteApplication.IProps> = typia.validate<IAutoBeInterfacePrerequisiteApplication.IProps>(next); if (result.success === false) return result; else if (result.data.request.type !== "complete") return props.preliminary.validate({ thinking: result.data.thinking, request: result.data.request, }); const operations: AutoBeInterfacePrerequisite[] = result.data.request.operations; const filteredOperations: AutoBeInterfacePrerequisite[] = []; const errors: IValidation.IError[] = []; props.includes.forEach((el) => { // Find the matched operation in the includes const matched: AutoBeInterfacePrerequisite | undefined = operations.find( (op) => op.endpoint.method === el.method && op.endpoint.path === el.path, ); // Remove duplicate operations in Prerequisites if (matched) { const prerequisites: Map<string, AutoBeOpenApi.IPrerequisite> = new Map(); matched.prerequisites.forEach((op) => { if ( prerequisites.get(op.endpoint.method + op.endpoint.path) !== undefined ) { return; } prerequisites.set(op.endpoint.method + op.endpoint.path, op); }); matched.prerequisites = Array.from(prerequisites.values()); filteredOperations.push(matched); } }); filteredOperations.forEach((op, i) => { op.prerequisites.forEach((p, j) => { if (props.dict.has(p.endpoint) === false) { errors.push({ value: p.endpoint, path: `$input.request.operations[${i}].prerequisites[${j}].endpoint`, expected: "AutoBeOpenApi.IEndpoint", description: props.prerequisitesNotFound, }); } if ( p.endpoint.method === op.endpoint.method && p.endpoint.path === op.endpoint.path ) { errors.push({ value: p.endpoint, path: `$input.request.operations[${i}].prerequisites[${j}].endpoint`, expected: "AutoBeOpenApi.IEndpoint", description: "Self-reference is not allowed.", }); } }); }); return errors.length === 0 ? { success: true, data: { ...result.data, request: { ...result.data.request, operations: filteredOperations, }, }, } : { success: false, data: { ...result.data, request: { ...result.data.request, operations: filteredOperations, }, }, errors, }; }; const application: ILlmApplication<Model> = collection[ props.model === "chatgpt" ? "chatgpt" : props.model === "gemini" ? "gemini" : "claude" ]( validate, ) satisfies ILlmApplication<any> as unknown as ILlmApplication<Model>; return { protocol: "class", name: SOURCE, application, execute: { process: (next) => { if (next.request.type === "complete") props.build(next.request.operations); }, } satisfies IAutoBeInterfacePrerequisiteApplication, }; } const collection = { chatgpt: (validate: Validator) => typia.llm.application<IAutoBeInterfacePrerequisiteApplication, "chatgpt">({ validate: { process: validate, }, }), claude: (validate: Validator) => typia.llm.application<IAutoBeInterfacePrerequisiteApplication, "claude">({ validate: { process: validate, }, }), gemini: (validate: Validator) => typia.llm.application<IAutoBeInterfacePrerequisiteApplication, "gemini">({ validate: { process: validate, }, }), }; type Validator = ( input: unknown, ) => IValidation<IAutoBeInterfacePrerequisiteApplication.IProps>; const SOURCE = "interfacePrerequisite" satisfies AutoBeEventSource;