Improve RAG accuracy with link-based rank - case study
Published: September 10, 2025 | Updated: August 9, 2026
When building LLM-based applications, we have many ways to improve retrieval accuracy in RAG. There is one simple and cheap technique often omitted in LLM/RAG tutorials that we can include in our arsenal.
It's calculating document rank by the number of links (referencing documents) and using this as a boost for embedding or rerank score. This solution has been known for decades and used in search engines. It's more primitive than modern intelligent approaches, but still viable from a business perspective.
This article is a case study explaining in detail how it works in my project Rechtina, an AI assistant in the legal domain. As usual, real-life implementation is more challenging than simple theory.
Problem
The most typical RAG pipeline is based on semantics. Results (document chunks) with closer meaning to the user query are ranked higher and used as a source for the LLM-generated response. Sometimes this can be combined with a non-semantic BM25 score. Both solutions try to find the best matching source, but what does this mean in a high-density legal domain?
By "high density" I mean that there are many similar results with very close embedding or rerank scores. The results don't differ much, but only the best matches should be selected.
Legal acts with good names, section titles, or texts clearly expressing meaning and containing strong keywords are ranked higher. Legal acts with poor or missing section titles, or worse wording, are penalized. Often, important sources are omitted.
This problem was already known to the first web search engines, and the solution they implemented is also applicable in modern LLM/RAG applications.
Context
Rechtina is based on Austrian federal law. The source on the government website contains links between legal acts (one legal act referencing another). I also have information about legal act type (legal provision, regulation, announcement, etc.) and effective date.
Goal
The goal is to calculate an authority boost for every document (a whole legal act). Every embedding chunk related to a document inherits that document's authority boost. Calculating separate boosts for different document parts is not the goal here.
Implementation
Boost calculation
To calculate the number of links we need structured information about which document links to which. For this simple case, I used a table with two columns (source_id, target_id). No graph database was needed.
In my case the links data distribution is as follows:
- 10750 total documents
- 7904 documents without any link
- 1188 documents with 1 link
- 1658 documents with 2 links or more
- The most linked document has 163 links
Even one link should have an impact, but a few documents with a very large number of links should not distort the results.
In my formula, I used a logarithmic scale and introduced a maximum number of links (if the maximum is exceeded, it's capped). It scales the number of links to a value between zero and the maximum allowed boost:
int linksCount = Math.min(inboundLinks, MAX_LINKS);
boost = Math.log(1 + linksCount) / Math.log(1 + MAX_LINKS) * MAX_LINK_BOOST;
My current implementation uses MAX_LINKS = 80 and MAX_LINK_BOOST = 0.2, and the result looks like on this chart.
links | boost
-------------
0 | 0
1 | 0.03
2 | 0.05
8 | 0.1
13 | 0.12
26 | 0.15
40 | 0.17
80 | 0.2
The final authority boost includes additional tweaks:
- Some document types are more important than others and preferred as answer sources. They get a bonus.
- Some documents are very old despite being still valid. To reduce the risk of generating answers based on ancient documents, they get a negative boost.
public static double calculateBoost(int inboundLinks, String documentType, LocalDate date) {
double boost = 0;
if (inboundLinks > 0) {
int linksCount = Math.min(inboundLinks, MAX_LINKS);
boost += Math.log(1 + linksCount) / Math.log(1 + MAX_LINKS) * MAX_LINK_BOOST;
}
if (Set.of("BVG", "BG").contains(documentType)) {
boost += DOCUMENT_TYPE_BONUS;
}
if (date != null && date.isBefore(OLD_DOCUMENT_THRESHOLD)) {
boost -= OLD_DOCUMENT_PENALTY;
}
return boost;
}
MAX_LINKS = 80;
MAX_LINK_BOOST = 0.2;
DOCUMENT_TYPE_BONUS = 0.1;
OLD_DOCUMENT_PENALTY = 0.2;
OLD_DOCUMENT_THRESHOLD = LocalDate.of(1920, 1, 1);
Boost usage
I use the calculated boost value as a percentage influencing embedding search and rerank scores: new_score = original_score * (1 + boost)
Example:
Score = 0.63, boost = 0.05, result = 0.63 * (1 + 0.05) = 0.6615Score = 0.75, boost = -0.2, result = 0.75 * (1 - 0.2) = 0.6
Challenges
What I learned while building an LLM-based solution is unpredictability. Every theoretical solution can improve results or make them worse. It can help in some queries, but hurt in others. This case is no different.
The calculated authority boost tells how important or influential a document is. The legal domain is high-density, so even a small score change makes a difference. Now, less influential documents with good titles and wording are no longer dominating results. One problem solved. Another problem introduced: in more specific niche queries, the answer should be based on niche, unpopular legal acts, not the main ones.
The solution is balance. Authority boost should be kept, but carefully adapted. More specific results are naturally better matches, and authority boost should be low enough so that popular documents do not dominate the results.
The values I provided in this article were tuned through experimentation and testing.
Applicability
This is one of the simplest techniques we can adopt to improve RAG accuracy if we have data about document interlinking, and if more links imply a more important document.
A classic LLM/RAG use case is to generate answers based on a company's internal knowledge base. In tools like a wiki or Confluence, links between documents occur naturally. We can also give a bonus for specified document types and a penalty to old documents, similar to what I did in Rechtina.
The universal formula
A year later I was working on Slopo, an open-source tool for code analysis, and had a similar problem to solve. The result of the embedding similarity calculation should be boosted by the distance of the code in the codebase.
Other formulas didn't give satisfactory results, so I stole it from Rechtina, and it worked perfectly. Relevant file here.
def _distance_boost(distance: int, max_distance: int, max_boost: float) -> float:
capped = min(distance, max_distance)
if capped <= 0:
return 0.0
return log2(1 + capped) / log2(1 + max_distance) * max_boost
