Asosiy qismga o'tish

chunker.py — matnni bo'laklash

8-bob 14 kod bloki 1432 so'z

Buni o'zingiz yozasiz. RAG ning eng muhim qismi.

Nega bo'laklash kerak

  1. Embedding modelining chegarasi bor — cheksiz matn qabul qilmaydi
  2. Uzun matn = suyuq vektor — 50 sahifa bitta vektorda hech narsa anglatmaydi
  3. LLM ga kontekst beramiz — 50 sahifa sig'maydi, kerakli 3 ta bo'lak sig'adi

Ikkita strategiya

Bu yerda tanlov bor va uni tushunish muhim.

A. Sliding window (overlap bilan)

python4 qator
[==========]
       [==========]
              [==========]
       ^^^^^ ustma-ust

Matnni belgilar bo'yicha teng kesadi, chegaralarda ustma-ust qoldiradi.

B. Paragraf bo'yicha (BIZNING TANLOV)

python6 qator
Paragraf 1  \
Paragraf 2   > 1-chunk (limitga sig'guncha)
Paragraf 3  /

Paragraf 4  \
Paragraf 5   > 2-chunk

Matnni \n\n bo'yicha bo'ladi, paragraflarni limitga sig'guncha yig'adi.

Biz IKKALASINI birga ishlatamiz: paragraf bo'yicha bo'lamiz VA overlap qo'shamiz.

Nega paragraf: paragraf — muallif yaratgan tabiiy ma'no birligi. Uni buzmasak, gap o'rtasidan kesilmaydi.

Nega overlap ham kerak: paragraf chegarasi GAPNI buzmaydi, lekin KONTEKSTNI buzadi. Misol:

matn3 qator
Paragraf 5: "Nihol Premium tarifi eng qimmat variant."
--- chunk chegarasi ---
Paragraf 6: "Narxi oyiga 450 000 so'm."

6-paragraf yolg'iz qolsa — "narxi" nimaning narxi? Chunk o'zi javob bermaydi. Savol "Premium qancha?" bo'lsa, bu chunk topilsa ham foydasiz.

Overlap bilan 6-chunk 5-paragrafni ham o'z ichiga oladi va ma'no butun qoladi.

Kod

python76 qator
from app.config import settings

MAX_TOKENS = 2000
CHARS_PER_TOKEN = 3


def _estimate_tokens(text: str) -> int:
    """Token sonini TAXMINIY hisoblaydi."""
    return len(text) // CHARS_PER_TOKEN


def _overlap_parts(parts: list[str], overlap_tokens: int) -> list[str]:
    """Yopilgan chunk oxiridan keyingi chunk uchun paragraf tanlaydi."""
    if overlap_tokens <= 0 or not parts:
        return []

    tanlangan, jami = [], 0
    for p in reversed(parts):              # oxiridan orqaga
        t = _estimate_tokens(p)
        if jami + t > overlap_tokens and tanlangan:
            break
        tanlangan.insert(0, p)
        jami += t

    # HAMMASINI qaytarmaymiz — aks holda sikl oldinga siljimaydi
    if len(tanlangan) >= len(parts):
        tanlangan = tanlangan[1:]
    return tanlangan


def chunk_text(text: str, title: str = "",
               max_tokens: int = MAX_TOKENS,
               overlap: int | None = None) -> list[str]:
    if overlap is None:
        overlap = settings.chunk_overlap   # .env dagi CHUNK_OVERLAP

    # Overlap chunk dan katta bo'lsa — CHEKSIZ SIKL. Chegaralaymiz.
    overlap = max(0, min(overlap, max_tokens // 2))

    if not text or not text.strip():
        return []

    prefix = title + "\n\n" if title else ""
    full_text = prefix + text

    if _estimate_tokens(full_text) <= max_tokens:
        return [full_text]

    paragraphs = text.split("\n\n")
    chunk_limit = max_tokens - _estimate_tokens(prefix)

    chunks = []
    current_parts = []
    current_tokens = 0

    for paragraph in paragraphs:
        paragraph = paragraph.strip()
        if not paragraph:
            continue

        para_tokens = _estimate_tokens(paragraph)
        would_exceed = (current_tokens + para_tokens) > chunk_limit

        if would_exceed and current_parts:
            chunks.append(prefix + "\n\n".join(current_parts))
            # OVERLAP: oxirgi paragraf(lar)ni keyingi chunk ga ko'chiramiz
            current_parts = _overlap_parts(current_parts, overlap)
            current_tokens = sum(_estimate_tokens(p) for p in current_parts)

        current_parts.append(paragraph)
        current_tokens += para_tokens

    if current_parts:
        chunks.append(prefix + "\n\n".join(current_parts))

    return chunks

Qator-qator

_estimate_tokens — nega taxminiy?

python1 qator
return len(text) // CHARS_PER_TOKEN      # 3 ga bo'lamiz

Aniq token sanash uchun modelning tokenizeri kerak — sekin va qo'shimcha kutubxona. Taxmin yetarli.

Nega 3, 4 emas? Ingliz tili uchun odatda ~4 belgi = 1 token. O'zbek va rus tillari uchun ko'proq token chiqadi (kamroq belgi bir tokenga). 3 ni tanlash — xavfsizlik chekinishi: haqiqiy token soni taxmindan kam bo'ladi, limitdan oshmaymiz.

prefix — sarlavha har bir chunkda

python1 qator
prefix = title + "\n\n" if title else ""

Har bir chunk sarlavha bilan boshlanadi. Nega?

Chunk bazadan yolg'iz chiqadi — konteksti yo'q. "Narxi 250 000 som" degan chunk nima haqida? Sarlavha bo'lsa — "Nihol tariflari" — ma'no tiklanadi. Embedding ham aniqroq bo'ladi.

chunk_limit = max_tokens - prefix_tokens

Prefix ham joy egallaydi. Uni hisobga olmasak, sarlavha uzun bo'lganda chunk limitdan oshadi.

would_exceed and current_parts

matn2 qator
# parcha
if would_exceed and current_parts:

current_parts sharti muhim. Busiz: birinchi paragrafning o'zi limitdan katta bo'lsa, bo'sh chunk qo'shiladi va paragraf yo'qoladi.

Bu shart bilan: katta paragraf yolg'iz chunk bo'ladi. Limitdan oshadi, lekin yo'qolmaydi. Ma'lumot yo'qotgandan ko'ra limitdan oshgan yaxshiroq.

Oxirgi qism

python2 qator
if current_parts:
    chunks.append(prefix + "\n\n".join(current_parts))

Sikl tugagach, yig'ilgan paragraflar qoladi. Bu qatorsiz oxirgi chunk yo'qoladi. Eng ko'p unutiladigan xato.

Overlap ishlayotganini KO'RING

python9 qator
matn = "\n\n".join(f"{i}-paragraf. Matn {i} haqida. " * 8 for i in range(20))

for ov in (0, 50, 200):
    c = chunk_text(matn, title="Sinov", max_tokens=400, overlap=ov)
    ust = 0
    for a, b in zip(c, c[1:]):
        pa = set(a.split("\n\n")[1:]); pb = set(b.split("\n\n")[1:])
        ust += len(pa & pb)
    print(f"overlap={ov:<4} chunk={len(c):<3} ustma-ust paragraf={ust}")

Natija:

python3 qator
overlap=0    chunk=4   ustma-ust paragraf=0
overlap=50   chunk=5   ustma-ust paragraf=4
overlap=200  chunk=6   ustma-ust paragraf=10

Overlap = 0 bo'lsa 4 chunk, 50 bo'lsa 5 chunk. Ko'proq chunk — chunki ma'lumot takrorlanadi. Bu NORMAL va shu narsa uchun to'laymiz: biroz ko'proq joy, lekin ancha yaxshi kontekst.

overlap oshgan sari ustma-ust tushgan paragraflar ham ko'payadi: 4 dan 10 ga. Bu to'g'ridan-to'g'ri sozlamaga bog'liq.

Chegaraviy holatlarni sinang

python11 qator
# overlap chunk dan KATTA — cheksiz sikl bo'lmasligi kerak
c = chunk_text(matn, title="T", max_tokens=100, overlap=999)
print(f"{len(c)} chunk — sikl tugadi")

print(chunk_text(""))                      # []
print(len(chunk_text("Salom", title="T"))) # 1

# matn yo'qolmadimi
asl = set(matn.split())
yig = set(" ".join(chunk_text(matn, max_tokens=400, overlap=50)).split())
print("yo'qolgan:", asl - yig or "yo'q")

Sinash (asosiy)

python7 qator
matn = "\n\n".join(f"Bu {i}-paragraf. " * 20 for i in range(30))
b = chunk_text(matn, title="Sinov hujjati", max_tokens=500)

print(f"chunk soni : {len(b)}")
print(f"o'lchamlar : {[len(x) for x in b]}")
print(f"tokenlar   : {[_estimate_tokens(x) for x in b]}")
print(f"sarlavha bormi: {all(x.startswith('Sinov hujjati') for x in b)}")

Barcha matn saqlanganini tekshiring:

python3 qator
asl = set(matn.split())
yigilgan = set(" ".join(b).split())
print("yo'qolgan so'zlar:", asl - yigilgan)

Bo'sh to'plam chiqishi kerak.

MAX_TOKENS ni qanday tanlash

max_tokens Qachon
200-500 FAQ, qisqa javoblar, aniq qidiruv
1000-2000 umumiy hujjatlar (BIZNING TANLOV: 2000)
4000+ ilmiy maqola, uzun kontekst kerak bo'lsa

2000 token ≈ 6000 belgi ≈ 1.5 sahifa.

Katta chunk — ko'proq kontekst, lekin qidiruv aniqligi pasayadi (vektor "suyuqlashadi"). Kichik chunk — aniq, lekin kontekst yetishmaydi.

Buni o'lchash kerak, taxmin qilish emas.

Amalda ko'ring

bash8 qator
python -c "
from scraper.chunker import chunk_text, _estimate_tokens
m = open('hujjat.txt').read()
b = chunk_text(m, title='Hujjat')
print(f'{len(m)} belgi -> {len(b)} chunk')
for i, c in enumerate(b[:3], 1):
    print(f'  {i}: {_estimate_tokens(c)} token, {len(c)} belgi')
"

Kutilmagan natija normal: kichik sahifadan bitta chunk chiqadi. 1000 belgi ≈ 333 token — bu 2000 limitdan ancha kam, shuning uchun bo'linmaydi. Bu to'g'ri xatti-harakat, xato emas.


Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar