@autobe/agent
Version:
AI backend server code generator
284 lines (268 loc) • 9.45 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import {
AutoBeDatabase,
AutoBeEventSource,
AutoBeInterfaceSchemaCastingEvent,
AutoBeOpenApi,
AutoBeProgressEventBase,
} from "@autobe/interface";
import { AutoBeOpenApiTypeChecker } from "@autobe/utils";
import { IPointer, Singleton } from "tstl";
import typia, { ILlmApplication, IValidation } from "typia";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { executeCachedBatch } from "../../utils/executeCachedBatch";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformInterfaceSchemaCastingHistory } from "./histories/transformInterfaceSchemaCastingHistory";
import { AutoBeInterfaceSchemaProgrammer } from "./programmers/AutoBeInterfaceSchemaProgrammer";
import { IAutoBeInterfaceSchemaCastingApplication } from "./structures/IAutoBeInterfaceSchemaCastingApplication";
import { AutoBeJsonSchemaFactory } from "./utils/AutoBeJsonSchemaFactory";
import { AutoBeJsonSchemaValidator } from "./utils/AutoBeJsonSchemaValidator";
import { fulfillJsonSchemaErrorMessages } from "./utils/fulfillJsonSchemaErrorMessages";
export async function orchestrateInterfaceSchemaCasting(
ctx: AutoBeContext,
props: {
document: AutoBeOpenApi.IDocument;
schemas: Record<string, AutoBeOpenApi.IJsonSchema>;
instruction: string;
progress: AutoBeProgressEventBase;
},
): Promise<Record<string, AutoBeOpenApi.IJsonSchema>> {
// Filter to only process non-object type schemas (potential degenerate primitives)
const typeNames: string[] = Object.keys(props.schemas).filter(
(k) =>
props.schemas[k] !== undefined &&
AutoBeJsonSchemaValidator.isPreset(k) === false &&
AutoBeOpenApiTypeChecker.isObject(props.schemas[k]) === false,
);
props.progress.total += typeNames.length;
const x: Record<string, AutoBeOpenApi.IJsonSchema> = {};
await executeCachedBatch(
ctx,
typeNames.map((it) => async (promptCacheKey) => {
const predicate = (key: string) =>
key === it ||
(AutoBeJsonSchemaValidator.isPage(key) &&
AutoBeJsonSchemaFactory.getPageName(key) === it);
const refineOperations: AutoBeOpenApi.IOperation[] =
props.document.operations.filter(
(op) =>
(op.requestBody && predicate(op.requestBody.typeName)) ||
(op.responseBody && predicate(op.responseBody.typeName)),
);
const originalSchema: AutoBeOpenApi.IJsonSchema = props.schemas[it];
const counter = new Singleton(() => ++props.progress.completed);
try {
const refined: AutoBeOpenApi.IJsonSchema | null = await process(ctx, {
instruction: props.instruction,
document: props.document,
typeName: it,
refineOperations,
originalSchema,
progress: props.progress,
counter,
promptCacheKey,
});
if (refined !== null) x[it] = refined;
} catch (error) {
counter.get();
throw error;
}
}),
);
return x;
}
async function process(
ctx: AutoBeContext,
props: {
instruction: string;
document: AutoBeOpenApi.IDocument;
typeName: string;
refineOperations: AutoBeOpenApi.IOperation[];
originalSchema: AutoBeOpenApi.IJsonSchema;
progress: AutoBeProgressEventBase;
counter: Singleton<number>;
promptCacheKey: string;
},
): Promise<AutoBeOpenApi.IJsonSchema | null> {
const preliminary: AutoBePreliminaryController<
| "analysisSections"
| "databaseSchemas"
| "interfaceOperations"
| "interfaceSchemas"
| "previousAnalysisSections"
| "previousDatabaseSchemas"
| "previousInterfaceOperations"
| "previousInterfaceSchemas"
| "complete"
> = new AutoBePreliminaryController({
dispatch: (e) => ctx.dispatch(e),
state: ctx.state(),
application:
typia.json.application<IAutoBeInterfaceSchemaCastingApplication>(),
source: SOURCE,
kinds: [
"analysisSections",
"previousAnalysisSections",
"databaseSchemas",
"previousDatabaseSchemas",
"interfaceOperations",
"previousInterfaceOperations",
"interfaceSchemas",
"previousInterfaceSchemas",
"complete",
],
config: {
database: "text",
databaseProperty: true,
},
all: {
interfaceOperations: props.document.operations,
interfaceSchemas: props.document.components.schemas,
},
local: {
interfaceOperations: props.refineOperations,
interfaceSchemas: {
// actually not "AutoBeOpenApi.IJsonSchemaDescriptive" type
[props.typeName]:
props.originalSchema as AutoBeOpenApi.IJsonSchemaDescriptive,
},
databaseSchemas:
AutoBeInterfaceSchemaProgrammer.getNeighborDatabaseSchemas({
typeName: props.typeName,
application: ctx.state().database!.result.data,
}),
},
});
const event: AutoBeInterfaceSchemaCastingEvent =
await preliminary.orchestrate(ctx, async (out) => {
const pointer: IPointer<IAutoBeInterfaceSchemaCastingApplication.IWrite | null> =
{
value: null,
};
const result: AutoBeContext.IResult = await ctx.conversate({
source: SOURCE,
controller: createController(ctx, {
typeName: props.typeName,
operations: props.document.operations,
schema: props.originalSchema,
preliminary,
pointer,
}),
enforceFunctionCall: true,
promptCacheKey: props.promptCacheKey,
...transformInterfaceSchemaCastingHistory({
state: ctx.state(),
instruction: props.instruction,
typeName: props.typeName,
refineOperations: props.refineOperations,
originalSchema: props.originalSchema,
preliminary,
}),
});
if (pointer.value === null) return out(result)(null);
// Fix schema if refined
const refinedSchema: AutoBeOpenApi.IJsonSchemaDescriptive.IObject | null =
pointer.value.casting !== null
? (AutoBeJsonSchemaFactory.fixDesign(
pointer.value.casting,
) as AutoBeOpenApi.IJsonSchemaDescriptive.IObject)
: null;
return out(result)({
type: SOURCE,
id: v7(),
typeName: props.typeName,
original: props.originalSchema,
observation: pointer.value.observation,
reasoning: pointer.value.reasoning,
verdict: pointer.value.verdict,
refined: refinedSchema,
acquisition: preliminary.getAcquisition(),
metric: result.metric,
tokenUsage: result.tokenUsage,
step: ctx.state().analyze?.step ?? 0,
total: props.progress.total,
completed: props.counter.get(),
created_at: new Date().toISOString(),
} satisfies AutoBeInterfaceSchemaCastingEvent);
});
ctx.dispatch(event);
return event.refined || null;
}
function createController(
ctx: AutoBeContext,
props: {
typeName: string;
schema: AutoBeOpenApi.IJsonSchema;
operations: AutoBeOpenApi.IOperation[];
pointer: IPointer<IAutoBeInterfaceSchemaCastingApplication.IWrite | null>;
preliminary: AutoBePreliminaryController<
| "analysisSections"
| "databaseSchemas"
| "interfaceOperations"
| "interfaceSchemas"
| "previousAnalysisSections"
| "previousDatabaseSchemas"
| "previousInterfaceOperations"
| "previousInterfaceSchemas"
| "complete"
>;
},
): IAgenticaController.IClass {
const everyModels: AutoBeDatabase.IModel[] =
ctx.state().database?.result.data.files.flatMap((f) => f.models) ?? [];
const validate: Validator = (next) => {
const result: IValidation<IAutoBeInterfaceSchemaCastingApplication.IProps> =
typia.validate<IAutoBeInterfaceSchemaCastingApplication.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,
});
const errors: IValidation.IError[] = [];
if (result.data.request.casting !== null)
AutoBeInterfaceSchemaProgrammer.validate({
path: "$input.request.design",
errors,
everyModels,
operations: props.operations,
typeName: props.typeName,
design: result.data.request.casting,
});
if (errors.length !== 0)
return {
success: false,
errors,
data: next,
};
return result;
};
const application: ILlmApplication = props.preliminary.fixApplication(
typia.llm.application<IAutoBeInterfaceSchemaCastingApplication>({
validate: {
process: validate,
},
}),
);
AutoBeInterfaceSchemaProgrammer.fixApplication({
application,
everyModels,
});
return {
protocol: "class",
name: SOURCE,
application,
execute: {
process: (input) => {
if (input.request.type === "write") props.pointer.value = input.request;
},
} satisfies IAutoBeInterfaceSchemaCastingApplication,
};
}
type Validator = (
input: unknown,
) => IValidation<IAutoBeInterfaceSchemaCastingApplication.IProps>;
const SOURCE = "interfaceSchemaCasting" satisfies AutoBeEventSource;