АРЧ ЮИ - остановка прогона через гуй

This commit is contained in:
2026-04-18 18:14:30 +03:00
parent 9710d81676
commit 12550606bb
39 changed files with 1783 additions and 93 deletions
+69 -13
View File
@@ -8,7 +8,7 @@ import { ApiError, ok } from "../utils/http";
import type { EvalRunMode, NormalizeRequestPayload } from "../types/normalizer";
import type { EvalTarget } from "../types/assistantEval";
type EvalAsyncStatus = "queued" | "running" | "completed" | "failed";
type EvalAsyncStatus = "queued" | "running" | "completed" | "failed" | "canceled";
interface EvalAsyncCaseInfo {
case_id: string;
@@ -41,6 +41,7 @@ interface EvalAsyncJob {
cases: EvalAsyncCaseInfo[];
error: string | null;
report: Record<string, unknown> | null;
abort_controller?: AbortController | null;
}
const ASYNC_JOBS = new Map<string, EvalAsyncJob>();
@@ -428,7 +429,7 @@ function readReportedCaseIds(job: EvalAsyncJob): Set<string> {
}
function isTerminalCaseStatus(status: EvalAsyncStatus): boolean {
return status === "completed" || status === "failed";
return status === "completed" || status === "failed" || status === "canceled";
}
function syncJobWithSessions(job: EvalAsyncJob): void {
@@ -559,6 +560,7 @@ export function buildEvalRouter(services: AppServices): Router {
}
const nowIso = new Date().toISOString();
const abortController = new AbortController();
const job: EvalAsyncJob = {
job_id: jobId,
status: "queued",
@@ -577,7 +579,8 @@ export function buildEvalRouter(services: AppServices): Router {
messages: []
})),
error: null,
report: null
report: null,
abort_controller: abortController
};
ASYNC_JOBS.set(job.job_id, job);
trimAsyncJobsStore();
@@ -586,24 +589,47 @@ export function buildEvalRouter(services: AppServices): Router {
void (async () => {
const target = ASYNC_JOBS.get(job.job_id);
if (!target) return;
if (target.status === "canceled") {
return;
}
target.status = "running";
target.updated_at = new Date().toISOString();
try {
const report = await services.evalService.run({
...payload,
caseSetFile: runtimeCaseSetFile,
runId
runId,
abortSignal: abortController.signal
});
target.report = report;
syncJobWithSessions(target);
target.completed_cases = target.total_cases;
target.status = "completed";
target.updated_at = new Date().toISOString();
const latestAfterRun = ASYNC_JOBS.get(job.job_id);
if (!latestAfterRun) {
return;
}
if (latestAfterRun.status === "canceled") {
latestAfterRun.updated_at = new Date().toISOString();
return;
}
latestAfterRun.report = report;
syncJobWithSessions(latestAfterRun);
latestAfterRun.completed_cases = latestAfterRun.total_cases;
latestAfterRun.status = "completed";
latestAfterRun.updated_at = new Date().toISOString();
latestAfterRun.abort_controller = null;
} catch (error) {
syncJobWithSessions(target);
target.status = "failed";
target.error = error instanceof Error ? error.message : String(error);
target.updated_at = new Date().toISOString();
const latestAfterError = ASYNC_JOBS.get(job.job_id);
if (!latestAfterError) {
return;
}
if (latestAfterError.status === "canceled") {
latestAfterError.updated_at = new Date().toISOString();
latestAfterError.abort_controller = null;
return;
}
syncJobWithSessions(latestAfterError);
latestAfterError.status = "failed";
latestAfterError.error = error instanceof Error ? error.message : String(error);
latestAfterError.updated_at = new Date().toISOString();
latestAfterError.abort_controller = null;
}
})();
});
@@ -638,5 +664,35 @@ export function buildEvalRouter(services: AppServices): Router {
}
});
router.post("/api/eval/run-async/:job_id/cancel", (req, res, next) => {
try {
const jobId = String(req.params.job_id ?? "").trim();
if (!jobId) {
throw new ApiError("INVALID_ASYNC_JOB_ID", "job_id is required.", 400);
}
const job = ASYNC_JOBS.get(jobId);
if (!job) {
throw new ApiError("ASYNC_JOB_NOT_FOUND", `Async eval job not found: ${jobId}`, 404);
}
if (!isTerminalCaseStatus(job.status)) {
job.status = "canceled";
job.error = "Остановлено оператором.";
job.updated_at = new Date().toISOString();
job.abort_controller?.abort();
job.abort_controller = null;
job.cases = job.cases.map((item) =>
item.status === "completed" ? item : { ...item, status: "canceled" }
);
syncJobWithSessions(job);
}
ok(res, {
ok: true,
job: snapshotJob(job)
});
} catch (error) {
next(error);
}
});
return router;
}