Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. RAG nima va nima uchun
- 2.2. Quvur bo'g'inlari
- 2.3. Chunking strategiyalari
- 2.4. Metadata va filtrlash
- 2.5. Indeksni yangilash
- 2.6. Kontekstni prompt ga sig'dirish va manba iqtiboslari
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — To'liq quvur: yuklash, tozalash, chunking, retrieval, prompt
- Misol 2 — Chunking strategiyalari va javobning bo'linishi
- Misol 3 — Metadata: versiyalar va filtrlash
- Misol 4 — Indeksni yangilash va kontekst byudjeti
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
25.8-dars: RAG — chunking va indeks
25-QISM — KATTA TIL MODELLARI · 8-dars
1. Kirish va motivatsiya
25.7-darsda savolga eng yaqin hujjatni topishni o'rgandik. Endi shu qidiruvni LLM bilan birlashtiramiz: RAG (Retrieval-Augmented Generation, qidiruv bilan boyitilgan generatsiya). G'oya oddiy: modelga savolni to'g'ridan-to'g'ri bermaymiz, avval o'z hujjatlarimizdan tegishli bo'laklarni topamiz va ularni promptga "manba" sifatida qo'shamiz; model javobni shu manbalarga tayanib yozadi va qaysi manbadan olganini ko'rsatadi.
Nega bu kerak? Birinchidan, bilim yangilanadi: model o'rgatilgandan keyin chiqqan buyruq yoki yangi tarif uning "boshida" yo'q, lekin indeksimizda bor — modelni qayta o'rgatmasdan (25.11-darsdagi fine-tuning dan ancha arzon) yangi bilim beramiz. Ikkinchidan, manba ko'rsatish: foydalanuvchi va auditor javob qaysi hujjatning qaysi bandidan olinganini tekshira oladi. Uchinchidan, gallyutsinatsiyani kamaytirish: model "eslab qolgan" narsani emas, oldida turgan matnni qayta aytadi (kamaytiradi, lekin yo'q qilmaydi — buni keyingi darsda o'lchaymiz).
RAG ning sifati ko'pincha modelga emas, quvurning oddiy bo'g'inlariga bog'liq: hujjat qanday o'qildi, qanday tozalandi, qanday bo'laklarga bo'lindi, qanday metadata saqlandi, indeks hujjatlar bilan sinxronmi. Bu darsda aynan shu bo'g'inlarni o'lchaymiz.
Real vaziyat. Kompaniya HR qoidalari bo'yicha ichki chat-bot ishga tushirdi. Bir oydan keyin shikoyatlar: bot ta'til muddatini noto'g'ri aytmoqda. Tekshiruv uchta sababni topdi. Birinchisi: PDF dan olingan matnda so'zlar qator oxirida defis bilan bo'lingan ("ta'-til"), va qidiruv bu bo'laklarni topmayapti. Ikkinchisi: hujjat 30 so'zlik qat'iy bo'laklarga bo'lingan — ba'zi javob jumlalari ikki bo'lak orasida qolgan, "Bu holatda muddat 14 kun" kabi jumlalar esa sarlavhasidan ajralib, nimaga tegishli ekani noma'lum bo'lib qolgan. Uchinchisi: yangi tahrirdagi nizom indeksga qo'shilgan, lekin eskisi o'chirilmagan — bot ikkalasini ham topadi va ko'pincha eskisidan javob beradi. Bu darsning to'rt misoli aynan shu uch muammoni o'lchaydi.
Bu darsda RAG quvurini boshidan oxirigacha quramiz: yuklash, tozalash, chunking strategiyalari va ularning retrieval sifatiga ta'siri, metadata va filtrlash, indeksni yangilash va kontekstni token byudjetiga sig'dirib, manba iqtiboslari bilan prompt yig'ish.
Bu darsda:
- RAG nima va nima uchun
- Quvur: yuklash → tozalash → chunking → embedding → indeks → retrieval → prompt
- Chunking strategiyalari va javobning chunk chegarasida bo'linishi
- Metadata va filtrlash: versiya, sana, bo'lim
- Indeksni yangilash
- Kontekstni prompt ga sig'dirish va manba iqtiboslari
- Tuzoqlar
ℹ Mashinada LLM API yo'q. Retrieval qismi haqiqiy va o'lchanadi (TF-IDF belgi n-gramlari — 25.7-darsdagi kabi). Generator o'rnida — ekstraktiv javob tanlovchi: topilgan chunklardan savolga eng mos raqamli jumlani tanlaydi. U LLM emas va "LLM shunday javob beradi" degan da'vo emas; u retrieval natijasi javobga qanday ta'sir qilishini oddiy va takrorlanadigan tarzda ko'rsatadi. Korpus — kod ichida yaratilgan kompaniya ichki qoidalari (6 hujjat, 30 bo'lim, 180 fakt, har fakt uchun bitta savol — jami 180 savol).
2. Nazariya — chuqur tushuntirish
2.1. RAG nima va nima uchun
ODDIY LLM: savol -> model -> javob (faqat o'rgatilgan bilim)
RAG: savol -> retrieval (o'z hujjatlarimiz) -> top-k bo'lak
-> prompt = ko'rsatma + manbalar [1..k] + savol
-> model -> javob + iqtiboslar [n]
NIMA BERADI:
bilim yangilanishi - indeksni yangilash modelni qayta o'rgatishdan arzon va tez
manba ko'rsatish - har da'vo [n] bilan tekshiriladigan hujjatga bog'lanadi
gallyutsinatsiya - kamayadi (model oldidagi matnga tayanadi), lekin yo'qolmaydi
maxfiylik/huquq - kim qaysi hujjatni ko'ra oladi - metadata filtri bilan
NIMA BERMAYDI:
retrieval topmagan narsani model ham bilmaydi (yoki o'ylab topadi)
noto'g'ri/eskirgan hujjat topilsa - javob ham noto'g'ri/eskirgan
murakkab xulosa (ko'p hujjatni birlashtirish) - retrieval va prompt dizayni talab qiladi
RAG va FINE-TUNING (LLM fine-tuning darsida batafsil):
RAG - FAKTLAR (tez o'zgaradi, manba kerak)
fine-tuning - USLUB va FORMAT, vazifaga moslashuv (kamroq o'zgaradi)RAG = qidiruv + prompt: javob sifati retrieval sifatidan yuqori bo'lolmaydi.
2.2. Quvur bo'g'inlari
1. YUKLASH PDF, DOCX, HTML, wiki -> matn
muammolar: sahifa sarlavhalari/raqamlari, defisli bo'linish,
jadvallar, ikki ustunli sahifa, skanerlangan rasm (OCR)
2. TOZALASH takrorlanuvchi sarlavha/footer ni olib tashlash,
"ta'-\ntil" -> "ta'til", qator uzilishlarini birlashtirish,
tuzilmani (## sarlavhalar) SAQLASH
3. CHUNKING matnni qidiriladigan bo'laklarga bo'lish 2.3-bob
4. EMBEDDING har chunk -> vektor 25.7-bob; hujjat va so'rov - bir xil model
5. INDEKS vektorlar + matn + metadata (manba, bo'lim, sana, versiya)
6. RETRIEVAL so'rov -> top-k chunk (filtrlar bilan)
7. PROMPT ko'rsatma + raqamlangan manbalar + savol, token byudjeti ichida
8. GENERATSIYA javob + [n] iqtiboslar; manbada yo'q bo'lsa - "bilmayman"
1-MISOL (180 savol, top-3):
variant hit@3 javob to'g'ri
xom matn + qat'iy 30 so'z 57.8% 57.8%
tozalangan + qat'iy 30 so'z 66.1% 70.6%
tozalangan + sarlavha + 3 jumla 96.7% 76.7%Tozalash javob to'g'riligini +0.128 ga oshirdi (SE 0.039 — sezilarli): defis bilan bo'lingan so'zlar va har sahifadagi "MAXFIY..." sarlavhasi qidiruvni chalg'itardi. Chunking ni o'zgartirish retrieval ni (hit@3) keskin oshirdi (66.1% → 96.7%), lekin ekstraktiv javob kamroq o'sdi (+0.061, SE 0.031 — sezilarli emas): endi to'g'ri chunk kontekstda bor, lekin generator uning ichidan to'g'ri jumlani har doim ham tanlay olmaydi. Bu RAG ning muhim darsi: retrieval va generatsiyani alohida o'lchang (keyingi dars).
Quvur eng zaif bo'g'inicha kuchli: yuklash va tozalash — "zerikarli", lekin birinchi navbatdagi ish.
2.3. Chunking strategiyalari
QAT'IY UZUNLIK (n so'z yoki token):
+ oddiy, har qanday matnga
- jumla o'rtasida kesadi -> javob ikki chunk orasida BO'LINADI
2-misol: 30 so'z - javob jumlalarining 26.7% i bo'lingan
OVERLAP (qo'shni chunklar m so'z bilan ustma-ust):
bo'linishni yo'qotadi (30 + overlap 10: 0.0%), lekin indeks kattalashadi
(96 -> 138 chunk) va natijalarda takrorlar paydo bo'ladi
JUMLA / JUMLALAR OYNASI:
tabiiy chegaralar, bo'linish yo'q
- bitta jumla - kontekstsiz: "Bu holatda muddati 14 kun" - nimaning?
2-misol: 'jumla' - yashirin mavzuli jumlalarda hit@3 2.0%
BO'LIM (sarlavha bo'yicha):
+ ma'no birligi; sarlavha kontekstni beradi
- bo'limlar notekis: real hujjatda bir bo'lim bir necha sahifa bo'lishi mumkin
SARLAVHA + JUMLALAR OYNASI (kontekstli chunk):
har chunk boshiga bo'lim sarlavhasini qo'shish: "Tibbiy ta'til. Bu holatda ..."
kichik chunk + yo'qolmagan kontekst
2-MISOL (180 savol, 300 tokenli teng byudjet):
strategiya chunk bo'lingan hit@3 hit@300t token (top-3)
qat'iy 30 96 26.7% 67.8% 68.3% 157
qat'iy 30 + overlap 10 138 0.0% 82.8% 93.9% 162
qat'iy 120 24 6.1% 93.3% 82.8% 642
jumla 330 0.0% 72.2% 95.0% 47
3 jumla, qadam 2 150 0.0% 93.9% 96.1% 137
sarlavha + 3 jumla 150 0.0% 97.8% 100.0% 149
bo'lim 30 0.0% 100.0% 100.0% 505Strategiyalarni solishtirishda teng kontekst byudjeti muhim: katta chunk top-3 da ko'proq matn olib keladi va hit@3 ni "sotib oladi". bo'lim hit@3 da 100%, lekin top-3 uchun 505 token; qat'iy 120 hit@3 da 93.3%, lekin 300 token byudjetga faqat bitta chunk sig'adi — 82.8%. Teng byudjetda sarlavha + 3 jumla va bo'lim ikkalasi 100%; qolganlari undan sezilarli yomon. Bizning bo'limlar qisqa (~170 token) — real hujjatlarda uzun bo'limlarni baribir bo'lish kerak, shuning uchun amaliy tavsiya — sarlavha kontekstli, jumla chegarasini hurmat qiladigan kichik chunklar.
Chunk — ma'no birligi: jumlani bo'lmang, sarlavha kontekstini saqlang va strategiyalarni teng token byudjetida taqqoslang.
2.4. Metadata va filtrlash
HAR CHUNK BILAN SAQLANADI:
manba (hujjat nomi/ID), bo'lim sarlavhasi, sahifa
versiya, kuchga kirgan sana, "amalda" belgisi
bo'lim/departament, kirish huquqi (kim ko'ra oladi), til
FILTRLASH:
OLDINDAN (pre-filter): faqat mos chunklar orasida qidirish
-> top-k to'liq, hammasi mos
KEYIN (post-filter): top-k ni topib, keyin mos kelmaganini tashlash
-> kam natija yoki BO'SH natija (3-misol: k = 1 da 56.8% bo'sh)
3-MISOL (2024 va 2025 versiyalar, qiymati o'zgargan 74 fakt, top-3):
usul to'g'ri (2025) eskirgan (2024) chunklar
filtrsiz 29.7% 37.8% 3.00
keyin filtr 60.8% 0.0% 1.36
oldindan filtr 67.6% 0.0% 3.00
NEGA FILTRSIZ YOMON:
eski va yangi versiya matni deyarli bir xil -> embedding ularni ajrata olmaydi
"qaysi biri amalda" - ma'no emas, METADATA savoli
KIRISH HUQUQI:
filtr qidiruvdan OLDIN - aks holda ruxsatsiz hujjat promptga tushadi
(LLM xavfsizligi darsida batafsil)Versiya, sana va huquq — metadata, embedding emas; eskirgan va ruxsatsiz hujjatni qidiruvdan oldin chiqarib tashlang.
2.5. Indeksni yangilash
HUJJAT O'ZGARDI:
1. o'zgarganini aniqlash: hujjat matni xeshi (sha256) yoki yangilanish sanasi
2. shu hujjatning ESKI chunklarini o'chirish (hujjat_id bo'yicha)
3. yangi chunklarni kodlab qo'shish
-> faqat o'zgargan hujjat qayta kodlanadi (4-misol: 1/6 hujjat, 28 chunk)
XATOLAR (4-misol, 6 o'zgargan + 4 yangi fakt):
yangilanmagan indeks to'g'ri 0/10, eski qiymat 6/6
faqat qo'shish to'g'ri 5/10, eski qiymat 4/6 <- eski chunklar o'chirilmagan
o'chirish + qo'shish to'g'ri 9/10
to'liq qayta qurish to'g'ri 10/10
EMBEDDING MODELI O'ZGARMAYDI - SHART:
neyron model: hujjat qo'shilganda eski vektorlar o'zgarmaydi -> inkremental to'g'ri
TF-IDF: IDF butun korpusga bog'liq -> inkremental indeks sekin "eskiradi"
4-misol: inkremental va to'liq qayta qurish top-3 mosligi 95.1%
model/vektorizator almashsa -> TO'LIQ qayta qurish
AMALDA:
hujjat_id + versiya + xesh ni har chunkda saqlang
vaqti-vaqti bilan to'liq qayta qurish va solishtirish
o'chirilgan hujjatlar ham indeksdan o'chsinYangilash = o'chirish + qo'shish; faqat qo'shish eskirgan javoblarni indeksda qoldiradi.
2.6. Kontekstni prompt ga sig'dirish va manba iqtiboslari
TOKEN BYUDJETI:
kontekst oynasi katta bo'lsa ham: har token pul va kechikish (25.2, 25.6)
ortiqcha chunklar - chalg'ituvchi (distraktor) matn
SIG'DIRISH ALGORITMI (4-misol):
top-k (masalan 10) -> ball bo'yicha tartib
ochko'z: byudjetga sig'sa - olamiz; sig'masa - keyingisiga o'tamiz
deyarli takror chunklarni (overlap, Jaccard > 0.6) tashlab ketamiz
4-MISOL (180 savol, top-10 dan):
byudjet token chunk javob kontekstda ekstraktiv to'g'ri
60 50 1.0 80.0% 70.0%
120 100 2.0 95.6% 78.9%
240 220 4.5 99.4% 77.2%
480 450 9.2 100.0% 75.6%
kontekst ko'paysa - javob kontekstda bo'lish ehtimoli oshadi,
lekin generator uchun chalg'ituvchilar ham ko'payadi
PROMPT TUZILISHI:
ko'rsatma: "Faqat manbalarga tayan; har da'vodan keyin [n];
manbada bo'lmasa - 'bilmayman' de"
Manbalar:
[1] (Ta'til nizomi, 2025, "O'quv ta'tili") ...
[2] ...
Savol: ...
TARTIB:
eng muhim manbalarni boshiga (va/yoki oxiriga) - uzun kontekstning
o'rtasidagi ma'lumot kamroq e'tibor olishi mumkin ("lost in the middle")
manba raqami va metadata (nom, versiya) - iqtibos tekshiriladigan bo'lsinKontekst — byudjet ichidagi eng foydali, takrorlanmagan chunklar; har manba raqamlangan va metadata bilan, ko'rsatma esa "bilmayman" ga ruxsat beradi.
2.7. Tuzoqlar
Asosiy tuzoqlar: PDF matnini tozalamasdan indekslash (sahifa sarlavhalari, defisli bo'linish); tuzilmani (sarlavhalarni) yo'qotib yuborish; jumla o'rtasida kesadigan qat'iy chunklar; kontekstsiz bitta jumla chunklar ("Bu holatda ..."); strategiyalarni teng token byudjetisiz taqqoslash; metadata saqlamaslik; eskirgan versiyani filtrlamaslik yoki faqat keyin filtrlash (bo'sh natija); kirish huquqini qidiruvdan keyin tekshirish; hujjat yangilanganda eski chunklarni o'chirmaslik; embedding modelini almashtirib indeksni qayta qurmaslik; kontekstga "iloji boricha ko'p" chunk tiqish; takror (overlap) chunklarni promptga ikki marta qo'yish; manbalarni raqamlamaslik va "bilmayman" ga ruxsat bermaslik; retrieval va generatsiyani alohida o'lchamaslik.
3. Tez ma'lumotnoma
import hashlib
import re
# ---- tozalash ----
t = re.sub(r"([\w'])-\n(\w)", r"\1\2", xom) # defisli bo'linish
qatorlar = [q for q in t.split("\n") if q.strip() != SAHIFA_SARLAVHASI]
# ---- kontekstli chunk: sarlavha + 3 jumla, qadam 2 ----
chunklar = [{"matn": f"{sarlavha}. " + " ".join(js[i:i + 3]),
"hujjat_id": hujjat_id, "bolim": sarlavha, "versiya": "2025", "amalda": True}
for sarlavha, js in bolimlar for i in range(0, len(js) - 1, 2)]
# ---- oldindan filtr ----
ball = np.where([c["amalda"] and c["bolim_turi"] in ruxsat for c in chunklar], ball, -np.inf)
# ---- yangilash ----
if hashlib.sha256(yangi_matn.encode()).hexdigest() != saqlangan_xesh[hujjat_id]:
chunklar = [c for c in chunklar if c["hujjat_id"] != hujjat_id] # o'chirish
chunklar += chunkla(yangi_hujjat) # qo'shish
# ---- prompt yig'ish (haqiqiy API bilan) ----
import anthropic
kontekst = "\n".join(f"[{i + 1}] ({c['hujjat_id']}, {c['versiya']}) {c['matn']}"
for i, c in enumerate(tanlangan))
client = anthropic.Anthropic()
javob = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=("Siz kompaniya qoidalari bo'yicha yordamchisiz. Faqat berilgan manbalarga tayaning. "
"Har da'vodan keyin manba raqamini [n] ko'rinishida yozing. "
"Javob manbalarda bo'lmasa, 'bilmayman' deng."),
messages=[{"role": "user", "content": f"Manbalar:\n{kontekst}\n\nSavol: {savol}"}],
)RAG: chunking va indeks xulosasi
RAG = retrieval + prompt; javob retrieval dan yaxshi bo'lolmaydi
yuklash va tozalash: sarlavha/footer, defis, tuzilmani saqlash
chunk: jumla chegarasi + sarlavha konteksti; strategiyalarni teng byudjetda solishtiring
metadata: manba, bo'lim, versiya, sana, huquq; filtr - qidiruvdan OLDIN
yangilash: xesh -> o'chirish + qo'shish; model almashsa - to'liq qayta qurish
kontekst: byudjet ichida, takrorsiz, raqamlangan manbalar, "bilmayman" ga ruxsat
retrieval va generatsiyani alohida o'lchang4. Batafsil misollar
Misollar real numpy/sklearn bilan (Python 3.14). Korpus kod ichida yaratiladi. Retrieval — TF-IDF belgi n-gramlari (3-5); generator o'rnida — ekstraktiv jumla tanlovchi (LLM emas).
Misol 1 — To'liq quvur: yuklash, tozalash, chunking, retrieval, prompt
"""RAG quvuri: yuklash -> tozalash -> chunking -> embedding -> indeks -> retrieval -> prompt -> javob."""
import math
import random
import re
import textwrap
import warnings
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
HUJJATLAR = {
"Ta'til nizomi": ["yillik ta'til", "o'quv ta'tili", "tibbiy ta'til", "haq to'lanmaydigan ta'til",
"ota-onalik ta'tili"],
"Xizmat safari": ["mahalliy safar", "xorijiy safar", "safar avansi", "mehmonxona xarajati",
"safar hisoboti"],
"Ish vaqti": ["kunlik ish vaqti", "tushlik tanaffusi", "qo'shimcha ish", "tungi smena",
"bayram navbatchiligi"],
"Masofaviy ish": ["uydan ishlash", "gibrid jadval", "internet kompensatsiyasi",
"onlayn yig'ilish", "masofaviy jihozlar"],
"Mukofotlash": ["choraklik mukofot", "yillik bonus", "tavsiya mukofoti", "loyiha mukofoti",
"stajyor mukofoti"],
"IT xavfsizlik": ["parol siyosati", "noutbuk", "vpn ulanish", "elektron pochta", "zaxira nusxa"],
}
ATRIBUTLAR = [("muddati", "kun"), ("eng ko'p davomiyligi", "kun"), ("ariza berish muddati", "kun"),
("kompensatsiya miqdori", "ming so'm"), ("tasdiqlash muddati", "ish kuni"),
("oylik limiti", "soat"), ("ustama foizi", "foiz")]
TOLDIRUVCHI = ["Xodim ushbu qoidalar bilan tanishib chiqqanini imzo bilan tasdiqlaydi.",
"Qo'shimcha savollar bo'yicha kadrlar bo'limiga murojaat qilinadi.",
"Qoidalar barcha filiallar uchun bir xilda amal qiladi.",
"Rahbar xodimning arizasini ko'rib chiqib, qaror qabul qiladi.",
"Istisno holatlar alohida buyruq bilan belgilanadi.",
"Hujjatlar elektron tizim orqali topshiriladi.",
"Qoidani buzish intizomiy javobgarlikka olib keladi.",
"Ma'lumotlar maxfiy saqlanadi va uchinchi shaxslarga berilmaydi."]
def korpus_yarat(rng, yashirin=0.3):
"""Qaytaradi: hujjatlar [{nom, bolimlar: [(sarlavha, [jumlalar])]}] va savollar
[{savol, qiymat, jumla, hujjat}]. yashirin - mavzu faqat sarlavhada bo'lgan jumlalar ulushi."""
hujjatlar, savollar = [], []
for nom, mavzular in HUJJATLAR.items():
bolimlar = []
for mavzu in mavzular:
jumlalar = []
for atr, birlik in rng.sample(ATRIBUTLAR, 6):
qiymat = rng.randint(2, 90)
if rng.random() < yashirin:
j = f"Bu holatda {atr} {qiymat} {birlik} etib belgilanadi."
else:
j = f"{mavzu.capitalize()} uchun {atr} {qiymat} {birlik}ni tashkil etadi."
jumlalar.append(j)
savollar.append({"savol": f"{mavzu} bo'yicha {atr} qancha?", "qiymat": qiymat,
"jumla": j, "hujjat": nom})
jumlalar += rng.sample(TOLDIRUVCHI, 5)
rng.shuffle(jumlalar)
bolimlar.append((mavzu.capitalize(), jumlalar))
hujjatlar.append({"nom": nom, "bolimlar": bolimlar})
return hujjatlar, savollar
def matn(hujjat):
return "\n\n".join(f"## {s}\n" + " ".join(j) for s, j in hujjat["bolimlar"])
SARLAVHA = "MAXFIY - FAQAT ICHKI FOYDALANISH UCHUN"
def pdf_dan_kelgandek(hujjat, rng):
"""Xom matn: 60 belgili qatorlar, so'z bo'linishi (defis), sahifa sarlavhasi va raqami."""
qatorlar = []
for blok in matn(hujjat).split("\n\n"):
bosh, tana = blok.split("\n", 1)
qatorlar.append(bosh) # bo'lim sarlavhasi alohida qator
for q in textwrap.wrap(tana, 60):
if rng.random() < 0.4 and qatorlar and " " in q:
oldingi = qatorlar[-1].rsplit(" ", 1)
soz, q = q.split(" ", 1)[0], q.split(" ", 1)[1]
yarim = max(2, len(soz) // 2)
qatorlar[-1] = " ".join(oldingi) + " " + soz[:yarim] + "-"
q = soz[yarim:] + " " + q
qatorlar.append(q)
qatorlar.append("")
xom, sahifa = [], 1
for i, q in enumerate(qatorlar):
if i % 14 == 0:
xom.append(SARLAVHA)
xom.append(q)
if i % 14 == 13:
xom.append(f"--- Sahifa {sahifa} ---")
sahifa += 1
return "\n".join(xom)
def tozala(xom):
qatorlar = [q for q in xom.split("\n")
if q.strip() != SARLAVHA and not re.fullmatch(r"--- Sahifa \d+ ---", q.strip())]
t = re.sub(r"([\w'])-\n(\w)", r"\1\2", "\n".join(qatorlar)) # defis bilan bo'lingan so'zlar
chiqish = []
for q in t.split("\n"):
if q.startswith("## "):
chiqish.append("\n" + q + "\n") # sarlavha - alohida qator
elif q.strip():
chiqish.append(q.strip() + " ") # paragraf ichidagi uzilish -> bo'shliq
return re.sub(r"[ \t]+", " ", "".join(chiqish)).strip()
def bolimlarga(toza):
bolimlar = []
for blok in re.split(r"\n(?=## )", toza.strip()):
bosh, _, tana = blok.partition("\n")
jumlalar = [j.strip() for j in re.split(r"(?<=\.)\s+", tana.strip()) if j.strip()]
bolimlar.append((bosh.replace("## ", "").strip(), jumlalar))
return bolimlar
def chunk_sarlavha(bolimlar, oyna=3, qadam=2):
return [f"{s}. " + " ".join(js[i:i + oyna])
for s, js in bolimlar for i in range(0, max(1, len(js) - oyna + qadam), qadam)]
def chunk_qatiy(matn_, n=30):
s = matn_.split()
return [" ".join(s[i:i + n]) for i in range(0, len(s), n)]
def harflar(t):
return re.sub(r"[^a-z0-9]", "", t.lower())
def ildizlar(t):
return {w[:5] for w in re.findall(r"[a-z']+", t.lower()) if len(w) > 3}
def ekstraktiv(savol, chunklar):
"""Stub generator emas - ekstraktiv: topilgan chunklardan eng mos raqamli jumlani tanlaydi."""
q, eng = ildizlar(savol), (-1.0, None, None)
for raqam, c in enumerate(chunklar):
for j in re.split(r"(?<=\.)\s+", c):
if re.search(r"\d", j):
ball = len(q & ildizlar(j)) + 0.5 * len(q & ildizlar(c)) - 0.01 * raqam
if ball > eng[0]:
eng = (ball, j, raqam + 1)
son = re.search(r"\d+", eng[1]) if eng[1] else None
return (int(son.group()) if son else None), eng[1], eng[2]
def prompt_yig(savol, chunklar, manbalar):
kontekst = "\n".join(f"[{i + 1}] ({m}) {c}" for i, (c, m) in enumerate(zip(chunklar, manbalar)))
return ("Faqat quyidagi manbalarga tayanib javob bering. Har da'vodan keyin manba raqamini "
"[n] ko'rinishida yozing. Javob manbalarda bo'lmasa, 'bilmayman' deng.\n\n"
f"Manbalar:\n{kontekst}\n\nSavol: {savol}")
def quvur(hujjatlar, savollar, rng, tozalash, strategiya):
chunklar, manba = [], []
for h in hujjatlar:
xom = pdf_dan_kelgandek(h, rng)
t = tozala(xom) if tozalash else xom
cs = chunk_sarlavha(bolimlarga(t)) if strategiya == "sarlavha" else chunk_qatiy(t)
chunklar += cs
manba += [h["nom"]] * len(cs)
tf = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), sublinear_tf=True).fit(chunklar)
C = tf.transform(chunklar)
top = np.argsort(-(tf.transform([s["savol"] for s in savollar]) @ C.T).toarray(), axis=1,
kind="stable")[:, :3]
hit = np.array([any(harflar(s["jumla"]) in harflar(chunklar[j]) for j in t)
for s, t in zip(savollar, top)], float)
togri = np.array([ekstraktiv(s["savol"], [chunklar[j] for j in t])[0] == s["qiymat"]
for s, t in zip(savollar, top)], float)
return chunklar, manba, top, hit, togri
def main() -> None:
hujjatlar, savollar = korpus_yarat(random.Random(0))
print("=== 1. Yuklash: 'PDF dan olingan' xom matn (parcha) ===")
xom = pdf_dan_kelgandek(hujjatlar[0], random.Random(5))
for q in xom.split("\n")[:5]:
print(" | " + q)
print("\n=== 2. Tozalash ===")
toza = tozala(xom)
print(f" belgilar {len(xom)} -> {len(toza)}; defisli bo'linishlar {len(re.findall(r'-' + chr(10), xom))} -> "
f"{len(re.findall(r'-' + chr(10), toza))}")
print(" | " + toza.split("\n")[0])
print(" | " + textwrap.shorten(toza.split("\n")[1], 80))
print(f"\n=== 3. Quvur variantlari ({len(savollar)} savol, top-3) ===")
print(" variant chunklar hit@3 javob to'g'ri")
natija = {}
for nom, t, s in [("xom + qat'iy 30", False, "qatiy"), ("tozalangan + qat'iy 30", True, "qatiy"),
("tozalangan + sarlavha 3 jumla", True, "sarlavha")]:
chunklar, manba, top, hit, togri = quvur(hujjatlar, savollar, random.Random(5), t, s)
natija[nom] = (hit, togri, chunklar, manba, top)
print(f" {nom:<29} {len(chunklar):>8} {hit.mean():>7.1%} {togri.mean():>14.1%}")
print("\n=== 4. Juftlashgan farq (javob to'g'riligi, SE) ===")
for a, b in [("tozalangan + qat'iy 30", "xom + qat'iy 30"),
("tozalangan + sarlavha 3 jumla", "tozalangan + qat'iy 30")]:
f = natija[a][1] - natija[b][1]
se = f.std(ddof=1) / math.sqrt(len(f))
print(f" {a} - {b}: {f.mean():+.3f}, SE {se:.3f} -> "
f"{'sezilarli' if abs(f.mean()) > 2 * se else 'sezilarli emas'}")
print("\n=== 5. Iqtiboslar bilan prompt va ekstraktiv javob (1 savol) ===")
_, _, chunklar, manba, top = natija["tozalangan + sarlavha 3 jumla"]
s = savollar[7]
tanlangan = [chunklar[j] for j in top[7]]
p = prompt_yig(s["savol"], [textwrap.shorten(c, 70) for c in tanlangan], [manba[j] for j in top[7]])
for q in p.split("\n")[2:8]:
print(" | " + q)
qiymat, jumla, raqam = ekstraktiv(s["savol"], tanlangan)
print(f" javob: {jumla} [{raqam}] (haqiqiy qiymat {s['qiymat']})")
print(" \u2b50 RAG sifati quvurning eng zaif bo'g'iniga teng: tozalash va chunking - birinchi")
if __name__ == "__main__":
with warnings.catch_warnings():
warnings.simplefilter("ignore")
main()Natijaning muhim qismi:
=== 1. Yuklash: 'PDF dan olingan' xom matn (parcha) ===
| MAXFIY - FAQAT ICHKI FOYDALANISH UCHUN
| ## Yillik ta'til
| Ma'lumotlar maxfiy saqlanadi va uchinchi shaxslarga
| berilmaydi. Istisno holatlar alohida buyruq bilan
| belgilanadi. Hujjatlar elektron tizim orqali topshiriladi.
=== 2. Tozalash ===
belgilar 3832 -> 3458; defisli bo'linishlar 32 -> 0
| ## Yillik ta'til
| Ma'lumotlar maxfiy saqlanadi va uchinchi shaxslarga berilmaydi. Istisno [...]
=== 3. Quvur variantlari (180 savol, top-3) ===
variant chunklar hit@3 javob to'g'ri
xom + qat'iy 30 110 57.8% 57.8%
tozalangan + qat'iy 30 96 66.1% 70.6%
tozalangan + sarlavha 3 jumla 149 96.7% 76.7%
=== 4. Juftlashgan farq (javob to'g'riligi, SE) ===
tozalangan + qat'iy 30 - xom + qat'iy 30: +0.128, SE 0.039 -> sezilarli
tozalangan + sarlavha 3 jumla - tozalangan + qat'iy 30: +0.061, SE 0.031 -> sezilarli emas
=== 5. Iqtiboslar bilan prompt va ekstraktiv javob (1 savol) ===
| Manbalar:
| [1] (Ta'til nizomi) O'quv ta'tili. O'quv ta'tili uchun ustama foizi 87 foizni [...]
| [2] (Ta'til nizomi) O'quv ta'tili. O'quv ta'tili uchun tasdiqlash muddati 9 ish [...]
| [3] (Ta'til nizomi) O'quv ta'tili. O'quv ta'tili uchun ariza berish muddati 80 kunni [...]
|
| Savol: o'quv ta'tili bo'yicha oylik limiti qancha?
javob: O'quv ta'tili uchun oylik limiti 72 soatni tashkil etadi. [1] (haqiqiy qiymat 72)
⭐ RAG sifati quvurning eng zaif bo'g'iniga teng: tozalash va chunking - birinchiNima ko'rsatdi: 1-bo'lim "PDF dan olingan" xom matnni ko'rsatadi: har 14 qatorda "MAXFIY..." sarlavhasi, sahifa raqamlari va qator oxirida defis bilan bo'lingan so'zlar (bizda 32 ta). Tozalash ularni olib tashladi (3832 → 3458 belgi, defisli bo'linishlar 32 → 0) va eng muhimi — ## sarlavha qatorlarini alohida saqladi, shunda keyin bo'limlarga ajratish mumkin. 3-bo'limda uchta quvur: xom matn + qat'iy 30 so'z — hit@3 57.8%, javob 57.8%; tozalangan + qat'iy 30 — 66.1% va 70.6%; tozalangan + sarlavha kontekstli chunklar — hit@3 96.7%, javob 76.7%. Tozalashning foydasi sezilarli (+0.128, SE 0.039). Chunking ni almashtirish retrieval ni 30 punktdan ko'proq oshirdi, lekin ekstraktiv javobni faqat +0.061 ga (SE 0.031 — sezilarli emas): to'g'ri chunk endi deyarli har doim kontekstda, lekin bitta bo'limning uchta chunki bir-biriga o'xshash va "muddati", "ariza berish muddati", "tasdiqlash muddati" kabi atributlar generatorni chalg'itadi. Demak, bu quvurda keyingi zaif bo'g'in — retrieval emas, generatsiya. 5-bo'lim prompt ko'rinishini ko'rsatadi: ko'rsatma, raqamlangan manbalar hujjat nomi bilan, savol; ekstraktiv javob 72 ni [1] iqtibos bilan qaytardi. Bog'liq bo'limlar: 2.1, 2.2, 2.6.
Misol 2 — Chunking strategiyalari va javobning bo'linishi
"""Chunking strategiyalari: qat'iy uzunlik, overlap, jumla va bo'lim - retrieval sifatiga ta'siri."""
import math
import random
import re
import warnings
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
HUJJATLAR = {
"Ta'til nizomi": ["yillik ta'til", "o'quv ta'tili", "tibbiy ta'til", "haq to'lanmaydigan ta'til",
"ota-onalik ta'tili"],
"Xizmat safari": ["mahalliy safar", "xorijiy safar", "safar avansi", "mehmonxona xarajati",
"safar hisoboti"],
"Ish vaqti": ["kunlik ish vaqti", "tushlik tanaffusi", "qo'shimcha ish", "tungi smena",
"bayram navbatchiligi"],
"Masofaviy ish": ["uydan ishlash", "gibrid jadval", "internet kompensatsiyasi",
"onlayn yig'ilish", "masofaviy jihozlar"],
"Mukofotlash": ["choraklik mukofot", "yillik bonus", "tavsiya mukofoti", "loyiha mukofoti",
"stajyor mukofoti"],
"IT xavfsizlik": ["parol siyosati", "noutbuk", "vpn ulanish", "elektron pochta", "zaxira nusxa"],
}
ATRIBUTLAR = [("muddati", "kun"), ("eng ko'p davomiyligi", "kun"), ("ariza berish muddati", "kun"),
("kompensatsiya miqdori", "ming so'm"), ("tasdiqlash muddati", "ish kuni"),
("oylik limiti", "soat"), ("ustama foizi", "foiz")]
TOLDIRUVCHI = ["Xodim ushbu qoidalar bilan tanishib chiqqanini imzo bilan tasdiqlaydi.",
"Qo'shimcha savollar bo'yicha kadrlar bo'limiga murojaat qilinadi.",
"Qoidalar barcha filiallar uchun bir xilda amal qiladi.",
"Rahbar xodimning arizasini ko'rib chiqib, qaror qabul qiladi.",
"Istisno holatlar alohida buyruq bilan belgilanadi.",
"Hujjatlar elektron tizim orqali topshiriladi.",
"Qoidani buzish intizomiy javobgarlikka olib keladi.",
"Ma'lumotlar maxfiy saqlanadi va uchinchi shaxslarga berilmaydi."]
def korpus_yarat(rng, yashirin=0.3):
"""Qaytaradi: hujjatlar [{nom, bolimlar: [(sarlavha, [jumlalar])]}] va savollar
[{savol, qiymat, jumla, hujjat}]. yashirin - mavzu faqat sarlavhada bo'lgan jumlalar ulushi."""
hujjatlar, savollar = [], []
for nom, mavzular in HUJJATLAR.items():
bolimlar = []
for mavzu in mavzular:
jumlalar = []
for atr, birlik in rng.sample(ATRIBUTLAR, 6):
qiymat = rng.randint(2, 90)
if rng.random() < yashirin:
j = f"Bu holatda {atr} {qiymat} {birlik} etib belgilanadi."
else:
j = f"{mavzu.capitalize()} uchun {atr} {qiymat} {birlik}ni tashkil etadi."
jumlalar.append(j)
savollar.append({"savol": f"{mavzu} bo'yicha {atr} qancha?", "qiymat": qiymat,
"jumla": j, "hujjat": nom})
jumlalar += rng.sample(TOLDIRUVCHI, 5)
rng.shuffle(jumlalar)
bolimlar.append((mavzu.capitalize(), jumlalar))
hujjatlar.append({"nom": nom, "bolimlar": bolimlar})
return hujjatlar, savollar
def matn(hujjat):
return "\n\n".join(f"## {s}\n" + " ".join(j) for s, j in hujjat["bolimlar"])
def jumlalarga(matn_):
return [j for j in re.split(r"(?<=\.)\s+", matn_.strip()) if j]
def qatiy(hujjat, n, overlap):
"""Hujjat matnini so'zlar bo'yicha n talik oynalarga bo'ladi (sarlavhalar ham oqimda)."""
sozlar = matn(hujjat).replace("\n", " ").split()
qadam = n - overlap
return [" ".join(sozlar[i:i + n]) for i in range(0, max(1, len(sozlar) - overlap), qadam)]
def jumla_oyna(hujjat, oyna, qadam, sarlavha):
chunklar = []
for s, js in hujjat["bolimlar"]:
for i in range(0, max(1, len(js) - oyna + qadam), qadam):
bolak = " ".join(js[i:i + oyna])
chunklar.append(f"{s}. {bolak}" if sarlavha else bolak)
return chunklar
def bolim(hujjat):
return [f"{s}. " + " ".join(js) for s, js in hujjat["bolimlar"]]
STRATEGIYALAR = {
"qat'iy 30 so'z": lambda h: qatiy(h, 30, 0),
"qat'iy 30 + overlap 10": lambda h: qatiy(h, 30, 10),
"qat'iy 120 so'z": lambda h: qatiy(h, 120, 0),
"jumla": lambda h: jumla_oyna(h, 1, 1, False),
"3 jumla, qadam 2": lambda h: jumla_oyna(h, 3, 2, False),
"sarlavha + 3 jumla": lambda h: jumla_oyna(h, 3, 2, True),
"bo'lim": bolim,
}
def main() -> None:
hujjatlar, savollar = korpus_yarat(random.Random(0))
print("=== 1. Korpus ===")
print(f" hujjatlar {len(hujjatlar)}, bo'limlar {sum(len(h['bolimlar']) for h in hujjatlar)}, "
f"savollar {len(savollar)}")
yash = [s for s in savollar if s["jumla"].startswith("Bu holatda")]
print(f" mavzusi faqat sarlavhada bo'lgan javob jumlalari: {len(yash)}")
print(f" savol: {savollar[0]['savol']}")
print(f" javob: {savollar[0]['jumla']}")
print("\n=== 2. Strategiyalar (top-3 chunk, TF-IDF belgi 3-5) ===")
print(" strategiya chunk bo'lingan hit@1 hit@3 hit@3 yashirin token hit@300t")
natija = {}
for nom, fn in STRATEGIYALAR.items():
chunklar = [(c, h["nom"]) for h in hujjatlar for c in fn(h)]
matnlar = [c for c, _ in chunklar]
tf = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), sublinear_tf=True).fit(matnlar)
C = tf.transform(matnlar)
B = (tf.transform([s["savol"] for s in savollar]) @ C.T).toarray()
top = np.argsort(-B, axis=1, kind="stable")[:, :3]
hit = np.array([any(s["jumla"] in matnlar[j] for j in t) for s, t in zip(savollar, top)], float)
hit1 = np.mean([s["jumla"] in matnlar[t[0]] for s, t in zip(savollar, top)])
bolingan = np.mean([not any(s["jumla"] in m for m in matnlar) for s in savollar])
yashirin = np.array([s["jumla"].startswith("Bu holatda") for s in savollar])
token = np.mean([sum(len(matnlar[j]) for j in t) / 4 for t in top])
byudjet = [] # teng kontekst byudjeti: ~300 token
for sv, qator in zip(savollar, np.argsort(-B, axis=1, kind="stable")):
olingan, jami = [], 0.0
for j in qator:
if olingan and jami + len(matnlar[j]) / 4 > 300:
break
olingan.append(j)
jami += len(matnlar[j]) / 4
byudjet.append(any(sv["jumla"] in matnlar[j] for j in olingan))
natija[nom] = np.array(byudjet, float)
print(f" {nom:<23} {len(matnlar):>5} {bolingan:>9.1%} {hit1:>6.1%} {hit.mean():>6.1%} "
f"{hit[yashirin].mean():>14.1%} {token:>6.0f} {natija[nom].mean():>9.1%}")
print("\n=== 3. Teng byudjetda (300 token) eng yaxshisi bilan juftlashgan farq ===")
eng = max(natija, key=lambda n: natija[n].mean())
for nom, hit in natija.items():
if nom == eng:
continue
f = natija[eng] - hit
se = f.std(ddof=1) / math.sqrt(len(f))
print(f" {eng} - {nom}: {f.mean():+.3f}, SE {se:.3f} -> "
f"{'sezilarli' if f.mean() > 2 * se else 'sezilarli emas'}")
print("\n=== 4. Xulosa (natijadan) ===")
print(f" 300 tokenli byudjetda eng yaxshi: {eng} ({natija[eng].mean():.1%})")
print(" \u2b50 Chunk - ma'no birligi: jumlani bo'lmang, sarlavha kontekstini saqlang")
if __name__ == "__main__":
with warnings.catch_warnings():
warnings.simplefilter("ignore")
main()Natijaning muhim qismi:
=== 1. Korpus ===
hujjatlar 6, bo'limlar 30, savollar 180
mavzusi faqat sarlavhada bo'lgan javob jumlalari: 51
savol: yillik ta'til bo'yicha ustama foizi qancha?
javob: Yillik ta'til uchun ustama foizi 53 foizni tashkil etadi.
=== 2. Strategiyalar (top-3 chunk, TF-IDF belgi 3-5) ===
strategiya chunk bo'lingan hit@1 hit@3 hit@3 yashirin token hit@300t
qat'iy 30 so'z 96 26.7% 39.4% 67.8% 56.9% 157 68.3%
qat'iy 30 + overlap 10 138 0.0% 53.9% 82.8% 68.6% 162 93.9%
qat'iy 120 so'z 24 6.1% 82.8% 93.3% 92.2% 642 82.8%
jumla 330 0.0% 71.7% 72.2% 2.0% 47 95.0%
3 jumla, qadam 2 150 0.0% 67.8% 93.9% 82.4% 137 96.1%
sarlavha + 3 jumla 150 0.0% 80.0% 97.8% 94.1% 149 100.0%
bo'lim 30 0.0% 100.0% 100.0% 100.0% 505 100.0%
=== 3. Teng byudjetda (300 token) eng yaxshisi bilan juftlashgan farq ===
sarlavha + 3 jumla - qat'iy 30 so'z: +0.317, SE 0.035 -> sezilarli
sarlavha + 3 jumla - qat'iy 30 + overlap 10: +0.061, SE 0.018 -> sezilarli
sarlavha + 3 jumla - qat'iy 120 so'z: +0.172, SE 0.028 -> sezilarli
sarlavha + 3 jumla - jumla: +0.050, SE 0.016 -> sezilarli
sarlavha + 3 jumla - 3 jumla, qadam 2: +0.039, SE 0.014 -> sezilarli
sarlavha + 3 jumla - bo'lim: +0.000, SE 0.000 -> sezilarli emas
=== 4. Xulosa (natijadan) ===
300 tokenli byudjetda eng yaxshi: sarlavha + 3 jumla (100.0%)
⭐ Chunk - ma'no birligi: jumlani bo'lmang, sarlavha kontekstini saqlangNima ko'rsatdi: 180 savolning 51 tasida javob jumlasi mavzuni aytmaydi ("Bu holatda ...") — mavzu faqat bo'lim sarlavhasida. Yetti strategiya bir xil retrieval bilan solishtirildi. "Bo'lingan" ustuni — javob jumlasi hech bir chunk ichida to'liq yo'qligi: qat'iy 30 so'zda 26.7%, qat'iy 120 da 6.1% — bu savollarga retrieval qanchalik yaxshi bo'lmasin, to'liq javob topilmaydi. Overlap 10 bo'linishni 0.0% ga tushirdi va hit@3 ni 67.8% dan 82.8% ga oshirdi, lekin chunklar soni 96 dan 138 ga ko'paydi. Jumla darajasidagi chunklar hech narsani bo'lmaydi, lekin "Bu holatda ..." jumlalari sarlavhasiz qolgani uchun ularda hit@3 atigi 2.0%. Sarlavhani har chunk boshiga qo'shish buni tuzatdi: yashirin jumlalarda 94.1% (sarlavhasiz 3 jumla oynasida 82.4%). Katta chunklar (qat'iy 120, bo'lim) hit@3 da yaxshi ko'rinadi, lekin top-3 uchun 642 va 505 token olib keladi — shuning uchun oxirgi ustun teng byudjetda (300 token) baholaydi. Unda sarlavha + 3 jumla va bo'lim — 100.0%, qat'iy 120 esa 82.8% gacha tushdi (byudjetga bitta chunk sig'adi). 3-bo'lim: teng byudjetda sarlavha + 3 jumla qolgan barcha strategiyalardan sezilarli yaxshi, bo'lim bilan farq yo'q. Bizning bo'limlar qisqa (11 jumla); real hujjatda uzun bo'lim byudjetga sig'maydi, shuning uchun kontekstli kichik chunk — xavfsizroq tanlov. Bog'liq bo'limlar: 2.3.
Misol 3 — Metadata: versiyalar va filtrlash
"""Metadata va filtrlash: hujjat versiyalari, oldindan va keyin filtrlash, eskirgan javoblar."""
import copy
import math
import random
import re
import warnings
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
HUJJATLAR = {
"Ta'til nizomi": ["yillik ta'til", "o'quv ta'tili", "tibbiy ta'til", "haq to'lanmaydigan ta'til",
"ota-onalik ta'tili"],
"Xizmat safari": ["mahalliy safar", "xorijiy safar", "safar avansi", "mehmonxona xarajati",
"safar hisoboti"],
"Ish vaqti": ["kunlik ish vaqti", "tushlik tanaffusi", "qo'shimcha ish", "tungi smena",
"bayram navbatchiligi"],
"Masofaviy ish": ["uydan ishlash", "gibrid jadval", "internet kompensatsiyasi",
"onlayn yig'ilish", "masofaviy jihozlar"],
"Mukofotlash": ["choraklik mukofot", "yillik bonus", "tavsiya mukofoti", "loyiha mukofoti",
"stajyor mukofoti"],
"IT xavfsizlik": ["parol siyosati", "noutbuk", "vpn ulanish", "elektron pochta", "zaxira nusxa"],
}
ATRIBUTLAR = [("muddati", "kun"), ("eng ko'p davomiyligi", "kun"), ("ariza berish muddati", "kun"),
("kompensatsiya miqdori", "ming so'm"), ("tasdiqlash muddati", "ish kuni"),
("oylik limiti", "soat"), ("ustama foizi", "foiz")]
TOLDIRUVCHI = ["Xodim ushbu qoidalar bilan tanishib chiqqanini imzo bilan tasdiqlaydi.",
"Qo'shimcha savollar bo'yicha kadrlar bo'limiga murojaat qilinadi.",
"Qoidalar barcha filiallar uchun bir xilda amal qiladi.",
"Rahbar xodimning arizasini ko'rib chiqib, qaror qabul qiladi.",
"Istisno holatlar alohida buyruq bilan belgilanadi.",
"Hujjatlar elektron tizim orqali topshiriladi.",
"Qoidani buzish intizomiy javobgarlikka olib keladi.",
"Ma'lumotlar maxfiy saqlanadi va uchinchi shaxslarga berilmaydi."]
def korpus_yarat(rng, yashirin=0.3):
"""Qaytaradi: hujjatlar [{nom, bolimlar: [(sarlavha, [jumlalar])]}] va savollar
[{savol, qiymat, jumla, hujjat}]. yashirin - mavzu faqat sarlavhada bo'lgan jumlalar ulushi."""
hujjatlar, savollar = [], []
for nom, mavzular in HUJJATLAR.items():
bolimlar = []
for mavzu in mavzular:
jumlalar = []
for atr, birlik in rng.sample(ATRIBUTLAR, 6):
qiymat = rng.randint(2, 90)
if rng.random() < yashirin:
j = f"Bu holatda {atr} {qiymat} {birlik} etib belgilanadi."
else:
j = f"{mavzu.capitalize()} uchun {atr} {qiymat} {birlik}ni tashkil etadi."
jumlalar.append(j)
savollar.append({"savol": f"{mavzu} bo'yicha {atr} qancha?", "qiymat": qiymat,
"jumla": j, "hujjat": nom})
jumlalar += rng.sample(TOLDIRUVCHI, 5)
rng.shuffle(jumlalar)
bolimlar.append((mavzu.capitalize(), jumlalar))
hujjatlar.append({"nom": nom, "bolimlar": bolimlar})
return hujjatlar, savollar
def matn(hujjat):
return "\n\n".join(f"## {s}\n" + " ".join(j) for s, j in hujjat["bolimlar"])
def yangi_versiya(hujjatlar, savollar, rng, ulush=0.4):
"""2025-versiya: faktlarning ~40% ida qiymat o'zgargan; qolgan matn aynan bir xil."""
yangi, ozgargan = copy.deepcopy(hujjatlar), {}
for s in savollar:
if rng.random() < ulush:
eski = s["qiymat"]
yangi_q = eski + rng.choice([-1, 1]) * rng.randint(3, 20)
yangi_q = yangi_q if yangi_q > 0 else eski + 25
ozgargan[s["savol"]] = (eski, yangi_q)
for h in yangi:
for i, (sar, js) in enumerate(h["bolimlar"]):
h["bolimlar"][i] = (sar, [j.replace(f" {eski} ", f" {yangi_q} ") if j == s["jumla"]
else j for j in js])
return yangi, ozgargan
def chunklar_meta(hujjatlar, versiya, sana, amalda):
natija = []
for h in hujjatlar:
for sar, js in h["bolimlar"]:
for i in range(0, len(js) - 1, 2):
natija.append({"matn": f"{sar}. " + " ".join(js[i:i + 3]), "manba": h["nom"],
"versiya": versiya, "sana": sana, "amalda": amalda,
"bolim": "IT" if h["nom"] == "IT xavfsizlik" else "HR"})
return natija
def ildizlar(t):
return {w[:5] for w in re.findall(r"[a-z']+", t.lower()) if len(w) > 3}
def ekstraktiv(savol, chunklar):
q, eng = ildizlar(savol), (-1.0, None)
for raqam, c in enumerate(chunklar):
for j in re.split(r"(?<=\.)\s+", c["matn"]):
if re.search(r"\d", j):
ball = len(q & ildizlar(j)) + 0.5 * len(q & ildizlar(c["matn"])) - 0.01 * raqam
if ball > eng[0]:
eng = (ball, j)
return int(re.search(r"\d+", eng[1]).group()) if eng[1] else None
def main() -> None:
rng = random.Random(0)
eski, savollar = korpus_yarat(rng)
yangi, ozgargan = yangi_versiya(eski, savollar, random.Random(1))
indeks = (chunklar_meta(eski, "2024", "2024-01-15", False)
+ chunklar_meta(yangi, "2025", "2025-02-01", True))
random.Random(2).shuffle(indeks) # indeks tartibi natijaga ta'sir qilmasin
matnlar = [c["matn"] for c in indeks]
tf = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), sublinear_tf=True).fit(matnlar)
C = tf.transform(matnlar)
print("=== 1. Indeks va metadata ===")
print(f" chunklar {len(indeks)} (2024: {sum(not c['amalda'] for c in indeks)}, "
f"2025: {sum(c['amalda'] for c in indeks)}); qiymati o'zgargan faktlar {len(ozgargan)}")
c = indeks[0]
print(f" chunk metadata: manba={c['manba']!r}, versiya={c['versiya']}, sana={c['sana']}, "
f"amalda={c['amalda']}, bolim={c['bolim']}")
def qidir(savol, usul, k=3):
b = (tf.transform([savol]) @ C.T).toarray()[0]
if usul == "oldindan filtr":
b = np.where([c["amalda"] for c in indeks], b, -np.inf)
tartib = np.argsort(-b, kind="stable")[:k]
topildi = [indeks[j] for j in tartib if np.isfinite(b[j])]
if usul == "keyin filtr":
topildi = [c for c in topildi if c["amalda"]]
return topildi
print("\n=== 2. Qiymati o'zgargan faktlar bo'yicha savollar ===")
print(" usul to'g'ri (2025) eskirgan (2024) bo'sh natija chunklar (o'rtacha)")
tsav = [s for s in savollar if s["savol"] in ozgargan]
natija = {}
for usul in ["filtrsiz", "keyin filtr", "oldindan filtr"]:
togri, eskirgan, bosh, eski_bor = [], 0, 0, 0
for s in tsav:
top = qidir(s["savol"], usul)
eski_bor += len(top)
if not top:
bosh += 1
togri.append(0.0)
continue
javob = ekstraktiv(s["savol"], top)
togri.append(float(javob == ozgargan[s["savol"]][1]))
eskirgan += javob == ozgargan[s["savol"]][0]
natija[usul] = np.array(togri)
n = len(tsav)
print(f" {usul:<16} {np.mean(togri):>15.1%} {eskirgan / n:>16.1%} {bosh / n:>13.1%} "
f"{eski_bor / n:>17.2f}")
print("\n=== 3. Juftlashgan farq (to'g'ri javob ulushi, SE) ===")
for a, b in [("oldindan filtr", "filtrsiz"), ("oldindan filtr", "keyin filtr")]:
f = natija[a] - natija[b]
se = f.std(ddof=1) / math.sqrt(len(f))
print(f" {a} - {b}: {f.mean():+.3f}, SE {se:.3f} -> "
f"{'sezilarli' if abs(f.mean()) > 2 * se else 'sezilarli emas'}")
bosh1 = np.mean([not qidir(s["savol"], "keyin filtr", k=1) for s in tsav])
print(f" keyin filtr, k = 1: bo'sh natija {bosh1:.1%} - top-1 eski versiya bo'lsa, hech narsa qolmaydi")
print("\n=== 4. Bo'lim bo'yicha filtr: qidiruv maydoni ===")
it = sum(c["bolim"] == "IT" and c["amalda"] for c in indeks)
print(f" bolim == 'IT' va amalda: {it} chunk ({it / len(indeks):.0%} indeks) - "
f"HR qoidalari IT savoliga aralashmaydi")
print("\n=== 5. Xulosa (natijadan) ===")
print(f" filtrsiz {natija['filtrsiz'].mean():.1%} -> oldindan filtr {natija['oldindan filtr'].mean():.1%}")
print(" \u2b50 Versiya va sana - metadata; eskirgan hujjatni qidiruvdan OLDIN chiqarib tashlang")
if __name__ == "__main__":
with warnings.catch_warnings():
warnings.simplefilter("ignore")
main()Natijaning muhim qismi:
=== 1. Indeks va metadata ===
chunklar 300 (2024: 150, 2025: 150); qiymati o'zgargan faktlar 74
chunk metadata: manba='Mukofotlash', versiya=2024, sana=2024-01-15, amalda=False, bolim=HR
=== 2. Qiymati o'zgargan faktlar bo'yicha savollar ===
usul to'g'ri (2025) eskirgan (2024) bo'sh natija chunklar (o'rtacha)
filtrsiz 29.7% 37.8% 0.0% 3.00
keyin filtr 60.8% 0.0% 0.0% 1.36
oldindan filtr 67.6% 0.0% 0.0% 3.00
=== 3. Juftlashgan farq (to'g'ri javob ulushi, SE) ===
oldindan filtr - filtrsiz: +0.378, SE 0.060 -> sezilarli
oldindan filtr - keyin filtr: +0.068, SE 0.035 -> sezilarli emas
keyin filtr, k = 1: bo'sh natija 56.8% - top-1 eski versiya bo'lsa, hech narsa qolmaydi
=== 4. Bo'lim bo'yicha filtr: qidiruv maydoni ===
bolim == 'IT' va amalda: 25 chunk (8% indeks) - HR qoidalari IT savoliga aralashmaydi
=== 5. Xulosa (natijadan) ===
filtrsiz 29.7% -> oldindan filtr 67.6%
⭐ Versiya va sana - metadata; eskirgan hujjatni qidiruvdan OLDIN chiqarib tashlangNima ko'rsatdi: indeksda qoidalarning 2024 va 2025 versiyalari bor (har biri 150 chunk); 2025 da 74 ta faktning qiymati o'zgargan, qolgan matn aynan bir xil. Har chunk metadata bilan saqlanadi: manba, versiya, sana, "amalda" belgisi, bo'lim. Qiymati o'zgargan faktlar bo'yicha savollarda filtrsiz qidiruv faqat 29.7% to'g'ri (2025) javob berdi va 37.8% hollarda eskirgan 2024 qiymatini qaytardi — eski va yangi chunk matni deyarli bir xil, embedding ularni ajrata olmaydi. Keyin filtrlash eskirgan javoblarni yo'qotdi (0.0%), lekin kontekstda o'rtacha 1.36 chunk qoldi (top-3 ning yarmidan ko'pi eski versiya edi) va to'g'rilik 60.8%. Oldindan filtrlash to'liq top-3 ni amaldagi versiyadan oldi: 67.6%, filtrsizdan +0.378 (SE 0.060, sezilarli); keyin filtrdan farqi +0.068 (SE 0.035) — sezilarli emas, lekin keyin filtrning boshqa xavfi bor: k = 1 da savollarning 56.8% ida natija bo'sh qoladi. 67.6% ning o'zi ham 100% emas — qolgan xatolar ekstraktiv generatordan (1-misoldagi kabi). 4-bo'lim: bolim == 'IT' filtri qidiruvni indeksning 8% iga toraytiradi. Bog'liq bo'limlar: 2.4.
Misol 4 — Indeksni yangilash va kontekst byudjeti
"""Indeksni yangilash (hujjat o'zgarganda) va kontekstni token byudjetiga sig'dirib prompt yig'ish."""
import copy
import hashlib
import random
import re
import warnings
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
HUJJATLAR = {
"Ta'til nizomi": ["yillik ta'til", "o'quv ta'tili", "tibbiy ta'til", "haq to'lanmaydigan ta'til",
"ota-onalik ta'tili"],
"Xizmat safari": ["mahalliy safar", "xorijiy safar", "safar avansi", "mehmonxona xarajati",
"safar hisoboti"],
"Ish vaqti": ["kunlik ish vaqti", "tushlik tanaffusi", "qo'shimcha ish", "tungi smena",
"bayram navbatchiligi"],
"Masofaviy ish": ["uydan ishlash", "gibrid jadval", "internet kompensatsiyasi",
"onlayn yig'ilish", "masofaviy jihozlar"],
"Mukofotlash": ["choraklik mukofot", "yillik bonus", "tavsiya mukofoti", "loyiha mukofoti",
"stajyor mukofoti"],
"IT xavfsizlik": ["parol siyosati", "noutbuk", "vpn ulanish", "elektron pochta", "zaxira nusxa"],
}
ATRIBUTLAR = [("muddati", "kun"), ("eng ko'p davomiyligi", "kun"), ("ariza berish muddati", "kun"),
("kompensatsiya miqdori", "ming so'm"), ("tasdiqlash muddati", "ish kuni"),
("oylik limiti", "soat"), ("ustama foizi", "foiz")]
TOLDIRUVCHI = ["Xodim ushbu qoidalar bilan tanishib chiqqanini imzo bilan tasdiqlaydi.",
"Qo'shimcha savollar bo'yicha kadrlar bo'limiga murojaat qilinadi.",
"Qoidalar barcha filiallar uchun bir xilda amal qiladi.",
"Rahbar xodimning arizasini ko'rib chiqib, qaror qabul qiladi.",
"Istisno holatlar alohida buyruq bilan belgilanadi.",
"Hujjatlar elektron tizim orqali topshiriladi.",
"Qoidani buzish intizomiy javobgarlikka olib keladi.",
"Ma'lumotlar maxfiy saqlanadi va uchinchi shaxslarga berilmaydi."]
def korpus_yarat(rng, yashirin=0.3):
"""Qaytaradi: hujjatlar [{nom, bolimlar: [(sarlavha, [jumlalar])]}] va savollar
[{savol, qiymat, jumla, hujjat}]. yashirin - mavzu faqat sarlavhada bo'lgan jumlalar ulushi."""
hujjatlar, savollar = [], []
for nom, mavzular in HUJJATLAR.items():
bolimlar = []
for mavzu in mavzular:
jumlalar = []
for atr, birlik in rng.sample(ATRIBUTLAR, 6):
qiymat = rng.randint(2, 90)
if rng.random() < yashirin:
j = f"Bu holatda {atr} {qiymat} {birlik} etib belgilanadi."
else:
j = f"{mavzu.capitalize()} uchun {atr} {qiymat} {birlik}ni tashkil etadi."
jumlalar.append(j)
savollar.append({"savol": f"{mavzu} bo'yicha {atr} qancha?", "qiymat": qiymat,
"jumla": j, "hujjat": nom})
jumlalar += rng.sample(TOLDIRUVCHI, 5)
rng.shuffle(jumlalar)
bolimlar.append((mavzu.capitalize(), jumlalar))
hujjatlar.append({"nom": nom, "bolimlar": bolimlar})
return hujjatlar, savollar
def matn(hujjat):
return "\n\n".join(f"## {s}\n" + " ".join(j) for s, j in hujjat["bolimlar"])
def chunkla(hujjat):
return [{"matn": f"{sar}. " + " ".join(js[i:i + 3]), "hujjat_id": hujjat["nom"]}
for sar, js in hujjat["bolimlar"] for i in range(0, len(js) - 1, 2)]
def xesh(hujjat):
return hashlib.sha256(matn(hujjat).encode()).hexdigest()
class Indeks:
def __init__(self, hujjatlar):
self.chunklar = [c for h in hujjatlar for c in chunkla(h)]
self.tf = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), sublinear_tf=True)
self.tf.fit([c["matn"] for c in self.chunklar])
self.xeshlar = {h["nom"]: xesh(h) for h in hujjatlar}
self.hisoblangan = len(self.chunklar)
self._qayta()
def _qayta(self):
self.C = self.tf.transform([c["matn"] for c in self.chunklar])
def yangila(self, hujjatlar, ochirish=True):
"""Faqat xeshi o'zgargan hujjatlar: eski chunklarini o'chirish + yangilarini qo'shish."""
ozgargan = [h for h in hujjatlar if self.xeshlar.get(h["nom"]) != xesh(h)]
for h in ozgargan:
if ochirish:
self.chunklar = [c for c in self.chunklar if c["hujjat_id"] != h["nom"]]
yangi = chunkla(h)
self.chunklar += yangi
self.hisoblangan += len(yangi)
self.xeshlar[h["nom"]] = xesh(h)
self._qayta() # vektorizator o'zgarmaydi - faqat yangi chunklar kodlanadi
return len(ozgargan)
def qidir(self, savol, k):
b = (self.tf.transform([savol]) @ self.C.T).toarray()[0]
t = np.argsort(-b, kind="stable")[:k]
return [(self.chunklar[j]["matn"], float(b[j])) for j in t]
def ildizlar(t):
return {w[:5] for w in re.findall(r"[a-z']+", t.lower()) if len(w) > 3}
def ekstraktiv(savol, chunklar):
q, eng = ildizlar(savol), (-1.0, None)
for raqam, c in enumerate(chunklar):
for j in re.split(r"(?<=\.)\s+", c):
if re.search(r"\d", j):
ball = len(q & ildizlar(j)) + 0.5 * len(q & ildizlar(c)) - 0.01 * raqam
if ball > eng[0]:
eng = (ball, j)
return int(re.search(r"\d+", eng[1]).group()) if eng[1] else None
def byudjetga_sigdir(topilgan, byudjet):
"""Ball bo'yicha ochko'z tanlov; ko'p takrorlanadigan (overlap) chunkni tashlab ketish."""
tanlandi, jami = [], 0
for m, _ in topilgan:
t = len(m) // 4
if jami + t > byudjet:
continue
s = set(m.split())
if any(len(s & set(x.split())) / len(s | set(x.split())) > 0.6 for x in tanlandi):
continue
tanlandi.append(m)
jami += t
return tanlandi, jami
def main() -> None:
hujjatlar, savollar = korpus_yarat(random.Random(0))
indeks = {"yangilanmagan": Indeks(hujjatlar), "faqat qo'shish": Indeks(hujjatlar),
"o'chirish + qo'shish": Indeks(hujjatlar)}
# "Masofaviy ish" yangilandi: 6 ta fakt qiymati o'zgardi va yangi bo'lim qo'shildi
rng = random.Random(3)
yangi = copy.deepcopy(hujjatlar)
h = next(x for x in yangi if x["nom"] == "Masofaviy ish")
tekshir = []
for s in [s for s in savollar if s["hujjat"] == "Masofaviy ish" and not s["jumla"].startswith("Bu")][:6]:
yq = s["qiymat"] + 11
h["bolimlar"] = [(sar, [j.replace(f" {s['qiymat']} ", f" {yq} ") if j == s["jumla"] else j
for j in js]) for sar, js in h["bolimlar"]]
tekshir.append((s["savol"], yq, s["qiymat"]))
bolim = []
for atr, birlik in rng.sample(ATRIBUTLAR, 4):
q = rng.randint(2, 90)
bolim.append(f"Kovorking markazi uchun {atr} {q} {birlik}ni tashkil etadi.")
tekshir.append((f"kovorking markazi bo'yicha {atr} qancha?", q, None))
h["bolimlar"].append(("Kovorking markazi", bolim + rng.sample(TOLDIRUVCHI, 3)))
print("=== 1. Yangilash: o'zgargan hujjatni xesh bo'yicha topish ===")
for nom, ind in indeks.items():
n = 0 if nom == "yangilanmagan" else ind.yangila(yangi, ochirish=nom != "faqat qo'shish")
print(f" {nom:<22} o'zgargan hujjatlar {n}/6, chunklar {len(ind.chunklar)}, "
f"jami kodlangan {ind.hisoblangan}")
toliq = Indeks(yangi)
indeks["to'liq qayta qurish"] = toliq
print(f" {'to' + chr(39) + 'liq qayta qurish':<22} chunklar {len(toliq.chunklar)}, "
f"kodlangan {toliq.hisoblangan} (+ vektorizator qayta o'rgatildi)")
print("\n=== 2. O'zgargan (6) va yangi (4) faktlar bo'yicha 10 savol (top-3, ekstraktiv) ===")
for nom, ind in indeks.items():
natija = [ekstraktiv(sv, [m for m, _ in ind.qidir(sv, 3)]) for sv, _, _ in tekshir]
togri = sum(r == y for r, (_, y, _) in zip(natija, tekshir))
eski = sum(r == e for r, (_, _, e) in zip(natija, tekshir) if e is not None)
print(f" {nom:<22} to'g'ri {togri}/10, eski qiymat qaytdi {eski}/6")
print(f"\n=== 3. Inkremental va to'liq qayta qurish mosligi ({len(savollar)} + 4 savol, top-3) ===")
savol_hammasi = [s["savol"] for s in savollar] + [t[0] for t in tekshir[6:]]
a, b = indeks["o'chirish + qo'shish"], toliq
mos = np.mean([{m for m, _ in a.qidir(s, 3)} == {m for m, _ in b.qidir(s, 3)} for s in savol_hammasi])
print(f" top-3 to'plami bir xil: {mos:.1%} (farq - IDF og'irliklari eskirganidan)")
print(f"\n=== 4. Kontekst byudjeti: top-10 dan tokenlarga sig'dirish ({len(savollar)} savol) ===")
asl = indeks["yangilanmagan"] # savollar asl hujjatlarga tegishli
print(" byudjet o'rtacha token chunk soni javob kontekstda ekstraktiv to'g'ri")
for byudjet in (60, 120, 240, 480):
kont, token, soni, togri = [], [], [], []
for s in savollar:
tanlandi, jami = byudjetga_sigdir(asl.qidir(s["savol"], 10), byudjet)
kont.append(any(s["jumla"] in m for m in tanlandi))
token.append(jami)
soni.append(len(tanlandi))
togri.append(ekstraktiv(s["savol"], tanlandi) == s["qiymat"])
print(f" {byudjet:>7} {np.mean(token):>15.0f} {np.mean(soni):>11.1f} {np.mean(kont):>17.1%} "
f"{np.mean(togri):>18.1%}")
print(" \u2b50 Indeks - hujjatlar bilan sinxron; kontekst - byudjet ichida, eng foydali chunklar")
if __name__ == "__main__":
with warnings.catch_warnings():
warnings.simplefilter("ignore")
main()Natijaning muhim qismi:
=== 1. Yangilash: o'zgargan hujjatni xesh bo'yicha topish ===
yangilanmagan o'zgargan hujjatlar 0/6, chunklar 150, jami kodlangan 150
faqat qo'shish o'zgargan hujjatlar 1/6, chunklar 178, jami kodlangan 178
o'chirish + qo'shish o'zgargan hujjatlar 1/6, chunklar 153, jami kodlangan 178
to'liq qayta qurish chunklar 153, kodlangan 153 (+ vektorizator qayta o'rgatildi)
=== 2. O'zgargan (6) va yangi (4) faktlar bo'yicha 10 savol (top-3, ekstraktiv) ===
yangilanmagan to'g'ri 0/10, eski qiymat qaytdi 6/6
faqat qo'shish to'g'ri 5/10, eski qiymat qaytdi 4/6
o'chirish + qo'shish to'g'ri 9/10, eski qiymat qaytdi 0/6
to'liq qayta qurish to'g'ri 10/10, eski qiymat qaytdi 0/6
=== 3. Inkremental va to'liq qayta qurish mosligi (180 + 4 savol, top-3) ===
top-3 to'plami bir xil: 95.1% (farq - IDF og'irliklari eskirganidan)
=== 4. Kontekst byudjeti: top-10 dan tokenlarga sig'dirish (180 savol) ===
byudjet o'rtacha token chunk soni javob kontekstda ekstraktiv to'g'ri
60 50 1.0 80.0% 70.0%
120 100 2.0 95.6% 78.9%
240 220 4.5 99.4% 77.2%
480 450 9.2 100.0% 75.6%
⭐ Indeks - hujjatlar bilan sinxron; kontekst - byudjet ichida, eng foydali chunklarNima ko'rsatdi: "Masofaviy ish" hujjatida 6 ta fakt o'zgardi va yangi "Kovorking markazi" bo'limi qo'shildi. Indeks hujjat xeshi bo'yicha faqat o'zgargan hujjatni topdi (1/6) va faqat uning chunklarini qayta kodladi (28 ta). To'rt holat solishtirildi. Yangilanmagan indeks o'zgargan 6 faktning hammasida eski qiymatni qaytardi va yangi bo'lim haqida hech narsa bilmadi (0/10). "Faqat qo'shish" (eski chunklar o'chirilmagan) — indeksda 178 chunk, yarim javoblar to'g'ri (5/10), 6 tadan 4 tasida eski qiymat qaytdi: eski va yangi chunklar raqobatlashadi. To'g'ri inkremental yangilash (o'chirish + qo'shish) — 9/10, to'liq qayta qurish — 10/10. 3-bo'lim: inkremental indeks vektorizatorni qayta o'rgatmagani uchun IDF og'irliklari biroz eskirgan — to'liq qayta qurilgan indeks bilan top-3 to'plami 95.1% savolda bir xil. Neyron embedding modelida bu farq bo'lmaydi (vektor boshqa hujjatlarga bog'liq emas); TF-IDF da esa vaqti-vaqti bilan to'liq qayta qurish kerak. 4-bo'lim kontekst byudjeti: 60 token byudjetda bitta chunk sig'adi va javob kontekstda 80.0% hollarda; 120 tokenda 95.6%, 480 da 100.0%. Lekin ekstraktiv javob 120 tokenda eng yuqori (78.9%), keyin pasaydi (77.2%, 75.6%) — ortiqcha chunklar generator uchun chalg'ituvchi. Bu ekstraktiv generatorning xulqi; LLM da ham uzun, shovqinli kontekst sifatni tushirishi mumkin, lekin buni o'z modelingizda o'lchash kerak. Bog'liq bo'limlar: 2.5, 2.6.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "RAG gallyutsinatsiyani yo'q qiladi" | Kamaytiradi; retrieval topmagan yoki eskirgan narsa baribir xato beradi |
| "PDF dan matn olish — texnik mayda ish" | 1-misol: tozalash javob to'g'riligini +0.128 ga oshirdi |
| "Chunk qancha katta bo'lsa, shuncha yaxshi" | Teng byudjetda qat'iy 120 82.8%, sarlavha + 3 jumla 100.0% |
| "Qat'iy uzunlikdagi chunklar yetarli" | 30 so'zda javoblarning 26.7% i ikki chunk orasida bo'lingan |
| "Bitta jumla — eng aniq chunk" | Sarlavhasiz "Bu holatda ..." jumlalarida hit@3 2.0% |
| "Embedding eski va yangi versiyani ajratadi" | 3-misol: filtrsiz 37.8% eskirgan javob; versiya — metadata |
| "Keyin filtrlash ham xuddi shunday" | k = 1 da 56.8% bo'sh natija |
| "Yangi hujjatni qo'shish yetarli" | Faqat qo'shishda 6 tadan 4 ta eski qiymat qaytdi |
| "Kontekstga qancha ko'p chunk, shuncha yaxshi" | 4-misol: 120 tokendan keyin ekstraktiv javob pasaydi |
| "Retrieval yaxshilansa, javob ham shuncha yaxshilanadi" | 1-misol: hit@3 +30 punkt, javob +6 punkt (sezilarli emas) |
6. Keng tarqalgan xatolar va yechimlari
1. Tozalanmagan PDF matni
chunklar = chunkla(pdf_matn) # ⚠️
chunklar = chunkla(bolimlarga(tozala(pdf_matn))) # ✅2. Jumla o'rtasida kesish
chunklar = [" ".join(s[i:i + 30]) for i in range(0, len(s), 30)] # ⚠️
chunklar = [f"{sar}. " + " ".join(js[i:i + 3]) # ✅
for sar, js in bolimlar for i in range(0, len(js) - 1, 2)]3. Metadata siz chunk
indeks.add(matn) # ⚠️
indeks.add(matn, {"hujjat_id": h, "bolim": s, "versiya": v, "amalda": True}) # ✅4. Keyin filtrlash
top = [c for c in qidir(savol, k=3) if c["amalda"]] # ⚠️ bo'sh bo'lishi mumkin
top = qidir(savol, k=3, filtr=lambda c: c["amalda"]) # ✅ oldindan5. Yangilashda eski chunklar qoladi
chunklar += chunkla(yangi_hujjat) # ⚠️
chunklar = [c for c in chunklar if c["hujjat_id"] != yangi_hujjat["nom"]] # ✅
chunklar += chunkla(yangi_hujjat)6. Byudjetsiz kontekst
kontekst = "\n".join(m for m, _ in qidir(savol, k=50)) # ⚠️
tanlandi, _ = byudjetga_sigdir(qidir(savol, k=10), byudjet=150) # ✅7. Iqtibossiz prompt
prompt = kontekst + "\n" + savol # ⚠️
prompt = prompt_yig(savol, tanlandi, manbalar) # ✅ [n], "bilmayman"7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 25.2-dars (o'tilgan): kontekst oynasi va token xarajati — byudjet ichida kontekst yig'ish
- 25.4-dars (o'tilgan): prompt engineering — RAG prompti: ko'rsatma, manbalar, "bilmayman"
- 25.5-dars (o'tilgan): strukturali chiqish — javob + iqtiboslarni JSON ko'rinishida olish va tekshirish
- 25.6-dars (o'tilgan): API — prefiks kesh (barqaror ko'rsatma boshida), xarajat hisobi
- 25.7-dars (o'tilgan): embedding, gibrid qidiruv, ANN — retrieval bo'g'ini
- Keyingi darslar: RAG: retrieval sifati va baholash — recall@k, qayta saralash, retrieval va generatsiyani alohida baholash; LLM ni baholash — javobning manbaga sodiqligi; Agentlar — qidiruv asbob sifatida; LLM xavfsizligi — hujjat ichidagi injeksiya va kirish huquqi
8. Eng yaxshi amaliyotlar
Yuklashdan keyin tozalang va tuzilmani (sarlavhalar, bo'limlar) saqlang.
Chunklarni jumla chegarasida kesing va har chunkka sarlavha kontekstini qo'shing.
Chunking strategiyalarini teng token byudjetida, juftlashgan holda solishtiring.
Har chunk bilan manba, bo'lim, versiya, sana va kirish huquqini saqlang.
Eskirgan va ruxsatsiz hujjatlarni qidiruvdan oldin filtrlang.
Indeksni xesh bo'yicha yangilang: o'chirish + qo'shish; model almashsa — to'liq qayta qurish.
Kontekstni byudjet ichida, takrorsiz yig'ing; manbalarni raqamlang va "bilmayman" ga ruxsat bering.
Retrieval (hit@k) va javob to'g'riligini alohida o'lchang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # RAG modelning o'zini o'zgartiradimi?
2. # "ta'-\ntil" qidiruvga qanday ta'sir qiladi?
3. # 30 so'zlik qat'iy chunkda javob jumlasi bo'linsa, top-k ni oshirish yordam beradimi?
4. # overlap nimani tuzatadi va nimaning narxiga?
5. # "Bu holatda muddati 14 kun" jumlasi alohida chunk bo'lsa, qaysi savolga topiladi?
6. # nega hit@3 da katta chunklar yaxshi ko'rinadi?
7. # eski va yangi versiyani embedding ajrata oladimi?
8. # keyin filtrda k = 1 bo'lsa, eng yomon holat?
9. # hujjat yangilanganda faqat yangi chunklarni qo'shsak?
10. # TF-IDF indeksini inkremental yangilashning kamchiligi?
11. # kontekstga 50 ta chunk qo'yishning ikki kamchiligi?
12. # nega promptda "bilmayman" ga ruxsat berish kerak?Javoblar
- Yo'q — faqat promptga manbalar qo'shiladi; model og'irliklari o'zgarmaydi
- So'z ikki bo'lakka bo'linadi; so'z/n-gram mosligi yo'qoladi (1-misol: tozalash
+0.128) - Yo'q — hech bir chunkda to'liq jumla yo'q; bo'linish indeks qurilayotganda yuz bergan
- Chegaradagi bo'linishni; narxi — ko'proq chunk va natijalarda takrorlar (96 → 138)
- Deyarli hech biriga — mavzu yo'q (2-misol:
2.0%); sarlavha qo'shish kerak - Ko'proq matn olib keladi — teng token byudjetida solishtirish kerak
- Deyarli yo'q — matn bir xil; versiya metadata bilan filtrlanadi
- Top-1 eski versiya bo'lsa, natija bo'sh (3-misol:
56.8%) - Eski chunklar qoladi va raqobatlashadi (4-misol: 6 tadan 4 ta eski qiymat)
- IDF eskiradi; natijalar to'liq qayta qurishdan farq qiladi (
95.1%moslik) - Qimmat va sekin; chalg'ituvchi matn ko'payadi (4-misol: javob sifati pasaydi)
- Aks holda model manbada yo'q javobni o'ylab topishga undaladi
Vazifa 2: Xatolarni tuzating
1. matn = pdf_dan_matn(fayl)
chunklar = [matn[i:i + 200] for i in range(0, len(matn), 200)]
2. top = qidir(savol, k=1)
top = [c for c in top if c["versiya"] == "2025"]
javob = llm(prompt_yig(savol, top))
3. def yangila(indeks, hujjat):
indeks.qosh(chunkla(hujjat))
4. kontekst = "\n".join(c["matn"] for c in qidir(savol, k=40))
prompt = kontekst + "\nSavol: " + savol
5. chunklar = [{"matn": j} for sar, js in bolimlar for j in js]Javoblar
1. toza = tozala(pdf_dan_matn(fayl)) # sarlavha/footer, defis
chunklar = chunk_sarlavha(bolimlarga(toza)) # jumla chegarasi + sarlavha
2. top = qidir(savol, k=3, filtr=lambda c: c["amalda"]) # oldindan filtr
if not top:
javob = "bilmayman"
else:
javob = llm(prompt_yig(savol, top))
3. def yangila(indeks, hujjat):
if xesh(hujjat) == indeks.xeshlar.get(hujjat["nom"]):
return
indeks.ochir(hujjat_id=hujjat["nom"]) # eski chunklar
indeks.qosh(chunkla(hujjat))
indeks.xeshlar[hujjat["nom"]] = xesh(hujjat)
4. tanlandi, _ = byudjetga_sigdir(qidir(savol, k=10), byudjet=150)
prompt = prompt_yig(savol, tanlandi, manbalar) # [n] va "bilmayman"
5. chunklar = [{"matn": f"{sar}. " + " ".join(js[i:i + 3]), "bolim": sar}
for sar, js in bolimlar for i in range(0, len(js) - 1, 2)]Vazifa 3: Quvur
Modellang:
- "PDF" xom matn generatori (sarlavha, sahifa raqami, defis)
- Tozalash va bo'limlarga ajratish
- Uch quvur: xom / tozalangan / tozalangan + kontekstli chunk
- hit@3 va ekstraktiv javob to'g'riligi, juftlashgan SE
Vazifa 4: Chunking
Modellang:
- Yetti strategiya (qat'iy, overlap, jumla, oyna, sarlavha, bo'lim)
- Bo'lingan javoblar ulushi
- hit@1, hit@3, yashirin jumlalarda hit@3
- Teng token byudjetida solishtirish va qaror qoidasi; qo'shimcha: byudjetni 150 va 600 ga o'zgartiring
Vazifa 5: Metadata
Modellang:
- Ikki versiyali indeks va metadata
- Filtrsiz, keyin va oldindan filtrlash
- To'g'ri, eskirgan va bo'sh natija ulushlari
- Bo'lim va kirish huquqi filtri
Vazifa 6: Yangilash va kontekst
Modellang:
- Xesh bo'yicha o'zgarishni aniqlash
- Yangilanmagan / faqat qo'shish / o'chirish + qo'shish / to'liq qayta qurish
- Inkremental va to'liq indeks mosligi
- Byudjetga sig'dirish, takrorni tashlash, byudjet bo'yicha egri chiziq
Vazifa 7: O'ylash
Jamoa rahbari aytdi: "Chat-botimiz ba'zan eskirgan javob beradi va ba'zan javobni topmaydi. Keling, eng katta kontekstli modelga o'tamiz va barcha hujjatlarni to'liq promptga qo'yamiz — RAG ham, chunking ham kerak bo'lmaydi."
Javob
Qisqa javob: katta kontekst ba'zi hollarda qulay, lekin bu ikki muammoning sababi kontekst hajmida emas — ular indeks va metadata muammolari. Hamma narsani promptga qo'yish ularni hal qilmaydi va narxni keskin oshiradi.
1. Eskirgan javoblar. 3- va 4-misollar ko'rsatganidek, sabab — indeksda eski versiyaning qolishi yoki filtr yo'qligi. Barcha hujjatlarni promptga qo'ysak, ikkala versiya ham kontekstda bo'ladi — model qaysi biri amalda ekanini bilmaydi. Yechim: versiya metadata, oldindan filtr, yangilashda o'chirish + qo'shish.
2. Javobni topmaslik. Tekshirish kerak: javob indeksda bormi (tozalash, bo'lingan chunklar — 1- va 2-misollar), retrieval uni topyaptimi (hit@k), generator uni ishlatyaptimi. Bular alohida o'lchanadi; 1-misolda retrieval 96.7% bo'lsa ham javob 76.7% edi.
3. Hamma narsa promptda. Har so'rovda butun korpus uchun to'lash (25.6: kirish tokenlari har safar), kechikish, va uzun, shovqinli kontekstda kerakli ma'lumotning "yo'qolishi" (4-misolda ortiqcha kontekst javobni yaxshilamadi). Iqtibos ham qiyinlashadi. Kichik va barqaror korpus uchun (bir necha hujjat) bu variant bo'lishi mumkin — prefiks kesh bilan — lekin buni o'lchab qaror qilish kerak.
Tavsiya:
# 1. indeks auditi: har hujjatning versiyasi, eski chunklar bormi
# 2. metadata filtri: amalda == True (oldindan)
# 3. tozalash va kontekstli chunking
# 4. baholash to'plami: savol -> to'g'ri hujjat/jumla; hit@k va javob to'g'riligi alohida
# 5. kichik korpus bo'lsa - "hammasi promptda + kesh" variantini ham shu to'plamda solishtiringRahbarga javob: "Eskirgan javoblar — indeksda eski versiya qolgani uchun; buni metadata filtri va to'g'ri yangilash bilan tuzatamiz. Topilmayotgan javoblar uchun retrieval va generatsiyani alohida o'lchaymiz. Katta kontekst variantini ham sinab ko'ramiz, lekin xarajat va sifatni bir xil savollar to'plamida solishtirib qaror qilamiz."
Nimani mustahkamlaydi: 2.2, 2.3, 2.4, 2.5, 2.6-bo'limlar.
Xulosa
Bu darsda RAG quvurini boshidan oxirigacha qurdik: yuklash va tozalash, chunking strategiyalari, metadata va filtrlash, indeksni yangilash va kontekstni token byudjetiga sig'dirib, iqtiboslar bilan prompt yig'ish. Retrieval haqiqiy va o'lchandi; generator o'rnida ekstraktiv jumla tanlovchi ishlatildi.
Eng muhim uch fikr:
Quvurning "oddiy" bo'g'inlari hal qiluvchi. 1-misolda tozalash javob to'g'riligini
57.8%dan70.6%ga oshirdi (+0.128,SE 0.039); sarlavha kontekstli chunklar hit@3 ni96.7%ga chiqardi. Lekin javob faqat76.7%— retrieval va generatsiya alohida o'lchanishi kerak.Chunk — ma'no birligi, taqqoslash — teng byudjetda. 2-misolda 30 so'zlik qat'iy chunklarda javoblarning
26.7%i bo'lingan, sarlavhasiz jumla chunklarda "Bu holatda ..." jumlalari deyarli topilmadi (2.0%). 300 tokenli teng byudjetda sarlavha + 3 jumla100.0%, qat'iy 120 so'z82.8%.Metadata va yangilash eskirgan javoblarni to'xtatadi. 3-misolda filtrsiz qidiruv o'zgargan faktlarda
37.8%eskirgan javob berdi; oldindan filtr to'g'rilikni29.7%dan67.6%ga oshirdi, keyin filtr esak = 1da56.8%bo'sh natija berdi. 4-misolda "faqat qo'shish" 6 tadan 4 ta eski qiymatni qaytardi, o'chirish + qo'shish9/10to'g'ri. Kontekst byudjetini oshirish javobni 120 tokendan keyin yaxshilamadi.
Keyingi darsda RAG: retrieval sifati va baholash: recall@k va MRR bilan retrieval ni baholash, qayta saralash (reranking), gibrid qidiruv, javobning manbaga sodiqligi va retrieval hamda generatsiya xatolarini ajratib o'lchashni ko'ramiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!