Asosiy qismga o'tish

embedder.py — matndan vektorga

9-bob 14 kod bloki 877 so'z

Buni ham o'zingiz yozasiz. Bu bobdan keyin bazangizda haqiqiy sonlar paydo bo'ladi — ikkinchi g'alaba.

Embedding nima

Matnni sonlar ro'yxatiga aylantiradi:

python2 qator
"Standart tarif 250 000 som"  ->  [0.023, -0.451, 0.882, ... ]
                                   <-------- 1024 ta son -------->

Bu sonlar tasodifiy emas. Ular matnning ma'nosini ifodalaydi. O'xshash ma'noli gaplar — o'xshash sonlar.

Shuning uchun "tarif qancha turadi?" va "narxi nechchi?" degan ikki xil gap bir-biriga yaqin vektor beradi. Aynan shu RAG ni kalit so'z qidiruvidan ustun qiladi.

Kod

python24 qator
import httpx
from app.config import settings


def embed_text(matn: str) -> list[float] | None:
    """Bitta matnni vektorga aylantiradi. Xato bo'lsa None qaytaradi."""
    if not matn or not matn.strip():
        return None

    try:
        javob = httpx.post(
            f"{settings.ollama_base_url}/api/embeddings",
            json={"model": settings.ollama_embed_model, "prompt": matn},
            timeout=60.0,
        )
        javob.raise_for_status()
        vektor = javob.json().get("embedding")
    except (httpx.HTTPError, ValueError) as e:
        print(f"[XATO] embedding olinmadi: {type(e).__name__}")
        return None

    if not vektor:
        return None
    return vektor

Nega None qaytaradi, xato tashlamaydi

Ollama ishlamayotgan bo'lishi mumkin. 1000 ta chunk'ni qayta ishlayotganda bitta xato butun jarayonni to'xtatmasligi kerak.

Lekin None ni tekshirish shart. Aks holda:

python2 qator
vektor = embed_text(savol)
print(len(vektor))        # TypeError: object of type 'NoneType' has no len()

Bu xato dars paytida chiqadi va sizni chalg'itadi. Shuning uchun har doim:

python3 qator
if not vektor:
    print("Ollama ishlamayapti — 'ollama serve' ni tekshiring")
    return

O'lchamni tekshirish

Quyidagi embed_and_check — faqat DARS uchun yordamchi. Haqiqiy loyihada bunday funksiya yo'q. Lekin g'oyasi foydali.

python13 qator
def embed_and_check(matn: str) -> list[float] | None:
    v = embed_text(matn)
    if v is None:
        return None

    from app.models.tables import LOCAL_DIM
    if len(v) != LOCAL_DIM:
        raise ValueError(
            f"Model {len(v)} o'lchamli vektor berdi, "
            f"jadval esa {LOCAL_DIM} kutadi. "
            f"Model almashtirilganmi? bootstrap.py ni qayta ishlating."
        )
    return v

Bu tekshiruv juda foydali. Modelni almashtirsangiz, o'lcham o'zgaradi va baza xato beradi:

matn1 qator
expected 1024 dimensions, not 768

Bu xabar qayerdan kelganini tushunish qiyin. Yuqoridagi tekshiruv esa sababni to'g'ridan-to'g'ri aytadi.

Barcha chunk'larni qayta ishlash

python37 qator
from sqlalchemy import text
from app.database import SessionLocal


def embed_all_chunks(session, batch: int = 20) -> int:
    db = session
    jami = 0
    try:
        while True:
            qatorlar = db.execute(text("""
                SELECT id, chunk_text FROM rag.chunks
                WHERE embedding_local IS NULL
                ORDER BY created_at
                LIMIT :lim
            """), {"lim": batch}).fetchall()

            if not qatorlar:
                break

            for q in qatorlar:
                v = embed_text(q.chunk_text)
                if v is None:
                    print("  Ollama javob bermadi — to'xtatildi")
                    return jami

                db.execute(text("""
                    UPDATE rag.chunks
                    SET embedding_local = :v
                    WHERE id = :id
                """), {"v": str(v), "id": q.id})
                jami += 1

            db.commit()
            print(f"  {jami} ta chunk qayta ishlandi")
    finally:
        pass
    return jami

Diqqat: haqiqiy loyihada funksiya nomi embed_all_chunks va u sessiyani tashqaridan oladi — bu testlashni osonlashtiradi.

WHERE embedding_local IS NULL — eng muhim qator. Faqat vektorsizlarni oladi. Shuning uchun skriptni qayta ishga tushirish xavfsiz: uzilib qolsa, qolganidan davom etadi.

LIMIT va batch — hammasini bir vaqtda xotiraga olmaymiz. 100 000 chunk bo'lsa, kompyuter yiqiladi.

db.commit() sikl ichida — har batch'dan keyin saqlaymiz. Oxirida commit qilsangiz va jarayon uzilsa, hamma ish yo'qoladi.

str(v) — pgvector vektorni matn sifatida kutadi: "[0.023, -0.451, ...]". Python ro'yxatini to'g'ridan-to'g'ri yubora olmaysiz.

Ishga tushiring

bash1 qator
python -m scraper.embedder
python4 qator
  20 ta chunk qayta ishlandi
  40 ta chunk qayta ishlandi
  ...
  Tayyor: 14 ta vektor yaratildi

Endi bazaga qarang

sql3 qator
SELECT count(*) AS jami,
       count(embedding_local) AS vektorli
FROM rag.chunks;

Endi vektorli = 14. Oldin 0 edi.

Haqiqiy sonlarni ko'ring:

sql1 qator
SELECT left(embedding_local::text, 80) FROM rag.chunks LIMIT 1;
matn1 qator
[0.0234,-0.4512,0.8821,-0.1043,0.5567,...

Talabalarga shuni ko'rsating. Ular birinchi marta "ma'no"ning son ko'rinishini ko'radi. Bu RAG ni sehrdan matematikaga aylantiradigan lahza.

Matematikani sinang

sql3 qator
SELECT 1 - ('[1,0,0]'::vector <=> '[1,0,0]'::vector) AS bir_xil,
       1 - ('[1,0,0]'::vector <=> '[0,1,0]'::vector) AS bogliq_emas,
       1 - ('[1,0,0]'::vector <=> '[-1,0,0]'::vector) AS qarama_qarshi;
matn3 qator
 bir_xil | bogliq_emas | qarama_qarshi
---------+-------------+---------------
       1 |           0 |            -1

<=> — cosine masofa operatori. 1 - masofa = o'xshashlik.

  • 1 — bir xil yo'nalish
  • 0 — bog'liq emas
  • -1 — qarama-qarshi

Keyingi bobda aynan shu operator bilan qidiramiz.


Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar