brainy/tests/integration/vector-recall.test.ts
David Snelling c605b34f98 test(8.0): A/B benchmark harness (open leg) — generic corpus+metrics lib, brainy-alone scaling bench, boundary guard, real-embedding recall guard
The open-core, Cortex-free half of the library A/B (handoff AJ/AK), authored once in
brainy so the proprietary A/B comparison can import it for both legs:

- tests/benchmarks/lib/corpus.js — deterministic clustered-mixture corpus generator
  (recompute-on-demand, O(clusters·dim) memory) for latency/ingest/memory at scale.
- tests/benchmarks/lib/metrics.js — percentiles, brute-force recall@k, RSS snapshot.
- tests/benchmarks/brainy-scale.js — brainy-alone scaling leg (ingest, find p50/p99 for
  vector/metadata/graph/triple, RSS). Recall is intentionally NOT measured on synthetic
  data — see below.
- tests/unit/boundary-no-cortex.test.ts — CI guard: fails if @soulcraft/cortex ever
  appears in a src/ or tests/ import or in any package.json dependency field.
- tests/integration/vector-recall.test.ts — semantic-search correctness on REAL
  embeddings (19-20/20 exact-text top-1).

Methodology note: synthetic vectors (random/one-hot/clustered/latent) are near-orthogonal
under cosine, so HNSW (any graph ANN, incl. DiskANN) cannot navigate them and recall
collapses regardless of engine — a property of the data, not the index. Brainy vector
search is verified correct on real embeddings. The A/B recall@10 column is therefore
measured on SIFT/BIGANN, identically for both legs.
2026-06-15 15:51:17 -07:00

52 lines
2.7 KiB
TypeScript

/**
* @module tests/integration/vector-recall
* @description Correctness guard for open-core semantic search on REAL
* embeddings (the production path): distinct documents embedded by Brainy's
* built-in model, queried by their own text, must return themselves as the top
* hit. This confirms the JS HNSW + cosine path retrieves correct neighbours on
* real embedding geometry.
*
* Why real embeddings and not a synthetic corpus: HNSW navigation depends on the
* smooth, locally-structured manifold that real embeddings occupy. Synthetic
* vectors (uniform-random, one-hot, or tight clusters) are near-orthogonal under
* cosine — concentration of measure leaves no gradient for greedy descent, so
* recall collapses regardless of the engine. That is a property of the data, not
* the index (verified: an exact query for the graph entry point returns it at
* cosine 1.0). The A/B's headline recall@10 column is therefore measured on
* SIFT/BIGANN (real data, canonical ground truth), identically for both legs.
*/
import { describe, it, expect, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType } from '../../src/types/graphTypes.js'
const TOPICS = [
'quantum physics particles', 'french cuisine recipes', 'basketball playoff games',
'jazz music history', 'volcanic rock formation', 'machine learning models',
'medieval castle architecture', 'tropical fish species', 'solar panel efficiency',
'ancient roman empire', 'coffee bean roasting', 'mountain climbing gear',
'classical piano sonatas', 'desert wildlife survival', 'space telescope images',
'wine fermentation process', 'bicycle frame materials', 'honeybee colony behavior',
'glacier ice formation', 'origami paper folding'
]
describe('open-core semantic search correctness (real embeddings)', () => {
const brains: Brainy[] = []
afterEach(async () => { for (const b of brains.splice(0)) await b.close() })
it('an exact-text query returns its own document as the top hit', async () => {
const brain = new Brainy({ storage: { type: 'memory' }, requireSubtype: false, silent: true })
await brain.init()
brains.push(brain)
for (let i = 0; i < TOPICS.length; i++) {
await brain.add({ data: TOPICS[i], type: NounType.Document, metadata: { idx: i } })
}
let hits = 0
for (let i = 0; i < TOPICS.length; i++) {
const got = await brain.find({ query: TOPICS[i], limit: 1 })
if ((got[0]?.metadata as { idx: number })?.idx === i) hits++
}
// Near-perfect; a couple of genuine semantic near-neighbours (e.g. glacier
// vs volcanic "rock/ice formation") may swap — allow a small margin.
expect(hits).toBeGreaterThanOrEqual(TOPICS.length - 3)
}, 240000)
})