Background: What I was already using
When studying for the board examination, I set up a RAG (Retrieval Augmented Generation) pipeline for myself. Using semantic search to look at similarity, I turned hundreds of physical medicine and rehabilitation (PM&R) textbooks and my own notes into something I could search and query. I use it for reading in Obsidian, while also letting Claude find what I need via MCP.
Its weakest point right now is connecting knowledge across different documents. So a few days ago, when I saw someone share a newly published paper, SAG (SQL-Retrieval Augmented Generation), claiming that adding SQL and graph concepts on top of semantic search makes cross-document search easier—with effects similar to GraphRAG but easier to build and maintain—my eyes lit up.
In this day and age, naturally, I just threw the paper link to Claude, asked it to read the paper, pull the original author’s GitHub, understand the approach, and then use my own data to reproduce the whole pipeline and actually run a benchmark XD
Paper: Wu et al., SAG: SQL-Retrieval Augmented Generation with Query-Time Dynamic Hyperedges, arXiv:2606.15971. Code: github.com/Zleap-AI/SAG-Benchmark
Step 1: Choosing a model (Tears of an RTX-3070)
The most critical step in the whole pipeline is using an LLM to extract “events + entities” from each text chunk and save them to a database. The original paper used Qwen, so I compared local Qwen (qwen3.5:4b) with cloud-based Claude Haiku.
It turned out Haiku’s information extraction capability was clearly better. The entity graph was denser and the naming more consistent (which is important for linking with SQL later), and it seemed very cheap. This matches my previous experience. Maybe my RTX-3070 is just too weak; it can’t run slightly larger models (tears of an era). Just using Haiku is fast and good!
By the way, I can use Haiku without an API subscription: I just dispatch Claude’s subagent to do it, running within my existing quota.
Three findings on textbooks
I used neurorehabilitation textbooks as a test corpus, designed a batch of clinical questions, and pitted SAG head-to-head against the original semantic search. The results came in three rounds:
Round 1, a single book: A tie. Semantic search was already strong enough; the multi-hop mechanism had no room to shine.
Round 2, cross-book, but all keywords were explicitly stated in the question: Still a tie. Because the question already spelled out “stroke, spasticity, gait,” semantic search fetched the relevant passages on its own.
Round 3, cross-book, but only asking about one end, deliberately omitting the middle hop (e.g., just asking “how to manage equinovarus,” expecting it to link to the underlying spasticity mechanism and treatment on its own): SAG finally won, and won by a lot.
| Implicit bridge questions (only giving one end) | Semantic baseline | SAG |
|---|---|---|
| Across 3 books | 75% | 100% |
I was quite happy at this point, thinking the advantage should be even bigger with more books. So I increased the corpus to 13 books. As a result, the advantage shrank instead of growing:
| Implicit bridge questions | Semantic baseline | SAG | Gap |
|---|---|---|---|
| 3 books | 75% | 100% | +25pp |
| 13 books | 79% | 83% | +4pp |
The reason is that as books increase, they become redundant. The same concept appears repeatedly across many books, so semantic search can bump into it without relying on that graph. The scenario where SAG truly thrives is when “clues are rare, hiding in only one or two places,” which is the exact opposite of my hundreds of highly repetitive textbooks.
So for a textbook library, this is a feature that doesn’t need to be added.
The twist: So where is the “sparse” data? My note vault
Since it thrives on sparse data, I thought of my own Obsidian medical note vault (about 2,510 notes). Each concept often only appears in one or two notes—perfectly sparse.
And the key is: my notes were already connected with wiki links, which is actually a knowledge graph I built manually (the exact kind of graph GraphRAG tries to generate automatically). In other words, the graph SAG has to spend LLM calls to extract, I mostly already had for free.
The problem was that I lost a lot of links when moving over from Evernote, so the graph was broken. When I measured it, I found that 86% of my “link targets” were dead ends (pointing to non-existent notes, or only appearing once). Only 700 or so could actually act as a “bridge”.
Restoring the links first (purely mechanical, no review needed)
I asked Claude to write a small tool that scanned through every note. Whenever the text literally mentioned the title of another note, it automatically added a [[link]] at the first occurrence. This was deterministic matching, not semantic judgment, so I basically didn’t have to review them line by line.
I hit a landmine along the way worth noting: at first, I included “aliases” in the matching, which resulted in a massive explosion of acronym mismatches. For example, “WHO (World Health Organization)” got linked to “Wrist-Hand Orthosis”, whose alias is WHO. The English word “was” got linked to the acronym WAS. Later I narrowed it down to “only link titles + safe aliases (multi-word phrases or Chinese translations, like tennis elbow → lateral epicondylitis)” to keep it clean. In the end, it safely restored thousands of links. Earning these links was a win in itself. Obsidian’s backlinks, graph view, and navigation all improved together.
Finding related notes: 34% → 60%
Then I tested the real use case: opening a note and automatically finding other related notes.
| recall@10 (finding correct related notes) | Semantic search | SAG (Graph) |
|---|---|---|
| Proportion of correct matches | 34% | 60% |
Almost doubled. And looking at actual examples makes the difference clear:
- Opening [Sono - Ankle]: The graph method linked to Haglund’s, supinated foot, Achilles tendinopathy, and sinus tarsi syndrome (all things you’d encounter when scanning an ankle); semantic search drifted from ankle instability to the knee, ACL, and even acetabulum.
- Opening [CVA (stroke)]: The graph method linked to spinal cord injury, transverse myelitis, traumatic brain injury, and post-stroke CRPS (the whole neurorehabilitation landscape); semantic search just spun around the literal word “stroke”.
The graph method links to notes that are “conceptually truly related”, while semantic search is easily led astray by literal similarity.
Having Claude the grad student check the literature: 60% → 84%
At this point I thought, there’s no way this is the only new paper adding “SQL to RAG”, so I asked Claude the grad student to continue searching the literature. Sure enough, hybrid retrieval of “semantics + graph” has been a red ocean over the past year or two:
- KG²RAG (NAACL 2025): Semantics provide seed chunks → knowledge graph expansion.
- HopRAG (ACL 2025): Starts with semantic similarity → multi-hop neighbor exploration → pruning.
- LinearRAG (2025): Explicitly says “using LLMs to extract relations and build graphs is expensive and unstable”, advocating for lightweight, relation-free graphs. Perfectly endorsing our approach.
- MiniRAG (2025): Lightweight graph RAG for small models, saving 75% storage.
- LightRAG (EMNLP 2024): Dual-level (local/global) hybrid retrieval.
Our stuff isn’t novel. But that’s good news; it means the direction is right and we can stand directly on the shoulders of giants.
After comparing, the most worthwhile thing to copy was the ranking method. My initial ranking was very naive, just “counting how many shared links”; switching to the personalized PageRank from the LinearRAG/HippoRAG school (starting from seed notes, doing a “random walk with restart” along the links, letting scores diffuse and aggregate along the graph), the results jumped another big step:
| Ranking method (same wiki link graph) | recall@10 |
|---|---|
| Semantic baseline | 34% |
| Shared link count (naive) | 60% |
| personalized PageRank | 84% |
It was consistent across different Ks (@5: 23% → 46%; @10: 34% → 60% → 84%; @20: 45% → 72%). Moreover, it doesn’t use an LLM at all and is instantaneous (pure numpy). In other words, between the graph source (readymade wiki links) and the ranking method (PageRank), the latter is the true key to widening the gap.
I also grabbed LightRAG (an off-the-shelf Graph RAG engine) for comparison: Stuffing my wiki link graph in using its insert_custom_kg API worked (without using its own LLM extraction), but it had to use an LLM to extract keywords for every query. It took about 20 seconds per run, much slower, and wasn’t any better for “finding related”. So I’m leaving LightRAG for “freeform Q&A”, and using our lightweight setup for “finding related notes”.
And then I put it straight into production
Finally, I asked Claude to wire this PageRank sorting into my own vault-search system (a local MCP server + Obsidian plugin):
- Added a pure PageRank “find related” tool.
- Swapped the pure semantic ranking previously used for “finding related notes” with a “semantics + PageRank” fusion (using reciprocal rank fusion to combine the two rankings).
Because my plugin’s “related notes” panel was already hitting that API, I didn’t even have to change the plugin, the whole thing just got better together 😄
Conclusion
- Textbook library (large, redundant): No need to add it; semantic search is already good enough.
- Note vault (small, sparse, with existing manual links): A great fit. It runs entirely on my existing wiki links, zero LLM extraction, zero maintenance drift. Already in production.
- Two key variables: One is “how to ask” (relying on note links to chain related notes is stronger than free-text search); the other is “how to rank” (PageRank-style diffusion > simply counting links).
- Plus, I conveniently restored my broken note links along the way.
Being able to throw a whole paper to an LLM like this, asking it to read, learn, test, and finally wire it into the system for me, is genuinely so convenient and fun. Just scrolling through Facebook, and I casually verified a new method on my own system and put it into production 😆
Next post, I’ll lay out exactly how this whole vault-search is built: Obsidian plugin + local MCP server, semantics + graph, how to index and how to query. That’s what I’m actually using every day.
References
- SAG: Wu et al., SAG: SQL-Retrieval Augmented Generation with Query-Time Dynamic Hyperedges, arXiv:2606.15971 (github.com/Zleap-AI/SAG-Benchmark)
- KG²RAG: Zhu et al., Knowledge Graph-Guided Retrieval Augmented Generation, NAACL 2025
- HopRAG: Liu et al., HopRAG: Multi-Hop Reasoning for Logic-Aware Retrieval-Augmented Generation, ACL 2025
- LinearRAG: Zhuang et al., LinearRAG: Linear Graph Retrieval Augmented Generation on Large-scale Corpora, 2025 (github.com/DEEP-PolyU/LinearRAG)
- MiniRAG: Fan et al., MiniRAG: Towards Extremely Simple Retrieval-Augmented Generation, 2025 (github.com/HKUDS/MiniRAG)
- LightRAG: Guo et al., LightRAG: Simple and Fast Retrieval-Augmented Generation, EMNLP 2024 (github.com/HKUDS/LightRAG)
- HippoRAG: Gutiérrez et al., HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models, NeurIPS 2024
—
