mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-29 19:01:39 +00:00
Closes multiple Phase 2 items in one commit since they form a cohesive
package: query schema enforcement + new query tiers + per-query-set
statistical rigor.
Added:
eval/runner/queries/validator.ts — hand-rolled Query schema validator
eval/runner/queries/validator.test.ts — 24 unit tests, all pass
eval/runner/queries/tier5-fuzzy.ts — 30 hand-authored Tier 5 Fuzzy/Vibe queries
eval/runner/queries/tier5_5-synthetic.ts — 50 SYNTHETIC-labeled outsider-style queries (author: "synthetic-outsider-v1")
eval/runner/queries/index.ts — aggregator + validateAll()
Modified:
eval/runner/multi-adapter.ts — N=5 runs per adapter (BRAINBENCH_N override), page-order shuffle, mean±stddev reporting
Query validator (hand-rolled, no zod dep to match gbrain codebase style):
- Temporal verb regex enforces as_of_date (per eng pass 2 spec):
/\\b(is|was|were|current|now|at the time|during|as of|when did)\\b/i
- Validates tier enum, expected_output_type enum, gold shape per type
- gold.relevant must be non-empty slug[] for cited-source-pages queries
- abstention requires gold.expected_abstention === true
- externally-authored tier requires author field
- batch validation catches duplicate IDs
Tier 5 Fuzzy/Vibe (30 queries, hand-authored):
- Vague recall: "Someone who was a senior engineer at a biotech company..."
- Trait-based: "The engineer who pushed back on microservices"
- Cultural/epithet: "Who is known as a 'systems builder' in security?"
- Abstention bait: "Which Layer 1 project did the crypto guy leave?" (prose
mentions but never names; good systems abstain)
- Addresses Codex's circularity critique — vague queries where graph-heavy
systems shouldn't inherently win.
Tier 5.5 Synthetic Outsider (50 queries, AI-authored placeholder):
- Clearly labeled author: "synthetic-outsider-v1"
- Phrasing variety not in the 4 template families:
* fragment style ("crypto founder Goldman Sachs background")
* polite/natural ("Can you pull up what we have on...")
* comparison ("What is the difference between X and Y?")
* follow-up ("And who else advises Orbit Labs?")
* typos/misspellings ("adam lopez bioinformatcis")
* similarity ("Find me someone like Alice Davis...")
* imperative ("Pull up Alice Davis")
- Real Tier 5.5 from outside researchers supersedes synthetic via
PRs to eval/external-authors/ (docs ship in follow-up commit).
N=5 tolerance bands:
- Default N=5, override via BRAINBENCH_N env var (e.g. BRAINBENCH_N=1 for dev loops)
- Per-run seeded Fisher-Yates shuffle of page ingest order (LCG seed = run_idx+1)
- Surfaces order-dependent adapter bugs (tie-break-by-first-seen etc.)
- Reports mean ± sample-stddev per metric
- "stddev = 0" is honest signal that the adapter is deterministic, not a bug.
LLM-judge metrics (future) will naturally produce non-zero stddev.
Validation: all 80 Tier 5 + 5.5 queries pass validateAll(). 24 validator
unit tests pass.
Next commit: world.html contributor explorer (Phase 3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
210 lines
7.0 KiB
TypeScript
210 lines
7.0 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { validateQuery, validateQuerySet, TEMPORAL_VERBS } from './validator.ts';
|
|
import type { Query } from '../types.ts';
|
|
|
|
function mkValidQuery(overrides: Partial<Query> = {}): Query {
|
|
return {
|
|
id: 'q-0001',
|
|
tier: 'easy',
|
|
text: 'Who founded Acme?',
|
|
expected_output_type: 'cited-source-pages',
|
|
gold: { relevant: ['people/alice-chen'] },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('validateQuery — required fields', () => {
|
|
test('valid query passes', () => {
|
|
const r = validateQuery(mkValidQuery());
|
|
expect(r.ok).toBe(true);
|
|
expect(r.issues.length).toBe(0);
|
|
});
|
|
|
|
test('missing id fails', () => {
|
|
const r = validateQuery(mkValidQuery({ id: '' }));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'id')).toBe(true);
|
|
});
|
|
|
|
test('missing text fails', () => {
|
|
const r = validateQuery(mkValidQuery({ text: '' }));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'text')).toBe(true);
|
|
});
|
|
|
|
test('invalid tier fails', () => {
|
|
const r = validateQuery(mkValidQuery({ tier: 'invalid' as unknown as 'easy' }));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'tier')).toBe(true);
|
|
});
|
|
|
|
test('invalid expected_output_type fails', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
expected_output_type: 'nonsense' as unknown as 'answer-string',
|
|
}));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'expected_output_type')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validateQuery — temporal as_of_date rule', () => {
|
|
test('non-temporal query without as_of_date passes', () => {
|
|
const r = validateQuery(mkValidQuery({ text: 'Who founded Acme?' }));
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
test('"is" verb without as_of_date fails', () => {
|
|
const r = validateQuery(mkValidQuery({ text: 'Where is Sarah working?' }));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'as_of_date')).toBe(true);
|
|
});
|
|
|
|
test('"was" verb without as_of_date fails', () => {
|
|
const r = validateQuery(mkValidQuery({ text: 'Who was at the meeting?' }));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'as_of_date')).toBe(true);
|
|
});
|
|
|
|
test('"as of" verb without as_of_date fails', () => {
|
|
const r = validateQuery(mkValidQuery({ text: 'As of 2024, who invested?' }));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'as_of_date')).toBe(true);
|
|
});
|
|
|
|
test('temporal query with "corpus-end" passes', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
text: 'Who is the CEO now?',
|
|
as_of_date: 'corpus-end',
|
|
}));
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
test('temporal query with "per-source" passes', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
text: 'Who was at the meeting?',
|
|
as_of_date: 'per-source',
|
|
}));
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
test('temporal query with ISO-8601 date passes', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
text: 'Who was at Acme in 2023?',
|
|
as_of_date: '2023-01-01',
|
|
}));
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
test('temporal query with bogus as_of_date fails', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
text: 'Who is the CEO now?',
|
|
as_of_date: 'yesterday',
|
|
}));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'as_of_date')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validateQuery — gold shape by expected_output_type', () => {
|
|
test('cited-source-pages requires gold.relevant array', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
expected_output_type: 'cited-source-pages',
|
|
gold: {},
|
|
}));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'gold.relevant')).toBe(true);
|
|
});
|
|
|
|
test('cited-source-pages with malformed slug fails', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
gold: { relevant: ['not-a-slug'] },
|
|
}));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'gold.relevant')).toBe(true);
|
|
});
|
|
|
|
test('abstention requires expected_abstention=true', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
expected_output_type: 'abstention',
|
|
gold: {},
|
|
}));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'gold.expected_abstention')).toBe(true);
|
|
});
|
|
|
|
test('abstention with expected_abstention=true passes', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
expected_output_type: 'abstention',
|
|
gold: { expected_abstention: true },
|
|
}));
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validateQuery — tier 5.5 author requirement', () => {
|
|
test('externally-authored without author fails', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
tier: 'externally-authored',
|
|
}));
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.field === 'author')).toBe(true);
|
|
});
|
|
|
|
test('externally-authored with author passes', () => {
|
|
const r = validateQuery(mkValidQuery({
|
|
tier: 'externally-authored',
|
|
author: 'synthetic-outsider-v1',
|
|
}));
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validateQuerySet — batch level', () => {
|
|
test('duplicate ids fail', () => {
|
|
const r = validateQuerySet([
|
|
mkValidQuery({ id: 'q-0001' }),
|
|
mkValidQuery({ id: 'q-0001' }),
|
|
]);
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.reason === 'duplicate id in batch')).toBe(true);
|
|
});
|
|
|
|
test('unique ids pass', () => {
|
|
const r = validateQuerySet([
|
|
mkValidQuery({ id: 'q-0001' }),
|
|
mkValidQuery({ id: 'q-0002' }),
|
|
]);
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
test('issues from multiple queries aggregated', () => {
|
|
const r = validateQuerySet([
|
|
mkValidQuery({ id: 'q-0001', text: '' }), // missing text
|
|
mkValidQuery({ id: 'q-0002', text: 'Who is CEO now?' }), // missing as_of_date
|
|
]);
|
|
expect(r.ok).toBe(false);
|
|
expect(r.issues.some(i => i.queryId === 'q-0001' && i.field === 'text')).toBe(true);
|
|
expect(r.issues.some(i => i.queryId === 'q-0002' && i.field === 'as_of_date')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('TEMPORAL_VERBS regex', () => {
|
|
test('matches common temporal verbs', () => {
|
|
expect(TEMPORAL_VERBS.test('Where is Sarah?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('Where was Sarah?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('Who were the founders?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('What was the current valuation?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('Where is she now?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('At the time, who led the round?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('During Q1, which deals closed?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('As of 2024, who works at Acme?')).toBe(true);
|
|
expect(TEMPORAL_VERBS.test('When did Alice join?')).toBe(true);
|
|
});
|
|
|
|
test('does not match non-temporal verbs', () => {
|
|
expect(TEMPORAL_VERBS.test('Who founded Acme?')).toBe(false);
|
|
expect(TEMPORAL_VERBS.test('Which investors backed Beta?')).toBe(false);
|
|
expect(TEMPORAL_VERBS.test('List the advisors at Gamma.')).toBe(false);
|
|
});
|
|
});
|