generator.py — javob yozish
Bu bob soddalashtirilgan. Haqiqiy
app/services/generator.pyikki jihatdan farq qiladi:
build_prompt(chunks, question, history)bitta MATN emas, OpenAI uslubidagi xabarlar ro'yxati (list[dict]) qaytaradi:[{"role": "system", ...}, {"role": "user", ...}]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_markdownvacut_repetitionskabi 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
[TIZIM QOIDASI] <- qanday javob berish kerak [KONTEKST] <- topilgan chunk'lar [SAVOL] <- foydalanuvchi savoli
Kod
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
# parcha "temperature": 0.2
0— har safar bir xil javob, quruq0.2— deyarli barqaror, biroz tabiiy <- BIZNING TANLOV0.7— ijodiy, lekin o'ylab topishi mumkin1.0+— RAG uchun yaramaydi
RAG'da ijodkorlik zarar. Bizga aniqlik kerak.
Manba ko'rsatish
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
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
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:
# 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:
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:
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:
python tekshir_prompt.py 5
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.