Repository navigation
Two changes since v0.2.0: the index got much smaller and vector queries got much faster, and retrieval quality is now measurable by anyone rather than by scripts that lived on my machine.
Packed binary vectors (#20)
Measured the index bucket by bucket before touching it. Vectors were 80.6% of it, stored as JSON arrays of floats:
| bucket | keys | MB | share |
|---|---|---|---|
| vectors | 20,340 | 248.0 | 80.6% |
| terms | 13,273 | 37.5 | 12.2% |
| blobs | 20,340 | 11.5 | 3.7% |
| chunks | 20,340 | 10.5 | 3.4% |
Vectors are now packed little-endian float32 with a 5-byte header and an optional JSON metadata suffix. The encoding is lossless, so results are unchanged.
On a 20,340-vector index:
| before | after | |
|---|---|---|
| file size | 439 MB | 192.7 MB (−66.7%) |
| semantic query | 2.58s | 0.13s (~20x) |
| top-3 results | 0.77 / 0.75 / 0.74 | identical |
The latency win was the surprise: JSON decoding on every process start dominated, not the similarity search. Brute-force scan over 20k vectors is ~0.1s.
rag compact is new. It rewrites legacy JSON records in place and reclaims free pages - no re-embedding, no quality change. Legacy records stay readable, so existing indexes keep working untouched:
$ rag compact -d ./books
Rewrote 20340 vector records as packed binary
Index: 579.3 MB -> 192.7 MB (66.7% smaller)
Note the file grows during the rewrite before compaction reclaims it, so leave headroom.
rageval (#21)
Every recall and MRR figure quoted in the README came from throwaway scripts in a scratch directory, which made the measurement rule in CLAUDE.md unfollowable by anyone else. That harness is now a first-class Go command:
go run ./cmd/rageval -corpus /path/to/indexed -k 1014 questions, recall@10, MMR disabled
MODE RECALL MRR RANKS
lexical 6/14 0.201 [- 4 - - - - - - 5 9 1 - 4 1]
semantic 6/14 0.329 [- 1 - - - - - - 1 - 1 2 9 1]
hybrid 6/14 0.310 [- 1 - - - - - - 2 - 1 3 2 1]
Each question is paired with an anchor phrase that must appear in a correctly retrieved passage, so it measures whether the right passage was found rather than whether an answer looked plausible. -modes hyde includes HyDE, -v prints per-question ranks. The bundled questions.json covers A Song of Ice and Fire and is meant to be replaced per corpus.
It has already earned its place: it falsified a plausible-sounding local reranking idea in four minutes, before any of it shipped.
Known limitations
Unchanged from v0.2.0 and worth restating:
- recall@10 is 6/14. recall@200 is 10/14, so the passages are retrieved but ranked 32-141. Closing that needs a cross-encoder reranker, which means genuine query-passage interaction and a second API call. A bi-encoder cannot do it, however the input is sliced.
- The agentic loop's context-sufficiency judge is too lenient, so
--max-iters 2often costs a call without changing the answer. - The index is still ~19x the source text.
terms(JSON postings),chunks(a redundant token array) andblobs(uncompressed text) are ~48 MB of lossless savings not yet taken. - All quality numbers come from 14 questions on one prose corpus. The tool is code-first - AST chunking, symbol extraction, path boosting - and none of that is measured.
embedding.include_pathdefaults to true on the assumption that paths help code, tested only on prose, where it hurt.