2-qism · Agentlar va bilim izlash·05-bob
RAG — modelga tashqi xotira berish
Gallyutsinatsiyani kamaytirish va bilimni yangilash. BM25 dan vektor qidiruvgacha, bo'lak strategiyalarini o'lchash, baholash metrikalari va to'liq ishlaydigan hujjat savol-javob tizimi.
O'tgan bobda agent qurdik. U internetdan qidira oladi — lekin topgan narsasini saqlay olmaydi.
Va yana bir muammo bor, undan ham jiddiyroq.
Muammoni ko'rib chiqamiz#
Modeldan o'z hujjatingiz haqida so'rang:
javob = client.messages.create(
model="claude-opus-5",
max_tokens=512,
messages=[{
"role": "user",
"content": "Bizning kompaniyaning ta'til siyosati qanday?",
}],
)Model ishonch bilan javob beradi. Va bu javob to'liq to'qib chiqarilgan bo'ladi — chunki u sizning ichki hujjatingizni hech qachon ko'rmagan.
2020-yilda Meta yechim taklif qildi: RAG — Retrieval-Augmented Generation.
G'oyasi juda oddiy:
Modeldan javob so'rashdan oldin, unga kerakli hujjatlarni topib berasiz.
Avval: an'anaviy qidiruv#
RAG'ni tushunish uchun avval oddiy qidiruv qanday ishlashini ko'raylik. Bu tarixiy ekskursiya emas — BM25 bugun ham RAG tizimlarining yarmida ishlatiladi.
Teskari indeks#
Million hujjatda so'z qidirish uchun hammasini o'qib chiqish kerakmi? Yo'q:
from collections import defaultdict
def teskari_indeks_qur(hujjatlar):
"""so'z → qaysi hujjatlarda uchraydi"""
indeks = defaultdict(set)
for i, hujjat in enumerate(hujjatlar):
for soz in hujjat.lower().split():
indeks[soz].add(i)
return indeks
hujjatlar = [
"mushuk uyda uxlaydi",
"it bog'da yuguradi",
"mushuk va it do'st",
]
indeks = teskari_indeks_qur(hujjatlar)
print(indeks["mushuk"]) # {0, 2}
print(indeks["it"]) # {1, 2}Bitta lug'at qidiruvi — va sizda mos hujjatlar ro'yxati. Bu 1960-yillardan beri ishlatiladi va hamon eng tez usul.
BM25 — noldan yozamiz#
TF-IDF ni birinchi bobda ko'rgan edik. BM25 — uning kuchli varianti va bugungi sanoat standarti.
Ikkita qo'shimcha sozlagichi bor:
- k
- Chastota to'yinishiSo'z 100 marta uchrasa, u 1 marta uchraganidan 100 barobar muhimroq EMAS. k parametri bu 'so'nayotgan foyda'ni boshqaradi. Odatda 1.2–2.0.
- b
- Uzunlik normallashtirishUzun hujjatda har qanday so'z ko'proq uchraydi. b=1 bo'lsa uzunlik to'liq hisobga olinadi, b=0 bo'lsa umuman olinmaydi. Odatda 0.75.
import math
from collections import Counter
class BM25:
def __init__(self, hujjatlar, k=1.5, b=0.75):
self.k = k
self.b = b
self.hujjatlar = [h.lower().split() for h in hujjatlar]
self.N = len(self.hujjatlar)
self.ortacha_uzunlik = sum(len(h) for h in self.hujjatlar) / self.N
# Har bir so'z nechta hujjatda uchraydi
self.df = Counter()
for h in self.hujjatlar:
for soz in set(h):
self.df[soz] += 1
# Har bir hujjatdagi so'z chastotalari
self.tf = [Counter(h) for h in self.hujjatlar]
def idf(self, soz):
"""Noyoblik: kam hujjatda uchrasa — yuqori ball."""
n = self.df.get(soz, 0)
return math.log((self.N - n + 0.5) / (n + 0.5) + 1)
def ball(self, sorov, hujjat_id):
h = self.hujjatlar[hujjat_id]
tf = self.tf[hujjat_id]
uzunlik = len(h)
jami = 0.0
for soz in sorov.lower().split():
f = tf.get(soz, 0)
if f == 0:
continue
surat = f * (self.k + 1)
maxraj = f + self.k * (1 - self.b + self.b * uzunlik / self.ortacha_uzunlik)
jami += self.idf(soz) * surat / maxraj
return jami
def qidir(self, sorov, top_k=3):
ballar = [(i, self.ball(sorov, i)) for i in range(self.N)]
ballar.sort(key=lambda x: x[1], reverse=True)
return [(i, b) for i, b in ballar[:top_k] if b > 0]
korpus = [
"Python dasturlash tili sodda sintaksisga ega",
"Python'da ro'yxatlarni saralash uchun sort metodi ishlatiladi",
"JavaScript brauzerda ishlaydigan dasturlash tili",
"Ma'lumotlar bazasi so'rovlari uchun SQL tili ishlatiladi",
]
bm25 = BM25(korpus)
for i, ball in bm25.qidir("Python saralash"):
print(f"{ball:.3f} {korpus[i]}")2.147 Python'da ro'yxatlarni saralash uchun sort metodi ishlatiladi
0.412 Python dasturlash tili sodda sintaksisga ega
Ishlaydi. Va juda tez — million hujjatda ham millisekundlarda.
Yechim: kontekstli vektorlar#
Muammoni transformer hal qiladi. U so'zning kontekstdagi ma'nosini vektorga aylantiradi.
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("all-MiniLM-L6-v2")
sorov = "ro'yxatni tartiblash"
vektorlar = model.encode(korpus, convert_to_tensor=True)
sorov_vektori = model.encode(sorov, convert_to_tensor=True)
ballar = util.cos_sim(sorov_vektori, vektorlar)[0]
for i in ballar.argsort(descending=True)[:2]:
print(f"{ballar[i]:.3f} {korpus[i]}")0.617 Python'da ro'yxatlarni saralash uchun sort metodi ishlatiladi
0.284 Python dasturlash tili sodda sintaksisga ega
"tartiblash" so'zi hujjatda umuman yo'q — lekin model ma'noni tushundi.
Bi-encoder va cross-encoder#
Vektorni ikki xil olish mumkin va farq juda muhim.
Farqni o'lchaymiz:
import time
from sentence_transformers import CrossEncoder
hujjatlar = korpus * 250 # 1000 ta hujjat
# Bi-encoder: hujjatlar OLDINDAN kodlanadi
bi = SentenceTransformer("all-MiniLM-L6-v2")
baza = bi.encode(hujjatlar, convert_to_tensor=True) # bir marta
boshlandi = time.time()
q = bi.encode("Python saralash", convert_to_tensor=True)
util.cos_sim(q, baza)
print(f"bi-encoder: {time.time() - boshlandi:.3f} s")
# Cross-encoder: har juftlik alohida
cross = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
boshlandi = time.time()
cross.predict([("Python saralash", h) for h in hujjatlar])
print(f"cross-encoder: {time.time() - boshlandi:.3f} s")bi-encoder: 0.021 s
cross-encoder: 3.847 s
180 barobar farq — va bu atigi 1000 ta hujjatda.
RAG'ni bosqichma-bosqich quramiz#
Endi to'liq tizim. Har bir qismni alohida yozamiz.
O'rnatish
terminalpip install sentence-transformers chromadb pypdf anthropicSuccessfully installed chromadb-0.6.3 sentence-transformers-3.4.1 pypdf-5.2.0 ...Hujjatlarni o'qish
from pathlib import Path from pypdf import PdfReader def pdf_oqi(yol: Path) -> str: """PDF dan matnni chiqaradi.""" reader = PdfReader(yol) sahifalar = [(sahifa.extract_text() or "") for sahifa in reader.pages] return "\n\n".join(sahifalar) def hujjatlarni_yukla(papka: str) -> list[dict]: """Papkadagi barcha PDF va matn fayllarni o'qiydi.""" hujjatlar = [] for yol in Path(papka).rglob("*"): if yol.suffix.lower() == ".pdf": matn = pdf_oqi(yol) elif yol.suffix.lower() in (".txt", ".md"): matn = yol.read_text(encoding="utf-8") else: continue if matn.strip(): hujjatlar.append({"manba": str(yol), "matn": matn}) return hujjatlarBo'laklarga bo'lish
Bu butun tizim sifatini belgilaydigan qaror.
def bolaklarga_bol(matn: str, olcham: int = 500, ustma_ust: int = 100) -> list[str]: """ Matnni bo'laklarga bo'ladi, jumla chegaralarini hurmat qilib. olcham — taxminiy bo'lak uzunligi (so'zda) ustma_ust — qo'shni bo'laklar necha so'z bilan qoplashadi """ sozlar = matn.split() bolaklar = [] boshi = 0 while boshi < len(sozlar): oxiri = boshi + olcham bolak = " ".join(sozlar[boshi:oxiri]) # Oxirgi to'liq jumlada tugatishga harakat qilamiz if oxiri < len(sozlar): oxirgi_nuqta = bolak.rfind(". ") if oxirgi_nuqta > len(bolak) * 0.5: # nuqta juda erta bo'lmasa bolak = bolak[: oxirgi_nuqta + 1] bolaklar.append(bolak.strip()) boshi += olcham - ustma_ust # ustma-ust siljish return [b for b in bolaklar if len(b) > 50] # juda qisqalarini tashlaymizNima uchun ustma-ust tushish kerak:
Ustma-ustsiz: bo'lak 1: "... ta'til yiliga 24 ish kuni." bo'lak 2: "Bu muddat 5 yildan ortiq ishlaganlarga 30 kunga oshadi. ..." ↑ "Bu muddat" nimaga ishora qilyapti? Kontekst yo'qoldi. Ustma-ust bilan: bo'lak 1: "... ta'til yiliga 24 ish kuni. Bu muddat 5 yildan ortiq..." bo'lak 2: "...ta'til yiliga 24 ish kuni. Bu muddat 5 yildan ortiq ishlaganlarga 30 kunga oshadi." ↑ ikkala bo'lakda ham to'liq ma'no borVektor bazasiga solish
import chromadb from sentence_transformers import SentenceTransformer class RAG: def __init__(self, embedder="all-MiniLM-L6-v2", kolleksiya="hujjatlar"): self.embedder = SentenceTransformer(embedder) self.client = chromadb.PersistentClient(path="./chroma_baza") self.kolleksiya = self.client.get_or_create_collection( name=kolleksiya, metadata={"hnsw:space": "cosine"}, # kosinus masofa ) def indeksla(self, hujjatlar: list[dict], olcham=500, ustma_ust=100): """Hujjatlarni bo'laklab bazaga yozadi.""" idlar, matnlar, meta = [], [], [] for h_id, hujjat in enumerate(hujjatlar): bolaklar = bolaklarga_bol(hujjat["matn"], olcham, ustma_ust) for b_id, bolak in enumerate(bolaklar): idlar.append(f"{h_id}-{b_id}") matnlar.append(bolak) meta.append({ "manba": hujjat["manba"], "bolak_raqami": b_id, "jami_bolak": len(bolaklar), }) # Paket-paket kodlaymiz — xotira tejaladi vektorlar = self.embedder.encode( matnlar, batch_size=32, show_progress_bar=True ).tolist() self.kolleksiya.add( ids=idlar, documents=matnlar, embeddings=vektorlar, metadatas=meta, ) print(f"{len(matnlar)} ta bo'lak indekslandi")Qidirish
def qidir(self, sorov: str, top_k: int = 5, filtr: dict | None = None): """So'rovga eng mos bo'laklarni topadi.""" sorov_vektori = self.embedder.encode(sorov).tolist() natija = self.kolleksiya.query( query_embeddings=[sorov_vektori], n_results=top_k, where=filtr, # masalan: {"manba": "siyosat.pdf"} ) return [ { "matn": matn, "manba": meta["manba"], "masofa": masofa, "oxshashlik": 1 - masofa, # kosinus masofadan o'xshashlikka } for matn, meta, masofa in zip( natija["documents"][0], natija["metadatas"][0], natija["distances"][0], ) ]Muhim: so'rov uchun aynan o'sha embedder ishlatiladi. Boshqa model — boshqa vektor fazosi — natija ma'nosiz bo'ladi.
Javob generatsiyasi
import anthropic def sora(self, savol: str, top_k: int = 5) -> dict: """Qidiradi va javob yozadi.""" bolaklar = self.qidir(savol, top_k) if not bolaklar: return {"javob": "Bazada hech narsa topilmadi.", "manbalar": []} # Kontekstni raqamlab tayyorlaymiz — model ularga havola qila olsin kontekst = "\n\n".join( f"[{i + 1}] (manba: {b['manba']})\n{b['matn']}" for i, b in enumerate(bolaklar) ) prompt = f"""Quyidagi kontekstga TAYANIB savolga javob bering. Qoidalar: - Faqat kontekstdagi ma'lumotdan foydalaning - Har bir da'vodan keyin manba raqamini qavsda ko'rsating, masalan [1] - Agar javob kontekstda BO'LMASA, aniq ayting: "Berilgan hujjatlarda bu haqda ma'lumot yo'q" - Taxmin qilmang va o'z bilimingizdan qo'shmang KONTEKST: {kontekst} SAVOL: {savol} JAVOB:""" javob = anthropic.Anthropic().messages.create( model="claude-opus-5", max_tokens=2048, messages=[{"role": "user", "content": prompt}], ) matn = "".join(b.text for b in javob.content if b.type == "text") return { "javob": matn, "manbalar": [{"manba": b["manba"], "oxshashlik": round(b["oxshashlik"], 3)} for b in bolaklar], "tokenlar": javob.usage.input_tokens + javob.usage.output_tokens, }Ishga tushirish
rag = RAG() hujjatlar = hujjatlarni_yukla("./hujjatlar") print(f"{len(hujjatlar)} ta hujjat topildi") rag.indeksla(hujjatlar) natija = rag.sora("Ta'til siyosati qanday?") print(natija["javob"]) print("\nManbalar:") for m in natija["manbalar"]: print(f" {m['oxshashlik']:.3f} {m['manba']}")
Bo'lak strategiyasini o'lchash#
Ko'pchilik bo'lak o'lchamini "his-tuyg'u bilan" tanlaydi. To'g'ri yo'l — o'lchash.
def strategiyalarni_solishtir(hujjatlar, test_savollar, variantlar):
"""
test_savollar — [(savol, kutilgan_matn_parchasi), ...]
variantlar — [(olcham, ustma_ust), ...]
"""
natijalar = []
for olcham, ustma_ust in variantlar:
rag = RAG(kolleksiya=f"test_{olcham}_{ustma_ust}")
rag.indeksla(hujjatlar, olcham=olcham, ustma_ust=ustma_ust)
topildi = 0
for savol, kutilgan in test_savollar:
bolaklar = rag.qidir(savol, top_k=5)
if any(kutilgan.lower() in b["matn"].lower() for b in bolaklar):
topildi += 1
natijalar.append({
"olcham": olcham,
"ustma_ust": ustma_ust,
"recall@5": topildi / len(test_savollar),
})
return natijalar
variantlar = [(200, 0), (200, 50), (500, 0), (500, 100), (1000, 200)]
for n in strategiyalarni_solishtir(hujjatlar, test_savollar, variantlar):
print(f"o'lcham={n['olcham']:>4} ustma-ust={n['ustma_ust']:>3} "
f"recall@5={n['recall@5']:.2%}")Tipik natija:
o'lcham= 200 ustma-ust= 0 recall@5=62.00%
o'lcham= 200 ustma-ust= 50 recall@5=74.00%
o'lcham= 500 ustma-ust= 0 recall@5=71.00%
o'lcham= 500 ustma-ust=100 recall@5=86.00% ← eng yaxshi
o'lcham=1000 ustma-ust=200 recall@5=79.00%
Boshqa strategiyalar#
| Usul | Qanday ishlaydi | Qachon |
|---|---|---|
| Qat'iy o'lcham | N so'z yoki belgi | Boshlash uchun; bir xil turdagi matn |
| Rekursiv | Avval paragraf, keyin jumla bo'yicha | Universal tanlov — LangChain standarti |
| Strukturaviy | Markdown sarlavhalari, HTML teglari bo'yicha | Hujjat tuzilmasi aniq bo'lsa |
| Semantik | Jumlalar embeddingi orasidagi farq chegaradan oshsa ajratadi | Mavzu o'zgarishini avtomatik topadi; qimmat |
| Propozitsiyalar | LLM matnni atomar faktlarga bo'ladi | Eng sifatli, eng qimmat |
# Rekursiv bo'luvchi — amalda eng ko'p ishlatiladigan
from langchain_text_splitters import RecursiveCharacterTextSplitter
bolgich = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""], # tartib bo'yicha sinaydi
)
bolaklar = bolgich.split_text(matn)separators tartibi muhim: avval paragraf bo'yicha bo'lishga urinadi, sig'masa jumla
bo'yicha, keyin so'z bo'yicha va oxirida belgi bo'yicha.
Embedding modelini tanlash#
Bu eng muhim qaror. Yomon embedder → noto'g'ri kontekst → va paradoksal ravishda gallyutsinatsiya ko'payadi, chunki model noto'g'ri hujjatga tayanib javob yozadi.
O'z ma'lumotingizda sinang#
from sentence_transformers import SentenceTransformer, util
import time
NOMZODLAR = [
"all-MiniLM-L6-v2", # 22M — tez
"paraphrase-multilingual-MiniLM-L12-v2", # 118M — ko'p tilli
"intfloat/multilingual-e5-base", # 278M — sifatli
"BAAI/bge-m3", # 568M — eng yaxshilaridan
]
def embedderlarni_sina(nomzodlar, test_juftlar):
"""test_juftlar — [(savol, to'g'ri_javob_matni, [noto'g'ri_variantlar]), ...]"""
for nom in nomzodlar:
model = SentenceTransformer(nom)
boshlandi = time.time()
togri = 0
for savol, javob, notogrilar in test_juftlar:
variantlar = [javob] + notogrilar
s = model.encode(savol, convert_to_tensor=True)
v = model.encode(variantlar, convert_to_tensor=True)
if util.cos_sim(s, v)[0].argmax().item() == 0:
togri += 1
vaqt = time.time() - boshlandi
print(f"{nom:<45} aniqlik={togri/len(test_juftlar):.2%} vaqt={vaqt:.1f}s")Xotira va narxni hisoblash#
def baza_hajmi(bolaklar_soni, olcham, bayt=4):
"""Vektor bazasi qancha joy egallaydi."""
return bolaklar_soni * olcham * bayt / 1024**3
for olcham in [384, 768, 1024]:
for n in [10_000, 1_000_000]:
print(f"{n:>9,} bo'lak × {olcham:>4} o'lcham = "
f"{baza_hajmi(n, olcham):>6.2f} GB") 10,000 bo'lak × 384 o'lcham = 0.01 GB
1,000,000 bo'lak × 384 o'lcham = 1.43 GB
10,000 bo'lak × 768 o'lcham = 0.03 GB
1,000,000 bo'lak × 768 o'lcham = 2.86 GB
1,000,000 bo'lak × 1024 o'lcham = 3.81 GB
Kvantlash bilan bu 4 barobar kamayadi (float32 → int8), sifat esa deyarli o'zgarmaydi.
Vektor bazasini tanlash#
| Turi | Misol | Qachon | Cheklovi |
|---|---|---|---|
| Kutubxona | FAISS | Baza o'zgarmaydi, faqat o'qish | CRUD yo'q |
| SQL kengaytmasi | pgvector | Mavjud Postgres bazangiz bor | Hajm katta bo'lsa sekinlashadi |
| Maxsus baza | Chroma, Qdrant, Weaviate | Production, kengayish | Alohida servis |
| Bulut | Pinecone | Infratuzilma bilan shug'ullanmaslik | Qimmat, ma'lumot tashqarida |
Boshlash uchun Chroma — o'rnatish oson, kod sodda, mahalliy ishlaydi. Hajm oshganda Qdrant yoki pgvector'ga o'tasiz.
Tizimni baholash#
Bu bosqichni tashlab ketish — eng ko'p uchraydigan xato. "Ishlayotgandek ko'rinadi" — bu o'lchov emas.
Qidiruvni baholash#
def baholash(rag, test_toplami, top_k=5):
"""
test_toplami — [(savol, [to'g'ri_bo'lak_id_lari]), ...]
"""
precision_lar, recall_lar, rr_lar = [], [], []
for savol, togri_idlar in test_toplami:
topilgan = rag.qidir(savol, top_k)
topilgan_idlar = [t["id"] for t in topilgan]
mos = set(topilgan_idlar) & set(togri_idlar)
precision_lar.append(len(mos) / len(topilgan_idlar) if topilgan_idlar else 0)
recall_lar.append(len(mos) / len(togri_idlar) if togri_idlar else 0)
# MRR — birinchi to'g'ri javob qaysi o'rinda
rr = 0.0
for orin, tid in enumerate(topilgan_idlar, 1):
if tid in togri_idlar:
rr = 1 / orin
break
rr_lar.append(rr)
n = len(test_toplami)
return {
f"precision@{top_k}": sum(precision_lar) / n,
f"recall@{top_k}": sum(recall_lar) / n,
"MRR": sum(rr_lar) / n,
}Metrikalarni tushunish:
| Metrika | Savoli | Qachon muhim |
|---|---|---|
| Precision@k | Topilganlarning nechtasi mos? | Kontekst toza bo'lishi kerak |
| Recall@k | Mos hujjatlarning nechtasi topildi? | Hech narsa qolib ketmasligi kerak |
| MRR | To'g'ri javob qaysi o'rinda? | Savol-javobda — eng muhimi |
MRR ni raqamda ko'ramiz:
1-o'rinda topildi → 1/1 = 1.00
2-o'rinda topildi → 1/2 = 0.50
5-o'rinda topildi → 1/5 = 0.20
umuman topilmadi → 0.00
LLM'ni hakam qilish#
Qidiruvni o'lchash oson. Javob sifatini o'lchash qiyinroq — buning uchun boshqa LLM ishlatiladi.
def faithfulness(savol, javob, kontekst, client):
"""Javobdagi da'volar kontekstdan kelib chiqadimi? 0 dan 1 gacha."""
prompt = f"""Quyidagi javobni tekshiring.
KONTEKST:
{kontekst}
JAVOB:
{javob}
Vazifa:
1. Javobdagi har bir alohida da'voni ajratib yozing
2. Har biri uchun: kontekstdan kelib chiqadimi? (HA/YO'Q)
3. Oxirida: "NATIJA: X/Y" formatida yozing, X — tasdiqlangan da'volar soni
Faqat shu formatda javob bering."""
j = client.messages.create(
model="claude-opus-5", max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
matn = "".join(b.text for b in j.content if b.type == "text")
import re
mos = re.search(r"NATIJA:\s*(\d+)\s*/\s*(\d+)", matn)
if mos:
x, y = int(mos.group(1)), int(mos.group(2))
return x / y if y else 0.0
return NoneAsosiy metrikalar:
| Metrika | Nima o'lchaydi |
|---|---|
| Faithfulness | Javob kontekstga sodiqmi? ← gallyutsinatsiyani o'lchaydi |
| Answer relevance | Javob savolga tegishlimi, yoki suvmi? |
| Context precision | Mos bo'laklar yuqoriroq turibdimi? |
| Answer correctness | Javob faktik jihatdan to'g'rimi? |
ragas kutubxonasi bularning hammasini tayyor beradi:
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
natija = evaluate(
dataset,
metrics=[faithfulness, answer_relevancy, context_precision],
)
print(natija)RAG yoki fine-tuning?#
| RAG | Fine-tuning | |
|---|---|---|
| Bilim yangilash | Bazani yangilaysiz — daqiqalar | Qayta o'qitish — kunlar |
| Narx | Har so'rovda qidiruv | Bir marta qimmat, keyin arzon |
| Manba ko'rsatish | Ha | Yo'q |
| Uslub o'rgatish | Yomon | Yaxshi |
| Yangi qobiliyat | Yo'q | Ha |
| Ma'lumot hajmi | Cheksiz | Cheklangan |
Sodda qoida:
- Modelga nimani bilishi kerak bo'lsa → RAG
- Modelga qanday gapirishi kerak bo'lsa → fine-tuning
- Ikkalasi ham kerak bo'lsa → birga ishlating, ular raqib emas
Uchinchi variant: embedder'ni fine-tuning qilish. Soha juda tor bo'lsa (tibbiyot,
huquq), umumiy embedder yomon ishlaydi. sentence-transformers bilan uni o'z
ma'lumotingizga moslash noldan o'qitishdan ancha arzon.
Nima noto'g'ri ketishi mumkin#
| Belgi | Sabab | Yechim |
|---|---|---|
| Javob "ma'lumot yo'q" deydi, lekin hujjatda bor | Bo'lak topilmayapti | top_k ni oshiring, bo'lak o'lchamini o'zgartiring |
| Javob noto'g'ri hujjatga tayanadi | Embedder sohaga mos emas | Boshqa model sinang, gibrid qidiruv qo'shing |
| Model kontekstni e'tiborsiz qoldiradi | Prompt kuchsiz | "Faqat kontekstdan" qoidasini kuchaytiring |
| Aniq atama topilmayapti | Vektor qidiruvi so'zni emas, ma'noni topadi | BM25 bilan gibrid qidiruv (keyingi bob) |
| Javob to'liq emas | Kerakli ma'lumot bir necha bo'lakda | top_k ni oshiring yoki ota hujjatni torting |
| Juda sekin | Har so'rovda embedding | So'rov embeddinglarini keshlang |
| PDF dan matn chiqmayapti | Skanerlangan hujjat | OCR kerak |
O'z hujjatlaringiz ustida RAG quring va o'lchang
O'zingizga tegishli 10-20 ta hujjat toping (universitet materiallari, ish hujjatlari, qonun matnlari — nima bo'lsa ham) va to'liq RAG tizimini quring.
Keyin o'lchang:
- LLM yordamida 30 ta test savoli generatsiya qiling
recall@5ni o'lchang — to'g'ri bo'lak topilyaptimi?faithfulnessni o'lchang — javob kontekstga sodiqmi?- Kamida uchta o'zgarish sinang: bo'lak o'lchami, embedder,
top_k
Natijalarni jadvalga yozing. Qaysi o'zgarish eng ko'p foyda berdi?
Bonus: top_k ni oshirganda recall va faithfulness qanday o'zgardi? Nima uchun?
Maslahatni ko'rsatish
Test to'plamini avtomatik tuzish: savollar = [] for bolak in bolaklar[:50]: j = client.messages.create( model='claude-haiku-4-5', max_tokens=256, messages=[{'role':'user','content': f'Quyidagi matn asosida BITTA aniq savol yozing. ' f'Faqat savolni yozing:\n\n{bolak}'}], ) savol = ''.join(b.text for b in j.content if b.type=='text') savollar.append((savol.strip(), bolak)) Keyin recall@5 ni o'lchang: savol berilganda o'sha bo'lak topilyaptimi? Tajriba jadvalini yuriting: variant recall@5 faithfulness -------------------------------------------------------- asosiy (500/100, MiniLM) 0.86 0.91 + top_k=10 0.94 0.88 + bge-m3 embedder 0.91 0.93 ... Diqqat: top_k ni oshirsangiz recall oshadi, lekin faithfulness TUSHISHI mumkin — chunki kontekstga shovqin qo'shiladi. Bu klassik murosa.
Xulosa#
- 01RAG — modelga tashqi xotira. Bilim yangilanadi, manba ko'rsatiladi, gallyutsinatsiya kamayadi. Bugungi eng keng tarqalgan naqsh.
- 02Bo'lak o'lchami va embedder tanlovi — sifatni belgilaydigan ikki qaror. Ular texnik mayda-chuydadek ko'rinadi, aslida butun tizim shularga tayanadi.
- 03Ustma-ust tushish deyarli har doim yordam beradi. 10-20% qo'shing — muhim jumla chegarada bo'lib qolmasin.
- 04Promptdagi «faqat kontekstdan foydalaning» va «bilmasangiz ayting» qoidalarini tashlab ketmang. Ular gallyutsinatsiyani sezilarli kamaytiradi.
- 05Bi-encoder tez, cross-encoder aniq. Amalda ikkalasi birga: keng qidiruv + tor saralash.
- 06«Ishlayotgandek ko'rinadi» — bu o'lchov emas. recall@k va faithfulness'ni o'lchang, keyin o'zgartiring.
Bu naïve RAG — eng sodda ko'rinishi. U ko'p holatlarda ishlaydi, lekin muammolari ham bor: noto'g'ri bo'lak topilishi, aniq atamalarni topa olmaslik, kontekst yetarli bo'lmasligi.
Keyingi bobda ilg'or RAG — bu muammolarning har biriga aniq yechim.