Пяць тэхнік пераранжавання для RAG: ад хуткага пошуку да дакладнага кантэксту
Чаму першы этап пошуку прапускае адказы і як гэта выправіць: крос-энкодэры, RRF, Cohere Rerank, ColBERT і LLM-суддзя, а таксама шматузроўневы канвеер для старту.
Крыніцы: **5 Reranking Techniques in RAG: From Fast Retrieval to Accurate Context**
Уявіце сабэрэжную паўтку, якая была правільна адбудована: дакументы разбілены на часткі, выбрана мадэль эмбедынгу, падключана база даных вектароў. Прыходзіць запіт, прыстрой для пошуку вяртае 50 частак, а пяць найболей рэлевантных потрапляюць у запрошэнне. Адпаведзь ўсё-такі прыемная. Не чырвоная. Інодзе ёй проста не вдаёцца знайсці правільную адпаведзь.
Калі паглядзець на тое, што было знайдзена, частка, якая мае адпаведзь, часта знаходзіцца на 23-й пазыцыі. Мадэль эмбедынгу оцаніла яе як сярэдзёва рэлевантную, але не як одну з пяці найболей рэлевантных, таму большая мовная мадэль яе проста не бачыла. Гэта зовсім не рэдкі кейс. Гэта адны з найбольш распашчэтных спосабоў абыякавасці ў практычнай рэалізацыі сабэрэжных паўтак.
Зазвычай прычыной не ёст той, хто ведае пошук. Проблема часта крыўціцца ў тым, што вадзіцься пасля самага пошуку.
Узгорнутасць пасля пошуку
Першыя этапы адзыскання падбіраюцца з увагай да швальнасці. Цэлкам заполненыя вектары, розрывчаты метод BM25 або ўзаўсёды сумешанне гэтых методаў — усе гэта рабіцца для таго, каб мільйон дакументаў быў скарыгаваны ў прыемныя для работы множкі за каліксекунды. Але гэта коштуе шваброга, паверхневага адліквідацыі прыналежнасці дакументаў да тэмы: мадэль аналізуе запіт і кожны дакумент окрэма, ставяе ўсім балы і вяртае той, які здаецца найбліжэйшым.
„Найбліжэйшы“ і „найболей корыстны для адпаведзення на гэты запіт“ — гэта разныя рычы. Пачатковае адзыскання сталкаецца з труднасцямі з чатырох прычын:
- Паверхневая адліквідацыя. Эмбедынгі вімерваюць сэродзеленасць, а не корыстнасць, таму частка дакумента можа знаходзіцца падалёк да запіту, але ўсё рав не міць нічага корыстнага.
- Абсэнцыя взаімадзеяння запіту і дакументаў. Бі-энкодэры кодуюць запіт і дакументы окрэма і ніколі не бачаць іх разам, што маскіруе тонкія зв’язкі межы ўсімі элементамі.
Рэаналіз балавання ёсць рашэннем.
Што рабіць за дапамою рэаналізу балавання
Рэаналіз балавання — гэта другі этап. Пасля шыранага і быстраога пошуку, які дае набор кандыдатаў, болей сільны модэль пераранжуе іх па тым, насколькі яны адпавядаюць конкрэтнаму запіту. Можна супрацаваць з гэтым як з контролем якасці пры перадачы кантэксту да LLM.
Працэс складаецца з трох крокаў:
- Пачатковы пошук: быстры і шырокі пошук дае 50–100 кандыдатаў у вялічыні рэчанак.
- Рэаналіз балавання: болей розумны модэль оцінюе кожага кандыдата па адпаведнасці запіту і пераранжуе спіс.
Вы отрымліўце швальнасць апрыксіматычнага пошуку разам з точнасцю глęбокай адзначкавання. Сістэму аднаходжэння дадзеных не заменяюць, а толькі дапамагаюць ёй.
Аднаходжэнне дадзеных і пераранжаванне паўнасцю
| Aspect | Initial Retrieval | Reranking |
| ------------------- | ------------------------ | ----------------------------- |
| Goal | Find candidates fast | Judge true relevance |
| Speed | Milliseconds | Tens to hundreds of milliseconds |
| Input | Query + index | Query + top-k candidates |
| Scoring depth | Shallow (embedding dot product) | Deep (cross-attention, token interaction) |
| Cost | Low (local compute) | Higher (model inference) |
| When to use | Every query | On top-k candidates only |
Сэрцаўная ідэя — утрымацца ад значных вычысловых затрат на мільйон дакументаў і спрамаваць іх на 50 найболей перспектывных.
Пяць тэхнік перэранкавання
Інструменты для перэранкавання варьююцца ад дышэўных і шырока распространённых да моцных і высокаякостных. Выбор правага залежыць ад вашаг бюджэту на затрымкі, патрабаванняў да точнасці і аперацыйных мерыяў. Шэсць згаданых тут методаў пакрываюць весь гэты дыяпазон.
1. Перэранкаванне за дапамою крос-энкодэраў
Крос-энкодэры ўважаюцца стандартам для ацэнкі точнасці. Бі-энкодэр отрымляе інфармацыю ад запіту і дакумента окрэма, тады калі крос-энкодэр чытае іх разам як адна вхідна дань. Гэта дазволяе яму фіксаваць взаімадзеянні на рэвэле маркероў, такія як распазнаванне займеннікаў, атрыбуты запароджэння і непрымытые зв’язкі, якія іншыя інструменты для пошуку абходзяць цэлкам.
Этапы:
- Інструмент для пошуку вяртае 50 кандыдатскіх фрагментаў.
- Для кожнага фрагмента з’едынаецца запіт і фрагмент у фармаце
[CLS] запит [SEP] фрагмент [SEP].
cross-encoder/ms-marco-MiniLM-L-6-v2.from sentence_transformers import CrossEncoder
# Load a cross-encoder reranker
# ms-marco-MiniLM-L-6-v2 is fast and accurate for general use
cross_encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank_with_cross_encoder(query: str, retrieved_docs: list[str], top_k: int = 5):
"""
Rerank retrieved documents using a cross-encoder.
Args:
query: The user question
retrieved_docs: List of document chunks from initial retrieval
top_k: Number of documents to return after reranking
Returns:
List of (document, score) tuples, sorted by relevance
"""
# Create query-document pairs
pairs = [[query, doc] for doc in retrieved_docs]
# Get relevance scores
scores = cross_encoder.predict(pairs)
# Combine docs with scores and sort
scored_docs = list(zip(retrieved_docs, scores))
scored_docs.sort(key=lambda x: x[1], reverse=True)
return scored_docs[:top_k]
# Example usage
query = "What are the side effects of amoxicillin?"
retrieved = [
"Amoxicillin is a penicillin antibiotic used to treat bacterial infections.",
"Common side effects include nausea, vomiting, and diarrhea.",
"The drug was first discovered in 1958 by researchers at Beecham.",
"Patients with penicillin allergies should avoid amoxicillin.",
"Side effects may include rash, itching, and in rare cases, anaphylaxis.",
]
top_docs = rerank_with_cross_encoder(query, retrieved, top_k=3)
for doc, score in top_docs:
print(f"Score: {score:.4f} | {doc}")
Выбірайце крос-кодэры, калі главныя ўмовы — гэта точнасць, а дадатковыя 50–200 мс ўзьемлівыя. Для большасці практычных застоў RAG гэта лепшая выхідная пункт. Адзінак — кожна пара запіт-документ обрабоўваная окрэма, таму час адпаведзення зрастае прыбліжна пры росце колькасці кандыдатаў.
2. Спалучэнне рангаў па прыняцоўкі (RRF)
Падазроўваючы, што вы вовсе не хочаце выканаць нейронную мадэль, Спалучэнне рангаў па прыняцоўкі з’еднае рангі з калькуляцыяй калякох абробнікаў даных за дапамогою дэтэрміністычнага формулы: без навчання, без аналізу, толькі арыфметыка.
RRF ўжоць лепш падходзіць, калі ужо існуе гібрыдны спосаб абробкі даных, напрыклад, шуканне за ўсёцінным вектарам разам з шуканнем за ключавымі словамі BM25. Кожны абробнік даных дае свой ранг, а RRF з’еднае іх у адны лепшы спіс.
Формула:
RRF_score(d) = sum over retrievers of 1 / (k + rank_r(d))
Тут k — гэта сталя, якая зазвычай дорэвнае 60, а rank_r(d) — гэта пазычкі дакумента d у сістэме аднаходжэння r. Дакументы, якія атрымоўваюць высокія рангі ў калькольніку супэльных сістэм аднаходжэння, падыходзяць да верху.
def reciprocal_rank_fusion(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
"""
Merge multiple document rankings using Reciprocal Rank Fusion.
Args:
rankings: List of rankings, where each ranking is a list of document IDs
ordered from most to least relevant
k: RRF constant (default 60, as recommended in the original paper)
Returns:
List of (document_id, rrf_score) tuples, sorted by fused score
"""
scores = {}
for ranking in rankings:
for rank, doc_id in enumerate(ranking, start=1):
if doc_id not in scores:
scores[doc_id] = 0.0
# RRF formula: 1 / (k + rank)
scores[doc_id] += 1.0 / (k + rank)
# Sort by score descending
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
# Example: merging BM25 and vector search results
bm25_results = ["doc_5", "doc_2", "doc_8", "doc_1", "doc_9"]
vector_results = ["doc_1", "doc_5", "doc_3", "doc_8", "doc_7"]
fused = reciprocal_rank_fusion([bm25_results, vector_results])
print("Fused ranking:")
for doc_id, score in fused:
print(f" {doc_id}: {score:.4f}")
# Notice: doc_5 and doc_1 appear in both retrievers and get boosted to the top
RRF — гэта найшырэйшы варыянт у гэтым случае, з яго выкарыстоўваннем адбываецца практычна без жадных дапаможных затрымкаў. Яго трэба выкорыстоўваць тады, калі вы вялікі час ужэ выкарыстоўваеце гібрыдны метод ахавання дакументаў і хочаце недорогія павышэння якосці без неабходнасці аналізу модэля. Ён не будзе перамагаць крос-кодэр за чыстай точнасцю, але працуе на дзівную лепую спосаб для методу, у яком няма машыннага навучэння.
3. Cohere Rerank API
Дзеяныя команды не хочуць адрабатваць і налаштаваць модэль для пераранжавання. Калі ваша прыорытэт — продукт, а не аператыванне модэля, тады кераваны API для пераранжавання ёсць прывабным варыянтам, а Cohere-ў ёсць найболей усталеным у гэтый сферы.
API прыме чысткаўку плюс спіс дакументаў і вярне іх у парадку значэння рэлевантнасці. За яго адбыўчасцю стоіць сучасны модэль для пераранжавання з оптымізацыямі, якія самостайна стварыць заўсёды займе тыяжкі час. Ён справляецца з багатымовным кантэнтам і дугімі дакументамі, а таксама масштабуецца па запотрэбе.
import cohere
from dotenv import load_dotenv
import os
load_dotenv()
# Initialize Cohere client
co = cohere.Client(os.getenv("COHERE_API_KEY"))
def rerank_with_cohere(query: str, documents: list[str], top_k: int = 5):
"""
Rerank documents using Cohere's managed Rerank API.
Args:
query: The user question
documents: List of document chunks from initial retrieval
top_k: Number of documents to return
Returns:
List of (document, relevance_score) tuples
"""
response = co.rerank(
model="rerank-v3.5",
query=query,
documents=documents,
top_n=top_k,
return_documents=True
)
results = []
for result in response.results:
results.append((
result.document.text,
result.relevance_score
))
return results
# Example usage
query = "How do I handle authentication in a FastAPI app?"
docs = [
"FastAPI is a modern web framework for building APIs with Python.",
"To add authentication, use OAuth2PasswordBearer and JWT tokens.",
"Pydantic models in FastAPI provide automatic request validation.",
"The OAuth2PasswordBearer class expects a token URL endpoint.",
"FastAPI was created by Sebastián Ramírez and released in 2018.",
]
ranked = rerank_with_cohere(query, docs, top_k=3)
for doc, score in ranked:
print(f"Score: {score:.4f} | {doc}")
Выберыце яго, калі хочаце высокую точнасць без додатковага навантажэння на апаратуру. Компрасамі ў такім случае являюцца вартасьць кожнага запытку і залежнасць ад званоўных рэсурсаў, а пры вельмі высокай актывацыі сеткі можа стаць значным паўза пад час сетавага запытку. Для большасці команд заўсёды пераважае час, заўсёды заштоўваны на хоставанні і оптымізацыі, над гэтымі вартасцямі.
4. ColBERT
Крос-кодэры ўсё ж такі точныя, але повольныя, а бі-кодэры — швядкія, але меркантныя. ColBERT знаходзіць срэдні шлях за дапамою „пазнейшай взаемадзейнасці“.
- Афлайн: кожны дакумент кодуецца токен за токенам у кантэкстныя эмбедынгі, якія прадзейсцвуюцца загадзя і храняцца ў індэксе.
- Онлайн: калі прыходзіць запытка, яна таксама прадзейсцвуецца на рэжыме токен-за-токенам.
- Ацэнка: кожны токен запыткі паўзаецца з яго найбліжэйшым токенам дакумента (этап MaxSim), а пасля сумуецца гэтыя максімумы, ёсць значэнне максімальной схожасці для всіх токенав запыткі, і такім чынам вылічваецца рэлевантнасць.
Пакалькі ўтрыманні дакументаў вырахоўваюцца загадзя, таму гэта значна быстрэй, чым іспользованне крос-кодувальніка. Услягае ўрахоўвання взаімадзейнасця на рэвэле токенаў, таму ён працюе лепшая за бі-кодувальнікі з пункту гляду точнасці.
from colbert import Searcher
from colbert.infra import Run, RunConfig
def setup_colbert_searcher(index_path: str, checkpoint: str):
"""
Initialize a ColBERT searcher for late-interaction reranking.
Args:
index_path: Path to the pre-built ColBERT index
checkpoint: Path to the ColBERT model checkpoint
Returns:
Configured Searcher instance
"""
with Run().context(RunConfig(nranks=1, experiment="reranking")):
searcher = Searcher(
index=index_path,
checkpoint=checkpoint
)
return searcher
def rerank_with_colbert(searcher, query: str, doc_ids: list[str], top_k: int = 5):
"""
Rerank documents using ColBERT's late interaction.
Args:
searcher: Initialized ColBERT Searcher
query: The user question
doc_ids: List of document IDs from initial retrieval
top_k: Number of documents to return
Returns:
List of (doc_id, score) tuples
"""
# Search within the candidate set
results = searcher.search(
query,
k=top_k,
filter_fn=lambda pid: pid in doc_ids # Only rerank candidates
)
return list(zip(results[0], results[2])) # doc_ids, scores
# Note: ColBERT requires a pre-built index and model checkpoint.
# For production use, build the index once and load it at startup.
ColBERT падходзіць для масштабных сістэм, які патрабуюць вышэйшай точнасці, чым у бі-енкодэраў, і меньшага часу адпаведзення, чым у крос-енкодэраў. Кост — гэта праця над стварэнням і падтрымкай індэкса. Калі вы знаётэся на векторных базах дадзеных і хочаце вышэйшай якосці без затрымакоў, якія расту лінейна з колькасцю кандыдатаў, то гэта ідэальны варыянт.
5. LLM-какі суддзя
У найболей важлівых сферах, такіх як медычная дыягнастыка, правовыя даследжэння і фінансавая адпаведальнасць, нават крос-енкодэр можа не падыходзіць. Запіты ўскладненыя, кантэкст тонкі, а неправільны адпаведзення мае вялікія наследкі. У такіх случаях найточнейшым інструментам для перэранківга можа стаць сам LLM.
Ідэя заключаецца у тым, каб прабачыць вялікую мадэль (GPT-4, Claude або Gemini) оцаніць ступень актуальнасці кожнага кандыдата для запіту. Благодарна шырокім светавым знанням і сільным разумаванню ёна можа ацэніць актуальнасць так, як не можу меньшыя інструменты для перэранківга. Тыповы прабач:
You are evaluating documents for a retrieval system.
Query: {query}
Document: {document}
Rate how relevant this document is for answering the query.
Respond with a single integer from 1 to 10, where 10 means perfectly relevant.
Relevance score:
Заўёршыце яго для кожнага кандыдата, аналізавайце рэйтынгі і вярніце топ-к.
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def score_document_with_llm(query: str, document: str) -> int:
"""
Ask an LLM to score a document's relevance to a query.
Args:
query: The user question
document: A candidate document chunk
Returns:
Integer relevance score from 1-10
"""
prompt = f"""You are evaluating documents for a retrieval system.
Query: {query}
Document: {document}
Rate how relevant this document is for answering the query.
Respond with a single integer from 1 to 10, where 10 means perfectly relevant.
Be strict: only give high scores to documents that directly help answer the query.
Relevance score:"""
response = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=5
)
try:
score = int(response.choices[0].message.content.strip())
return max(1, min(10, score)) # Clamp to 1-10
except ValueError:
return 5 # Default on parse failure
def rerank_with_llm_judge(query: str, documents: list[str], top_k: int = 3):
"""
Rerank documents using an LLM as a relevance judge.
Args:
query: The user question
documents: List of candidate document chunks
top_k: Number of documents to return
Returns:
List of (document, score) tuples, sorted by relevance
"""
scored = []
for doc in documents:
score = score_document_with_llm(query, doc)
scored.append((doc, score))
scored.sort(key=lambda x: x[1], reverse=True)
return scored[:top_k]
# Example usage
query = "What are the tax implications of RSU vesting for employees in California?"
docs = [
"RSUs are restricted stock units granted to employees as part of compensation.",
"In California, RSU income is taxed as ordinary income at vesting, not at grant.",
"Employers typically withhold federal and state taxes at vesting time.",
"Stock options and RSUs have different tax treatments under IRS rules.",
"California has one of the highest state income tax rates in the US.",
]
ranked = rerank_with_llm_judge(query, docs, top_k=3)
for doc, score in ranked:
print(f"Score: {score}/10 | {doc}")
Эта тэхніка ў даным случае ёсць найточнейшая, але таксама і найпамедленнейшая і найдорожэйшая. Яе трэба вікорыстоўваць для складных, ценных запітаў, калі некорэктная адпаведзь коштуе рэальных грошаў або несе рэальныя рызыкі. Для большасці застосоўкаў крос-кодэры або кераваныя API даходзяць да 90% точнасці за 10% ад вартасці (гэта прыблізны показнік, а не стандарт). Заклады LLM трэба застаўляць толькі для тых случаяў, калі гэта справды прыемліва.
Выбір межы ямі
Ніякі з методаў перэранкавання не ёсць ідэальным у всіх сітуацыях. У гэтай табелі падсумованы ўсе абмежэння:
| Technique | Best For | Latency | Cost |
| --------------------- | ------------------------------------------------- | ------------ | -------------- |
| Cross-Encoder | Maximum quality on top-k candidates | 50-200ms | Local GPU/CPU |
| RRF | Hybrid retrieval without adding model inference | ~0ms | Free |
| Cohere Rerank API | Speed without operational overhead | 100-300ms | Per API call |
| ColBERT | Large-scale, low-latency use cases | 20-100ms | Index + GPU |
| LLM-as-a-Judge | Complex, high-value queries (medical, legal) | 1-5 seconds | Per API call |
Розумным падчым стаўцем ёсць пачатак з крос-энкодэра, адколькі ён дае максымальны падых у тачнасці за мінімальную складнасць. Калі затрымка стане проблемай, рассмотрзіце ColBERT або кераваны API, такі як Cohere. Дадзіце RRF, калі вы вікорыстоўваеце гібрыдны спосаб выкарыстоўвання даных, а LLM-as-a-Judge — для запытак, дзе максымальная тачнасць прабачае такія віткі.
Команды рэдка калі мусяць выбіраць толькі аднаго. Тыповая структура робочага слоя выглядае так:
- Гібрыдны спосаб выкарыстоўвання даных (dense плюс BM25) з фюзыяй RRF.
- Пераранжаванне крос-энкодэрам праз першыя 50 кандыдатаў.
- Альтэрнатыва у вазе LLM-as-a-Judge для запытак высокага значэння, якія былі пазначаны.
Гэта забезпечвае шырочыну ў звычных случаях і тачнасць, калі гэта мае значэнне.
Заключныя мысляй
Пераранжаванне, верагчыма, ўважаецца найменш адзначаным элементам у процэсе стварэння RAG. Команды вітрачаюць месцы на налагоджэнне розмеру фрагментаў і модэляў укладання, пакуль ўжо існуючы найлепшы кантэкст застаецца на 23-й пазіцыі, а хорашы прыстрой для пераранжавання можа выправіць гэта з мінімальнымі тэхнічнымі зусіллямі.
Тэхнікі пераранжавання варьуюцца ад безкоштовных і шырока распространеных (RRF) да дорогіх і моцных (LLM-as-a-Judge), і ў яго застосаванні не патрэбна або ўсё, або нічога. Пачніце з простага, поступова дадавайце новыя методы і збільшуйце складнась толькі тады, калі гэтага захоцуць вашы показнікі.
- Першая стадыя пошуку швытка збірае кандыдатавакі; яна не вяршыць выбор адпаведных адказоў.
- Пераранжаванне выступае як контроль якасці — гэта другі этап, які адмаўляе справжню рэлевантнась.
- Крос-энкодэры ўтвараюць найлепую вачоўку для початку, адзначаючыся высокай точнасцю і сярэднью складнасцю.
- RRF дапамагае без додатковых зусіль, калі вы ужо вікорыстоўваеце гібрыдны метод пошуку.
Калі вы запускаеце RAG у працэйнай асяроддзя без перэранківання, якасць адпаведзей застаецца незабезпечанай. Выберыце адну з тэхнік, рэалізуйце яю ў гэты тыдзень і пераканайцеся ў разліку.