@autobe/agent
Version:
AI backend server code generator
294 lines (282 loc) • 9.46 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import {
AutoBeDatabase,
AutoBeEventSource,
AutoBeInterfaceSchemaDesign,
AutoBeInterfaceSchemaEvent,
AutoBeOpenApi,
AutoBeProgressEventBase,
} from "@autobe/interface";
import { IPointer, Singleton } from "tstl";
import typia, { ILlmApplication, ILlmSchema, IValidation } from "typia";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { executeCachedBatch } from "../../utils/executeCachedBatch";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformInterfaceSchemaWriteHistory } from "./histories/transformInterfaceSchemaWriteHistory";
import { AutoBeInterfaceSchemaProgrammer } from "./programmers/AutoBeInterfaceSchemaProgrammer";
import { IAutoBeInterfaceSchemaApplication } from "./structures/IAutoBeInterfaceSchemaApplication";
import { AutoBeJsonSchemaFactory } from "./utils/AutoBeJsonSchemaFactory";
import { AutoBeJsonSchemaValidator } from "./utils/AutoBeJsonSchemaValidator";
import { fulfillJsonSchemaErrorMessages } from "./utils/fulfillJsonSchemaErrorMessages";
export async function orchestrateInterfaceSchemaWrite(
ctx: AutoBeContext,
props: {
operations: AutoBeOpenApi.IOperation[];
instruction: string;
},
): Promise<Record<string, AutoBeOpenApi.IJsonSchema>> {
// gather type names
const collection: Set<string> = new Set();
const gather = (key: string): void => {
if (AutoBeJsonSchemaValidator.isPage(key))
collection.add(AutoBeJsonSchemaFactory.getPageName(key));
collection.add(key);
};
for (const op of props.operations) {
if (op.requestBody !== null) gather(op.requestBody.typeName);
if (op.responseBody !== null) gather(op.responseBody.typeName);
}
const presets: Record<string, AutoBeOpenApi.IJsonSchema> =
AutoBeJsonSchemaFactory.presets(collection);
// divide and conquer
const typeNames: string[] = Array.from(collection).filter(
(k) => AutoBeJsonSchemaValidator.isPreset(k) === false,
);
const progress: AutoBeProgressEventBase = {
total: typeNames.length,
completed: 0,
};
const x: Record<string, AutoBeOpenApi.IJsonSchema> = {
...presets,
};
await executeCachedBatch(
ctx,
typeNames.map((it) => async (promptCacheKey) => {
const counter = new Singleton(() => ++progress.completed);
const predicate = (key: string) =>
key === it ||
(AutoBeJsonSchemaValidator.isPage(key) &&
AutoBeJsonSchemaFactory.getPageName(key) === it);
const operations: AutoBeOpenApi.IOperation[] = props.operations.filter(
(op) =>
(op.requestBody && predicate(op.requestBody.typeName)) ||
(op.responseBody && predicate(op.responseBody.typeName)),
);
try {
const row: AutoBeOpenApi.IJsonSchema = await process(ctx, {
operations,
progress,
otherTypeNames: typeNames.filter((k) => k !== it),
promptCacheKey,
typeName: it,
instruction: props.instruction,
counter,
});
x[it] = row;
} catch (error) {
console.log("interfaceSchema failure", it, error);
counter.get();
}
}),
);
return x;
}
async function process(
ctx: AutoBeContext,
props: {
operations: AutoBeOpenApi.IOperation[];
typeName: string;
otherTypeNames: string[];
progress: AutoBeProgressEventBase;
promptCacheKey: string;
instruction: string;
counter: Singleton<number>;
},
): Promise<AutoBeOpenApi.IJsonSchema> {
const preliminary: AutoBePreliminaryController<
| "analysisSections"
| "databaseSchemas"
| "interfaceOperations"
| "previousAnalysisSections"
| "previousDatabaseSchemas"
| "previousInterfaceOperations"
| "previousInterfaceSchemas"
| "complete"
> = new AutoBePreliminaryController({
dispatch: (e) => ctx.dispatch(e),
application: typia.json.application<IAutoBeInterfaceSchemaApplication>(),
source: SOURCE,
kinds: [
"analysisSections",
"databaseSchemas",
"interfaceOperations",
"previousAnalysisSections",
"previousDatabaseSchemas",
"previousInterfaceOperations",
"previousInterfaceSchemas",
"complete",
],
config: {
database: "text",
databaseProperty: true,
},
state: ctx.state(),
all: {
interfaceOperations: props.operations,
},
local: {
interfaceOperations: props.operations.filter((o) => {
const predicate = (key: string) =>
key === props.typeName ||
(AutoBeJsonSchemaValidator.isPage(key) &&
AutoBeJsonSchemaFactory.getPageName(key) === props.typeName);
return (
(o.requestBody && predicate(o.requestBody.typeName)) ||
(o.responseBody && predicate(o.responseBody.typeName))
);
}),
databaseSchemas:
AutoBeInterfaceSchemaProgrammer.getNeighborDatabaseSchemas({
typeName: props.typeName,
application: ctx.state().database!.result.data,
}),
},
});
const event: AutoBeInterfaceSchemaEvent = await preliminary.orchestrate(
ctx,
async (out) => {
const pointer: IPointer<IAutoBeInterfaceSchemaApplication.IWrite | null> =
{
value: null,
};
const result: AutoBeContext.IResult = await ctx.conversate({
source: SOURCE,
controller: createController(ctx, {
build: async (next) => {
pointer.value = next;
},
preliminary,
typeName: props.typeName,
operations: props.operations,
}),
enforceFunctionCall: true,
promptCacheKey: props.promptCacheKey,
...transformInterfaceSchemaWriteHistory({
preliminary,
operations: props.operations,
instruction: props.instruction,
typeName: props.typeName,
otherTypeNames: props.otherTypeNames,
}),
});
if (pointer.value === null) return out(result)(null);
const schema: AutoBeOpenApi.IJsonSchema =
AutoBeJsonSchemaFactory.fixDesign(pointer.value.design);
return out(result)({
type: SOURCE,
id: v7(),
typeName: props.typeName,
analysis: pointer.value.analysis,
rationale: pointer.value.rationale,
schema,
acquisition: preliminary.getAcquisition(),
metric: result.metric,
tokenUsage: result.tokenUsage,
completed: props.counter.get(),
total: props.progress.total,
step: ctx.state().database?.step ?? 0,
created_at: new Date().toISOString(),
} satisfies AutoBeInterfaceSchemaEvent);
},
);
ctx.dispatch(event);
return event.schema;
}
function createController(
ctx: AutoBeContext,
props: {
build: (next: IAutoBeInterfaceSchemaApplication.IWrite) => Promise<void>;
preliminary: AutoBePreliminaryController<
| "analysisSections"
| "databaseSchemas"
| "interfaceOperations"
| "previousAnalysisSections"
| "previousDatabaseSchemas"
| "previousInterfaceOperations"
| "previousInterfaceSchemas"
| "complete"
>;
operations: AutoBeOpenApi.IOperation[];
typeName: string;
},
): IAgenticaController.IClass {
const everyModels: AutoBeDatabase.IModel[] =
ctx.state().database?.result.data.files.flatMap((f) => f.models) ?? [];
const validate = (
next: unknown,
): IValidation<IAutoBeInterfaceSchemaApplication.IProps> => {
const result: IValidation<IAutoBeInterfaceSchemaApplication.IProps> =
typia.validate<IAutoBeInterfaceSchemaApplication.IProps>(next);
if (result.success === false) {
fulfillJsonSchemaErrorMessages(result.errors);
return result;
} else if (result.data.request.type !== "write")
return props.preliminary.validate({
thinking: result.data.thinking,
request: result.data.request,
});
// Check all IAuthorized types
const errors: IValidation.IError[] = [];
AutoBeInterfaceSchemaProgrammer.validate({
path: "$input.request.design",
errors,
operations: props.operations,
everyModels,
typeName: props.typeName,
design: result.data.request.design,
});
if (errors.length !== 0)
return {
success: false,
errors,
data: next,
};
return result;
};
const application: ILlmApplication = props.preliminary.fixApplication(
typia.llm.application<IAutoBeInterfaceSchemaApplication>({
validate: {
process: validate,
},
}),
);
if (
AutoBeJsonSchemaValidator.isObjectType({
operations: props.operations,
typeName: props.typeName,
}) === true
)
(
(
application.functions[0].parameters.$defs[
typia.reflect.name<AutoBeInterfaceSchemaDesign>()
] as ILlmSchema.IObject
).properties.schema as ILlmSchema.IReference
).$ref = "AutoBeOpenApi.IJsonSchema.IObject";
AutoBeInterfaceSchemaProgrammer.fixApplication({
application,
everyModels,
});
return {
protocol: "class",
name: SOURCE,
application,
execute: {
process: async (next) => {
if (next.request.type === "write") await props.build(next.request);
},
} satisfies IAutoBeInterfaceSchemaApplication,
};
}
const SOURCE = "interfaceSchema" satisfies AutoBeEventSource;