@autobe/agent
Version:
AI backend server code generator
270 lines (253 loc) • 8.44 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import {
AutoBeDatabase,
AutoBeDatabaseCorrectEvent,
AutoBeEventSource,
IAutoBeCompiler,
IAutoBeDatabaseValidation,
} from "@autobe/interface";
import { IPointer } from "tstl";
import typia, { ILlmApplication, IValidation } from "typia";
import { v7 } from "uuid";
import { AutoBeConfigConstant } from "../../constants/AutoBeConfigConstant";
import { AutoBeContext } from "../../context/AutoBeContext";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformDatabaseCorrectHistory } from "./histories/transformDatabaseCorrectHistory";
import { AutoBeDatabaseModelProgrammer } from "./programmers/AutoBeDatabaseModelProgrammer";
import { IAutoBeDatabaseCorrectApplication } from "./structures/IAutoBeDatabaseCorrectApplication";
export function orchestrateDatabaseCorrect(
ctx: AutoBeContext,
application: AutoBeDatabase.IApplication,
): Promise<IAutoBeDatabaseValidation> {
const unique: Set<string> = new Set();
for (const file of application.files)
file.models = file.models.filter((model) => {
if (unique.has(model.name)) return false;
unique.add(model.name);
return true;
});
application.files = application.files.filter((f) => f.models.length !== 0);
return iterate(ctx, application, AutoBeConfigConstant.DATABASE_CORRECT_RETRY);
}
async function iterate(
ctx: AutoBeContext,
application: AutoBeDatabase.IApplication,
life: number,
): Promise<IAutoBeDatabaseValidation> {
const compiler: IAutoBeCompiler = await ctx.compiler();
const result: IAutoBeDatabaseValidation =
await compiler.database.validate(application);
if (result.success)
return result; // SUCCESS
else if (life < 0) return result; // FAILURE
// VALIDATION FAILED
const schemas: Record<string, string> =
await compiler.database.writePrismaSchemas(application, "postgres");
ctx.dispatch({
type: "databaseValidate",
id: v7(),
result,
schemas,
compiled: await compiler.database.compilePrismaSchemas({
files: schemas,
}),
step: ctx.state().analyze?.step ?? 0,
created_at: new Date().toISOString(),
});
const data: AutoBeDatabase.IApplication = await (async () => {
try {
const next: IExecutionResult = await process(ctx, result);
return next.correction;
} catch (error) {
console.log("prismaCorrect iterate failure", error);
return result.data;
}
})();
return await iterate(ctx, data, life - 1);
}
async function process(
ctx: AutoBeContext,
failure: IAutoBeDatabaseValidation.IFailure,
capacity: number = 8,
): Promise<IExecutionResult> {
const count: number = getTableCount(failure);
if (count <= capacity) return execute(ctx, failure);
let correction: AutoBeDatabase.IApplication = failure.data;
const volume: number = Math.ceil(count / capacity);
const plannings: string[] = [];
const models: Record<string, AutoBeDatabase.IModel> = {};
let i: number = 0;
while (i++ < volume && failure.errors.length !== 0) {
const next: IExecutionResult = await execute(ctx, {
...failure,
errors: (() => {
const unique: Set<string | null> = new Set();
const errors: IAutoBeDatabaseValidation.IError[] = [];
for (const err of failure.errors) {
unique.add(err.table ?? null);
if (unique.size > capacity) break;
else errors.push(err);
}
return errors;
})(),
});
plannings.push(next.planning);
for (const m of next.models) models[m.name] = m;
const compiler: IAutoBeCompiler = await ctx.compiler();
const result: IAutoBeDatabaseValidation = await compiler.database.validate(
next.correction,
);
correction = next.correction;
if (result.success === true) break;
else failure = result;
}
return {
type: "write",
planning: plannings.join("\n\n"),
models: Object.values(models),
correction,
};
}
async function execute(
ctx: AutoBeContext,
failure: IAutoBeDatabaseValidation.IFailure,
): Promise<IExecutionResult> {
const preliminary: AutoBePreliminaryController<
| "analysisSections"
| "databaseSchemas"
| "previousAnalysisSections"
| "previousDatabaseSchemas"
> = new AutoBePreliminaryController({
dispatch: (e) => ctx.dispatch(e),
application: typia.json.application<IAutoBeDatabaseCorrectApplication>(),
source: SOURCE,
kinds: [
"analysisSections",
"previousAnalysisSections",
"databaseSchemas",
"previousDatabaseSchemas",
],
state: ctx.state(),
all: {
databaseSchemas: failure.data.files.map((f) => f.models).flat(),
},
local: {
databaseSchemas: Array.from(
new Set(failure.errors.map((e) => e.table).filter((t) => t !== null)),
)
.map((table: string): AutoBeDatabase.IModel | undefined =>
failure.data.files
.map((f) => f.models)
.flat()
.find((m) => m.name === table),
)
.filter((m) => m !== undefined),
},
config: {
database: "ast",
},
});
const pointer: IPointer<IAutoBeDatabaseCorrectApplication.IWrite | null> = {
value: null,
};
const event: AutoBeDatabaseCorrectEvent = await preliminary.orchestrate(
ctx,
async (out) => {
const result: AutoBeContext.IResult = await ctx.conversate({
source: SOURCE,
controller: createController({
preliminary,
build: (next) => {
pointer.value = next;
},
}),
enforceFunctionCall: true,
...transformDatabaseCorrectHistory({
preliminary,
result: failure,
}),
});
if (pointer.value === null) return out(result)(null);
const correction: AutoBeDatabase.IApplication = {
files: failure.data.files.map((file) => ({
filename: file.filename,
namespace: file.namespace,
models: file.models.map((model) => {
AutoBeDatabaseModelProgrammer.emend(model);
const newbie = pointer.value?.models.find(
(m) => m.name === model.name,
);
return newbie ?? model;
}),
})),
};
return out(result)({
type: SOURCE,
id: v7(),
failure,
planning: pointer.value.planning,
correction: correction,
acquisition: preliminary.getAcquisition(),
metric: result.metric,
tokenUsage: result.tokenUsage,
step: ctx.state().analyze?.step ?? 0,
created_at: new Date().toISOString(),
} satisfies AutoBeDatabaseCorrectEvent);
},
);
ctx.dispatch(event);
return {
...pointer.value!,
correction: event.correction,
};
}
interface IExecutionResult extends IAutoBeDatabaseCorrectApplication.IWrite {
correction: AutoBeDatabase.IApplication;
}
const getTableCount = (failure: IAutoBeDatabaseValidation.IFailure): number => {
const unique: Set<string | null> = new Set(
failure.errors.map((error) => error.table ?? null),
);
return unique.size;
};
function createController(props: {
preliminary: AutoBePreliminaryController<
| "analysisSections"
| "previousAnalysisSections"
| "databaseSchemas"
| "previousDatabaseSchemas"
>;
build: (next: IAutoBeDatabaseCorrectApplication.IWrite) => void;
}): IAgenticaController.IClass {
const validate: Validator = (input) => {
const result =
typia.validate<IAutoBeDatabaseCorrectApplication.IProps>(input);
if (result.success === false || result.data.request.type === "write")
return result;
return props.preliminary.validate({
thinking: result.data.thinking,
request: result.data.request,
});
};
const application: ILlmApplication = props.preliminary.fixApplication(
typia.llm.application<IAutoBeDatabaseCorrectApplication>({
validate: {
process: validate,
},
}),
);
return {
protocol: "class",
name: SOURCE satisfies AutoBeEventSource,
application,
execute: {
process: (next) => {
if (next.request.type === "write") props.build(next.request);
},
} satisfies IAutoBeDatabaseCorrectApplication,
};
}
type Validator = (
input: unknown,
) => IValidation<IAutoBeDatabaseCorrectApplication.IProps>;
const SOURCE = "databaseCorrect" satisfies AutoBeEventSource;