Files
AgentCompany/tests/eval/harness.test.ts
T
ZacharyZhang-NY e7f879bed0 Build minimal LLM eval harness scaffold (B5) — fixtures recorded separately
Land the B5 harness skeleton: fixture loader (strict JSONL contract with
meta header + ordered turn records + assertions tail), replay-client that
swaps in for pi-ai's streamSimple by replaying recorded assistant turns,
structural assertion judge (tool_called / no_tool_called /
message_contains_substring / tool_call_arg_matches / trace_max_turns), an
eval-runner that sums the token ledger and produces an EvalResult, a
vitest driver (tests/eval/harness.test.ts) that discovers *.jsonl
fixtures, and a record-fixture CLI that writes fixture meta headers.

Hard contracts:
- No fallback: replay-client and fixture-loader throw on exhaustion /
  malformed input rather than synthesizing defaults.
- No mock: types reuse @mariozechner/pi-ai AgentMessage / ToolCall
  verbatim; shadow types are forbidden.
- No fake fixture data: tests/eval/fixtures/ ships only README +
  .gitkeep. Real traces are recorded by the user against live providers.

package.json adds test:eval (vitest --passWithNoTests so an empty
fixture dir is green) and eval:record (CLI entrypoint). electron-builder
already excludes tests/ via its files: glob — fixtures never enter the
shipped .asar.
2026-04-20 11:39:53 -04:00

50 lines
1.8 KiB
TypeScript

/**
* B5 eval harness — driver spec.
*
* This spec discovers every `*.jsonl` fixture under `tests/eval/fixtures/`
* and runs it through the assertion judge. If there are zero fixtures, the
* spec file has zero tests — vitest is invoked with `--passWithNoTests`
* from the `test:eval` script so this is a green no-op until the user
* records real fixtures.
*
* The harness does NOT synthesize placeholder fixtures under any
* circumstance. An empty `fixtures/` directory is a real state — the tier
* is simply not yet exercising anything — and the green run reflects that
* honestly.
*/
import { readdir } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { loadFixture } from "./fixture-loader.js";
import { runEval } from "./eval-runner.js";
const FIXTURE_DIR = join(__dirname, "fixtures");
async function listFixtures(): Promise<string[]> {
const entries = await readdir(FIXTURE_DIR, { withFileTypes: true });
return entries
.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
.map((entry) => join(FIXTURE_DIR, entry.name))
.sort();
}
const fixturePaths = await listFixtures();
describe.each(fixturePaths)("eval fixture %s", (fixturePath) => {
it("passes all assertions", async () => {
const fixture = await loadFixture(fixturePath);
const result = runEval(fixture);
if (!result.passed) {
const failures = result.assertions
.filter((assertion) => !assertion.passed)
.map((assertion) => `- ${assertion.assertion.kind}: ${assertion.reason ?? "<no reason>"}`)
.join("\n");
throw new Error(
`fixture ${result.fixtureId} failed:\n${failures}\ntoken ledger: ${JSON.stringify(result.tokenLedger)}`,
);
}
expect(result.passed).toBe(true);
});
});