Files
santifer--career-ops/evals/README.md
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

113 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Golden-set eval for cheap-model routing (#1354)
> **Status: v1.** The *mechanism* (`eval-golden.mjs`) is design-invariant and runs
> today. Reference labels are now frozen (10 synthetic cases — see **Labeling
> methodology** below); the gate threshold and per-model cost remain tunable
> constants, and wiring into CI is still deferred (see **Open design questions**).
## What this is
A small labeled golden-set plus a harness that measures how well a *candidate*
cheap model agrees with reference labels, so "is model X good enough to route to?"
becomes a number instead of a hunch. It reuses the `---SCORE_SUMMARY---` contract
that every `*-eval.mjs` already emits (`SCORE` + `ARCHETYPE`), so there is no new
scoring surface.
## Labeling methodology (v1)
The metric is **agreement-with-reference, not absolute correctness.** For #1354's
purpose — *which cheap model can hold which task* — distance-to-reference is exactly
the right measure: if a candidate model reproduces the reference verdict, it is safe
to route that task to it.
**v1 labels are the frozen verdict of a reference (Claude-tier / premium) evaluation**
applied to each synthetic JD under the repo's own rubric (`modes/_shared.md` §
Scoring System + § Archetype Detection):
- **`archetype`** is the gate. It is a property of the JD alone (the 6-archetype
keyword taxonomy in `_shared.md`), so it is profile-independent and reproducible.
- **`score`** (15) is the reference model's fit verdict. It is profile-relative by
construction, but because both reference and candidate are scored under the *same*
conditions, distance-to-reference stays meaningful regardless of whose profile
drives it. It is a secondary, tolerance-banded signal — it does **not** gate.
The 10 cases cover all 6 archetypes and deliberately favor **edge archetypes over
easy wins**: four are hybrids/ambiguous (`platform-agentic-hybrid`,
`pm-architect-ambiguous`, `forward-deployed-vs-architect`, `transformation-vs-pm`)
where a cheaper model is most likely to diverge, and scores span 3.24.3 so the
tolerance band has real range to catch drift on red-flag postings.
**Future upgrade path:** hand-curated labels. Freezing the reference verdict is the
cheap, reproducible v1; a later pass can replace or reconcile individual labels with
human-graded ground truth (flip `provenance` to `hand-curated`) without changing the
harness.
## Layout
```
evals/
golden/ labeled cases — one JSON per case (synthetic JDs, no user data)
fixtures/ recorded candidate outputs for $0 deterministic replay in CI
README.md this file
eval-golden.mjs the harness (root level, sibling to openai-eval.mjs)
```
### Golden case format (`evals/golden/*.json`)
```json
{
"id": "ai-platform-llmops",
"synthetic": true,
"jd": "<full synthetic job description text>",
"label": { "archetype": "AI Platform / LLMOps", "score": 4.2, "provenance": "reference-frozen-v1" }
}
```
`provenance` records how the label was set (`reference-frozen-v1` today; future
hand-curated labels flip it). Edge cases may also carry an `edge_note` explaining
why the reference resolved an ambiguous JD the way it did. Both are advisory — the
harness only requires `archetype` (string) and `score` (number). All JDs are
**synthetic** so the set stays clear of the `no-user-data` guard.
### Fixture format (`evals/fixtures/<case-id>__<model>.txt`)
A recorded candidate-model output containing a `---SCORE_SUMMARY---` block. Only
that block is parsed; surrounding prose is illustrative and trimmed. Slash-form
provider ids (`deepseek/deepseek-chat`) are flattened to a path-safe token for the
filename (`<case>__deepseek-deepseek-chat.txt`), so a fixture never lands in a
phantom subdirectory.
## Running
```bash
npm run eval:golden -- --replay --model cheap-stub # offline, deterministic, $0
npm run eval:golden -- --live --model gpt-4o-mini # real call via openai-eval.mjs (needs key + cv.md)
```
Replay is the CI-friendly path: no API keys, no `cv.md`, fully deterministic.
The harness reports per-case archetype/score agreement, mean |Δscore|, median
latency (live only), and a placeholder $/run, then exits `0/1` on the archetype
agreement gate.
## Open design questions (TODO #1354)
Resolved in v1:
- **Reference labels** — frozen reference (Claude-tier) verdict; see **Labeling
methodology** above. Hand-curation is the documented future upgrade path.
- **Set size / spread** — grown from 2 to 10 cases across all 6 archetypes, favoring
edge/hybrid archetypes, scores spanning 3.24.3.
Still tunable (named constants, safe defaults today):
| Question | Where it lives | v1 default |
|----------|----------------|------------|
| `SCORE` agreement: tolerance band width | `SCORE_TOLERANCE` in `eval-golden.mjs` | ±0.5 (band, per distance-to-reference) |
| CI gate threshold for archetype agreement | `MIN_ARCHETYPE_AGREEMENT` in `eval-golden.mjs` | 0.8 |
| Per-model $/run rates | `COST_PER_RUN_USD` in `eval-golden.mjs` | empty — needs real provider rates |
Wiring this into the required CI job (`.github/workflows/test.yml`) is intentionally
deferred until the gate threshold is confirmed, so a default value can't make `main`
go red. The replay path is deterministic and $0, so it is ready to wire whenever the
threshold is signed off.