@autobe/agent
Version:
AI backend server code generator
426 lines (393 loc) • 12.9 kB
text/typescript
import {
AutoBeOpenApi,
AutoBeProgressEventBase,
AutoBeRealizeAuthorization,
AutoBeRealizeFunction,
AutoBeRealizeValidateEvent,
IAutoBeTypeScriptCompileResult,
} from "@autobe/interface";
import {
ILlmApplication,
ILlmController,
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 { validateEmptyCode } from "../../utils/validateEmptyCode";
import { IAutoBeCommonCorrectCastingApplication } from "../common/structures/IAutoBeCommonCorrectCastingApplication";
import { transformRealizeCorrectCastingHistory } from "./histories/transformRealizeCorrectCastingHistory";
import { compileRealizeFiles } from "./internal/compileRealizeFiles";
import { IAutoBeRealizeFunctionFailure } from "./structures/IAutoBeRealizeFunctionFailure";
import { IAutoBeRealizeScenarioResult } from "./structures/IAutoBeRealizeScenarioResult";
import { replaceImportStatements } from "./utils/replaceImportStatements";
/** Result of attempting to correct a single function */
type CorrectionResult = {
result: "success" | "ignore" | "exception";
func: AutoBeRealizeFunction;
};
export const orchestrateRealizeCorrectCasting = async <
Model extends ILlmSchema.Model,
>(
ctx: AutoBeContext<Model>,
scenarios: IAutoBeRealizeScenarioResult[],
authorizations: AutoBeRealizeAuthorization[],
functions: AutoBeRealizeFunction[],
progress: AutoBeProgressEventBase,
life: number = ctx.retry,
): Promise<AutoBeRealizeFunction[]> => {
const validateEvent: AutoBeRealizeValidateEvent = await compileRealizeFiles(
ctx,
{
authorizations,
functions,
},
);
return predicate(
ctx,
{
scenarios,
authorizations,
functions,
previousFailures: [],
progress,
event: validateEvent,
},
life,
);
};
const predicate = async <Model extends ILlmSchema.Model>(
ctx: AutoBeContext<Model>,
props: {
scenarios: IAutoBeRealizeScenarioResult[];
authorizations: AutoBeRealizeAuthorization[];
functions: AutoBeRealizeFunction[];
previousFailures: IAutoBeRealizeFunctionFailure[][];
progress: AutoBeProgressEventBase;
event: AutoBeRealizeValidateEvent;
},
life: number,
): Promise<AutoBeRealizeFunction[]> => {
if (props.event.result.type === "failure") {
ctx.dispatch(props.event);
return await correct(ctx, props, life);
}
return props.functions;
};
const correct = async <Model extends ILlmSchema.Model>(
ctx: AutoBeContext<Model>,
props: {
scenarios: IAutoBeRealizeScenarioResult[];
authorizations: AutoBeRealizeAuthorization[];
functions: AutoBeRealizeFunction[];
previousFailures: IAutoBeRealizeFunctionFailure[][];
progress: AutoBeProgressEventBase;
event: AutoBeRealizeValidateEvent;
},
life: number,
): Promise<AutoBeRealizeFunction[]> => {
// Early returns for non-correctable cases
if (props.event.result.type !== "failure" || life < 0) {
return props.functions;
}
const failure = props.event.result;
const locations: string[] = diagnose(props.event).filter((l) =>
props.functions.map((f) => f.location).includes(l),
);
// If no locations to correct, return original functions
if (locations.length === 0) {
return props.functions;
}
props.progress.total += locations.length;
const converted: CorrectionResult[] = await executeCachedBatch(
ctx,
locations.map((location) => async (): Promise<CorrectionResult> => {
const func: AutoBeRealizeFunction = props.functions.find(
(f) => f.location === location,
)!;
const scenario: IAutoBeRealizeScenarioResult = props.scenarios.find(
(s) => s.location === func.location,
)!;
const operation: AutoBeOpenApi.IOperation = scenario.operation;
const authorization: AutoBeRealizeAuthorization | undefined =
props.authorizations.find(
(a) => a.actor.name === operation.authorizationActor,
);
const pointer: IPointer<
IAutoBeCommonCorrectCastingApplication.IProps | false | null
> = {
value: null,
};
const { metric, tokenUsage } = await ctx.conversate({
source: "realizeCorrect",
controller: createController({
model: ctx.model,
functionName: scenario.functionName,
then: (next) => {
pointer.value = next;
},
reject: () => {
pointer.value = false;
},
}),
enforceFunctionCall: true,
...transformRealizeCorrectCastingHistory(ctx, {
scenario,
authorization,
function: func,
failures: [
...props.previousFailures
.map(
(pf) =>
pf.find((f) => f.function.location === func.location) ?? null,
)
.filter((x) => x !== null),
{
function: func,
diagnostics: failure.diagnostics.filter(
(d) => d.file === func.location,
),
},
],
}),
});
++props.progress.completed;
if (pointer.value === null)
return { result: "exception" as const, func: func };
else if (pointer.value === false)
return { result: "ignore" as const, func: func };
pointer.value.draft = await replaceImportStatements(ctx, {
schemas: ctx.state().interface!.document.components.schemas,
operation: operation,
code: pointer.value.draft,
decoratorType: authorization?.payload.name,
});
if (pointer.value.revise.final)
pointer.value.revise.final = await replaceImportStatements(ctx, {
schemas: ctx.state().interface!.document.components.schemas,
operation: operation,
code: pointer.value.revise.final,
decoratorType: authorization?.payload.name,
});
ctx.dispatch({
id: v7(),
type: "realizeCorrect",
kind: "casting",
content: pointer.value.revise.final ?? pointer.value.draft,
created_at: new Date().toISOString(),
location: func.location,
step: ctx.state().analyze?.step ?? 0,
metric,
tokenUsage,
completed: props.progress.completed,
total: props.progress.total,
});
return {
result: "success" as const,
func: {
...func,
content: pointer.value.revise.final ?? pointer.value.draft,
},
};
}),
);
// Get functions that were not modified (not in locations array)
const unchangedFunctions: AutoBeRealizeFunction[] = props.functions.filter(
(f) => !locations.includes(f.location),
);
// Merge converted functions with unchanged functions for validation
const allFunctionsForValidation = [
...converted.map((c) => c.func),
...unchangedFunctions,
];
const newValidate: AutoBeRealizeValidateEvent = await compileRealizeFiles(
ctx,
{
authorizations: props.authorizations,
functions: allFunctionsForValidation,
},
);
const newResult: IAutoBeTypeScriptCompileResult = newValidate.result;
if (newResult.type === "success") {
return allFunctionsForValidation;
} else if (newResult.type === "exception") {
// Compilation exception, return current functions. because retrying won't help.
return props.functions;
}
if (
newResult.diagnostics.every((d) => !d.file?.startsWith("src/providers"))
) {
// No diagnostics related to provider functions, stop correcting
return allFunctionsForValidation;
}
const newLocations: string[] = diagnose(newValidate);
// Separate successful, failed, and ignored corrections
const { success, failed, ignored } = separateCorrectionResults(
converted,
newLocations,
);
// If no failures to retry, return all functions
if (failed.length === 0) {
return [...success, ...ignored, ...unchangedFunctions];
}
// Recursively retry failed functions
const retriedFunctions: AutoBeRealizeFunction[] = await predicate(
ctx,
{
scenarios: props.scenarios,
authorizations: props.authorizations,
functions: failed,
previousFailures: [
...props.previousFailures,
failed.map(
(f) =>
({
function: f,
diagnostics:
newValidate.result.type === "failure"
? newValidate.result.diagnostics.filter(
(d) => d.file === f.location,
)
: [],
}) satisfies IAutoBeRealizeFunctionFailure,
),
],
progress: props.progress,
event: newValidate,
},
life - 1,
);
return [...success, ...ignored, ...retriedFunctions, ...unchangedFunctions];
};
/**
* Extract unique file locations from validation event diagnostics
*
* @param event - Validation event containing compilation results
* @returns Array of unique file paths that have errors
*/
const diagnose = (event: AutoBeRealizeValidateEvent): string[] => {
if (event.result.type !== "failure") {
return [];
}
const diagnostics = event.result.diagnostics;
const locations = diagnostics
.map((d) => d.file)
.filter((f): f is string => f !== null)
.filter((f) => f.startsWith("src/providers"));
return Array.from(new Set(locations));
};
/**
* Separate correction results into successful, failed, and ignored functions
*
* @param corrections - Array of correction results
* @param errorLocations - File paths that still have errors
* @returns Object with success, failed, and ignored function arrays
*/
const separateCorrectionResults = (
corrections: CorrectionResult[],
errorLocations: string[],
): {
success: AutoBeRealizeFunction[];
failed: AutoBeRealizeFunction[];
ignored: AutoBeRealizeFunction[];
} => {
const success = corrections
.filter(
(c) =>
c.result === "success" && !errorLocations.includes(c.func.location),
)
.map((c) => c.func);
const failed = corrections
.filter(
(c) => c.result === "success" && errorLocations.includes(c.func.location),
)
.map((c) => c.func);
const ignored = corrections
.filter((c) => c.result === "ignore" || c.result === "exception")
.map((c) => c.func);
return { success, failed, ignored };
};
const createController = <Model extends ILlmSchema.Model>(props: {
model: Model;
functionName: string;
then: (next: IAutoBeCommonCorrectCastingApplication.IProps) => void;
reject: () => void;
}): ILlmController<Model> => {
assertSchemaModel(props.model);
const validate: Validator = (input) => {
const result: IValidation<IAutoBeCommonCorrectCastingApplication.IProps> =
typia.validate<IAutoBeCommonCorrectCastingApplication.IProps>(input);
if (result.success === false) return result;
const errors: IValidation.IError[] = validateEmptyCode({
functionName: props.functionName,
draft: result.data.draft,
revise: result.data.revise,
});
return errors.length
? {
success: false,
errors,
data: result.data,
}
: result;
};
const application = collection[
props.model === "chatgpt"
? "chatgpt"
: props.model === "gemini"
? "gemini"
: "claude"
](validate) satisfies ILlmApplication<any> as any as ILlmApplication<Model>;
return {
protocol: "class",
name: "correctInvalidRequest",
application,
execute: {
rewrite: (next) => {
props.then(next);
},
reject: () => {
props.reject();
},
} satisfies IAutoBeCommonCorrectCastingApplication,
};
};
const collection = {
chatgpt: (validate: Validator) =>
typia.llm.application<IAutoBeCommonCorrectCastingApplication, "chatgpt">({
validate: {
rewrite: validate,
reject: () => ({
success: true,
data: undefined,
}),
},
}),
claude: (validate: Validator) =>
typia.llm.application<IAutoBeCommonCorrectCastingApplication, "claude">({
validate: {
rewrite: validate,
reject: () => ({
success: true,
data: undefined,
}),
},
}),
gemini: (validate: Validator) =>
typia.llm.application<IAutoBeCommonCorrectCastingApplication, "gemini">({
validate: {
rewrite: validate,
reject: () => ({
success: true,
data: undefined,
}),
},
}),
};
type Validator = (
input: unknown,
) => IValidation<IAutoBeCommonCorrectCastingApplication.IProps>;