// Frontend Evals helpers — PR e2 switched the data source from // localStorage to the real `/v1/datasets` + `/v1/eval_runs` endpoints // (see api-client.jsx). What stays here: // // - GRADER_LIBRARY: the catalog of grader_ids the UI offers. PR e4 will // wire real implementations on the backend; for now the catalog only // exists in the frontend (datasets carry grader_ids strings; the // backend just round-trips them without validating yet). // // - decorateDataset / decorateCase: thin adapters that alias the // backend snake_case fields to the camelCase / `id` that existing // UI components reference, so PR e2 didn't have to touch every // render line. Future cleanup PR can drop the aliases and read // `dataset_id` directly. // // Previously this file also held seedDatasets() / seedRuns() / a Store // object that persisted everything to localStorage. All gone — the // real backend (PR e1) is now the source of truth. const GRADER_LIBRARY = [ { id: "exact_match", name: "exact_match", desc: "Output equals expected (strict).", kind: "deterministic" }, { id: "contains", name: "contains", desc: "Output contains the expected substring.", kind: "deterministic" }, { id: "regex", name: "regex", desc: "Output matches a regex pattern.", kind: "deterministic" }, { id: "json_schema", name: "json_schema", desc: "Output validates against a JSON schema.", kind: "deterministic" }, { id: "llm_helpfulness", name: "llm.helpfulness", desc: "LLM-as-judge on a 1-5 helpfulness rubric.", kind: "llm" }, { id: "llm_faithfulness", name: "llm.faithfulness", desc: "LLM-as-judge: no claim is unsupported.", kind: "llm" }, { id: "tool_called", name: "tool_called", desc: "Agent called the listed tool(s).", kind: "trajectory" }, { id: "trajectory_match", name: "trajectory_match", desc: "Tool call sequence matches expected.", kind: "trajectory" }, { id: "no_extra_tools", name: "no_extra_tools", desc: "Agent did not call un-whitelisted tools.", kind: "trajectory" }, { id: "max_cost", name: "max_cost", desc: "Run cost stays under threshold.", kind: "budget" }, { id: "max_latency", name: "max_latency", desc: "Run latency stays under threshold.", kind: "budget" }, ]; // Backend → UI shape adapter. The existing components read `id`, // `createdAt` (ms), `cases` (array even when missing). Keep the rest of // the API response as-is so dataset_id etc. are still available if a // component wants them. function decorateDataset(apiDs) { if (!apiDs) return null; return { ...apiDs, id: apiDs.dataset_id, createdAt: (apiDs.created_at || 0) * 1000, cases: (apiDs.cases || []).map(decorateCase), }; } function decorateCase(apiCase) { if (!apiCase) return null; return { ...apiCase, id: apiCase.case_id, addedAt: (apiCase.added_at || 0) * 1000, }; } // EvalRun (PR e3 will wire execution; for now status is always "pending" // and cases[] is empty). UI components that previously read the seeded // mock shape need adapters too — keep them simple. function decorateEvalRun(apiRun) { if (!apiRun) return null; const epochMs = (value) => value && value < 100000000000 ? value * 1000 : value; return { ...apiRun, id: apiRun.eval_run_id, datasetId: apiRun.dataset_id, datasetName: apiRun.dataset_name, agentId: apiRun.agent_id, agentName: apiRun.agent_name, // Eval `started_at` is already milliseconds, unlike dataset timestamps. startedAt: epochMs(apiRun.started_at || apiRun.created_at || 0), durationMs: apiRun.duration_ms || 0, cases: (apiRun.cases || []).map((c) => ({ ...c, caseId: c.case_id, latencyMs: c.latency_ms, costUSD: c.cost_usd, inputTokens: c.input_tokens, outputTokens: c.output_tokens, pass: c.passed, })), }; } Object.assign(window, { GRADER_LIBRARY, decorateDataset, decorateCase, decorateEvalRun, });