mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
docs(eval): Phase 3 contributor docs + CI workflow for eval/ tests
Ships the contributor-onboarding surface promised in the plan. With this
commit, external researchers have a self-serve path from clone to PR in
under 5 minutes.
Added:
eval/README.md — 5-minute quickstart,
directory map, methodology
one-pager, adapter scorecard
eval/CONTRIBUTING.md — three contributor paths:
1. Write Tier 5.5 queries
2. Submit an external adapter
3. Reproduce a scorecard
eval/RUNBOOK.md — operational troubleshooting:
generation failures, runner
failures, query validation,
world.html rendering, CI
eval/CREDITS.md — contributor attribution
(synthetic-outsider-v1 labeled
as placeholder; real submissions
land here)
.github/PULL_REQUEST_TEMPLATE/tier5-queries.md — structured PR template
for Tier 5.5 submissions
.github/workflows/eval-tests.yml — CI: validates queries,
runs all eval unit tests,
renders world.html on every PR
touching eval/** or
src/core/link-extraction.ts
CI scope (intentionally narrow):
- Triggers on paths: eval/**, src/core/link-extraction.ts, src/core/search/**
- Runs: bun run eval:query:validate (80 queries), test:eval (57 tests),
eval:world:render (smoke-test the HTML renderer)
- Pinned actions by commit SHA (matches existing .github/workflows/test.yml)
- Zero API calls — all Opus/OpenAI paths stubbed or skipped in unit tests
- Fast: ~30s total wall clock
Contributor TTHW (clone → first merged PR):
- Path 1 (Tier 5.5 queries): ~5 min
- Path 2 (external adapter): ~30 min for a simple adapter
- Path 3 (reproduce scorecard): ~15 min wall clock (N=5 run)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
f0649e2f32
commit
b81373d4c2
@@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
Tier 5.5 Externally-Authored Query Submission template
|
||||||
|
See eval/CONTRIBUTING.md for the full workflow.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Submitting **N** Tier 5.5 queries for BrainBench.
|
||||||
|
|
||||||
|
- Author handle: `@your-handle`
|
||||||
|
- File location: `eval/external-authors/your-handle/queries.json`
|
||||||
|
- Queries authored fresh (not copy-pasted from a model output)
|
||||||
|
- Slugs verified against `eval/data/world-v1/` (via `bun run eval:world:view`)
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] `bun run eval:query:validate eval/external-authors/your-handle/queries.json` passes
|
||||||
|
- [ ] At least 20 queries
|
||||||
|
- [ ] Each query has either `gold.relevant` (with real slugs) or `gold.expected_abstention: true`
|
||||||
|
- [ ] Temporal queries have `as_of_date` set (`corpus-end` | `per-source` | ISO-8601)
|
||||||
|
- [ ] Phrasing is varied (not all the same template)
|
||||||
|
- [ ] `author` field matches my handle
|
||||||
|
|
||||||
|
## Phrasing variety (optional self-audit)
|
||||||
|
|
||||||
|
Tick the styles represented in your batch:
|
||||||
|
|
||||||
|
- [ ] Full sentence questions
|
||||||
|
- [ ] Fragment-style ("crypto founder Goldman Sachs background")
|
||||||
|
- [ ] Comparison ("X vs Y")
|
||||||
|
- [ ] Follow-up ("And who else...")
|
||||||
|
- [ ] Imperative ("Pull up Alice Davis")
|
||||||
|
- [ ] Trait-based ("the demanding engineering leader")
|
||||||
|
- [ ] Abstention bait (answer is "not in corpus")
|
||||||
|
|
||||||
|
## Notes to reviewer
|
||||||
|
|
||||||
|
Anything worth flagging — ambiguous cases, corpus gaps you found, specific
|
||||||
|
phrasings you were uncertain about.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: Eval tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- 'eval/**'
|
||||||
|
- 'src/core/link-extraction.ts'
|
||||||
|
- 'src/core/search/**'
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- 'eval/**'
|
||||||
|
- 'src/core/link-extraction.ts'
|
||||||
|
- 'src/core/search/**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
eval-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
- run: bun install
|
||||||
|
|
||||||
|
# Validate the built-in Tier 5 + 5.5 query set.
|
||||||
|
- name: Validate built-in queries
|
||||||
|
run: bun run eval:query:validate
|
||||||
|
|
||||||
|
# Pure-function unit tests — zero API calls, fast.
|
||||||
|
- name: Run eval unit tests
|
||||||
|
run: bun run test:eval
|
||||||
|
|
||||||
|
# Smoke-test the world.html renderer against the committed corpus.
|
||||||
|
- name: Render world.html
|
||||||
|
run: bun run eval:world:render
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# Contributing to BrainBench
|
||||||
|
|
||||||
|
Three contribution paths. Each has a separate workflow.
|
||||||
|
|
||||||
|
## 1. Write Tier 5.5 externally-authored queries
|
||||||
|
|
||||||
|
Tier 5.5 exists to neutralize the "gbrain wrote its own exam" critique. The
|
||||||
|
queries currently in the repo are AI-authored synthetic placeholders; real
|
||||||
|
outside researcher submissions supersede them.
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Step 1. Understand the canonical world.
|
||||||
|
bun run eval:world:view
|
||||||
|
# Browser opens. Click through entities. Note down what's real.
|
||||||
|
|
||||||
|
# Step 2. Scaffold a query.
|
||||||
|
bun run eval:query:new --tier externally-authored --author "@your-handle"
|
||||||
|
# Prints a Query template. Save to a file.
|
||||||
|
|
||||||
|
# Step 3. Edit the template.
|
||||||
|
# - Replace text with your actual question
|
||||||
|
# - Replace gold.relevant with slug(s) that actually exist
|
||||||
|
# - If the query has temporal verbs (is/was/were/now/...), set as_of_date
|
||||||
|
# to "corpus-end", "per-source", or ISO-8601
|
||||||
|
# - Fill in tags
|
||||||
|
|
||||||
|
# Step 4. Validate before submitting.
|
||||||
|
bun run eval:query:validate path/to/your-queries.json
|
||||||
|
|
||||||
|
# Step 5. Submit a PR.
|
||||||
|
# File location: eval/external-authors/<your-handle>/queries.json
|
||||||
|
# PR template: .github/PULL_REQUEST_TEMPLATE/tier5-queries.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query-authoring guidelines
|
||||||
|
|
||||||
|
- **Write like you'd naturally ask.** Don't adapt your voice to an "AI
|
||||||
|
benchmark style." Fragments, typos, comparisons, follow-ups, imperatives
|
||||||
|
— all welcome. Variety is the value.
|
||||||
|
- **Gold must be real slugs.** Every slug in `gold.relevant` must exist in
|
||||||
|
`eval/data/world-v1/`. The validator checks format; you verify existence.
|
||||||
|
- **Abstention is a valid answer.** If your query has no answer in the
|
||||||
|
corpus (e.g. you're asking about someone who isn't there), set
|
||||||
|
`expected_output_type: 'abstention'` and `gold.expected_abstention: true`.
|
||||||
|
- **Temporal queries need `as_of_date`.** The validator will reject
|
||||||
|
"Where is Sarah now?" without it. Use `"corpus-end"` for "as of the most
|
||||||
|
recent data," `"per-source"` for "whatever the cited source says," or a
|
||||||
|
specific ISO date.
|
||||||
|
- **Partial answers are OK** if you flag them via `known_failure_modes`.
|
||||||
|
|
||||||
|
### Query quality bar
|
||||||
|
|
||||||
|
We'll merge your PR if:
|
||||||
|
- `bun run eval:query:validate` passes
|
||||||
|
- Slugs resolve to real entities
|
||||||
|
- At least 20 queries (one batch)
|
||||||
|
- Queries have genuine phrasing variety
|
||||||
|
|
||||||
|
## 2. Submit an external adapter
|
||||||
|
|
||||||
|
The `Adapter` interface is `eval/runner/types.ts`. Three methods:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Adapter {
|
||||||
|
readonly name: string;
|
||||||
|
init(rawPages: Page[], config: AdapterConfig): Promise<BrainState>;
|
||||||
|
query(q: Query, state: BrainState): Promise<RankedDoc[]>;
|
||||||
|
snapshot?(state: BrainState): Promise<string>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Step 1. Create your adapter file.
|
||||||
|
# eval/runner/adapters/my-adapter.ts
|
||||||
|
|
||||||
|
# Step 2. Write it.
|
||||||
|
# - import types from '../types.ts'
|
||||||
|
# - export class MyAdapter implements Adapter { ... }
|
||||||
|
# - BrainState is opaque to the runner. Internal shape is yours.
|
||||||
|
# - `rawPages: Page[]` is all you get. Never read from gold/ — the
|
||||||
|
# runner doesn't give you that path on purpose.
|
||||||
|
|
||||||
|
# Step 3. Write a unit test.
|
||||||
|
# eval/runner/adapters/my-adapter.test.ts
|
||||||
|
# Cover at minimum: init, query, deterministic tie-break.
|
||||||
|
|
||||||
|
# Step 4. Wire into multi-adapter.ts.
|
||||||
|
# import { MyAdapter } from './adapters/my-adapter.ts';
|
||||||
|
# const allAdapters: Adapter[] = [
|
||||||
|
# ...existing,
|
||||||
|
# new MyAdapter(),
|
||||||
|
# ];
|
||||||
|
|
||||||
|
# Step 5. Test locally.
|
||||||
|
bun run test:eval
|
||||||
|
bun run eval:run:dev --adapter=my-adapter
|
||||||
|
|
||||||
|
# Step 6. Open a PR.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adapter quality bar
|
||||||
|
|
||||||
|
- Deterministic over sorted input (stddev=0 across N=5 runs is the
|
||||||
|
expected default; non-zero is a signal worth understanding)
|
||||||
|
- `query()` returns rank order — `rank: i + 1`, 1-based, no duplicates
|
||||||
|
- Tie-breaks documented (e.g. "alphabetical by slug when scores tie")
|
||||||
|
- No network calls in unit tests (mock any API dependencies)
|
||||||
|
- Pass `bun run test:eval`
|
||||||
|
|
||||||
|
## 3. Reproduce / verify a published scorecard
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Step 1. Check the scorecard's commit hash.
|
||||||
|
# Reports in docs/benchmarks/ include the gbrain version + commit.
|
||||||
|
|
||||||
|
# Step 2. Pin the same commit.
|
||||||
|
git checkout <commit-sha>
|
||||||
|
|
||||||
|
# Step 3. Run the full benchmark.
|
||||||
|
bun run eval:run
|
||||||
|
|
||||||
|
# Step 4. Compare to the published scorecard.
|
||||||
|
# For deterministic adapters, numbers should match exactly.
|
||||||
|
# For embedding-based adapters, numbers should land within the published
|
||||||
|
# tolerance bands (mean ± stddev).
|
||||||
|
```
|
||||||
|
|
||||||
|
If your numbers drift outside tolerance, file an issue with:
|
||||||
|
- Your `bun --version`
|
||||||
|
- Your `uname -sr`
|
||||||
|
- Your OpenAI model ID (for embedding-model drift)
|
||||||
|
- A diff of the scorecard
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
|
||||||
|
- Match existing gbrain patterns (hand-rolled where appropriate, no new
|
||||||
|
deps unless genuinely needed)
|
||||||
|
- Bun's built-in test runner (`bun:test`), not jest/vitest
|
||||||
|
- No em dashes in prose (`—`, `–`); use parentheses or sentences
|
||||||
|
- Commit messages: `feat(eval):`, `fix(eval):`, `docs(eval):`, `test(eval):`
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
See `eval/CREDITS.md` for the full list. All Tier 5.5 external-author
|
||||||
|
submissions credited there + in the scorecard. Synthetic placeholders are
|
||||||
|
labeled `synthetic-outsider-v1`.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# BrainBench credits
|
||||||
|
|
||||||
|
## Core team
|
||||||
|
|
||||||
|
- **garrytan** — BrainBench v1 + v1.1 architecture, adapter interface,
|
||||||
|
extraction regex residuals (v0.10.5), multi-axis type-accuracy runner
|
||||||
|
- **Claude Opus 4.7** — pair programming, test coverage, documentation
|
||||||
|
|
||||||
|
## External query authors (Tier 5.5)
|
||||||
|
|
||||||
|
No human external authors yet. The Tier 5.5 query set currently comprises
|
||||||
|
50 synthetic queries labeled `author: "synthetic-outsider-v1"` as a
|
||||||
|
placeholder. Real submissions via `eval/external-authors/<handle>/queries.json`
|
||||||
|
PRs supersede synthetic entries.
|
||||||
|
|
||||||
|
**Want to be credited here?** See `eval/CONTRIBUTING.md`.
|
||||||
|
|
||||||
|
## External adapters
|
||||||
|
|
||||||
|
No third-party adapters yet. The shipping adapter set:
|
||||||
|
|
||||||
|
- `gbrain-after` — gbrain v0.10.3+ (internal; the system under test)
|
||||||
|
- `hybrid-nograph` — gbrain hybrid search with graph layer disabled
|
||||||
|
(internal comparator; closest apples-to-apples to `gbrain-after`)
|
||||||
|
- `ripgrep-bm25` — classic IR baseline built in an afternoon
|
||||||
|
- `vector-only` — commodity vector RAG, same embedder as gbrain
|
||||||
|
|
||||||
|
Third-party submissions (mem0, supermemory, Letta, Cognee, etc.) via
|
||||||
|
`eval/runner/adapters/<adapter>.ts` PRs. See `eval/CONTRIBUTING.md` for
|
||||||
|
the adapter interface and submission flow.
|
||||||
|
|
||||||
|
## Data
|
||||||
|
|
||||||
|
- Corpus generator: Claude Opus
|
||||||
|
- Canonical world: `eval/data/world-v1/` (committed, 240 entities)
|
||||||
|
- Generation cost: ~$3.14 USD (one-time)
|
||||||
|
|
||||||
|
## Inspiration
|
||||||
|
|
||||||
|
- **SWE-bench** — taught us that a benchmark's credibility comes from real
|
||||||
|
baselines, not from the authoring team saying nice things about their
|
||||||
|
own stack
|
||||||
|
- **Codex** — cold-read critique that "this isn't a standard, it's an
|
||||||
|
internal test" drove the Phase 2 external-baselines work that became
|
||||||
|
the headline of this PR
|
||||||
|
- **MTEB** — embedding-model reproducibility card pattern; we copy the
|
||||||
|
"pin every version in every scorecard" discipline
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
# BrainBench
|
||||||
|
|
||||||
|
Public benchmark for personal knowledge brain agent stacks. Ships 4 adapter
|
||||||
|
configurations scored side-by-side on a 240-page rich-prose fictional corpus
|
||||||
|
(`twin-amara`). Measures retrieval, extraction quality, and per-link-type
|
||||||
|
accuracy.
|
||||||
|
|
||||||
|
**What this answers:** "Does the knowledge graph layer do useful work, or is
|
||||||
|
gbrain just a thin wrapper over vector+keyword hybrid?" Headline: gbrain
|
||||||
|
beats the closest external baseline (hybrid-without-graph, same embedder,
|
||||||
|
same chunking) by **+31 points P@5**. The graph layer is load-bearing.
|
||||||
|
|
||||||
|
## 5-minute quickstart
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Run the full benchmark (4 adapters × 5 runs, ~15 min wall clock)
|
||||||
|
bun run eval:run
|
||||||
|
|
||||||
|
# 2. Fast iteration (N=1 single run)
|
||||||
|
bun run eval:run:dev
|
||||||
|
|
||||||
|
# 3. Just the type-accuracy report
|
||||||
|
bun run eval:type-accuracy
|
||||||
|
|
||||||
|
# 4. Explore the canonical world (contributor-facing UI)
|
||||||
|
bun run eval:world:view
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's in the box
|
||||||
|
|
||||||
|
```
|
||||||
|
eval/
|
||||||
|
├── data/world-v1/ Canonical world (committed). 240 sharded JSON files.
|
||||||
|
│ One file per entity + _ledger.json metadata.
|
||||||
|
├── generators/
|
||||||
|
│ ├── gen.ts Opus-backed corpus generator (run once; output cached)
|
||||||
|
│ ├── world.ts World-schema scaffolder
|
||||||
|
│ └── world-html.ts World explorer HTML renderer (XSS-safe)
|
||||||
|
├── runner/
|
||||||
|
│ ├── multi-adapter.ts 4-adapter side-by-side scorer (N=5)
|
||||||
|
│ ├── type-accuracy.ts Per-link-type accuracy vs gold from _facts
|
||||||
|
│ ├── before-after.ts Original v1 BEFORE/AFTER retrieval run
|
||||||
|
│ ├── types.ts Adapter, Page, Query, RankedDoc interfaces
|
||||||
|
│ ├── adapters/
|
||||||
|
│ │ ├── ripgrep-bm25.ts EXT-1: classic IR baseline (BM25 over grep hits)
|
||||||
|
│ │ ├── vector-only.ts EXT-2: pure cosine similarity, same embedder
|
||||||
|
│ │ └── hybrid-nograph.ts EXT-3: gbrain hybrid with graph disabled
|
||||||
|
│ └── queries/
|
||||||
|
│ ├── tier5-fuzzy.ts 30 vague-recall queries (hand-authored)
|
||||||
|
│ ├── tier5_5-synthetic.ts 50 synthetic outsider queries (AI-authored, labeled)
|
||||||
|
│ ├── validator.ts Query schema enforcement (temporal as_of_date rule)
|
||||||
|
│ └── index.ts Aggregator + validateAll()
|
||||||
|
├── cli/
|
||||||
|
│ ├── world-view.ts Render + open world.html
|
||||||
|
│ ├── query-validate.ts Validate a Query[] file
|
||||||
|
│ └── query-new.ts Scaffold a Query template
|
||||||
|
└── reports/ Benchmark scorecards (gitignored)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Three contributor paths
|
||||||
|
|
||||||
|
### Path 1: Reproduce a published scorecard
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Check out the specific gbrain commit referenced in the scorecard
|
||||||
|
git checkout <commit-sha>
|
||||||
|
# 2. Run the full benchmark
|
||||||
|
bun run eval:run
|
||||||
|
# 3. Compare your numbers to the scorecard. Deterministic adapters should
|
||||||
|
# match exactly. Embedding-based adapters should land within tolerance bands.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path 2: Submit a new external adapter
|
||||||
|
|
||||||
|
See `CONTRIBUTING.md` for the adapter submission flow. Short version:
|
||||||
|
|
||||||
|
1. Implement `eval/runner/adapters/<your-adapter>.ts` conforming to the
|
||||||
|
`Adapter` interface in `eval/runner/types.ts`.
|
||||||
|
2. Add a unit test file alongside.
|
||||||
|
3. Wire your adapter into `eval/runner/multi-adapter.ts` (one line).
|
||||||
|
4. `bun run eval:run:dev` to verify.
|
||||||
|
5. Open a PR.
|
||||||
|
|
||||||
|
### Path 3: Write Tier 5.5 externally-authored queries
|
||||||
|
|
||||||
|
The T5.5 queries currently in the repo are AI-authored (`author:
|
||||||
|
"synthetic-outsider-v1"`) as a placeholder. Real outside researchers should:
|
||||||
|
|
||||||
|
1. `bun run eval:world:view` to understand the canonical world
|
||||||
|
2. `bun run eval:query:new --tier externally-authored --author "@your-handle"`
|
||||||
|
3. Edit the scaffolded template with a real query + gold slugs
|
||||||
|
4. `bun run eval:query:validate path/to/your.json`
|
||||||
|
5. Submit via `eval/external-authors/<your-handle>/queries.json` in a PR
|
||||||
|
|
||||||
|
See `CONTRIBUTING.md` for the query-submission template.
|
||||||
|
|
||||||
|
## Methodology one-pager
|
||||||
|
|
||||||
|
- **Corpus:** 240 Opus-generated fictional biographical pages. Fixed,
|
||||||
|
committed, zero private data. Reproducibility baseline for any run.
|
||||||
|
- **Gold:** Each page's `_facts` metadata defines canonical relationships.
|
||||||
|
The scorer never shows `_facts` to the adapters — **raw pages only**
|
||||||
|
cross the ingestion boundary (structural enforcement in `Adapter.init`).
|
||||||
|
- **Metrics:** P@5 and R@5 on relational queries (145 canonical from
|
||||||
|
`_facts`, 80 tier-5 + tier-5.5). Type accuracy on extracted edges
|
||||||
|
(`eval/runner/type-accuracy.ts`).
|
||||||
|
- **N=5 runs per adapter** with page-order shuffle (seeded LCG; runs are
|
||||||
|
reproducible). Stddev surfaces order-dependent adapter bugs. Deterministic
|
||||||
|
adapters correctly show stddev=0.
|
||||||
|
- **Temporal queries** require explicit `as_of_date` (validated at query
|
||||||
|
authoring time; rejected at load if a temporal verb is present without it).
|
||||||
|
|
||||||
|
## Adapter scorecard (most recent, N=5)
|
||||||
|
|
||||||
|
See `docs/benchmarks/2026-04-18-brainbench-v1.md` for the full report.
|
||||||
|
Quick summary from `bun run eval:run`:
|
||||||
|
|
||||||
|
| Adapter | P@5 | R@5 |
|
||||||
|
|-----------------|--------|--------|
|
||||||
|
| gbrain-after | 49.1% | 97.9% |
|
||||||
|
| hybrid-nograph | 17.8% | 65.1% |
|
||||||
|
| ripgrep-bm25 | 17.1% | 62.4% |
|
||||||
|
| vector-only | 10.8% | 40.7% |
|
||||||
|
|
||||||
|
The graph layer beats vector+keyword hybrid on relational queries by ~31
|
||||||
|
points; hybrid-without-graph barely edges BM25. That's the story.
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
# BrainBench runbook
|
||||||
|
|
||||||
|
Operational troubleshooting for the most common failures. One fix per entry.
|
||||||
|
|
||||||
|
## Generation failures
|
||||||
|
|
||||||
|
### "OPENAI_API_KEY environment variable is missing"
|
||||||
|
|
||||||
|
The embedding adapter (`vector-only`) and any run of `eval/generators/gen.ts`
|
||||||
|
calls the OpenAI API. You need an API key.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export OPENAI_API_KEY=sk-proj-...
|
||||||
|
# or source from a dotenv file
|
||||||
|
source ~/.zshrc # if the key is in your shell profile
|
||||||
|
bun run eval:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### "ANTHROPIC_API_KEY environment variable is missing"
|
||||||
|
|
||||||
|
Only needed if you regenerate the corpus (`eval/generators/gen.ts`). If
|
||||||
|
you're using the committed `eval/data/world-v1/` shards, you don't need it.
|
||||||
|
|
||||||
|
### `bun install` fails with "Cannot find package 'openai'"
|
||||||
|
|
||||||
|
The `openai` package is in `package.json` dependencies. Run `bun install`
|
||||||
|
to fetch it. This shouldn't happen post-clone if you followed the normal
|
||||||
|
setup; see CLAUDE.md troubleshooting.
|
||||||
|
|
||||||
|
## Runner failures
|
||||||
|
|
||||||
|
### `multi-adapter.ts` times out on hybrid-nograph
|
||||||
|
|
||||||
|
hybrid-nograph embeds all 240 pages per run (via `importFromContent`). At
|
||||||
|
N=5, that's 5 re-embeddings. Typical wall clock: ~10 minutes.
|
||||||
|
|
||||||
|
If you're iterating, use the dev mode:
|
||||||
|
```sh
|
||||||
|
BRAINBENCH_N=1 bun run eval:run:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Or skip embedding-based adapters for focused runs:
|
||||||
|
```sh
|
||||||
|
bun run eval:run -- --adapter=gbrain-after
|
||||||
|
bun run eval:run -- --adapter=ripgrep-bm25
|
||||||
|
```
|
||||||
|
|
||||||
|
### "hybrid-nograph returned P@5 0.0%"
|
||||||
|
|
||||||
|
Likely the adapter is calling `hybridSearch()` on an engine that doesn't
|
||||||
|
have chunks/embeddings populated. This shouldn't happen with current code
|
||||||
|
— `importFromContent` populates them. If it does happen:
|
||||||
|
|
||||||
|
1. Check the adapter uses `importFromContent(engine, slug, content)`,
|
||||||
|
not bare `engine.putPage(...)`. The latter skips chunking.
|
||||||
|
2. Check `auto_link` is OFF (the adapter sets it, but if someone edits
|
||||||
|
the engine's default, verify).
|
||||||
|
|
||||||
|
### "ripgrep-bm25 crashes on a query"
|
||||||
|
|
||||||
|
The adapter has no query-size ceiling by design. If a specific query crashes,
|
||||||
|
run it in isolation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Drop other adapters temporarily and bisect the query list.
|
||||||
|
bun run eval:run -- --adapter=ripgrep-bm25
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query validation failures
|
||||||
|
|
||||||
|
### `validateAll()` fails with "temporal verb detected; as_of_date required"
|
||||||
|
|
||||||
|
The query text matches the temporal verb regex. Pick one:
|
||||||
|
|
||||||
|
1. **The query is actually temporal.** Add `as_of_date: 'corpus-end' |
|
||||||
|
'per-source' | '2024-01-15'` (ISO-8601).
|
||||||
|
2. **The query isn't really temporal.** Rephrase to avoid the trigger verb.
|
||||||
|
"Where is Sarah working?" → "Sarah's current employer" (adjective-form
|
||||||
|
doesn't trigger).
|
||||||
|
3. **Edge case bug in the regex.** File an issue; the regex lives at
|
||||||
|
`eval/runner/queries/validator.ts:TEMPORAL_VERBS`.
|
||||||
|
|
||||||
|
### `validateAll()` fails with "slug does not match 'dir/slug' format"
|
||||||
|
|
||||||
|
Gold slugs must be `dir/slug` — e.g. `people/alice-chen`, not just
|
||||||
|
`alice-chen` or `people/Alice Chen`. Lowercase, hyphens, no spaces.
|
||||||
|
|
||||||
|
### `validateAll()` fails with "duplicate id in batch"
|
||||||
|
|
||||||
|
Two queries share an `id`. Renumber. Convention:
|
||||||
|
- Tier 5 (fuzzy): `q5-NNNN`
|
||||||
|
- Tier 5.5 (externally-authored): `q55-NNNN`
|
||||||
|
- Scaffolder default: `q-<timestamp-suffix>` (via `eval:query:new`)
|
||||||
|
|
||||||
|
## World.html rendering
|
||||||
|
|
||||||
|
### "world.html doesn't open automatically"
|
||||||
|
|
||||||
|
`eval:world:view` tries `open` (macOS), `xdg-open` (Linux), `start`
|
||||||
|
(Windows). If none work:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun run eval:world:render # generate only
|
||||||
|
# then open manually in your browser
|
||||||
|
open eval/data/world-v1/world.html # or xdg-open, start, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
### "world.html looks weird / broken"
|
||||||
|
|
||||||
|
Regenerate from scratch — shard files might have drifted since last render:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rm eval/data/world-v1/world.html
|
||||||
|
bun run eval:world:view
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I see unescaped HTML in world.html"
|
||||||
|
|
||||||
|
That's a security regression. Open an issue IMMEDIATELY with the specific
|
||||||
|
entity slug. Every string should route through `escapeHtml()` in
|
||||||
|
`eval/generators/world-html.ts`.
|
||||||
|
|
||||||
|
## Dataset regeneration (advanced)
|
||||||
|
|
||||||
|
Don't regenerate unless you know why. The committed corpus is the stable
|
||||||
|
baseline everyone benchmarks against. Regenerating produces a DIFFERENT
|
||||||
|
dataset (Opus isn't byte-deterministic), which becomes a new version.
|
||||||
|
|
||||||
|
If you need to regenerate (e.g. for a v1.2 dataset):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Clean slate
|
||||||
|
rm -rf eval/data/world-v1
|
||||||
|
# Regenerate (~$3 Opus cost, 30 min)
|
||||||
|
bun eval/generators/gen.ts --max 240 --concurrency 6
|
||||||
|
# Validate
|
||||||
|
bun run eval:type-accuracy
|
||||||
|
```
|
||||||
|
|
||||||
|
The new dataset should be committed as `eval/data/world-vX.Y/` with a
|
||||||
|
new ledger. Don't overwrite `world-v1/` — that's the reproducibility baseline.
|
||||||
|
|
||||||
|
## CI failures
|
||||||
|
|
||||||
|
### `bun run test:eval` fails on a fresh checkout
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun install # fetch openai (+ deps)
|
||||||
|
bun run test:eval # retry
|
||||||
|
```
|
||||||
|
|
||||||
|
If tests still fail, bisect:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun test eval/runner/queries/validator.test.ts # pure functions
|
||||||
|
bun test eval/runner/adapters/ripgrep-bm25.test.ts # pure functions
|
||||||
|
bun test eval/runner/adapters/vector-only.test.ts # pure functions (cosine math only)
|
||||||
|
bun test eval/generators/world-html.test.ts # HTML rendering + XSS
|
||||||
|
```
|
||||||
|
|
||||||
|
One of these should fail deterministically — report it.
|
||||||
Reference in New Issue
Block a user