Skip to content

v0.3.0 - Smaller index, 20x faster vector queries, measurable retrieval

Latest

Choose a tag to compare

@hypnagonia hypnagonia released this 06 Sep 12:29
· 3 commits to main since this release
385e9a7

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 10
14 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 2 often costs a call without changing the answer.
  • The index is still ~19x the source text. terms (JSON postings), chunks (a redundant token array) and blobs (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_path defaults to true on the assumption that paths help code, tested only on prose, where it hurt.