@autobe/agent
Version:
AI backend server code generator
239 lines (224 loc) • 7.72 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import { AutoBeEventSource, AutoBePrisma } from "@autobe/interface";
import { AutoBePrismaSchemaEvent } from "@autobe/interface/src/events/AutoBePrismaSchemaEvent";
import { StringUtil } from "@autobe/utils";
import { ILlmApplication, ILlmSchema, IValidation } from "@samchon/openapi";
import { IPointer } from "tstl";
import typia from "typia";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { assertSchemaModel } from "../../context/assertSchemaModel";
import { executeCachedBatch } from "../../utils/executeCachedBatch";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformPrismaSchemaHistory } from "./histories/transformPrismaSchemaHistory";
import { IAutoBePrismaSchemaApplication } from "./structures/IAutoBePrismaSchemaApplication";
export async function orchestratePrismaSchema<Model extends ILlmSchema.Model>(
ctx: AutoBeContext<Model>,
instruction: string,
componentList: AutoBePrisma.IComponent[],
): Promise<AutoBePrismaSchemaEvent[]> {
const start: Date = new Date();
const total: number = componentList
.map((c) => c.tables.length)
.reduce((x, y) => x + y, 0);
const completed: IPointer<number> = { value: 0 };
return await executeCachedBatch(
ctx,
componentList.map((component) => async (promptCacheKey) => {
const otherTables: string[] = componentList
.filter((y) => component !== y)
.map((c) => c.tables)
.flat();
const event: AutoBePrismaSchemaEvent = await process(ctx, {
instruction,
component,
otherTables,
start,
total,
completed,
promptCacheKey,
});
ctx.dispatch(event);
return event;
}),
);
}
async function process<Model extends ILlmSchema.Model>(
ctx: AutoBeContext<Model>,
props: {
instruction: string;
component: AutoBePrisma.IComponent;
otherTables: string[];
start: Date;
total: number;
completed: IPointer<number>;
promptCacheKey: string;
},
): Promise<AutoBePrismaSchemaEvent> {
const preliminary: AutoBePreliminaryController<"analysisFiles"> =
new AutoBePreliminaryController({
application: typia.json.application<IAutoBePrismaSchemaApplication>(),
source: SOURCE,
kinds: ["analysisFiles"],
state: ctx.state(),
});
return await preliminary.orchestrate(ctx, async (out) => {
const pointer: IPointer<IAutoBePrismaSchemaApplication.IComplete | null> = {
value: null,
};
const result: AutoBeContext.IResult<Model> = await ctx.conversate({
source: SOURCE,
controller: createController(ctx, {
preliminary,
targetComponent: props.component,
otherTables: props.otherTables,
build: (next) => {
pointer.value = next;
},
}),
enforceFunctionCall: true,
promptCacheKey: props.promptCacheKey,
...transformPrismaSchemaHistory({
analysis:
ctx
.state()
.analyze?.files.map((file) => ({ [file.filename]: file.content }))
.reduce((acc, cur) => {
return Object.assign(acc, cur);
}, {}) ?? {},
targetComponent: props.component,
otherTables: props.otherTables,
instruction: props.instruction,
}),
});
if (pointer.value !== null)
return out(result)({
type: SOURCE,
id: v7(),
created_at: props.start.toISOString(),
plan: pointer.value.plan,
models: pointer.value.models,
file: {
filename: props.component.filename,
namespace: props.component.namespace,
models: pointer.value.models,
},
metric: result.metric,
tokenUsage: result.tokenUsage,
completed: (props.completed.value += props.component.tables.length),
total: props.total,
step: ctx.state().analyze?.step ?? 0,
} satisfies AutoBePrismaSchemaEvent);
return out(result)(null);
});
}
function createController<Model extends ILlmSchema.Model>(
ctx: AutoBeContext<Model>,
props: {
preliminary: AutoBePreliminaryController<"analysisFiles">;
targetComponent: AutoBePrisma.IComponent;
otherTables: string[];
build: (next: IAutoBePrismaSchemaApplication.IComplete) => void;
},
): IAgenticaController.IClass<Model> {
assertSchemaModel(ctx.model);
const validate = (
input: unknown,
): IValidation<IAutoBePrismaSchemaApplication.IProps> => {
const result: IValidation<IAutoBePrismaSchemaApplication.IProps> =
defaultValidate(input);
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 actual: AutoBePrisma.IModel[] = result.data.request.models;
const expected: string[] = props.targetComponent.tables;
const missed: string[] = expected.filter(
(x) => actual.some((a) => a.name === x) === false,
);
if (missed.length === 0) return result;
ctx.dispatch({
type: "prismaInsufficient",
id: v7(),
created_at: new Date().toISOString(),
component: props.targetComponent,
actual,
missed,
});
return {
success: false,
data: result.data,
errors: [
{
path: "$input.request.models",
value: result.data.request.models,
expected: `Array<AutoBePrisma.IModel>`,
description: StringUtil.trim`
You missed some tables from the current domain's component.
Look at the following details to fix the schemas. Never forget to
compose the \`missed\` tables at the next function calling.
- filename: current domain's filename
- namespace: current domain's namespace
- expected: expected tables in the current domain
- actual: actual tables you made
- missed: tables you have missed, and you have to compose again
${JSON.stringify({
filename: props.targetComponent.filename,
namespace: props.targetComponent.namespace,
expected,
actual,
missed,
})}
`,
},
],
};
};
const application: ILlmApplication<Model> = collection[
ctx.model === "chatgpt"
? "chatgpt"
: ctx.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);
},
} satisfies IAutoBePrismaSchemaApplication,
};
}
const collection = {
chatgpt: (validate: Validator) =>
typia.llm.application<IAutoBePrismaSchemaApplication, "chatgpt">({
validate: {
process: validate,
},
}),
claude: (validate: Validator) =>
typia.llm.application<IAutoBePrismaSchemaApplication, "claude">({
validate: {
process: validate,
},
}),
gemini: (validate: Validator) =>
typia.llm.application<IAutoBePrismaSchemaApplication, "gemini">({
validate: {
process: validate,
},
}),
};
type Validator = (
input: unknown,
) => IValidation<IAutoBePrismaSchemaApplication.IProps>;
const defaultValidate: Validator =
typia.createValidate<IAutoBePrismaSchemaApplication.IProps>();
const SOURCE = "prismaSchema" satisfies AutoBeEventSource;