Asosiy qismga o'tish

reranker.py — qayta tartiblash

12-bob 10 kod bloki 1059 so'z

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:

python3 qator
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

matn1 qator
[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

python7 qator
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

python23 qator
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. Haqiqiy app/services/reranker.py da _rerank_llm va _rerank_jina funksiyalari 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

python20 qator
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)

python5 qator
# 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:

python8 qator
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:

python3 qator
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:

python2 qator
# parcha
yangi = rerank(savol, chunklar, top_n=top_n)     # XATO

To'g'risi:

python2 qator
# 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:

  1. Argument tartibini adashtirish oson — Python nom bo'yicha tekshirmaydi
  2. except ichida 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.

Telegramda yozish Narxlar