UNPKG

@autobe/agent

Version:

AI backend server code generator

293 lines (281 loc) 9.13 kB
import { IAgenticaController, MicroAgentica } from "@agentica/core"; import { AutoBeOpenApi } from "@autobe/interface"; import { ILlmApplication, ILlmSchema } from "@samchon/openapi"; import { OpenApiV3_1Emender } from "@samchon/openapi/lib/converters/OpenApiV3_1Emender"; import { IPointer } from "tstl"; import typia from "typia"; import { v4 } from "uuid"; import { AutoBeSystemPromptConstant } from "../../constants/AutoBeSystemPromptConstant"; import { AutoBeContext } from "../../context/AutoBeContext"; import { assertSchemaModel } from "../../context/assertSchemaModel"; import { divideArray } from "../../utils/divideArray"; import { enforceToolCall } from "../../utils/enforceToolCall"; import { transformInterfaceHistories } from "./transformInterfaceHistories"; export async function orchestrateInterfaceComponents< Model extends ILlmSchema.Model, >( ctx: AutoBeContext<Model>, operations: AutoBeOpenApi.IOperation[], capacity: number = 12, ): Promise<AutoBeOpenApi.IComponents> { const typeNames: Set<string> = new Set(); for (const op of operations) { if (op.requestBody !== null) typeNames.add(op.requestBody.typeName); if (op.responseBody !== null) typeNames.add(op.responseBody.typeName); } const matrix: string[][] = divideArray({ array: Array.from(typeNames), capacity, }); let progress: number = 0; const x: AutoBeOpenApi.IComponents = { schemas: {}, }; for (const y of await Promise.all( matrix.map(async (it) => { const row: AutoBeOpenApi.IComponents = await divideAndConquer( ctx, operations, it, 3, (count) => { progress += count; }, ); ctx.dispatch({ type: "interfaceComponents", components: row, completed: progress, total: typeNames.size, step: ctx.state().analyze?.step ?? 0, created_at: new Date().toISOString(), }); return row; }), )) { Object.assign(x.schemas, y.schemas); if (y.authorization) x.authorization = y.authorization; } return x; } async function divideAndConquer<Model extends ILlmSchema.Model>( ctx: AutoBeContext<Model>, operations: AutoBeOpenApi.IOperation[], typeNames: string[], retry: number, progress: (completed: number) => void, ): Promise<AutoBeOpenApi.IComponents> { const remained: Set<string> = new Set(typeNames); const components: AutoBeOpenApi.IComponents = { schemas: {}, }; for (let i: number = 0; i < retry; ++i) { if (remained.size === 0) break; const before: number = remained.size; const newbie: AutoBeOpenApi.IComponents = await process( ctx, operations, components, remained, ); for (const key of Object.keys(newbie.schemas)) { components.schemas[key] = newbie.schemas[key]; remained.delete(key); } if (before - remained.size !== 0) progress(before - remained.size); } return components; } async function process<Model extends ILlmSchema.Model>( ctx: AutoBeContext<Model>, operations: AutoBeOpenApi.IOperation[], oldbie: AutoBeOpenApi.IComponents, remained: Set<string>, ): Promise<AutoBeOpenApi.IComponents> { const pointer: IPointer<AutoBeOpenApi.IComponents | null> = { value: null, }; const agentica: MicroAgentica<Model> = new MicroAgentica({ model: ctx.model, vendor: ctx.vendor, config: { ...(ctx.config ?? {}), executor: { describe: null, }, }, histories: [ ...transformInterfaceHistories( ctx.state(), AutoBeSystemPromptConstant.INTERFACE_SCHEMA, ), { id: v4(), created_at: new Date().toISOString(), type: "assistantMessage", text: [ "Here is the OpenAPI operations generated by phase 2.", "", "```json", JSON.stringify(operations), "```", ].join("\n"), }, ], tokenUsage: ctx.usage(), controllers: [ createApplication({ model: ctx.model, build: async (components) => { pointer.value ??= { schemas: {}, }; pointer.value.authorization ??= components.authorization; Object.assign(pointer.value.schemas, components.schemas); }, pointer, }), ], }); enforceToolCall(agentica); const already: string[] = Object.keys(oldbie.schemas); await agentica.conversate( [ "Make type components please.", "", "Here is the list of request/response bodies' type names from", "OpenAPI operations. Make type components of them. If more object", "types are required during making the components, please make them", "too.", "", ...Array.from(remained).map((k) => `- \`${k}\``), ...(already.length !== 0 ? [ "", "> By the way, here is the list of components schemas what you've", "> already made. So, you don't need to make them again.", ">", ...already.map((k) => `> - \`${k}\``), ] : []), ].join("\n"), ); if (pointer.value === null) { // never be happened throw new Error("Failed to create components."); } return OpenApiV3_1Emender.convertComponents( pointer.value, ) as AutoBeOpenApi.IComponents; } function createApplication<Model extends ILlmSchema.Model>(props: { model: Model; build: (components: AutoBeOpenApi.IComponents) => Promise<void>; pointer: IPointer<AutoBeOpenApi.IComponents | null>; }): IAgenticaController.IClass<Model> { assertSchemaModel(props.model); const application: ILlmApplication<Model> = collection[ props.model ] as unknown as ILlmApplication<Model>; return { protocol: "class", name: "interface", application, execute: { makeComponents: async (next) => { await props.build(next.components); }, } satisfies IApplication, }; } const claude = typia.llm.application< IApplication, "claude", { reference: true } >(); const collection = { chatgpt: typia.llm.application< IApplication, "chatgpt", { reference: true } >(), claude, llama: claude, deepseek: claude, "3.1": claude, "3.0": typia.llm.application<IApplication, "3.0">(), }; interface IApplication { /** * Generate OpenAPI components containing named schema types. * * This method receives a complete set of schema components and integrates * them into the final OpenAPI specification. It processes all entity schemas, * their variants, and related type definitions to ensure a comprehensive and * consistent API design. * * The provided components should include schemas for all entities identified * in the previous phases of API path/method definition and operation * creation. This ensures that the final OpenAPI document has complete type * coverage for all operations. * * CRITICAL: All schema definitions must follow the established naming * conventions (IEntityName, IEntityName.ICreate, etc.) and must be thoroughly * documented with descriptions that reference the original Prisma schema * comments. * * @param props Properties containing components to generate. */ makeComponents(props: IMakeComponentsProps): void; } interface IMakeComponentsProps { /** * Complete set of schema components for the OpenAPI specification. * * This property contains comprehensive type definitions for all entities in * the system. It is the central repository of all named schema types that * will be used throughout the API specification. * * CRITICAL REQUIREMENT: All object types MUST be defined as named types in * the components.schemas section. Inline anonymous object definitions are * strictly prohibited. * * This components object should include: * * - Main entity types (IEntityName) * - Operation-specific variants (.ICreate, .IUpdate, .ISummary, etc.) * - Container types (IPage<T> for pagination) * - Enumeration types * * All schema definitions must include detailed descriptions that reference * the original Prisma schema comments and thoroughly document each property. * Every property that references an object must use a $ref to a named type in * the components.schemas section. This applies to all objects in request * bodies, response bodies, and properties that are objects or arrays of * objects. * * Example structure: * * ```typescript * { * components: { * schemas: { * IUser: { * type: "object", * properties: { * id: { type: "string", format: "uuid" }, * email: { type: "string", format: "email" }, * profile: { "$ref": "#/components/schemas/IUserProfile" } * }, * required: ["id", "email"], * description: "User entity representing system account holders..." * }, * "IUser.ICreate": { ... }, * // Additional schemas * } * } * } * ``` */ components: AutoBeOpenApi.IComponents; }