Asosiy qismga o'tish

generator.py — javob yozish

13-bob 10 kod bloki 1246 so'z

Bu bob soddalashtirilgan. Haqiqiy app/services/generator.py ikki jihatdan farq qiladi:

  1. build_prompt(chunks, question, history) bitta MATN emas, OpenAI uslubidagi xabarlar ro'yxati (list[dict]) qaytaradi: [{"role": "system", ...}, {"role": "user", ...}]
  2. generate_stream(...) javobni oqim (stream) qilib beradi — so'zma-so'z, kutmasdan.

Quyidagi soddalashtirilgan variant g'oyani tushuntirish uchun. Tushunganingizdan keyin haqiqiy faylni oching va solishtiring. U yerda strip_markdown va cut_repetitions kabi qo'shimcha tozalash funksiyalari ham bor.

Chunk'lar topildi va tartiblandi. Endi ulardan javob yasaymiz.

Bu yerda ko'pchilik xato qiladi: LLM ga chunk'larni tashlab, "javob ber" deydi. Natija — model o'ylab topgan ma'lumot bilan aralashgan javob.

Prompt tuzilishi

matn3 qator
[TIZIM QOIDASI]  <- qanday javob berish kerak
[KONTEKST]       <- topilgan chunk'lar
[SAVOL]          <- foydalanuvchi savoli

Kod

python49 qator
import httpx
from app.config import settings

TIZIM = """Sen yordamchi botsan. Quyidagi QOIDALARGA qat'iy amal qil:

1. FAQAT berilgan kontekstdagi ma'lumotdan foydalanib javob ber.
2. Agar kontekstda javob YO'Q bo'lsa, "Bu haqda ma'lumotim yo'q"
   deb ayt. Hech narsa O'YLAB TOPMA.
3. Javobni foydalanuvchi tilida ber.
4. Qisqa va aniq yoz.
5. Raqamlarni (narx, muddat) kontekstdan AYNAN ko'chir."""


def build_prompt_oddiy(chunks: list[dict], question: str) -> str:
    qismlar = []
    for i, c in enumerate(chunks, 1):
        qismlar.append(f"[{i}] {c['title']}\n{c['chunk_text']}")

    kontekst = "\n\n".join(qismlar)

    return (
        f"{TIZIM}\n\n"
        f"=== KONTEKST ===\n{kontekst}\n\n"
        f"=== SAVOL ===\n{question}\n\n"
        f"=== JAVOB ==="
    )


def generate_oddiy(chunks: list[dict], question: str) -> str:
    if not chunks:
        return "Bu haqda ma'lumotim yo'q."

    prompt = build_prompt_oddiy(chunks, question)

    try:
        j = httpx.post(
            f"{settings.ollama_base_url}/api/generate",
            json={
                "model": settings.ollama_model,
                "prompt": prompt,
                "stream": False,
                "options": {"temperature": 0.2, "num_predict": 500},
            },
            timeout=120.0,
        )
        j.raise_for_status()
        return j.json().get("response", "").strip()
    except httpx.HTTPError as e:
        return f"Javob olinmadi: Ollama ishlayaptimi? ({type(e).__name__})"

Har bir qoida nima uchun

1-qoida: faqat kontekstdan

Busiz model o'z bilimidan javob beradi. Sizning saytingizda "Standart tarif 250 000" yozilgan, model esa "odatda 300 000 atrofida" deydi. Foydalanuvchi noto'g'ri ma'lumot oladi.

2-qoida: bilmayman deyish

LLM'lar javob berishga moyil. Bilmasa ham o'ylab topadi (gallyutsinatsiya). Bu qoida unga "bilmayman" deyishga ruxsat beradi.

Buni sinang: kontekstda yo'q narsani so'rang. Agar model javob yozsa — prompt kuchsiz.

5-qoida: raqamlarni aynan ko'chir

LLM'lar raqamlarni "yumaloqlashtiradi". 250 000 → 250 ming → "taxminan 250 ming". Narx bilan bunday qilish mumkin emas.

temperature nega 0.2

matn2 qator
# parcha
"temperature": 0.2
  • 0 — har safar bir xil javob, quruq
  • 0.2 — deyarli barqaror, biroz tabiiy <- BIZNING TANLOV
  • 0.7 — ijodiy, lekin o'ylab topishi mumkin
  • 1.0+ — RAG uchun yaramaydi

RAG'da ijodkorlik zarar. Bizga aniqlik kerak.

Manba ko'rsatish

python16 qator
def javob_va_manba(chunks: list[dict], question: str) -> dict:
    javob = generate_oddiy(chunks, question)

    manbalar = []
    korilgan = set()
    for c in chunks:
        kalit = (c["title"], c.get("source_url"))
        if kalit not in korilgan:
            korilgan.add(kalit)
            manbalar.append({
                "title": c["title"],
                "url": c.get("source_url"),
                "similarity": round(c["similarity"], 3),
            })

    return {"answer": javob, "sources": manbalar}

Manba — ishonchning asosi. Foydalanuvchi javobni tekshira olishi kerak. Manbasiz RAG — shunchaki ishonchli ko'rinadigan matn.

korilgan to'plami takrorlanishni oldini oladi: bitta hujjatdan uchta chunk kelsa, manba bir marta ko'rsatiladi.

Prompt'ni O'Z KO'ZINGIZ bilan ko'ring

python3 qator
p = build_prompt_oddiy(chunklar, "Nihol narxi qancha?")
print(p)
print(f"\nUzunligi: {len(p)} belgi, ~{len(p)//4} token")

Buni darsda albatta qiling. Talabalar RAG ni "sehr" deb o'ylaydi. Prompt'ni ko'rgach tushunadi: bu shunchaki kontekst + savol. Boshqa hech narsa yo'q.

Kontekst uzunligi

python9 qator
def kontekst_cheklash(chunks: list[dict], max_belgi: int = 6000) -> list[dict]:
    natija, jami = [], 0
    for c in chunks:
        uz = len(c["chunk_text"])
        if jami + uz > max_belgi:
            break
        natija.append(c)
        jami += uz
    return natija

Modelning kontekst oynasi cheklangan. Oshib ketsa, model matnning boshini unutadi yoki xato beradi.

Taxminan: 1 token ≈ 4 belgi. 6000 belgi ≈ 1500 token. qwen2.5:7b 32k token ko'taradi, lekin uzun kontekst sifatni pasaytiradi.

O'LCHANGAN SABOQ: promptga NAMUNA JUMLA yozmang

Bu loyihada haqiqiy tajriba o'tkazildi. Kontekst ARALASH edi — bir xil ma'lumot o'zbek va rus tilida.

2-qoida ("ma'lumot topilmasa nima deyish") uch xil yozildi:

2-qoida qanday yozilgan To'g'ri tilda javob
faqat RUSCHA namuna jumla 3/4 — o'zbekcha savolga RUSCHA javob
ruscha + O'ZBEKCHA namuna 2/4 — endi ruscha savolga O'ZBEKCHA javob
namunasiz, faqat qoida 4/4

Uchinchisi 5 marta takrorlab sinaldi: 20/20, takror 0.

Nega shunday: promptga yozilgan har qanday jumla model uchun "namuna" bo'ladi. Ruscha jumla yozsangiz — model ruscha javob berishga moyil bo'ladi. O'zbekcha qo'shsangiz — teskarisi.

To'g'ri yo'l — qoidani AYTING, ko'rsatmang:

matn3 qator
# parcha
"2. If the context lacks the information, state that clearly "
"in the SAME language as the question. Do not invent an answer.\n"

Prompt tili: ingliz yoki o'zbek?

Xuddi shu tajribada BUTUN prompt o'zbekchaga o'girildi. Natija aralash kontekstda:

Prompt tili O'zbekcha savol Ruscha savol
Ingliz 0 kirill / 207 lotin — TOZA 214 kirill / 0 lotin — TOZA
O'zbek 168/158 — ARALASHDI, takrorladi buzuq, pastga qarang

O'zbekcha prompt bilan model ruscha savolga shunday javob berdi:

matn2 qator
1. Нихол Стандарт тарифи ойга 250 000 so'm турadi.
2. Нихол Стандарт тарифи 100 GB интернетни озимайди.

Bu rus tili EMAS — bu kirill harflari bilan yozilgan o'zbek tili. Oxirgi so'z (yuqoridagi blokda) hech qanday tilda mavjud emas — model uni o'ylab topgan. Ikki til aralashib ketdi va har bir tarif ikki marta sanab chiqildi.

Xulosa: SYSTEM PROMPT ingliz tilida bo'lsin. Lokal modellar asosan ingliz tilida o'qitilgan — ko'rsatmaga ingliz tilida yaxshiroq amal qiladi. Bu JAVOB tiliga ta'sir qilmaydi: javob 4-qoida bo'yicha foydalanuvchi tilida keladi.

Buni o'zingiz tekshiring:

bash1 qator
python tekshir_prompt.py 5

num_predict — javob uzunligi chegarasi

Bu parametr LLM maksimal nechta token yozishini belgilaydi. Loyihada u avval 1536 edi. O'lchangan natija:

num_predict To'g'ri tilda Bo'sh javob
1536 11/12 0
400 16/16 0

Nima bo'lgan edi: 1536 token bilan model yaxshi javobni yozib bo'lgach ham to'xtamasdi. Davom etar ekan, o'zbekcha javob asta-sekin kirill yozuviga o'tib ketardi — chunki kontekstda ruscha chunk lar ham bor edi va model ularga "sirg'alardi".

400 token bu sirg'alishni to'xtatadi: model kerakli faktni aytadi va tugatadi. O'rtacha javob 305 belgi — RAG uchun yetarli.

Umumiy qoida: RAG javobi uzun bo'lishi SHART EMAS. Vazifa — kontekstdagi faktni aytish, insho yozish emas. Chegara qancha past bo'lsa, model shuncha kam "o'ylab topadi".

Buni o'zingiz sinang:

bash1 qator
python tekshir_prompt.py 5

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar