Skip to content

feat(blog): add SQLite FTS5 + Dense hybrid retrieval article - #251

Open
ishwar170695 wants to merge 9 commits into
kubesimplify:mainfrom
ishwar170695:post/sqlite-fts5-dense-hybrid-retrieval
Open

ishwar170695 wants to merge 9 commits into
kubesimplify:mainfrom
ishwar170695:post/sqlite-fts5-dense-hybrid-retrieval

Conversation

@ishwar170695

@ishwar170695 ishwar170695 commented Jul 11, 2026

Copy link
Copy Markdown

Changes proposed

Adds a new, technical, practitioner-led article to the Kubesimplify blog focusing on hybrid retrieval architectures in local RAG systems.

  • New: content/blog/sqlite-fts5-dense-hybrid-retrieval.md — Deep-dive into sparse vs. dense search limitations, SQLite FTS5 BM25 configurations, Reciprocal Rank Fusion (RRF), deterministic domain reranking, and vector cache memory footprints.
  • New: public/img/blog/sqlite-fts5-dense-hybrid-retrieval/ — Static assets (architecture diagrams, benchmarks, UI screenshots).
  • Modified: content/authors.json — Add author profile entry for ishwar.
  • New: public/img/authors/ishwar.jpg — Author avatar placeholder.

No other changes are made to the site's code.

Note to reviewers

This post focuses on systems-level search engineering and database schemas (SQLite), which is well-suited for KubeSimplify's backend, cloud-native, and infrastructure audience.

@ishwar170695
ishwar170695 force-pushed the post/sqlite-fts5-dense-hybrid-retrieval branch from 919d215 to 0e21673 Compare July 11, 2026 11:46
Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>
@ishwar170695
ishwar170695 force-pushed the post/sqlite-fts5-dense-hybrid-retrieval branch from 0e21673 to 9e914a0 Compare July 11, 2026 11:47

@saiyam1814 saiyam1814 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together. I read through the article and the added assets. Overall, this is a strong technical draft: it has a clear retrieval problem, a concrete architecture, code snippets, benchmark numbers, screenshots, and a useful tradeoff section. I think it can fit the KubeSimplify audience well if we frame it as AI infrastructure / backend retrieval engineering rather than only as a generic RAG article.

A few changes I would suggest before publishing:

  1. Use the architecture diagram in the article
    The PR adds public/img/blog/sqlite-fts5-dense-hybrid-retrieval/architecture.png, but the post currently shows the pipeline as an ASCII diagram. This topic benefits a lot from a visual architecture diagram, so I would embed the image in the Architecture Overview section and optionally keep the text explanation around it.

  2. Fix the author image
    public/img/authors/ishwar.jpg appears to be the same file as the blog cover.jpg. That looks accidental. Please replace it with an actual author avatar or a suitable placeholder image.

  3. Add more context around the benchmark numbers
    The reported improvements are compelling (466 ms to 12 ms, 320 MB to 48 MB, 68% to 91% Top-5 retrieval), but they need enough context to be credible and reproducible. Please add details such as corpus size, hardware, Node/SQLite versions, embedding model/vector dimension, how the 100-query benchmark was constructed, and how relevance was judged.

  4. Verify math rendering for the RRF formula
    The post uses $$...$$ for the RRF equation. Please verify this renders correctly in the KubeSimplify site. If math rendering is not enabled, convert it to a plain-text/code-block formula so readers do not see raw LaTeX.

  5. Make the KubeSimplify fit more explicit
    I would add a short section on where this belongs operationally: local-first backends, edge apps, small/medium corpora, Kubernetes workloads with persistent storage, serverless constraints, and when teams should move to Qdrant/Milvus/Elasticsearch. This would connect the article more strongly with KubeSimplify’s cloud-native/infrastructure audience.

  6. Add a note on FTS5 table population/sync
    The SQLite FTS5 external-content table example is useful, but readers may wonder how sections_fts stays in sync with sections. A short ingestion snippet or note about triggers/rebuild flow would make the implementation more complete.

  7. Polish the style for KubeSimplify
    Consider removing emojis from section headings and using a cleaner tutorial/deep-dive style. The current content is good, but simpler headings would make it feel more consistent with existing KubeSimplify technical posts.

Overall: this is close and worth publishing after the above polish. The idea is useful, the implementation is practical, and the performance/tradeoff framing is exactly the kind of detail readers will appreciate once the claims and visuals are tightened up.

@ishwar170695

Copy link
Copy Markdown
Author

@saiyam1814 Thanks for the detailed review. I've addressed all the suggested changes:

  • Embedded the architecture diagram and cleaned up the section headings.
  • Replaced the incorrect author image with a custom author avatar.
  • Expanded the benchmark methodology with corpus size details (~4,900 sections), hardware specifications (AMD Ryzen 5 5600H), software versions (Node.js v22.x, SQLite v3.x), embedding model details, and target relevance metrics.
  • Replaced the LaTeX RRF equation with Markdown-friendly plain text code block formatting.
  • Added an Operational Fit section covering local-first, Kubernetes sidecar workloads, serverless endpoints, and when to scale up to distributed vector databases.
  • Documented FTS5 synchronization using SQLite trigger templates and rebuild commands.
  • Aligned schemas/snippets with the actual codebase variables and table names (laws and laws_fts instead of the illustrative sections/sections_fts placeholders).

I also verified that the site builds successfully with these changes. Thanks again for the suggestions!

@saiyam1814

Copy link
Copy Markdown
Member

Did you test this on real hardware?

@ishwar170695

Copy link
Copy Markdown
Author

Did you test this on real hardware?

Yes. I tested everything on my local machine.

@saiyam1814

Copy link
Copy Markdown
Member

for some reason I am not able to see the preview on cloud flare, let me pull in locally and check

@shkatara

Copy link
Copy Markdown
Collaborator

@saiyam1814

Copy link
Copy Markdown
Member

@shkatara can you add feedback for this blog?

@shkatara

Copy link
Copy Markdown
Collaborator

@ishwar170695

I'm writing this from my phone so keeping it short.

Architecture diagram is not understandable at all to people who are new to this. So many flows and nodes make it confusing than clearer.

Flow representation is also not clear. How does one query branch off to different endpoints to search. Where is this configured.

FTS5. / BM25. I'm guessing searching something over text so full text search. People not working with db have no idea what this is. Avoid using short forms or at least write full form after them in ()

Synchronizing the FTS5 Virtual Table: this is worded in a way someone from not a db background would fail to understand. External content table ? What is that. What is a virtual table ?

We load only the id (string) and the coordinate list—pre-processed into a compact Float32Array object—into memory: what id is this of ? What is in the float32 array

Explain the sql queries in english

Because the architecture or packet flow is not clear, I can't put a mental model of why memory usage comes down by 85%

What is RRF. What is BM25.

The problem statement is good. And one that makes sense. The answer goes into a lot of depth and jargons that makes it hard to follow. I had to try at least three times to read it and always I could not finish.

The blog should focus on keeping it simple. Keeping in mind people have no idea what we are writing about and would need help at each layer to put a mental model. If they are fighting to understand what these short forms are, the purpose is defeated.

It's better to give them ideas that something like this is possible if the topic is dense, and let them explore their own cases.

@saiyam1814

Copy link
Copy Markdown
Member

@ishwar170695 can you work on the feedback from @shkatara

@ishwar170695
ishwar170695 force-pushed the post/sqlite-fts5-dense-hybrid-retrieval branch from 018a736 to 7d7b6d0 Compare August 11, 2026 14:53
@ishwar170695

Copy link
Copy Markdown
Author

@saiyam1814 @shkatara Thanks for the feedback. I've reworked the article to make it more beginner-friendly by simplifying the diagrams, reducing jargon, introducing concepts before acronyms, and focusing on the mental model first. I'd appreciate another review when you have a chance.

Sorry for the delay, and thanks for your patience.

@saiyam1814

Copy link
Copy Markdown
Member

Thanks for the rework @ishwar170695, and no worries at all about the delay.

The clarity pass really landed. The new pipeline diagram is exactly what @shkatara was asking for: one clean top-to-bottom flow instead of the earlier tangle. Putting the mental model up front before any code was the right call, the 50 → 20 → 5 funnel is a genuinely nice way to teach candidate reduction, and the opening (asking about a forged signature and getting counterfeit coins back) is one of the better hooks we have had on the blog. The trade-offs section and the "fix retrieval before you reach for a bigger model" conclusion are the parts I would keep untouched.

I then went through the article side by side with the LawDecoder repo, and a few things have drifted apart between the post and the code. I want to close those before we publish, mainly to protect you: a post like this attracts readers who will clone the repo and run your snippets, and it is much better if everything they find matches.

Before we publish

1. The FTS5 snippet does not match init_db.js

The post (lines 109-120) shows an external content table:

CREATE VIRTUAL TABLE IF NOT EXISTS laws_fts USING fts5(
    id UNINDEXED, title, content,
    content='laws',
    content_rowid='rowid'
);

with the caption that content='laws' avoids duplicating text on disk. But backend/utils/init_db.js creates a standard FTS5 table with no content= option, over (id UNINDEXED, law_name, chapter, title, content), and the ingest transaction runs both insertLaw.run(...) and insertFts.run(...) for every section. So the text is duplicated today, and the caption claims the opposite.

Two ways to fix, either is fine:

  • Show what the code actually does, and mention external content tables as the next optimization, or
  • Switch the code to external content and show that. If you go this route you will also need the sync triggers, which ties back to my earlier point 6. That note is not in the current draft anymore, and it is worth having: a reader who copies an external content table without triggers gets a silently empty index.

Also, content_rowid='rowid' is a no-op as written, since rowid is already the default.

2. The repo link promises benchmark scripts

Line 228 says the benchmark scripts are on GitHub, but I could not find a harness in the repo, and .gitignore excludes backend/data/*, so the corpus, the vectors, and the .db do not ship either. That means a reader cannot reproduce any of the four headline numbers even if they want to. Easiest fix is to commit the script you used (even a rough one is fine and honestly makes the post stronger), or reword the line to promise only the source and the indexing pipeline and say plainly that the numbers are from your local run.

3. The reranker deserves its own honest paragraph

In generateLegalAnswer.js the domain reranker is:

const isDocumentForgeryRelated = queryLower.includes('signature') || queryLower.includes('sign') || queryLower.includes('document');
// ... coin/stamp/currency matches: adjustedScore *= 0.01
// ... titles containing forgery/forged: adjustedScore *= 3.0

Hardcoded guardrails like this are completely legitimate, plenty of production retrieval systems ship exactly this. The issue is presentation. Right now the article's hook is the one query this rule was written for, so a reader naturally credits FTS5 and RRF for turning counterfeit coins into forgery sections, when part of the credit belongs to the rule. Two options: run a quick ablation over your 100 queries (RRF only vs RRF plus reranker) and publish both numbers, or add a short paragraph saying the reranker is a deliberate domain guardrail for this query class rather than a general component. The ablation would be a great addition if you have the time, since "how much did each stage buy me" is the question every reader will have.

One small bug while you are in there: includes('sign') is a substring match, so it also fires on design, assign, resignation, and consignment.

4. The methodology details from the last round got lost

Your earlier reply mentioned the Ryzen 5 5600H, Node and SQLite versions, and the embedding model, but they are not in the current file, I think the rewrite dropped them. Worth putting back, especially the embedding model and dimension (Xenova/all-MiniLM-L6-v2, 384-dim, from cache_embedding.js), since readers will want to know whether their own model choice changes the picture. Your repo README also carries a caveat the post does not: memory is process-level heap at startup. That kind of precision helps the number rather than weakening it.

5. The memory story needs a breakdown

This is the one I got stuck on, and I think it is also what @shkatara meant when he said he could not build a mental model for the 85%. Running the arithmetic: 4,892 sections at 384 dims and 4 bytes each is roughly 7.5 MB for the entire vector cache, and the statutory text for 4,892 sections is maybe 10 to 20 MB. Neither of those explains a 270 MB drop. My guess is the heap is actually dominated by the transformers models (distilbart-cnn-12-6 is not small) plus JSON.parse overhead on the old per-query scan, rather than by document strings.

If that is right, the fix is easy and the post gets more interesting, not less: show a rough heap breakdown, and say the win came from dropping a full JSON parse and keeping typed arrays instead of object graphs. If I have the wrong end of it, a couple of numbers in the section will settle it.

6. Name the real cause of the latency win

The table labels v1 as a linear JSON scan, which is honest, but the surrounding narrative lets the reader attribute 466 ms to 12 ms to hybrid retrieval. Most of that gap is really "we stopped re-parsing a JSON file on every query" and would have shown up even without FTS5 or RRF. One sentence saying so keeps the claim solid, and the hybrid architecture still has plenty to stand on with the accuracy result.

Polish

  • Percentages: 320 MB to 50 MB is 84.4%, not 85.0%. Your README says ~48 MB, which is where 85.0% comes from, so I think the post rounded the MB and kept the old percentage. Also, 68% to 91% is +23 percentage points, a 34% relative gain, so "percentage points" is the safer wording.
  • Citation label: "Evidence Act Sec 65" should be "Bharatiya Sakshya Adhiniyam, 2023 Sec 65". BSA s.65 is proof of signature and handwriting (the old IEA s.67), whereas IEA s.65 is secondary evidence of documents, so a lawyer reading the table would be sent to the wrong provision. Worth being precise in a legal-domain post. Also, the post lists the corpus as BNS, BNSS, and BSA, while the README adds the IT Act, the Constitution, and the personal laws.
  • Screenshots: landing_page.png is an empty search box and a disclaimer, so it does not show anything working. citations_view.png is the good one, it matches the article's table, though its alt text mentions RRF ranks that are not visible in the frame. Your app has a Developer Mode toggle that does show the RRF ranks, and the Developer Notes and Benchmarks tab is where the numbers live, so screenshots of those two would be much stronger than the landing page. One small thing: the screenshot order is 340, 336, 339, 335 while the table has 336 first, worth reconciling or noting they are different runs.
  • Image sizes: diagram_pipeline.png and chart_memory.png are about 1.2 MB each. Converting to webp will cut that by 90% or more. Recent posts on the site use webp covers too, and the cover here is a 1024x682 jpg where our pattern is 1200x630.
  • Diagram tweak: two cheap additions would make it carry more weight: put the 50/50, 20, 5 counts on the arrows, and mark which boxes are RAM and which are disk. That is precisely the mental model your memory section depends on.
  • Front matter: datePublished is 2026-07-11, so bump it to the actual publish date, and add draft: false to match the other posts.
  • Where this fits for our readers: this was my earlier point 5 and it is not in the current draft. The trade-offs section covers when not to use the approach, but not why a Kubernetes-focused reader should care. A short section would connect it: retrieval as a sidecar with the .db baked into the image or mounted from a PVC, single-writer meaning read-only replicas scale fine, and the point where you would move to Qdrant or Milvus.
  • House style: our posts do not usually number the H2s, so plain headings would fit better, and we tend to avoid em dashes.
  • Title: "Beat Naive RAG" is a bit strong given points 3, 5, and 6. Something flatter, closer to "Hybrid retrieval with SQLite FTS5 and dense vectors", matches what the post actually demonstrates.

Where that leaves us

The 6 items in the first section are what I would like fixed before we merge, and 1, 2, and 3 are the important ones since they are what a reader would notice when they open the repo. The polish list is quick and mostly mechanical.

To be clear about the overall read: the idea is good, the engineering is real, and the writing is a big step up from the first draft. This is worth publishing once the post and the code tell the same story. Ping me when you have pushed and I will do another pass.

@ishwar170695

Copy link
Copy Markdown
Author

Updated the article and LawDecoder implementation based on the latest review.

The current version now:

  • aligns the article with the actual standalone FTS5 implementation,
  • fixes the reranker substring-matching bug,
  • adds a reproducible npm run benchmark harness with component ablation,
  • separates heap and RSS memory measurements,
  • accurately attributes the latency and memory improvements,
  • documents the domain reranker as a deterministic guardrail,
  • updates the BSA Section 65 citation, and
  • refreshes the citation screenshot from the live Developer Mode output.

The benchmark and article now use the same measured results and methodology.

@saiyam1814

Copy link
Copy Markdown
Member

Thanks @ishwar170695, and thanks for the patience on the long review cycle. Fresh preview is rebuilding here:

https://post-sqlite-fts5-dense-hybri.website-dab.pages.dev/sqlite-fts5-dense-hybrid-retrieval

cc @shkatara

I went through the post against the LawDecoder repo again and test-merged the branch onto current main with a real build. All six pre-publish items from the last round are genuinely closed, and a couple of them you closed better than I asked:

  1. The FTS5 snippet now matches init_db.js, and the post says plainly that the text is duplicated today and names external content tables as the next optimisation. That is the honest version.
  2. benchmark.js and npm run benchmark ship.
  3. The ablation is there, and you published the result that did not flatter the feature: the domain reranker adds zero aggregate points, and the post says so in as many words while still explaining why it earns its place for that query class. That paragraph is the most credible thing in the article. The includes('sign') substring bug is fixed with word boundaries, so no more firing on "design" and "assign".
  4. Methodology is back with the Ryzen, Node version, model and dimension.
  5. Heap and RSS are separated, with the ONNX and native-buffer caveat on RSS.
  6. The latency win is attributed to the data representation rather than to hybrid search, which is exactly right and makes the post more interesting, not less.

Every number re-checks: 161 / 7.8 is about 20x, 438 to 16 MB is 96%, 507 to 218 is 57%, 60 to 90 is +30 percentage points and you used "percentage points". The BSA citation is fixed, the corpus list matches the README, the screenshot order now matches the table, and the title is flatter. Good pass.

One blocker left, and it is outside the post.

The README contradicts the article on every headline number

"Code & Repository" sends readers straight to the repo. The README there still carries the pre-review figures:

README today This article
Latency 466 ms to 12 ms (97.4%) ~161 ms to ~7.8 ms (~20x)
Memory ~320 MB to ~48 MB (85.0%) 438 to 16 MB heap, 507 to 218 MB RSS
Accuracy ~68% to ~91% 60% to 90%
Eval set 100 queries 10 queries

A reader who follows the link gets two different stories about the same system, and the older one is the one you and I agreed was overstated. That was the whole point of the last round, so please carry the corrected numbers and the 10-query methodology into the README before this goes out. While you are there, the README footnote points at evaluation_queries.md as "the sample benchmark list", and that file no longer has the table.

Related, and quick: backend/data/ is not in the repo, but benchmark.js opens data/laws.db and data/parsed_laws_vectors.json. A fresh clone running cd backend && npm run benchmark crashes on the missing file, so the post's "You can run the controlled benchmark and ablation study locally" is not true yet. Either commit laws.db (16.4 MB is fine for GitHub) plus the vectors, or add one line to that section saying the harness needs the corpus built first through the ingest pipeline and pointing at the setup steps.

Fix in the post

  • Line 178 renders LaTeX literally. 4,892 vectors $\times$ 384 dimensions $\times$ 4 bytes shows up on the built page exactly like that, dollar signs and backslashes included. We have no KaTeX in the pipeline. Use ×. This was cleaned up two rounds ago and crept back in, so worth a grep for $ before you push.
  • Line 228, better-sqlite3 v3.53 is wrong. Your backend/package.json pins ^12.11.1. 3.53 is the SQLite engine version bundled inside it. Write it as better-sqlite3 v12.11 (SQLite 3.53) in WAL mode, which is more useful anyway since it names both.
  • Image weight, carried over from last round. diagram_pipeline.png is 1.1 MB and chart_memory.png is 1.1 MB, both unchanged. These are flat-colour diagrams, so WebP should land them under 100 KB each: cwebp -q 82 diagram_pipeline.png -o diagram_pipeline.webp. Shubham reads on mobile and 2.2 MB of diagrams is felt.
  • Two images are committed but never referenced: chart_memory.png and landing_page.png, 1.25 MB of dead weight in the repo. landing_page.png can go, it is the one that does not show anything working. But chart_memory.png is worth using rather than deleting: a memory chart is precisely what "Solving the Memory Bottleneck" wants next to the before/after ASCII block, and the heap-versus-RSS distinction is much easier to see as bars than as a table row.
  • "LawDecoder" first appears at line 200 with no introduction. Up to that point it is "my legal assistant prototype". Name it in the opening paragraph so the reader knows what they are reading about and that the repo at the end is the same thing.

Polish

  • The two diagram additions I asked for last round are not in: the 50/50 → 20 → 5 counts on the arrows, and marking which boxes live in RAM and which on disk. That diagram is what your memory section leans on, so those two annotations do real work. Also its Reranker box says "Removes irrelevant results", but your reranker multiplies scores, it does not drop candidates. "Re-scores domain matches" is closer.
  • Cover is 1024x682 jpg at 328 KB. Our pattern is 1200x630, which is what the social card crops to.
  • The two ASCII diagrams use bare fences. Tag them ````text ```` so they do not get guessed at by the highlighter.
  • "System Screens" is one screenshot under its own H2. Either fold citations_view.png into the reranker section where it proves the point, or add the Developer Notes / Benchmarks screen so the section earns the heading.
  • n=10 is a small evaluation set. The post is honest about scope ("In this evaluation"), but one explicit sentence saying ten queries is indicative rather than statistically settled, and naming the domains they cover, will save you the first comment you get.
  • DCO: only the first of your four commits is signed off. I will squash on merge, so just confirm you are fine with the sign-off from 9e914a0 standing for the whole set, or amend the others.

Where that leaves us

The article itself is basically ready. The blocker is the README, and it is a fifteen-minute fix: copy the corrected numbers and the 10-query methodology across so the post and the repo say the same thing, then the LaTeX, the version string, the images, and the LawDecoder intro.

Worth saying plainly, this went from a post I was not sure about to one I am glad we are running. The thing that turned it was you measuring the reranker and publishing the number that made your own feature look unnecessary. That is rarer than it should be, and it is why the rest of the numbers in here are believable.

…nd assets

Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>
Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>
Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>
…diagram pipeline with 50-20-5 funnel

Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>
…est harness scope

Signed-off-by: Ishwar <ishwarcm@iitbhilai.ac.in>
@ishwar170695

Copy link
Copy Markdown
Author

@saiyam1814 Thanks again for the detailed review and for being patient with all the back and forth.

I’ve addressed the remaining feedback across the article and LawDecoder repo, including the benchmark methodology, FTS5 implementation details, 50 → 20 → 5 architecture diagram, updated dashboard/screenshots, and asset cleanup.

I also did a fresh-clone check and corrected the benchmark instructions so they accurately reflect the local dataset scope.

Also confirmed, I’m fine with the Signed-off-by from 9e914a0 standing for the whole squash on merge.

This is my first time taking a technical blog through a review process like this, so I really appreciate the detailed feedback. It helped me catch quite a few things I would have otherwise missed.

Please take another look when you get a chance.

@saiyam1814

Copy link
Copy Markdown
Member

Thanks @ishwar170695. I went through the post against the LawDecoder repo again and checked the built assets rather than just reading the diff. The README blocker is closed, and closed properly.

cc @shkatara

Confirmed fixed

The README now carries the corrected numbers end to end: ~161 ms → ~7.8 ms, ~438 → ~16 MB heap, ~507 → ~218 MB RSS, 60% → 90% on the 10-query set, with the RSS caveat about the ONNX runtime and native buffers in the footnote. evaluation_queries.md has the ablation table back and the methodology line matches the article word for word. The post and the repo finally tell the same story, which was the whole point of the last round.

The rest of the list is done too, and I checked each one on the branch rather than taking it on trust:

  • LaTeX is gone. Zero $ characters in the file now. The × renders properly on the built page.
  • better-sqlite3 v12.11 (SQLite 3.53) at line 229, which is the more useful form since it names both.
  • Images are all WebP and all small. diagram_pipeline went from 1.1 MB to 69 KB, chart_memory from 1.1 MB to 49 KB. Whole post is now under 400 KB of images against 2.2 MB for the two diagrams alone last round. Cover is exactly 1200×630.
  • landing_page.png is gone, chart_memory.webp is now used in "Solving the Memory Bottleneck" where the heap-versus-RSS distinction is much easier to read as bars, and developer_notes_tab.webp means "System Screens" is two screenshots and earns its heading.
  • LawDecoder is named in the opening sentence, so the repo at the end is obviously the same thing.
  • The n=10 caveat is in, with the six practice areas named. That is the sentence that saves you the first comment you get.

The diagram did all three things I asked for and is genuinely good now. Top 50 matches on both branches, Top 20 Candidates, Top 5 Provisions, and the RAM/disk split marked explicitly: Disk: FTS5 Index, RAM: 7.17 MB Cache, Disk: Hydrates Full Text. That last one is what makes the memory section land, because a reader can now see that the 7.17 MB number is the only thing resident. The reranker box says "Re-scores domain matches" instead of "Removes irrelevant results", which is what it actually does.

One thing left

The article still promises a benchmark a fresh clone cannot run.

backend/data/ is not in the repo. The tree has benchmark.js, but no laws.db and no parsed_laws_vectors.json, and benchmark.js opens both by path. You did add the caveat to the README (line 159, "against the compiled laws.db and parsed_laws_vectors.json datasets from the author's local run"), and that is the right note. It just did not make it into the post.

"Code & Repository" still reads:

cd backend
npm install
npm run benchmark   # Runs the 4-stage ablation and latency evaluation against the benchmark dataset

Someone who clones and follows that gets a crash on a missing file, and "the benchmark harness can be run directly from the backend directory" is the sentence that sent them there. Same fix as the README, one line under the block:

The harness runs against the compiled laws.db and parsed_laws_vectors.json produced by the ingest pipeline, which are not committed to the repo. Build the corpus first using the setup steps in the README, or read the recorded results in evaluation_queries.md.

Committing laws.db (16 MB is fine for GitHub) plus the vectors is still the stronger option if you want people reproducing the numbers, but the one-line honest version is enough to merge on.

Two nits while you are in there

  • Two em dashes came back at line 278: practice areas—criminal law and consumer protection—specifically. Commas or a colon. That line is otherwise exactly the caveat I wanted, so it is a shame to lose it to house style.
  • In the diagram's Reranker box there is faint leftover text under the "Re-scores domain matches" caption, visible at full size and invisible at blog width. Your call whether it is worth a re-export.

Where that leaves us

One line in the post and the em dashes, and I will merge this. Sign-off from 9e914a0 standing for the squash is noted, thanks for confirming.

And for a first time through a review process this long, you handled it well. The thing I keep coming back to is that you measured the reranker and published the number that made your own feature look unnecessary, then explained why it still earns its place. That is the paragraph that makes the rest of the numbers in the post believable, and most people would have quietly dropped the ablation instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants