Improve benchmark case details and score chart scaling (#146)

Co-authored-by: Yaowei Zheng <hiyouga@buaa.edu.cn>
This commit is contained in:
Jingzhe Xu
2026-07-31 20:42:32 +08:00
committed by GitHub
parent 5308002d72
commit 740f101242
19 changed files with 519 additions and 212 deletions
+2
View File
@@ -1292,6 +1292,8 @@ export interface BenchmarksResponse {
benchmarks: BenchmarkSummary[];
}
export type CaseMaterial = "statement" | "rubric";
/** Public Benchmark Case metadata. Rubric and Gold content are never included. */
export interface BenchmarkCaseSummary {
id: string;
+61 -47
View File
@@ -4,16 +4,72 @@
* GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases
* GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/files
* GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/files/content
* GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/rubric/files
* GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/rubric/files/content
* Returns the Agent's Benchmark list (title/description from benchmark_config.toml)
* along with the evaluations[] from scoreboard.yaml.
*/
import { Hono } from "hono";
import { Hono, type Context } from "hono";
import type { AppEnv } from "../../auth/middleware.js";
import type { AppDeps } from "../../app.js";
import type { CaseMaterial } from "../../api/types.js";
import { requireValidId } from "../validate.js";
const TEXT_PREVIEW_BYTES = 256 * 1024;
function listCaseFiles(deps: AppDeps, material: CaseMaterial) {
return async (c: Context<AppEnv>) => {
const projectId = requireValidId(c, "projectId");
const agentId = requireValidId(c, "agentId");
const benchmarkId = requireValidId(c, "benchmarkId");
const caseId = requireValidId(c, "caseId");
deps.projectService.requireProjectAccess(c.var.user.userId, projectId);
await deps.agentConfigService.requireExists(projectId, agentId);
return c.json(
await deps.benchmarks.listCaseFiles(
projectId,
agentId,
benchmarkId,
caseId,
c.req.query("path") ?? "",
material,
),
);
};
}
function readCaseFile(deps: AppDeps, material: CaseMaterial) {
return async (c: Context<AppEnv>) => {
const projectId = requireValidId(c, "projectId");
const agentId = requireValidId(c, "agentId");
const benchmarkId = requireValidId(c, "benchmarkId");
const caseId = requireValidId(c, "caseId");
deps.projectService.requireProjectAccess(c.var.user.userId, projectId);
await deps.agentConfigService.requireExists(projectId, agentId);
const download = c.req.query("download") === "1";
const boundedPreview = !download && c.req.query("preview") === "1";
const { data, fileName, contentType, scriptable, truncated } =
await deps.benchmarks.readCaseFile(
projectId,
agentId,
benchmarkId,
caseId,
c.req.query("path") ?? "",
material,
boundedPreview ? { maxBytes: TEXT_PREVIEW_BYTES } : undefined,
);
return new Response(new Uint8Array(data), {
status: 200,
headers: {
"Content-Type": !download && scriptable ? "text/plain; charset=utf-8" : contentType,
"Content-Disposition": `${download ? "attachment" : "inline"}; filename*=UTF-8''${encodeURIComponent(fileName)}`,
"X-Content-Type-Options": "nosniff",
...(truncated ? { "X-Content-Truncated": "1" } : {}),
},
});
};
}
export function benchmarksRoutes(deps: AppDeps): Hono<AppEnv> {
const app = new Hono<AppEnv>();
@@ -34,52 +90,10 @@ export function benchmarksRoutes(deps: AppDeps): Hono<AppEnv> {
return c.json(await deps.benchmarks.listCases(projectId, agentId, benchmarkId));
});
app.get("/:benchmarkId/cases/:caseId/files", async (c) => {
const projectId = requireValidId(c, "projectId");
const agentId = requireValidId(c, "agentId");
const benchmarkId = requireValidId(c, "benchmarkId");
const caseId = requireValidId(c, "caseId");
deps.projectService.requireProjectAccess(c.var.user.userId, projectId);
await deps.agentConfigService.requireExists(projectId, agentId);
return c.json(
await deps.benchmarks.listCaseFiles(
projectId,
agentId,
benchmarkId,
caseId,
c.req.query("path") ?? "",
),
);
});
app.get("/:benchmarkId/cases/:caseId/files/content", async (c) => {
const projectId = requireValidId(c, "projectId");
const agentId = requireValidId(c, "agentId");
const benchmarkId = requireValidId(c, "benchmarkId");
const caseId = requireValidId(c, "caseId");
deps.projectService.requireProjectAccess(c.var.user.userId, projectId);
await deps.agentConfigService.requireExists(projectId, agentId);
const download = c.req.query("download") === "1";
const boundedPreview = !download && c.req.query("preview") === "1";
const { data, fileName, contentType, scriptable, truncated } =
await deps.benchmarks.readCaseFile(
projectId,
agentId,
benchmarkId,
caseId,
c.req.query("path") ?? "",
boundedPreview ? { maxBytes: TEXT_PREVIEW_BYTES } : undefined,
);
return new Response(new Uint8Array(data), {
status: 200,
headers: {
"Content-Type": !download && scriptable ? "text/plain; charset=utf-8" : contentType,
"Content-Disposition": `${download ? "attachment" : "inline"}; filename*=UTF-8''${encodeURIComponent(fileName)}`,
"X-Content-Type-Options": "nosniff",
...(truncated ? { "X-Content-Truncated": "1" } : {}),
},
});
});
app.get("/:benchmarkId/cases/:caseId/files", listCaseFiles(deps, "statement"));
app.get("/:benchmarkId/cases/:caseId/files/content", readCaseFile(deps, "statement"));
app.get("/:benchmarkId/cases/:caseId/rubric/files", listCaseFiles(deps, "rubric"));
app.get("/:benchmarkId/cases/:caseId/rubric/files/content", readCaseFile(deps, "rubric"));
return app;
}
@@ -25,6 +25,7 @@ import type {
BenchmarkRunScore,
BenchmarkSummary,
BenchmarksResponse,
CaseMaterial,
WorkspaceFilesResponse,
} from "../api/types.js";
import type {
@@ -235,7 +236,13 @@ export class BenchmarkService {
.sort((a, b) => a.name.localeCompare(b.name))) {
const fallback: BenchmarkCaseSummary = { id: entry.name, title: entry.name };
try {
const statementDir = await this.statementRoot(projectId, agentId, benchmarkId, entry.name);
const statementDir = await this.caseMaterialRoot(
projectId,
agentId,
benchmarkId,
entry.name,
"statement",
);
const realReadme = await fs.realpath(path.join(statementDir, "README.md"));
if (!isWithin(statementDir, realReadme)) throw new Error("README escapes Statement");
cases.push({
@@ -255,9 +262,16 @@ export class BenchmarkService {
benchmarkId: string,
caseId: string,
rel: string,
material: CaseMaterial,
): Promise<WorkspaceFilesResponse> {
const statementDir = await this.statementRoot(projectId, agentId, benchmarkId, caseId);
return this.workspaceFiles.list(statementDir, rel);
const materialRoot = await this.caseMaterialRoot(
projectId,
agentId,
benchmarkId,
caseId,
material,
);
return this.workspaceFiles.list(materialRoot, rel);
}
async readCaseFile(
@@ -266,37 +280,45 @@ export class BenchmarkService {
benchmarkId: string,
caseId: string,
rel: string,
material: CaseMaterial,
options?: WorkspaceFileReadOptions,
): Promise<WorkspaceFileContent> {
const statementDir = await this.statementRoot(projectId, agentId, benchmarkId, caseId);
return this.workspaceFiles.read(statementDir, rel, options);
const materialRoot = await this.caseMaterialRoot(
projectId,
agentId,
benchmarkId,
caseId,
material,
);
return this.workspaceFiles.read(materialRoot, rel, options);
}
private async statementRoot(
private async caseMaterialRoot(
projectId: string,
agentId: string,
benchmarkId: string,
caseId: string,
material: CaseMaterial,
): Promise<string> {
const benchDir = path.join(benchmarksDir(this.root, projectId, agentId), benchmarkId);
const caseDir = path.join(benchDir, caseId);
const statementDir = path.join(caseDir, "statement");
const materialRoot = path.join(caseDir, material);
try {
const [realBenchDir, realCaseDir, realStatementDir] = await Promise.all([
const [realBenchDir, realCaseDir, realMaterialRoot] = await Promise.all([
fs.realpath(benchDir),
fs.realpath(caseDir),
fs.realpath(statementDir),
fs.realpath(materialRoot),
]);
if (
!isWithin(realBenchDir, realCaseDir) ||
path.dirname(realStatementDir) !== realCaseDir ||
path.basename(realStatementDir) !== "statement"
path.dirname(realMaterialRoot) !== realCaseDir ||
path.basename(realMaterialRoot) !== material
) {
throw new Error("Statement path is not canonical");
throw new Error("Case material is not canonical");
}
return realStatementDir;
return realMaterialRoot;
} catch {
throw new HttpError(404, "not_found", "Public Case Statement does not exist.");
throw new HttpError(404, "not_found", `Case ${material} does not exist.`);
}
}