reranker.py — qayta tartiblash
MUHIM — bu bosqich IXTIYORIY.
Ishlab turgan WDE Nihol boti reranker ISHLATMAYDI. U faqat semantik (vektor) qidiruvdan foydalanadi va yaxshi ishlaydi.
RagDemo ga reranker o'rgatish uchun qo'shilgan — talaba bi-encoder va cross-encoder farqini ko'rsin.
Ya'ni: 11-bobdan to'g'ridan-to'g'ri 13-bobga o'tsangiz ham tizim to'liq ishlaydi. Reranker — keyingi bosqich, majburiy emas.
Bu darsda aytishga arziydigan gap: har bir RAG tizimiga reranker kerak emas. Avval oddiy qilib qiling, o'lchang, keyin kerak bo'lsa qo'shing.
Buni ham o'zingiz yozasiz.
Vektor qidiruv tez, lekin qo'pol. Reranker sekin, lekin aniq. Ikkalasini birga ishlatamiz.
Muammo: bi-encoder cheklovi
Vektor qidiruvda savol va chunk alohida vektorga aylanadi:
savol -> [0.1, 0.5, ...] \
-> masofa hisoblanadi
chunk -> [0.2, 0.4, ...] /
Bu bi-encoder. Tez — chunk vektorlari oldindan hisoblangan.
Lekin model savol va chunk'ni birga ko'rmaydi. Shuning uchun nozik farqlarni sezmaydi.
Yechim: cross-encoder
[savol + chunk] birga -> model -> ball
Model ikkalasini bir vaqtda o'qiydi. Ancha aniq.
Lekin sekin: har bir chunk uchun modelni qayta ishlatish kerak. Oldindan hisoblab bo'lmaydi.
Amaliy yechim: ikki bosqich
10 000 chunk
| bi-encoder (tez)
v
10 ta
| cross-encoder (sekin, lekin faqat 10 ta)
v
5 ta -> LLM ga
Bu ikkalasining kuchli tomonini oladi.
Kod
import httpx from app.config import settings def rerank(chunks: list[dict], question: str, top_n: int = 5) -> list[dict]: if not chunks: return [] if len(chunks) <= 1: return chunks[:top_n] ballar = [] for c in chunks: ball = _ball_hisobla(question, c["chunk_text"]) ballar.append((ball, c)) ballar.sort(key=lambda x: x[0], reverse=True) natija = [] for ball, c in ballar[:top_n]: yangi = dict(c) yangi["rerank_score"] = ball natija.append(yangi) return natija
DIQQAT — argument tartibi: rerank(chunks, question), teskarisi
emas. Adashsangiz Python xato bermaydi — natija shunchaki
ma'nosiz bo'ladi. Bu topish qiyin xato.
dict(c) — nusxa olamiz, asl obyektni o'zgartirmaymiz. Bu
muhim odat: kirish ma'lumotini o'zgartirmang.
Ball hisoblash
Quyidagi
_ball_hisobla— soddalashtirilgan dars varianti. Haqiqiyapp/services/reranker.pyda_rerank_llmva_rerank_jinafunksiyalari bor — bittasi LLM, ikkinchisi Jina API orqali. G'oya bir xil.
Ollama'da cross-encoder yo'q. Ikki yo'l bor.
A yo'l: LLM dan ball so'rash
def _ball_hisobla(savol: str, matn: str) -> float: prompt = ( f"Savol: {savol}\n\n" f"Matn: {matn}\n\n" f"Bu matn savolga qanchalik mos? Faqat 0 dan 10 gacha " f"raqam yozing, boshqa hech narsa." ) try: j = httpx.post( f"{settings.ollama_base_url}/api/generate", json={"model": settings.ollama_model, "prompt": prompt, "stream": False, "options": {"temperature": 0}}, timeout=30.0, ) javob = j.json().get("response", "0") raqam = "".join(ch for ch in javob if ch.isdigit() or ch == ".") return float(raqam) if raqam else 0.0 except Exception: return 0.0
temperature: 0 — ball har safar bir xil bo'lishi kerak.
Tasodifiylik bu yerda zarar.
Kamchilik: har bir chunk uchun LLM chaqiruvi. 10 ta chunk = 10 ta so'rov = sekin.
B yo'l: sentence-transformers (tavsiya)
# parcha from sentence_transformers import CrossEncoder model = CrossEncoder("BAAI/bge-reranker-v2-m3") ballar = model.predict([(savol, c["chunk_text"]) for c in chunklar])
Ancha tez va aniqroq. Lekin qo'shimcha paket va model yuklab olish kerak (~600 MB).
Darsda A yo'lidan boshlang — u tushunarli. Keyin B ni ko'rsating.
Foydasini KO'RING
Bu eng muhim qism. Reranker foydasini raqamda ko'rsating:
oldin = search_chunks(db, "Nihol narxi qancha?", top_k=10) keyin = rerank(oldin, "Nihol narxi qancha?", top_n=5) eski = {c["id"]: i for i, c in enumerate(oldin, 1)} for i, c in enumerate(keyin, 1): e = eski[c["id"]] belgi = f"{e} -> {i}" if e != i else "=" print(f"{i} {belgi:<10} {c['chunk_text'][:45]}")
Natija:
1 3 -> 1 To'lov Payme, Click yoki Uzcard orqali... 2 1 -> 2 Standart tarif oyiga 250 000 som turadi... 3 = Premium tarif oyiga 450 000 som...
3 -> 1 — uchinchi o'rindagi chunk birinchiga chiqdi. Mana
reranker ning ishi.
Nega bu Recall@k da ko'rinmaydi
Reranker yangi chunk topmaydi. U bor chunk'larni qayta tartiblaydi.
Shuning uchun:
Recall@5— o'zgarmaydi (chunk allaqachon top-5 da edi)MRR— oshadi (to'g'ri chunk yuqoriga chiqdi)NDCG— oshadi
Agar reranker qo'shib faqat Recall'ni o'lchasangiz, "foyda bermadi" degan noto'g'ri xulosaga kelasiz.
Tartib nega muhim: LLM kontekst o'rtasidagi ma'lumotni yomonroq ishlatadi ("lost in the middle"). To'g'ri chunk birinchi bo'lsa — javob aniqroq.
Qachon kerak emas
Chunk'lar kam bo'lsa (masalan 20 ta), reranker deyarli foyda bermaydi — vektor qidiruv allaqachon yaxshi tartiblagan.
Reranker katta bazada kerak, u yerda top-10 ichida ham sifat farqi katta.
Haqiqiy misol: WDE Nihol botida reranker yo'q. Hujjatlar soni cheklangan, vektor qidiruv yetarli aniq. Qo'shimcha bosqich — qo'shimcha kechikish va qo'shimcha buziladigan joy.
Qoida: o'lchamasdan qo'shmang. Avval MRR/NDCG ni o'lchang, keyin reranker qo'shib qayta o'lchang. Farq bo'lmasa — kerak emas.
Haqiqiy xato — argument tartibi
Bu kitob yozilayotganda demo_pipeline.py da shunday yozilgan edi:
# parcha yangi = rerank(savol, chunklar, top_n=top_n) # XATO
To'g'risi:
# parcha yangi = rerank(chunklar, savol, top_n=top_n) # TO'G'RI
Natija: TypeError: string indices must be integers. Lekin xato
try/except ichida yutib yuborilgan edi, shuning uchun demo
har safar "tartib o'zgarmadi" deb chiqarardi va hech kim
sezmasdi.
Ikki saboq:
- Argument tartibini adashtirish oson — Python nom bo'yicha tekshirmaydi
exceptichida xatoni yashirmang. Yashirsangiz, ishlamayotgan bosqich ishlayotgandek ko'rinadi
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.