IlmHamroh
Data Science va sun'iy intellekt/Katta til modellari5/14-dars48 daqiqa
Mundarija (22)

25.5-dars: Strukturali chiqish va validatsiya

25-QISM — KATTA TIL MODELLARI · 5-dars


1. Kirish va motivatsiya

25.4-darsda promptni qanday yozishni ko'rdik: rol, ko'rsatma, misollar, chiqish formati. Endi keyingi savol: LLM javobini kim o'qiydi? Chat oynasida — odam. Lekin real tizimlarda javobni ko'pincha dastur o'qiydi: buyurtma bazaga yoziladi, murojaat navbatga qo'yiladi, maydonlar hisobotga tushadi. Odam "Albatta! Mana natija:" degan kirish so'zini, kod bloki belgilarini yoki ortiqcha vergulni ko'z bilan o'tkazib yuboradi. json.loads esa o'tkazib yubormaydi — birinchi ortiqcha belgida xato beradi.

Shuning uchun LLM ni dastur bilan bog'lashda uchta qatlam kerak. Birinchisi — ajratib olish: javob matnidan JSON ni xavfsiz topish. Ikkinchisi — validatsiya: topilgan JSON kutilgan shaklga (sxema) va ma'noga (biznes qoidalari) mosmi. Uchinchisi — tiklash: xato bo'lsa, modelga xatoni aytib qayta so'rash, lekin cheklangan marta, keyin esa zaxira yo'l (fallback).

Real vaziyat. Onlayn do'kon mijozlarning erkin yozilgan xabarlaridan buyurtma kartochkasini LLM yordamida to'ldirdi. Birinchi hafta hammasi yaxshi ko'rindi: xatolar kam, operatorlar mamnun. Oy oxirida hisob-kitob bo'limi mos kelmaslikni topdi: bir qator buyurtmalarda "jami" summa mahsulotlar narxlari yig'indisiga teng emas edi — model arifmetikada adashgan, lekin JSON to'g'ri shaklda bo'lgani uchun hech qanday tekshiruv uni ushlamagan. Boshqa buyurtmalarda mijoz telefon raqamini yozmagan bo'lsa ham, kartochkada "+998 90 123 45 67" turardi — model maydonni bo'sh qoldirish o'rniga uni "to'ldirib" qo'ygan. Bu darsning 2- va 4-misollari aynan shu ikki xatoni o'lchaydi.

Bu darsda LLM javobidan JSON ni xavfsiz ajratib olishni, JSON Schema ning kichik qismini noldan yozishni, xato bilan qayta so'rash siklini va ma'lumot ajratib olish (extraction) vazifasini maydon bo'yicha baholashni ko'ramiz.

Bu darsda:

  • Nega strukturali chiqish: javobni dastur o'qiydi
  • Buzilgan JSON ni xavfsiz ajratib olish
  • Validatsiya: sxema, biznes qoidalari, dataclass
  • Moslashtirish (coercion): qaysi tuzatish xavfsiz
  • Xato bilan qayta so'rash va urinishlar chegarasi
  • Tool use va structured outputs: API da sxemani majburlash
  • Extraction va maydon bo'yicha baholash
  • Tuzoqlar

ℹ Mashinada LLM API yo'q. Misollarda LLM o'rnida STUB — SoxtaLLM / soxta_llm deb nomlangan deterministik soxta mijoz ishlatiladi. U haqiqiy model emas: xatolarni biz tanlagan faraziy ehtimollar bilan chiqaradi. Stub natijalari "LLM shunday qiladi" degan da'vo emas — ular atrofdagi muhandislik kodining (ajratish, validatsiya, qayta so'rash, baholash) qanday ishlashini ko'rsatadi. Kod tuzilishi haqiqiy API bilan bir xil.


2. Nazariya — chuqur tushuntirish

2.1. Nega strukturali chiqish

text
ERKIN MATN:
  "Mijoz Dilnoza Karimova, 2 ta kitob, Samarqandga, jami 90 ming so'm"
  odam uchun tushunarli; dastur uchun - har safar boshqa shakl

STRUKTURALI CHIQISH (JSON):
  {"mijoz": "Dilnoza Karimova", "soni": 2, "mahsulot": "kitob",
   "shahar": "Samarqand", "summa": 90000}
  kalitlar - oldindan kelishilgan; turlar - aniq; bazaga to'g'ridan-to'g'ri

KONTRAKT = SXEMA:
  qaysi maydonlar majburiy, qaysi turda, qanday qiymatlar ruxsat
  promptda ham, validatsiyada ham AYNAN bir xil sxema

JAVOB ZANJIRI:
  LLM matni -> ajratish (JSON topish) -> json.loads -> sxema tekshiruvi
            -> biznes qoidalari -> dataclass / baza
  har bosqichda xato bo'lishi mumkin; har biriga alohida chora

LLM — ehtimoliy generator (24.9-dars): u keyingi tokenni tanlaydi, "JSON grammatikasi" haqida kafolat bermaydi. Promptda "faqat JSON qaytaring" deyish ehtimolni oshiradi, lekin kafolat emas. Kafolat ikki yo'l bilan olinadi: (a) API darajasida sxema majburlash 2.6-bob, (b) o'z tomonimizda ajratish + validatsiya + qayta so'rash. Amalda ikkalasi birga ishlatiladi: majburlash shaklni kafolatlaydi, lekin ma'noni emas.

LLM javobini dastur o'qisa — kontrakt (sxema) kerak, va bu kontrakt dastur tomonida tekshiriladi, modelning "va'dasi" ga ishonilmaydi.

2.2. Buzilgan JSON ni xavfsiz ajratib olish

text
TIPIK BUZILISHLAR (1-misoldagi stub turlari):
  kod bloki          ```json { ... } ```
  matn bilan         "Albatta! Mana natija: {...} Yana savol bo'lsa..."
  ortiqcha vergul    {"a": 1, "b": 2,}
  izoh               "yosh": 21,  // taxminiy
  Python lug'ati     {'talaba': True, 'izoh': None}
  qo'shtirnoq xato   "izoh": "u "a'lo" deb yozdi"
  qirqilgan          {"ism": "Ra'no", "yosh": 21, "sha        (max_tokens)

BOSQICHLI AJRATISH (arzondan qimmatga):
  1. json.loads(matn)                        - toza bo'lsa, shu yetadi
  2. ```json ... ``` bloki                   - regex
  3. qavs skaneri: birinchi '{' dan qavslarni sanash,
     satr ichidagi '{', '}' ni hisobga olmay
  4. yengil tuzatish, faqat SATRDAN TASHQARIDA:
     izohlarni olib tashlash, ortiqcha vergul, True/False/None,
     bitta qo'shtirnoqli satrlar -> JSON satr
  5. bo'lmasa - xato; taxmin qilmaymiz

TUZATISH CHEGARASI:
  bir ma'noli -> tuzatamiz ('True' -> true, ortiqcha vergul)
  ko'p ma'noli -> RAD ETAMIZ va qayta so'raymiz:
    ikki obyekt ("Misol: {...}. Javob: {...}") - qaysi biri javob?
    ichki qo'shtirnoq - satr qayerda tugaydi?
    qirqilgan - yetishmagan qism nima edi?

QIRQILISH BELGISI:
  API javobida stop_reason == "max_tokens" -> javob to'liq emas
  ajratishdan OLDIN tekshiriladi

Eng muhim o'lchov — jim xato: ajratish muvaffaqiyatli ko'rindi, lekin qiymat noto'g'ri. U "yiqilish" dan xavfliroq: yiqilishni ko'ramiz va qayta so'raymiz, jim xato esa bazaga kirib ketadi. 1-misolda 600 ta stub javobda naiv json.loads faqat toza javoblarni o'qidi (39.0%), bosqichli ajratish — 92.8%, jim xato esa 0. Tiklab bo'lmagan ikki tur — ichki qo'shtirnoq va qirqilgan javob — ataylab rad etildi.

Hech qachon eval() ishlatmang. Python lug'ati ko'rinishidagi javobni eval bilan o'qish — model (yoki model o'qigan hujjat, 25.13-dars) yozgan ixtiyoriy kodni bajarish demakdir. ast.literal_eval xavfsizroq (faqat literallar), lekin u ham JSON emas: true/null ni tanimaydi. 1-misoldagi kabi satrlarni hisobga oluvchi tuzatish — boshqariladigan yo'l.

Ajratish — bosqichli va konservativ: bir ma'noli buzilishni tuzating, shubhalisini rad eting; jim xato sonini alohida o'lchang.

2.3. Validatsiya: sxema, biznes qoidalari, dataclass

text
JSON SCHEMA (sanoat standarti; biz uning kichik qismini yozamiz):
  type                  string / integer / number / boolean / array / object / null
  required              majburiy kalitlar
  additionalProperties  false -> noma'lum kalit xato
  enum                  ruxsat etilgan qiymatlar ro'yxati
  minimum / maximum     son chegaralari
  minLength / pattern   satr uzunligi, regex (masalan, ^\+998\d{9}$)
  items / minItems      ro'yxat elementlari sxemasi

XATO XABARI - YO'L BILAN:
  $.mahsulotlar[0].soni: kutilgan integer, keldi str ('2')
  aniq xabar -> modelga qaytarsa bo'ladi 2.5-bob

PYTHON TUZOG'I:
  isinstance(True, int) == True   (bool - int ning quyi sinfi)
  "soni": true sxemadan o'tib ketadi -> type(x) is int deb tekshiring

SHAKL va MA'NO:
  sxema - SHAKL: turlar, kalitlar, chegaralar
  biznes qoidalari - MA'NO: maydonlar orasidagi bog'liqlik
    jami == sum(soni * narx);  boshlanish < tugash;  yosh va tug'ilgan yil
  2-misol: jami 1000 so'mga xato - sxema xatolari 0, biznes qoidasi ushladi

DATACLASS:
  tekshirilgan lug'at -> @dataclass(frozen=True) obyekt
  qolgan kod lug'at kalitlari bilan emas, turi aniq maydonlar bilan ishlaydi
  pydantic (o'rnatilgan bo'lsa) - sxema + tur + dataclass bir joyda

Sxema shaklni tekshiradi, ma'noni emas: 2-misolda moslashtirishdan keyin sxemadan o'tgan javoblar ichida 16 tasi noto'g'ri edi — hammasi maydonlararo qoida bilan ushlandi.

2.4. Moslashtirish (coercion): qaysi tuzatish xavfsiz

text
XAVFSIZ (bir ma'noli, qiymat o'zgarmaydi):
  "3"            -> 3
  "Karta"        -> "karta"         (enum kichik harfda)
  "90 123 45 67" -> "+998901234567" (9 raqam -> O'zbekiston kodi)
  noma'lum kalit -> tashlash (agar siyosat shuni aytsa)

XAVFLI (taxmin):
  "1,5"          -> 1.5 yoki 15?     (o'nlik vergul yoki minglik ajratuvchi)
  "03.04"        -> 3-aprel yoki 4-mart?
  "ertaga"       -> qaysi sana?     (so'rov vaqtiga bog'liq)
  bo'sh maydon   -> standart qiymat? (uydirma bilan bir xil)

SIYOSATLAR (2-misol, 400 javob):
  qat'iy               qabul 74.0%,  qabul qilingan noto'g'ri 11
  moslashtirish        qabul 98.8%,  qabul qilingan noto'g'ri 16
  moslash + biznes     qabul 94.8%,  qabul qilingan noto'g'ri 0

Diqqat: qat'iy siyosat ham noto'g'ri javobni qabul qildi (11 ta). Qat'iylik shaklni kuchaytiradi, ma'noni emas — "jami" dagi arifmetik xato shaklan to'g'ri. Moslashtirish qabulni oshiradi, lekin shunda biznes qoidalari yanada muhim bo'ladi.

Moslashtirish faqat bir ma'noli holatlarda; har bir tuzatish qoidasi — "bu qiymatni o'zgartiradimi?" degan savolga "yo'q" javobi bilan.

2.5. Xato bilan qayta so'rash (repair loop)

text
SIKL:
  prompt = asosiy
  urinish 1..K:
    javob = llm(prompt)
    d, xato = ajrat_va_tekshir(javob)
    agar xato yo'q -> qaytar d
    prompt = asosiy + "Oldingi javobingiz: ..." + "XATO: <aniq xabar>"
  K urinishdan keyin -> FALLBACK (None, navbat, operator, boshqa model)

NEGA XATO MATNI:
  bir xil prompt + deterministik dekodlash (T = 0) -> BIR XIL javob
  3-misol (stub): T = 0 da 1 va 5 urinish - 70.7% va 70.7%;
                  lekin 5 urinish 2.17 marta chaqiruv
  xato matni promptni o'zgartiradi -> yangi javob

XARAJAT:
  har urinish = yangi so'rov: kirish tokenlari qayta to'lanadi
  xato bilan so'rashda prompt uzayadi (oldingi javob + xato)
  3-misol: 'xato bilan' K 3 -> 5: muvaffaqiyat 96.7% -> 96.9%,
           kirish tokenlari 61 -> 66 (so'rovga o'rtacha)

KIM YIQILADI:
  3-misol: yiqilganlarning 76% i 'qiyin' yozuvlar (jami ~10%)
  qayta so'rash ularni tuzatmaydi -> fallback kerak

Bu yerdagi raqamlar stubdan: "xato matni bilan qayta so'rash tuzatish ehtimolini oshiradi" — biz stubga kiritgan faraz (0.25 → 0.10), shuning uchun 3-misolning xato bilan - qayta, T=0.7 farqi dalil emas, farazning oqibati. Stubdan mustaqil haqiqatlar esa: (1) bir xil prompt va deterministik dekodlashda qayta so'rash foydasiz; (2) urinishlar soni oshgani sari foyda kamayadi, xarajat esa chiziqli o'sadi; (3) doimiy qiyin holatlar uchun fallback kerak. Haqiqiy modelda o'z ma'lumotingizda o'lchang: urinish raqami bo'yicha muvaffaqiyat ulushini loglang.

Eslatma: ba'zi yangi modellarda temperature parametri umuman qabul qilinmaydi — "bir xil prompt → aynan bir xil javob" kafolati yo'q, lekin xulosa o'zgarmaydi: promptni o'zgartirmaydigan qayta so'rash — tasodifga umid.

Qayta so'rash promptni o'zgartirishi kerak (aniq xato matni); urinishlarni 2-3 bilan cheklang va fallback ni oldindan loyihalang.

2.6. Tool use va structured outputs: API da sxemani majburlash

text
G'OYA (24.9 dagi generatsiya sikli bilan bog'liq):
  har qadamda model logit beradi; server sxema grammatikasiga zid
  tokenlarni -inf bilan niqoblaydi (constrained decoding)
  -> chiqish sxemaga mos JSON bo'lishi KAFOLATLANADI

IKKI SHAKL (Anthropic API):
  structured outputs: output_config={"format": {"type": "json_schema",
                                                "schema": {...}}}
    javob matni - sxemaga mos JSON
  tool use: tools=[{"name": ..., "input_schema": {...}, "strict": True}]
    model "asbobni chaqiradi": tool_use bloki, .input - lug'at
    (asboblar va agent sikli - Agentlar va tool calling darsida)

KAFOLAT NIMANI QAMRAYDI:
  + kalitlar, turlar, enum, additionalProperties
  - ma'no: jami summa, sana mantiqi, uydirma qiymat
  - to'liqlik: max_tokens da qirqilsa - stop_reason tekshiring
  -> biznes qoidalari va manbaga bog'lash BARIBIR kerak

QACHON O'Z ISHIMIZ KERAK:
  provayder sxema majburlashni qo'llamasa yoki sxema juda murakkab bo'lsa
  lokal / kichik modellar
  majburlash bo'lsa ham: validatsiya ikkinchi himoya chizig'i

Sxema majburlash shaklni kafolatlaydi, ma'noni emas — validatsiya va biznes qoidalari baribir dastur tomonida qoladi.

2.7. Extraction va maydon bo'yicha baholash

text
VAZIFA: erkin matn -> maydonlar
  "Men Bobur Aliyev, Xivadan yozyapman. 2 ta daftar kerak, Termizga
   yetkazib bering. Telefonim: 905871484. 11-sentabr kuni kerak bo'ladi."
  -> ism, shahar (yetkazish), mahsulot, soni, summa, telefon, sana

BAHOLASH DARAJALARI:
  maydon bo'yicha aniqlik  - har maydon alohida: qaysi maydon zaif?
  yozuv bo'yicha aniq mos  - 7 maydonning hammasi to'g'ri (qattiq)
  uydirma (hallucination)  - haqiqat None, model qiymat bergan

NORMALLASHTIRISH - BAHOLASHDA HAM:
  "145 ming so'm", "145 000 so'm", "145000 so'm" -> 145000
  "11-sentabr", "11.09" -> "09-11"
  qat'iy satr taqqoslash format farqini xato deb sanaydi
  4-misol: qat'iy 51.0%, normallangan 94.4% - farq 43.5 foiz punkt

MANBAGA BOG'LASH (grounding):
  ajratilgan qiymat manba matnida uchraydimi?  (telefon - raqamlar bo'yicha)
  yo'q bo'lsa -> None (uydirma rad etiladi)
  4-misol: uydirmalar 105 -> 0
  CHEKLOV: matnda BOR, lekin noto'g'ri qiymatni ushlamaydi
  (jo'natuvchi shahri va yetkazish shahri - ikkalasi ham matnda)

Extraction ni maydon bo'yicha, normallangan qiymatlarda baholang; uydirmani alohida sanang va manbaga bog'lash bilan kamaytiring.

2.8. Tuzoqlar

Asosiy tuzoqlar: "faqat JSON qaytaring" deb yozib json.loads(javob) ni to'g'ridan-to'g'ri chaqirish; eval bilan Python lug'atini o'qish; ko'p ma'noli buzilishni "tuzatish" (ikki obyektdan birinchisini olish, qirqilgan JSON ni qavs bilan yopish); stop_reason ni tekshirmaslik; isinstance(x, int) bilan butun sonni tekshirish (True o'tib ketadi); faqat sxema bilan cheklanib, maydonlararo qoidalarni yozmaslik; noaniq qiymatlarni "moslashtirish" ("03.04", "1,5"); bo'sh maydonni standart qiymat bilan to'ldirish; T = 0 da bir xil prompt bilan qayta so'rash; urinishlar sonini cheklamaslik yoki fallback siz qoldirish; extraction ni qat'iy satr taqqoslash bilan baholash; uydirmani alohida o'lchamaslik; stub natijasini "model shunday qiladi" deb taqdim etish.


3. Tez ma'lumotnoma

python
import json

import anthropic

# ---- 1. Haqiqiy API: sxema majburlangan chiqish (structured outputs) ----
client = anthropic.Anthropic()          # kalit ANTHROPIC_API_KEY muhit o'zgaruvchisidan
SXEMA = {
    "type": "object",
    "properties": {"ism": {"type": "string"}, "yosh": {"type": "integer"},
                   "shahar": {"type": "string"}},
    "required": ["ism", "yosh", "shahar"],
    "additionalProperties": False,
}
javob = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system="Matndan ma'lumot ajratuvchisiz.",
    messages=[{"role": "user", "content": "Men Malika, 34 yoshdaman, Xivada yashayman."}],
    output_config={"format": {"type": "json_schema", "schema": SXEMA}},
)
if javob.stop_reason == "max_tokens":
    raise RuntimeError("javob qirqilgan - max_tokens ni oshiring")
matn = next(b.text for b in javob.content if b.type == "text")
d = json.loads(matn)                     # shakl kafolatlangan; ma'noni baribir tekshiring

# ---- 2. Tool use: sxema asbob parametrlari sifatida (strict) ----
javob = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    tools=[{"name": "buyurtma_saqla", "description": "Buyurtmani saqlaydi",
            "strict": True, "input_schema": SXEMA}],
    messages=[{"role": "user", "content": "..."}],
)
for blok in javob.content:
    if blok.type == "tool_use":
        parametrlar = blok.input         # lug'at

# ---- 3. pydantic (agar o'rnatilgan bo'lsa) ----
from pydantic import BaseModel, Field, ValidationError


class Shaxs(BaseModel):
    ism: str = Field(min_length=2)
    yosh: int = Field(ge=0, le=130)
    shahar: str


try:
    s = Shaxs.model_validate_json(matn)
except ValidationError as e:
    xato_matni = str(e)                  # modelga qaytarish uchun

# ---- 4. O'z qo'limiz bilan (1-4 misollar) ----
# obj, bosqich = ajrat(matn)           # to'g'ridan -> kod bloki -> qavs skaneri -> tuzatish
# xatolar = tekshir(obj, SXEMA)        # "$.yosh: kutilgan integer, keldi str"
# xatolar += biznes_qoidalari(obj)     # maydonlararo moslik
# for urinish in range(K): ... prompt += "XATO: " + xatolar[0]

Strukturali chiqish xulosasi

javob matni -> ajrat (bosqichli, konservativ) -> sxema -> biznes qoidalari -> dataclass
jim xato - eng xavfli; alohida o'lchang
eval yo'q; stop_reason ni tekshiring
moslashtirish - faqat bir ma'noli
qayta so'rash: aniq xato matni, K = 2-3, keyin fallback
API da sxema majburlash - shakl; ma'no - sizning kodingizda
extraction: maydon bo'yicha, normallangan, uydirma alohida

4. Batafsil misollar

Misollar Python 3.14 standart kutubxonasi va numpy bilan. LLM o'rnida — STUB (haqiqiy model emas); kod tuzilishi haqiqiy API bilan bir xil.

Misol 1 — Buzilgan JSON ni bosqichma-bosqich ajratish

python
"""LLM javobidan JSON ni xavfsiz ajratib olish: kod bloki, ortiqcha matn, izoh, vergul va qirqilish."""

import json
import random
import re

ISMLAR = ["Dilnoza", "Bobur", "Malika", "Sardor", "Nodira", "Jasur", "Ra'no", "O'tkir"]
SHAHARLAR = ["Toshkent", "Samarqand", "Buxoro", "Farg'ona", "Namangan", "Qo'qon"]
IZOHLAR = ["o'qishni yaxshi ko'radi", "sport bilan shug'ullanadi", None,
           "ingliz tilini o'rganmoqda", None]
TURLAR = ["toza", "kod_bloki", "matn_bilan", "ortiqcha_vergul", "izohli",
          "python_dict", "qoshtirnoq_xato", "qirqilgan"]
VAZNLAR = [40, 20, 12, 8, 6, 6, 4, 4]
UCH = "`" * 3                     # kod bloki belgisi (uchta teskari tirnoq)


class SoxtaLLM:
    """STUB - haqiqiy model emas. Bir xil yozuvni turli 'buzilgan' formatlarda qaytaradi.

    Format turlari va ularning ulushi (VAZNLAR) biz tanlagan faraz; haqiqiy modelda
    ular boshqacha bo'ladi. Qaytaradi: (matn, stop_reason) - xuddi API dagi kabi.
    """

    def __init__(self, urug):
        self.rng = random.Random(urug)

    def javob(self, yozuv):
        tur = self.rng.choices(TURLAR, VAZNLAR)[0]
        return tur, *self.shakllantir(yozuv, tur)

    def shakllantir(self, yozuv, tur):
        j = json.dumps(yozuv, ensure_ascii=False)
        j2 = json.dumps(yozuv, ensure_ascii=False, indent=2)
        if tur == "toza":
            return j, "end_turn"
        if tur == "kod_bloki":
            return UCH + "json\n" + j2 + "\n" + UCH, "end_turn"
        if tur == "matn_bilan":
            return "Albatta! Mana natija:\n" + j + "\nYana savol bo'lsa, yozing.", "end_turn"
        if tur == "ortiqcha_vergul":
            return j2[:-2] + ",\n}", "end_turn"
        if tur == "izohli":
            q = j2.split("\n")
            q[2] += "  // yosh taxminiy"
            return "\n".join(q), "end_turn"
        if tur == "python_dict":
            return str(yozuv), "end_turn"
        if tur == "qoshtirnoq_xato":
            j = json.dumps(dict(yozuv, izoh="__Q__"), ensure_ascii=False)
            return j.replace('"__Q__"', '"u "a\'lo" deb yozdi"'), "end_turn"
        return j[: int(len(j) * 0.7)], "max_tokens"


def kod_blokidan(matn):
    m = re.search(UCH + r"(?:json)?[ \t]*\n(.*?)" + UCH, matn, re.S)
    return m.group(1) if m else None


def muvozanatli_obyektlar(matn):
    """Yuqori darajadagi {...} larni qavs sanab topadi; satr ichidagi qavslar hisobga olinmaydi."""
    topildi, chuqurlik, ichida, escape, bosh = [], 0, None, False, None
    for i, c in enumerate(matn):
        if ichida:
            if escape:
                escape = False
            elif c == "\\":
                escape = True
            elif c == ichida:
                ichida = None
        elif chuqurlik and (c == '"' or (c == "'" and matn[i - 1] in " {[:,")):
            ichida = c
        elif c == "{":
            if chuqurlik == 0:
                bosh = i
            chuqurlik += 1
        elif c == "}" and chuqurlik:
            chuqurlik -= 1
            if chuqurlik == 0:
                topildi.append(matn[bosh:i + 1])
    return topildi                         # yopilmagan obyekt (qirqilgan) ro'yxatga kirmaydi


def yengil_tuzat(s):
    """Satrlardan TASHQARIDA: izohlar, ortiqcha vergul, True/False/None, bitta qo'shtirnoq."""
    chiqish, i, n = [], 0, len(s)
    while i < n:
        c = s[i]
        if c == '"':                                  # oddiy JSON satr - o'zgarishsiz
            j = i + 1
            while j < n and s[j] != '"':
                j += 2 if s[j] == "\\" else 1
            chiqish.append(s[i:j + 1])
            i = j + 1
        elif c == "'" and "".join(chiqish).rstrip()[-1:] in ("{", "[", ":", ","):
            j = i + 1
            while j < n and not (s[j] == "'" and s[j + 1:j + 2].strip()[:1] in (",", "}", "]", ":")):
                j += 2 if s[j] == "\\" else 1
            chiqish.append(json.dumps(s[i + 1:j], ensure_ascii=False))
            i = j + 1
        elif s.startswith("//", i):
            while i < n and s[i] != "\n":
                i += 1
        elif c == ",":
            k = i + 1
            while k < n and s[k] in " \t\r\n":
                k += 1
            if k < n and s[k] in "}]":
                i += 1                                # ortiqcha vergul tashlanadi
            else:
                chiqish.append(c)
                i += 1
        else:
            m = re.match(r"True|False|None", s[i:])
            if m and not (i and (s[i - 1].isalnum() or s[i - 1] == "_")):
                chiqish.append({"True": "true", "False": "false", "None": "null"}[m.group()])
                i += m.end()
            else:
                chiqish.append(c)
                i += 1
    return "".join(chiqish)


def ajrat(matn):
    """Bosqichma-bosqich: to'g'ridan -> kod bloki -> qavs skaneri -> yengil tuzatish."""
    try:
        return json.loads(matn), "to'g'ridan"
    except json.JSONDecodeError:
        pass
    blok = kod_blokidan(matn)
    if blok is not None:
        nomzod = blok
    else:
        obyektlar = muvozanatli_obyektlar(matn)
        if not obyektlar:
            return None, "topilmadi"
        if len(obyektlar) > 1:
            return None, "bir nechta obyekt"      # qaysi biri javob - noaniq
        nomzod = obyektlar[0]
    try:
        return json.loads(nomzod), "kod bloki" if blok is not None else "qavs skaneri"
    except json.JSONDecodeError:
        pass
    try:
        return json.loads(yengil_tuzat(nomzod)), "tuzatish bilan"
    except json.JSONDecodeError:
        return None, "buzilgan"


def naiv(matn):
    try:
        return json.loads(matn)
    except json.JSONDecodeError:
        return None


def yozuv_yasa(rng):
    return {"ism": rng.choice(ISMLAR), "yosh": rng.randint(17, 60),
            "shahar": rng.choice(SHAHARLAR), "talaba": rng.random() < 0.4,
            "izoh": rng.choice(IZOHLAR)}


def main() -> None:
    rng = random.Random(0)
    llm = SoxtaLLM(1)

    print("=== 1. Buzilgan javoblar namunasi (stub) ===")
    namuna = {"ism": "Ra'no", "yosh": 21, "shahar": "Qo'qon", "talaba": True, "izoh": None}
    for tur in ["matn_bilan", "python_dict", "qirqilgan"]:
        matn, sabab = llm.shakllantir(namuna, tur)
        qisqa = matn.replace("\n", "\\n")
        print(f"  {tur:<13} {qisqa[:62]!s}")
        print(f"  {'':<13} -> naiv: {'OK' if naiv(matn) else 'xato'}, "
              f"ajrat: {ajrat(matn)[1]}, stop_reason={sabab}")

    print("\n=== 2. 600 ta javob: naiv json.loads va bosqichli ajratish ===")
    natija = {t: [0, 0, 0, 0] for t in TURLAR}       # soni, naiv OK, ajrat OK, jim xato
    bosqich = {}
    for _ in range(600):
        yozuv = yozuv_yasa(rng)
        tur, matn, sabab = llm.javob(yozuv)
        obj, b = ajrat(matn)
        s = natija[tur]
        s[0] += 1
        s[1] += naiv(matn) == yozuv
        s[2] += obj == yozuv
        s[3] += obj is not None and obj != yozuv
        bosqich[b] = bosqich.get(b, 0) + 1
    print("  tur               soni   naiv OK  ajrat OK  jim xato")
    for t in TURLAR:
        s = natija[t]
        print(f"  {t:<16} {s[0]:>5} {s[1] / s[0]:>8.0%} {s[2] / s[0]:>9.0%} {s[3]:>9}")
    jami = [sum(natija[t][k] for t in TURLAR) for k in range(4)]
    print(f"  {'JAMI':<16} {jami[0]:>5} {jami[1] / jami[0]:>8.1%} {jami[2] / jami[0]:>9.1%} "
          f"{jami[3]:>9}")

    print("\n=== 3. Qaysi bosqich ishladi ===")
    for b, n in sorted(bosqich.items(), key=lambda x: -x[1]):
        print(f"  {b:<15} {n:>4}")

    print("\n=== 4. Chekka holatlar: satr ichidagi '}', '//' va apostrof ===")
    holatlar = [
        'Natija: {"sayt": "https://wisar.uz", "izoh": "qavs } ichida"} tamom',
        '{"sayt": "https://wisar.uz", "teglar": ["a", "b",],}',
        "{'ism': 'Ra'no', 'yosh': 21}",
        'Misol: {"yosh": 0}. Javob: {"yosh": 34}',
    ]
    for h in holatlar:
        obj, b = ajrat(h)
        print(f"  {h[:44]:<45} -> {b}: {json.dumps(obj, ensure_ascii=False)[:40]}")

    print("\n=== 5. Xulosa (natijadan) ===")
    if jami[3] == 0:
        print("  jim xato 0: tuzatish hech qachon noto'g'ri qiymat 'ixtiro' qilmadi")
    else:
        print(f"  DIQQAT: {jami[3]} ta jim xato - tuzatish qiymatni buzmoqda")
    yiqilgan = [t for t in TURLAR if natija[t][2] == 0]
    print(f"  tiklab bo'lmadi: {', '.join(yiqilgan)} - bular qayta so'rash kerak")
    print("  qirqilganini stop_reason == 'max_tokens' dan oldindan bilish mumkin")
    print("  \u2b50 Tuzatish - faqat bir ma'noli holatlar; shubhali javobni tuzatmang, qayta so'rang")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Buzilgan javoblar namunasi (stub) ===
  matn_bilan    Albatta! Mana natija:\n{"ism": "Ra'no", "yosh": 21, "shahar":
                -> naiv: xato, ajrat: qavs skaneri, stop_reason=end_turn
  python_dict   {'ism': "Ra'no", 'yosh': 21, 'shahar': "Qo'qon", 'talaba': Tru
                -> naiv: xato, ajrat: tuzatish bilan, stop_reason=end_turn
  qirqilgan     {"ism": "Ra'no", "yosh": 21, "shahar": "Qo'qon", "tala
                -> naiv: xato, ajrat: topilmadi, stop_reason=max_tokens

=== 2. 600 ta javob: naiv json.loads va bosqichli ajratish ===
  tur               soni   naiv OK  ajrat OK  jim xato
  toza               234     100%      100%         0
  kod_bloki          124       0%      100%         0
  matn_bilan          70       0%      100%         0
  ortiqcha_vergul     47       0%      100%         0
  izohli              40       0%      100%         0
  python_dict         42       0%      100%         0
  qoshtirnoq_xato     16       0%        0%         0
  qirqilgan           27       0%        0%         0
  JAMI               600    39.0%     92.8%         0

=== 3. Qaysi bosqich ishladi ===
  to'g'ridan       234
  tuzatish bilan   129
  kod bloki        124
  qavs skaneri      70
  topilmadi         27
  buzilgan          16

=== 4. Chekka holatlar: satr ichidagi '}', '//' va apostrof ===
  Natija: {"sayt": "https://wisar.uz", "izoh":  -> qavs skaneri: {"sayt": "https://wisar.uz", "izoh": "qa
  {"sayt": "https://wisar.uz", "teglar": ["a",  -> tuzatish bilan: {"sayt": "https://wisar.uz", "teglar": [
  {'ism': 'Ra'no', 'yosh': 21}                  -> tuzatish bilan: {"ism": "Ra'no", "yosh": 21}
  Misol: {"yosh": 0}. Javob: {"yosh": 34}       -> bir nechta obyekt: null

=== 5. Xulosa (natijadan) ===
  jim xato 0: tuzatish hech qachon noto'g'ri qiymat 'ixtiro' qilmadi
  tiklab bo'lmadi: qoshtirnoq_xato, qirqilgan - bular qayta so'rash kerak
  qirqilganini stop_reason == 'max_tokens' dan oldindan bilish mumkin
  ⭐ Tuzatish - faqat bir ma'noli holatlar; shubhali javobni tuzatmang, qayta so'rang

Nima ko'rsatdi: stub bir xil yozuvni sakkiz xil ko'rinishda qaytardi — tur ulushlari (VAZNLAR) biz tanlagan faraz, haqiqiy modelda ular boshqacha bo'ladi. Naiv json.loads faqat toza javoblarni o'qidi: 600 tadan 39.0%. Bosqichli ajrat — 92.8%: kod bloki regex bilan, "Albatta! Mana natija:" kabi ortiqcha matn qavs skaneri bilan, ortiqcha vergul, // izoh va Python lug'ati (True, None, bitta qo'shtirnoq) yengil tuzatish bilan o'qildi. 3-bo'lim qaysi bosqich necha marta ishlaganini ko'rsatadi — amalda bu statistikani loglash foydali: masalan, "tuzatish bilan" ulushi to'satdan o'ssa, prompt yoki model versiyasi o'zgargan bo'lishi mumkin. Eng muhim ustun — jim xato: 0, ya'ni tuzatish biror marta ham noto'g'ri qiymat "ixtiro" qilmadi. Ichki qo'shtirnoqli va qirqilgan javoblar ataylab 0% — ularni ishonchli tiklab bo'lmaydi, qirqilganini esa stop_reason == "max_tokens" dan oldindan bilish mumkin. 4-bo'lim chekka holatlar: satr ichidagi } va https:// dagi // skanerni ham, tuzatishni ham chalg'itmadi; 'Ra'no' dagi apostrof to'g'ri o'qildi (satr tugashi — keyingi belgi , } : bo'lgan qo'shtirnoq). Oxirgi holat (Misol: {...}. Javob: {...}) — ikki obyekt: birinchisini olish jim xato bo'lardi (yosh: 0), shuning uchun ajrat uni rad etdi. Bog'liq bo'limlar: 2.2.

Misol 2 — Sxema validatori, biznes qoidasi va moslashtirish

python
"""JSON Schema ning kichik qismi noldan: validator, biznes qoidalari, dataclass va xavfsiz moslashtirish."""

import copy
import random
import re
from dataclasses import dataclass

SXEMA = {
    "type": "object",
    "required": ["mijoz", "telefon", "mahsulotlar", "tolov", "jami"],
    "additionalProperties": False,
    "properties": {
        "mijoz": {"type": "string", "minLength": 2},
        "telefon": {"type": "string", "pattern": r"^\+998\d{9}$"},
        "mahsulotlar": {
            "type": "array", "minItems": 1,
            "items": {
                "type": "object", "required": ["nomi", "soni", "narx"],
                "additionalProperties": False,
                "properties": {"nomi": {"type": "string", "minLength": 1},
                               "soni": {"type": "integer", "minimum": 1, "maximum": 100},
                               "narx": {"type": "number", "minimum": 0}}}},
        "tolov": {"type": "string", "enum": ["naqd", "karta", "click", "payme"]},
        "jami": {"type": "number", "minimum": 0},
        "izoh": {"type": ["string", "null"]},
    },
}


def tur_mosmi(x, tur):
    if tur == "null":
        return x is None
    if tur == "boolean":
        return type(x) is bool
    if tur == "integer":
        return type(x) is int                       # True/False int EMAS deb hisoblanadi
    if tur == "number":
        return type(x) in (int, float)
    return isinstance(x, {"string": str, "array": list, "object": dict}[tur])


def tekshir(x, sx, yol="$"):
    """JSON Schema kichik qismi: type, required, additionalProperties, enum,
    minimum/maximum, minLength, pattern, minItems, items. Xatolar ro'yxatini qaytaradi."""
    turlar = sx["type"] if isinstance(sx["type"], list) else [sx["type"]]
    if not any(tur_mosmi(x, t) for t in turlar):
        return [f"{yol}: kutilgan {'/'.join(turlar)}, keldi {type(x).__name__} ({x!r})"]
    xatolar = []
    if "enum" in sx and x not in sx["enum"]:
        xatolar.append(f"{yol}: {x!r} ruxsat etilganlar ichida yo'q {sx['enum']}")
    if "minimum" in sx and x < sx["minimum"]:
        xatolar.append(f"{yol}: {x} < minimum {sx['minimum']}")
    if "maximum" in sx and x > sx["maximum"]:
        xatolar.append(f"{yol}: {x} > maximum {sx['maximum']}")
    if isinstance(x, str):
        if len(x) < sx.get("minLength", 0):
            xatolar.append(f"{yol}: satr juda qisqa")
        if "pattern" in sx and not re.search(sx["pattern"], x):
            xatolar.append(f"{yol}: {x!r} naqshga mos emas {sx['pattern']}")
    if isinstance(x, list):
        if len(x) < sx.get("minItems", 0):
            xatolar.append(f"{yol}: kamida {sx['minItems']} element kerak")
        for i, el in enumerate(x):
            xatolar += tekshir(el, sx["items"], f"{yol}[{i}]")
    if isinstance(x, dict):
        for k in sx.get("required", []):
            if k not in x:
                xatolar.append(f"{yol}: majburiy maydon yo'q: {k!r}")
        for k, v in x.items():
            if k in sx["properties"]:
                xatolar += tekshir(v, sx["properties"][k], f"{yol}.{k}")
            elif sx.get("additionalProperties", True) is False:
                xatolar.append(f"{yol}: ortiqcha maydon {k!r}")
    return xatolar


def biznes_qoidalari(x):
    """Sxema ifodalay olmaydigan qoidalar: maydonlar orasidagi moslik."""
    hisob = sum(m["soni"] * m["narx"] for m in x["mahsulotlar"])
    return [] if abs(hisob - x["jami"]) < 0.5 else [f"$.jami: {x['jami']} != hisoblangan {hisob}"]


@dataclass(frozen=True)
class Qator:
    nomi: str
    soni: int
    narx: float


@dataclass(frozen=True)
class Buyurtma:
    mijoz: str
    telefon: str
    mahsulotlar: tuple
    tolov: str
    jami: float
    izoh: str | None = None

    @classmethod
    def yarat(cls, d):
        return cls(d["mijoz"], d["telefon"], tuple(Qator(**m) for m in d["mahsulotlar"]),
                   d["tolov"], d["jami"], d.get("izoh"))


def moslashtir(d):
    """Faqat BIR MA'NOLI tuzatishlar: '3' -> 3, 'Karta' -> 'karta',
    '90 123 45 67' -> '+998901234567', noma'lum maydonlarni tashlash."""
    d = copy.deepcopy(d)
    for m in d.get("mahsulotlar", []):
        if isinstance(m.get("soni"), str) and m["soni"].strip().isdigit():
            m["soni"] = int(m["soni"])
    if isinstance(d.get("tolov"), str):
        d["tolov"] = d["tolov"].strip().lower()
    if isinstance(d.get("telefon"), str):
        raqam = re.sub(r"\D", "", d["telefon"])
        if len(raqam) == 9:
            raqam = "998" + raqam
        if len(raqam) == 12 and raqam.startswith("998"):
            d["telefon"] = "+" + raqam
    for k in list(d):
        if k not in SXEMA["properties"]:
            del d[k]
    return d


MAHSULOT = [("kitob", 45000), ("daftar", 6000), ("ruchka", 3500), ("sumka", 120000)]
BUZILISHLAR = [("soni_satr", 0.08), ("telefon_format", 0.10), ("tolov_katta", 0.06),
               ("ortiqcha_maydon", 0.05), ("jami_xato", 0.04), ("soni_nol", 0.02)]


def haqiqat(rng):
    qatorlar = [{"nomi": n, "soni": rng.randint(1, 5), "narx": float(p)}
                for n, p in rng.sample(MAHSULOT, rng.randint(1, 3))]
    return {"mijoz": rng.choice(["Dilnoza Karimova", "Bobur Aliyev", "Ra'no Saidova"]),
            "telefon": "+99890" + "".join(str(rng.randint(0, 9)) for _ in range(7)),
            "mahsulotlar": qatorlar, "tolov": rng.choice(["naqd", "karta", "click", "payme"]),
            "jami": sum(q["soni"] * q["narx"] for q in qatorlar), "izoh": None}


def soxta_llm(h, rng):
    """STUB - haqiqiy model emas: to'g'ri javobni olib, BUZILISHLAR ehtimollari bilan buzadi."""
    d = copy.deepcopy(h)
    for tur, p in BUZILISHLAR:
        if rng.random() >= p:
            continue
        if tur == "soni_satr":
            d["mahsulotlar"][0]["soni"] = str(d["mahsulotlar"][0]["soni"])
        elif tur == "telefon_format":
            r = d["telefon"][4:]
            d["telefon"] = f"{r[:2]} {r[2:5]} {r[5:7]} {r[7:]}"
        elif tur == "tolov_katta":
            d["tolov"] = d["tolov"].capitalize()
        elif tur == "ortiqcha_maydon":
            d["valyuta"] = "so'm"
        elif tur == "jami_xato":
            d["jami"] = d["jami"] + 1000
        elif tur == "soni_nol":
            d["mahsulotlar"][-1]["soni"] = 0
    return d


def main() -> None:
    rng = random.Random(0)

    print("=== 1. Validator xabarlari (yo'l bilan) ===")
    yaxshi = haqiqat(rng)
    holatlar = {
        "to'g'ri": yaxshi,
        "soni satr": {**yaxshi, "mahsulotlar": [{**yaxshi["mahsulotlar"][0], "soni": "2"}]},
        "enum+ortiqcha": {**yaxshi, "tolov": "Karta", "valyuta": "so'm"},
        "yo'q maydon": {k: v for k, v in yaxshi.items() if k != "telefon"},
        "soni = True": {**yaxshi, "mahsulotlar": [{**yaxshi["mahsulotlar"][0], "soni": True}]},
    }
    for nom, d in holatlar.items():
        x = tekshir(d, SXEMA)
        print(f"  {nom:<14} {'OK' if not x else x[0]}" + (f"  (+{len(x) - 1})" if len(x) > 1 else ""))
    print(f"  isinstance(True, int) = {isinstance(True, int)} - shuning uchun type(x) is int")

    print("\n=== 2. Sxema o'tdi, lekin biznes qoidasi buzildi ===")
    d = {**yaxshi, "jami": yaxshi["jami"] + 1000}
    print(f"  sxema xatolari: {len(tekshir(d, SXEMA))}, biznes: {biznes_qoidalari(d)}")

    print("\n=== 3. Tasdiqlangan lug'at -> dataclass ===")
    b = Buyurtma.yarat(yaxshi)
    print(f"  {type(b).__name__}: mijoz={b.mijoz!r}, qatorlar={len(b.mahsulotlar)}, "
          f"1-qator={b.mahsulotlar[0]}")

    print("\n=== 4. 400 ta stub javob: qat'iy va moslashtirilgan qabul ===")
    print("  siyosat           qabul  qabul, lekin noto'g'ri  rad etildi")
    juftlar = []
    for _ in range(400):
        h = haqiqat(rng)
        juftlar.append((h, soxta_llm(h, rng)))
    natija = {}
    for siyosat in ["qat'iy", "moslashtirish", "moslash + biznes"]:
        qabul = notogri = 0
        for h, d in juftlar:
            if siyosat != "qat'iy":
                d = moslashtir(d)
            xato = tekshir(d, SXEMA)
            if not xato and siyosat == "moslash + biznes":
                xato = biznes_qoidalari(d)
            if not xato:
                qabul += 1
                notogri += d != h
        natija[siyosat] = (qabul, notogri)
        print(f"  {siyosat:<17} {qabul / 400:>6.1%} {notogri:>18} ta {400 - qabul:>12}")
    toza = sum(d == h for h, d in juftlar)
    print(f"  stub javoblari ichida aynan to'g'rilari: {toza / 400:.1%}")

    print("\n=== 5. Xulosa (natijadan) ===")
    q, m, mb = natija["qat'iy"], natija["moslashtirish"], natija["moslash + biznes"]
    print(f"  moslashtirish qabulni {q[0] / 400:.1%} -> {m[0] / 400:.1%} ga oshirdi")
    if m[1] > 0 and mb[1] == 0:
        print(f"  lekin {m[1]} ta noto'g'ri javob o'tib ketdi; biznes qoidasi ularni to'xtatdi")
    print("  \u2b50 Sxema - shakl; ma'no uchun maydonlararo qoidalar ham kerak")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Validator xabarlari (yo'l bilan) ===
  to'g'ri        OK
  soni satr      $.mahsulotlar[0].soni: kutilgan integer, keldi str ('2')
  enum+ortiqcha  $.tolov: 'Karta' ruxsat etilganlar ichida yo'q ['naqd', 'karta', 'click', 'payme']  (+1)
  yo'q maydon    $: majburiy maydon yo'q: 'telefon'
  soni = True    $.mahsulotlar[0].soni: kutilgan integer, keldi bool (True)
  isinstance(True, int) = True - shuning uchun type(x) is int

=== 2. Sxema o'tdi, lekin biznes qoidasi buzildi ===
  sxema xatolari: 0, biznes: ['$.jami: 586000.0 != hisoblangan 585000.0']

=== 3. Tasdiqlangan lug'at -> dataclass ===
  Buyurtma: mijoz='Bobur Aliyev', qatorlar=2, 1-qator=Qator(nomi='sumka', soni=3, narx=120000.0)

=== 4. 400 ta stub javob: qat'iy va moslashtirilgan qabul ===
  siyosat           qabul  qabul, lekin noto'g'ri  rad etildi
  qat'iy             74.0%                 11 ta          104
  moslashtirish      98.8%                 16 ta            5
  moslash + biznes   94.8%                  0 ta           21
  stub javoblari ichida aynan to'g'rilari: 71.2%

=== 5. Xulosa (natijadan) ===
  moslashtirish qabulni 74.0% -> 98.8% ga oshirdi
  lekin 16 ta noto'g'ri javob o'tib ketdi; biznes qoidasi ularni to'xtatdi
  ⭐ Sxema - shakl; ma'no uchun maydonlararo qoidalar ham kerak

Nima ko'rsatdi: 1-bo'limda validator har xatoni yo'l bilan qaytardi: $.mahsulotlar[0].soni: kutilgan integer, keldi str ('2') — bunday xabarni modelga qaytarsa, u aynan qaysi maydonni tuzatishni biladi. soni = True holati Python tuzog'ini ko'rsatadi: isinstance(True, int) — True, shuning uchun validator type(x) is int bilan tekshiradi va bool ni rad etadi. 2-bo'lim: jami summa 1000 so'mga oshirilgan buyurtma sxemadan xatosiz o'tdi (sxema xatolari: 0), uni faqat maydonlararo qoida ushladi. 3-bo'lim: tekshirilgan lug'at frozen dataclass ga aylandi. 4-bo'lim 400 ta stub javobda uch siyosatni solishtirdi (stub buzilish ehtimollari faraziy). Qat'iy siyosat 74.0% ni qabul qildi, lekin ular ichida ham 11 ta noto'g'ri bor — bular "jami" xatolari, shaklan to'g'ri. Moslashtirish ("3" → 3, "Karta" → "karta", telefon formati, noma'lum kalitni tashlash) qabulni 98.8% ga oshirdi, noto'g'ri qabullar esa 16 ga ko'paydi — endi ko'proq javob validatordan o'tadi, jumladan "jami" xatolilari ham. Biznes qoidasi qo'shilganda noto'g'ri qabul 0, qabul 94.8%: rad etilgan 21 ta — "jami" xatolari va soni = 0 holatlari. Stub javoblarining faqat 71.2% i aynan to'g'ri edi; moslashtirish + biznes qoidasi 94.8% ni xatosiz qabul qildi. Bog'liq bo'limlar: 2.3, 2.4.

Misol 3 — Xato bilan qayta so'rash: temperatura, urinishlar va xarajat

python
"""Xato bilan qayta so'rash (repair loop): urinishlar chegarasi, temperatura va xarajat."""

import hashlib
import json
import math
import random

import numpy as np

MAYDONLAR = ["ism", "shahar", "yosh"]
SHAHARLAR = ["Toshkent", "Samarqand", "Buxoro", "Xiva", "Termiz", "Nukus"]
ISMLAR = ["Dilnoza", "Bobur", "Malika", "Sardor", "Nodira", "Jasur", "Zarina", "Aziz"]


class SoxtaLLM:
    """STUB - haqiqiy model emas. Kod tuzilishi haqiqiy API bilan bir xil, xulqi esa FARAZ:

    - oddiy yozuvda buzuq javob ehtimoli 0.25, 'qiyin' yozuvda 0.80;
    - promptda oldingi xato matni bo'lsa: 0.10 va 0.60 (faraz, haqiqiy modelda o'lchang);
    - temperature == 0 da tasodif faqat prompt matnidan (sha256) olinadi: bir xil prompt ->
      aynan bir xil javob; temperature > 0 da har chaqiruv yangi tasodif.
    """

    def __init__(self, urug):
        self.rng = random.Random(urug)
        self.chaqiruvlar = 0

    def yarat(self, prompt, haqiqat, qiyin, temperature):
        self.chaqiruvlar += 1
        if temperature == 0:
            r = random.Random(int(hashlib.sha256(prompt.encode()).hexdigest()[:12], 16))
        else:
            r = self.rng
        p = (0.60 if qiyin else 0.10) if "XATO:" in prompt else (0.80 if qiyin else 0.25)
        if r.random() >= p:
            return json.dumps(haqiqat, ensure_ascii=False)
        tur = r.choice(["maydon_yoq", "json_buzuq", "yosh_satr"])
        if tur == "maydon_yoq":
            return json.dumps({k: v for k, v in haqiqat.items() if k != "shahar"}, ensure_ascii=False)
        if tur == "yosh_satr":
            return json.dumps({**haqiqat, "yosh": f"{haqiqat['yosh']} yosh"}, ensure_ascii=False)
        return json.dumps(haqiqat, ensure_ascii=False)[:-1]


def tekshir(matn):
    """None - hammasi joyida; aks holda modelga qaytariladigan aniq xato matni."""
    try:
        d = json.loads(matn)
    except json.JSONDecodeError as e:
        return None, f"JSON o'qilmadi ({e.msg}, {e.pos}-belgi)"
    yoq = [k for k in MAYDONLAR if k not in d]
    if yoq:
        return None, f"majburiy maydon yo'q: {', '.join(yoq)}"
    if type(d["yosh"]) is not int:
        return None, f"'yosh' butun son bo'lishi kerak, keldi {d['yosh']!r}"
    return d, None


def token(matn):
    return math.ceil(len(matn) / 4)          # taxminiy hisob (25.2-dars), faqat solishtirish uchun


def sorov(llm, matn, haqiqat, qiyin, strategiya, maks_urinish):
    """Qaytaradi: (natija yoki None, chaqiruvlar soni, jami kirish tokenlari)."""
    asosiy = ("Matndan ism, shahar va yosh ni JSON ko'rinishida qaytaring. "
              "Faqat JSON, boshqa matn yo'q.\nMatn: " + matn)
    prompt, kirish = asosiy, 0
    temperature = 0.7 if strategiya == "qayta, T=0.7" else 0.0
    for urinish in range(1, maks_urinish + 1):
        kirish += token(prompt)
        javob = llm.yarat(prompt, haqiqat, qiyin, temperature)
        d, xato = tekshir(javob)
        if d is not None:
            return d, urinish, kirish
        if strategiya == "xato bilan":
            prompt = (asosiy + "\n\nOldingi javobingiz: " + javob
                      + "\nXATO: " + xato + "\nFaqat tuzatilgan JSON ni qaytaring.")
    return None, maks_urinish, kirish


def tajriba(urug, strategiya, maks_urinish, n=300):
    rng = random.Random(1000 + urug)
    llm = SoxtaLLM(urug)
    ok, chaqiruv, kirish, qiyin_yiqildi, yiqildi = 0, 0, 0, 0, 0
    for _ in range(n):
        h = {"ism": rng.choice(ISMLAR), "shahar": rng.choice(SHAHARLAR), "yosh": rng.randint(18, 70)}
        qiyin = rng.random() < 0.10
        matn = f"Men {h['ism']}, {h['yosh']} yoshdaman, {h['shahar']}da yashayman."
        d, c, k = sorov(llm, matn, h, qiyin, strategiya, maks_urinish)
        ok += d == h
        chaqiruv += c
        kirish += k
        if d is None:
            yiqildi += 1
            qiyin_yiqildi += qiyin
    return ok / n, chaqiruv / n, kirish / n, qiyin_yiqildi, yiqildi


def main() -> None:
    print("=== 1. Bitta so'rov: xato matni modelga qaytariladi ===")
    llm = SoxtaLLM(3)
    h = {"ism": "Malika", "shahar": "Xiva", "yosh": 34}
    asosiy = "Matndan ism, shahar va yosh ni JSON ko'rinishida qaytaring.\nMatn: ..."
    prompt = asosiy
    for urinish in range(1, 4):
        javob = llm.yarat(prompt, h, True, 0.0)
        d, xato = tekshir(javob)
        print(f"  urinish {urinish}: prompt {token(prompt):>3} token -> "
              f"{'OK ' + str(d) if d else 'XATO: ' + xato}")
        if d:
            break
        prompt = asosiy + "\nOldingi javobingiz: " + javob + "\nXATO: " + xato

    print("\n=== 2. Strategiya va urinishlar chegarasi (5 urug' x 300 so'rov) ===")
    print("  strategiya       K  muvaffaqiyat  chaqiruv/so'rov  kirish token/so'rov")
    jadval = {}
    for strategiya in ["qayta, T=0", "qayta, T=0.7", "xato bilan"]:
        for K in (1, 2, 3, 5):
            r = np.array([tajriba(s, strategiya, K)[:3] for s in range(5)])
            jadval[strategiya, K] = r
            if K in (1, 3, 5) or strategiya == "xato bilan":
                print(f"  {strategiya:<14} {K:>3} {r[:, 0].mean():>12.1%} {r[:, 1].mean():>15.2f} "
                      f"{r[:, 2].mean():>19.1f}")

    print("\n=== 3. K = 3 da juftlashgan farq (bir xil urug'lar, SE) ===")
    for a, b in [("qayta, T=0.7", "qayta, T=0"), ("xato bilan", "qayta, T=0.7")]:
        f = jadval[a, 3][:, 0] - jadval[b, 3][:, 0]
        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=== 4. Kim yiqiladi: 'xato bilan', K = 3 ===")
    q = [tajriba(s, "xato bilan", 3) for s in range(5)]
    yiq, qyiq = sum(x[4] for x in q), sum(x[3] for x in q)
    print(f"  yiqilgan so'rovlar: {yiq} / 1500, ulardan 'qiyin': {qyiq} ({qyiq / max(1, yiq):.0%})")
    print("  (qiyin yozuvlar jami ~10%) -> ularga qo'lda ko'rish yoki boshqa model kerak")

    print("\n=== 5. Xulosa (natijadan) ===")
    t0 = jadval["qayta, T=0", 5][:, 0].mean() - jadval["qayta, T=0", 1][:, 0].mean()
    if abs(t0) < 1e-9:
        print("  T=0 va bir xil prompt: 5 urinish 1 urinishdan yaxshi emas - javob aynan takrorlanadi")
    x3, x5 = jadval["xato bilan", 3], jadval["xato bilan", 5]
    print(f"  'xato bilan' K 3 -> 5: muvaffaqiyat {x3[:, 0].mean():.1%} -> {x5[:, 0].mean():.1%}, "
          f"kirish token {x3[:, 2].mean():.0f} -> {x5[:, 2].mean():.0f}")
    print("  (farqlar stubdagi FARAZIY ehtimollardan kelib chiqadi - haqiqiy modelda o'lchang)")
    print("  \u2b50 Qayta so'rash promptni o'zgartirishi kerak; urinishlarni cheklang, keyin fallback")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bitta so'rov: xato matni modelga qaytariladi ===
  urinish 1: prompt  18 token -> XATO: 'yosh' butun son bo'lishi kerak, keldi '34 yosh'
  urinish 2: prompt  50 token -> OK {'ism': 'Malika', 'shahar': 'Xiva', 'yosh': 34}

=== 2. Strategiya va urinishlar chegarasi (5 urug' x 300 so'rov) ===
  strategiya       K  muvaffaqiyat  chaqiruv/so'rov  kirish token/so'rov
  qayta, T=0       1        70.7%            1.00                35.6
  qayta, T=0       3        70.7%            1.59                56.4
  qayta, T=0       5        70.7%            2.17                77.2
  qayta, T=0.7     1        72.5%            1.00                35.6
  qayta, T=0.7     3        94.8%            1.39                49.3
  qayta, T=0.7     5        96.9%            1.47                52.3
  xato bilan       1        70.7%            1.00                35.6
  xato bilan       2        94.9%            1.29                57.1
  xato bilan       3        96.7%            1.34                60.8
  xato bilan       5        96.9%            1.41                65.6

=== 3. K = 3 da juftlashgan farq (bir xil urug'lar, SE) ===
  qayta, T=0.7 - qayta, T=0: +0.241, SE 0.011 -> sezilarli
  xato bilan - qayta, T=0.7: +0.019, SE 0.005 -> sezilarli

=== 4. Kim yiqiladi: 'xato bilan', K = 3 ===
  yiqilgan so'rovlar: 50 / 1500, ulardan 'qiyin': 38 (76%)
  (qiyin yozuvlar jami ~10%) -> ularga qo'lda ko'rish yoki boshqa model kerak

=== 5. Xulosa (natijadan) ===
  T=0 va bir xil prompt: 5 urinish 1 urinishdan yaxshi emas - javob aynan takrorlanadi
  'xato bilan' K 3 -> 5: muvaffaqiyat 96.7% -> 96.9%, kirish token 61 -> 66
  (farqlar stubdagi FARAZIY ehtimollardan kelib chiqadi - haqiqiy modelda o'lchang)
  ⭐ Qayta so'rash promptni o'zgartirishi kerak; urinishlarni cheklang, keyin fallback

Nima ko'rsatdi: avval ogohlantirish — stubning xulqi faraz: oddiy yozuvda buzuq javob ehtimoli 0.25, qiyin yozuvda 0.80, promptda xato matni bo'lsa 0.10 va 0.60. Demak, jadvaldagi mutlaq raqamlar va xato bilan - qayta, T=0.7 farqi (+0.019, SE 0.005) — farazning oqibati, haqiqiy model haqida da'vo emas. Stubdan mustaqil xulosalar esa bor. Birinchisi: T = 0 da bir xil prompt bilan qayta so'rash mutlaqo foydasiz — 1, 3 va 5 urinishda muvaffaqiyat bir xil 70.7%, lekin 5 urinish so'rovga o'rtacha 2.17 chaqiruv va 77.2 kirish tokeni sarfladi (1 urinishda 35.6). Sabab: deterministik dekodlashda bir xil kirish — aynan bir xil chiqish. T = 0.7 da urinishlar mustaqil bo'ldi (K = 1, 3, 5 da 72.5% → 94.8% → 96.9%), xato matni esa T = 0 da ham promptni o'zgartirdi (K = 1, 2, 3 da 70.7% → 94.9% → 96.7%). Ikkinchisi: foyda tez to'yinadi — xato bilan da K 3 dan 5 ga: 96.7% → 96.9%, kirish tokenlari esa 61 → 66; xato bilan so'rashda prompt uzayadi (oldingi javob + xato), shuning uchun u T=0.7 dan ko'proq token sarfladi (60.8 va 49.3, K = 3). Uchinchisi: yiqilgan 50 so'rovning 76% i "qiyin" yozuvlar (ular jami ~10%) — qayta so'rash bunday holatlarni hal qilmaydi, ular uchun fallback (operator, boshqa model, "aniqlanmadi") kerak. Bog'liq bo'limlar: 2.5.

Misol 4 — Extraction: normallashtirish, manbaga bog'lash va maydon bo'yicha baho

python
"""Ma'lumot ajratib olish (extraction): normallashtirish, manbaga bog'lash va maydon bo'yicha baholash."""

import math
import random
import re
import textwrap

import numpy as np

ISMLAR = ["Dilnoza Karimova", "Bobur Aliyev", "Malika Tosheva", "Sardor Rahimov",
          "Nodira Yusupova", "Jasur Qodirov", "Zarina Ergasheva", "Aziz Nazarov"]
SHAHARLAR = ["Toshkent", "Samarqand", "Buxoro", "Xiva", "Termiz", "Nukus", "Andijon", "Qarshi"]
MAHSULOTLAR = ["kitob", "daftar", "ruchka", "sumka", "kalkulyator", "globus"]
OYLAR = ["yanvar", "fevral", "mart", "aprel", "may", "iyun", "iyul", "avgust",
         "sentabr", "oktabr", "noyabr", "dekabr"]
SON_SOZ = {"bir": 1, "ikki": 2, "uch": 3, "to'rt": 4, "besh": 5, "olti": 6}
MAYDONLAR = ["ism", "shahar", "mahsulot", "soni", "summa", "telefon", "sana"]


def hujjat(rng):
    """Matn + haqiqiy (normallangan) qiymatlar + matndagi yuzaki ko'rinishi."""
    h, y = {}, {}
    h["ism"] = y["ism"] = rng.choice(ISMLAR)
    manba, h["shahar"] = rng.sample(SHAHARLAR, 2)
    y["shahar"] = h["shahar"]
    h["mahsulot"] = y["mahsulot"] = rng.choice(MAHSULOTLAR)
    h["soni"] = rng.randint(1, 6)
    y["soni"] = rng.choice([str(h["soni"]), [k for k, v in SON_SOZ.items() if v == h["soni"]][0]])
    matn = (f"Assalomu alaykum! Men {h['ism']}, {manba}dan yozyapman. {y['soni']} ta "
            f"{h['mahsulot']} kerak, {h['shahar']}ga yetkazib bering.")
    h["summa"] = h["telefon"] = h["sana"] = y["summa"] = y["telefon"] = y["sana"] = None
    if rng.random() < 0.75:
        ming = rng.randint(20, 900)
        h["summa"] = ming * 1000
        y["summa"] = rng.choice([f"{ming} 000 so'm", f"{ming} ming so'm", f"{ming * 1000} so'm"])
        matn += " " + rng.choice(["Jami", "Narxi", "To'lov:"]) + " " + y["summa"] + "."
    if rng.random() < 0.7:
        r = "9" + str(rng.choice([0, 1, 3, 4, 7, 9])) + "".join(str(rng.randint(0, 9)) for _ in range(7))
        h["telefon"] = "+998" + r
        y["telefon"] = rng.choice([f"+998 {r[:2]} {r[2:5]} {r[5:7]} {r[7:]}",
                                   f"{r[:2]}-{r[2:5]}-{r[5:7]}-{r[7:]}", r])
        matn += f" Telefonim: {y['telefon']}."
    if rng.random() < 0.6:
        kun, oy = rng.randint(1, 28), rng.randint(1, 12)
        h["sana"] = f"{oy:02d}-{kun:02d}"
        y["sana"] = rng.choice([f"{kun}-{OYLAR[oy - 1]}", f"{kun:02d}.{oy:02d}"])
        matn += f" {y['sana']} kuni kerak bo'ladi."
    return matn, h, y, manba


def soxta_llm(yuzaki, manba, rng):
    """STUB - haqiqiy model emas: matndagi yuzaki qiymatlarni (formatini o'zgartirmay) qaytaradi
    va biz tanlagan FARAZIY xatolarni qo'shadi: 12% - yetkazish shahri o'rniga
    jo'natuvchi shahrini qaytarish, yo'q maydonni 30% ehtimol bilan 'o'ylab topish'."""
    d = dict(yuzaki)
    if rng.random() < 0.12:
        d["shahar"] = manba
    for k, uydirma in [("telefon", "+998 90 123 45 67"), ("sana", "1-yanvar"),
                       ("summa", "100 000 so'm")]:
        if d[k] is None and rng.random() < 0.30:
            d[k] = uydirma
    return d


def norm_son(x):
    if x is None:
        return None
    x = str(x).strip().lower()
    return int(x) if x.isdigit() else SON_SOZ.get(x)


def norm_summa(x):
    if x is None:
        return None
    m = re.fullmatch(r"(\d+)\s*(ming|000)?\s*so'm", str(x).strip())
    if not m:
        return None
    return int(m.group(1)) * (1000 if m.group(2) else 1)


def norm_telefon(x):
    if x is None:
        return None
    r = re.sub(r"\D", "", str(x))
    r = "998" + r if len(r) == 9 else r
    return "+" + r if len(r) == 12 and r.startswith("998") else None


def norm_sana(x):
    if x is None:
        return None
    x = str(x).strip().lower()
    m = re.fullmatch(r"(\d{1,2})-([a-z]+)", x)
    if m and m.group(2) in OYLAR:
        return f"{OYLAR.index(m.group(2)) + 1:02d}-{int(m.group(1)):02d}"
    m = re.fullmatch(r"(\d{1,2})\.(\d{1,2})", x)
    return f"{int(m.group(2)):02d}-{int(m.group(1)):02d}" if m else None


NORM = {"ism": lambda x: x, "shahar": lambda x: x, "mahsulot": lambda x: x,
        "soni": norm_son, "summa": norm_summa, "telefon": norm_telefon, "sana": norm_sana}


def manbada_bormi(xom, matn):
    """Manbaga bog'lash (grounding): ajratilgan qiymat matnda haqiqatan uchraydimi?"""
    if xom is None:
        return True
    if re.sub(r"\D", "", str(xom)) and len(re.sub(r"\D", "", str(xom))) >= 7:
        return re.sub(r"\D", "", str(xom))[-9:] in re.sub(r"\D", "", matn)
    return str(xom).lower() in matn.lower()


def quvur(xom, matn):
    """B: normallashtirish + manbaga bog'lash; tekshiruvdan o'tmagan qiymat -> None."""
    return {k: (NORM[k](xom[k]) if manbada_bormi(xom[k], matn) else None) for k in MAYDONLAR}


def main() -> None:
    rng, rng_llm = random.Random(0), random.Random(1)
    data = []
    for _ in range(400):
        matn, h, y, manba = hujjat(rng)
        data.append((matn, h, soxta_llm(y, manba, rng_llm)))

    print("=== 1. Bitta hujjat ===")
    matn, h, xom = data[3]
    for i, qator in enumerate(textwrap.wrap(matn, 80)):
        print(("  matn: " if i == 0 else "        ") + qator)
    print(f"  stub (xom):  soni={xom['soni']!r}, summa={xom['summa']!r}, telefon={xom['telefon']!r}")
    b = quvur(xom, matn)
    print(f"  quvur (B):   soni={b['soni']!r}, summa={b['summa']!r}, telefon={b['telefon']!r}")

    print("\n=== 2. Maydon bo'yicha aniqlik (400 hujjat) ===")
    print("  maydon     A qat'iy  A norm.  B quvur   uydirma A -> B")
    toliq = {"A qat'iy": [], "A norm.": [], "B quvur": []}
    for matn, h, xom in data:
        a_norm = {k: NORM[k](xom[k]) for k in MAYDONLAR}
        bq = quvur(xom, matn)
        toliq["A qat'iy"].append([xom[k] == h[k] for k in MAYDONLAR])
        toliq["A norm."].append([a_norm[k] == h[k] for k in MAYDONLAR])
        toliq["B quvur"].append([bq[k] == h[k] for k in MAYDONLAR])
    uyd = {}
    for i, k in enumerate(MAYDONLAR):
        yoq = [(matn, xom) for matn, h, xom in data if h[k] is None]
        ua = sum(xom[k] is not None for _, xom in yoq)
        ub = sum(quvur(xom, matn)[k] is not None for matn, xom in yoq)
        uyd[k] = (ua, ub, len(yoq))
        qatorlar = [np.mean([r[i] for r in toliq[s]]) for s in toliq]
        print(f"  {k:<9} {qatorlar[0]:>9.1%} {qatorlar[1]:>8.1%} {qatorlar[2]:>8.1%} "
              + (f"  {ua:>3} -> {ub} (/{len(yoq)})" if yoq else ""))

    print("\n=== 3. Yozuv darajasida va juftlashgan farq ===")
    for s in toliq:
        print(f"  {s:<9} barcha 7 maydon to'g'ri: {np.mean([all(r) for r in toliq[s]]):.1%}, "
              f"maydonlar o'rtachasi {np.mean(toliq[s]):.1%}")
    f = np.mean(toliq["B quvur"], axis=1) - np.mean(toliq["A norm."], axis=1)
    se = f.std(ddof=1) / math.sqrt(len(f))
    print(f"  B - A norm. (hujjat bo'yicha): {f.mean():+.4f}, SE {se:.4f} -> "
          f"{'sezilarli' if abs(f.mean()) > 2 * se else 'sezilarli emas'}")

    print("\n=== 4. B da qolgan xatolar ===")
    qoldi = {}
    for matn, h, xom in data:
        bq = quvur(xom, matn)
        for k in MAYDONLAR:
            if bq[k] != h[k]:
                qoldi[k] = qoldi.get(k, 0) + 1
    for k, n in sorted(qoldi.items(), key=lambda x: -x[1]):
        print(f"  {k:<9} {n:>3} ta")

    print("\n=== 5. Xulosa (natijadan) ===")
    aq, an = np.mean(toliq["A qat'iy"]), np.mean(toliq["A norm."])
    print(f"  qat'iy satr taqqoslash aniqlikni {an - aq:.1%} ga past ko'rsatdi - format farqi xato emas")
    ua, ub = sum(v[0] for v in uyd.values()), sum(v[1] for v in uyd.values())
    print(f"  manbaga bog'lash uydirmalarni {ua} -> {ub} ga tushirdi")
    if qoldi.get("shahar", 0) == max(qoldi.values(), default=0):
        print("  eng ko'p qolgan xato - shahar: noto'g'ri, lekin MATNDA BOR qiymatni tekshiruv ushlamaydi")
    print("  \u2b50 Extraction ni maydon bo'yicha, normallangan qiymatlarda baholang")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bitta hujjat ===
  matn: Assalomu alaykum! Men Bobur Aliyev, Xivadan yozyapman. 2 ta daftar kerak,
        Termizga yetkazib bering. Telefonim: 905871484. 11-sentabr kuni kerak bo'ladi.
  stub (xom):  soni='2', summa="100 000 so'm", telefon='905871484'
  quvur (B):   soni=2, summa=None, telefon='+998905871484'

=== 2. Maydon bo'yicha aniqlik (400 hujjat) ===
  maydon     A qat'iy  A norm.  B quvur   uydirma A -> B
  ism          100.0%   100.0%   100.0%
  shahar        87.2%    87.2%    87.2%
  mahsulot     100.0%   100.0%   100.0%
  soni           0.0%   100.0%   100.0%
  summa         19.2%    93.0%   100.0%    28 -> 0 (/105)
  telefon       20.5%    92.0%   100.0%    32 -> 0 (/114)
  sana          29.8%    88.8%   100.0%    45 -> 0 (/164)

=== 3. Yozuv darajasida va juftlashgan farq ===
  A qat'iy  barcha 7 maydon to'g'ri: 0.0%, maydonlar o'rtachasi 51.0%
  A norm.   barcha 7 maydon to'g'ri: 68.2%, maydonlar o'rtachasi 94.4%
  B quvur   barcha 7 maydon to'g'ri: 87.2%, maydonlar o'rtachasi 98.2%
  B - A norm. (hujjat bo'yicha): +0.0375, SE 0.0035 -> sezilarli

=== 4. B da qolgan xatolar ===
  shahar     51 ta

=== 5. Xulosa (natijadan) ===
  qat'iy satr taqqoslash aniqlikni 43.5% ga past ko'rsatdi - format farqi xato emas
  manbaga bog'lash uydirmalarni 105 -> 0 ga tushirdi
  eng ko'p qolgan xato - shahar: noto'g'ri, lekin MATNDA BOR qiymatni tekshiruv ushlamaydi
  ⭐ Extraction ni maydon bo'yicha, normallangan qiymatlarda baholang

Nima ko'rsatdi: 400 ta o'zbekcha buyurtma xabari kod ichida yaratildi, har birining haqiqiy (normallangan) maydonlari ma'lum. Stub matnni tushunmaydi: u matndagi yuzaki qiymatlarni formatini o'zgartirmay qaytaradi va ikki xil faraziy xato qo'shadi — 12% holatda yetkazish shahri o'rniga jo'natuvchi shahrini beradi, matnda yo'q maydonni esa 30% ehtimol bilan "o'ylab topadi". 2-bo'lim uch baholash usulini ko'rsatadi. A qat'iy — xom qiymatni satr sifatida taqqoslash: soni 0.0% ('2' va 2 teng emas), summa 19.2% — faqat bo'sh maydonlar mos keldi. Bu model xatosi emas, baholash xatosi: bir xil javob A norm. da (baholashda normallashtirish) 100.0% va 93.0%. Qat'iy taqqoslash o'rtacha aniqlikni 43.5 foiz punkt past ko'rsatdi (51.0% va 94.4%). B quvur normallashtirishni quvurning o'ziga qo'shdi va manbaga bog'lashni (qiymat matnda uchraydimi) qo'shdi: uydirmalar 28 → 0, 32 → 0, 45 → 0 — jami 105 → 0, shuning uchun summa, telefon, sana 100.0% ga chiqdi. Diqqat: bu stubda uydirma qiymatlar hech qachon matnda uchramaydi, shuning uchun bog'lash ularning hammasini ushladi — haqiqiy modelda model matndagi boshqa raqamni noto'g'ri maydonga qo'yishi mumkin va bunday xato bog'lashdan o'tib ketadi. Buni shahar ko'rsatib turibdi: 87.2% — uchala usulda bir xil, B da qolgan yagona xato turi (51 ta). Jo'natuvchi shahri ham matnda bor, shuning uchun manbaga bog'lash uni ushlamaydi; bunga prompt aniqligi (qaysi shahar kerakligi) yoki kontekstli qoida kerak. 3-bo'lim: yozuv darajasida "hammasi to'g'ri" ulushi A qat'iyda 0.0%, A norm. da 68.2%, B da 87.2%; hujjat bo'yicha juftlashgan farq B - A norm. +0.0375, SE 0.0035 — sezilarli. Bog'liq bo'limlar: 2.7.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Promptda 'faqat JSON' desam, javob JSON bo'ladi" Ehtimol oshadi, kafolat yo'q; 1-misoldagi stub turlari — tipik buzilishlar
"json.loads o'qidimi — demak to'g'ri" Shakl to'g'ri; 2-misolda qat'iy siyosat ham 11 ta noto'g'ri javobni qabul qildi
"Buzilgan JSON ni har doim tuzatish mumkin" Ichki qo'shtirnoq, qirqilish va ikki obyekt — ko'p ma'noli; rad etib qayta so'rang
"eval tezroq va oson" Model yozgan kodni bajarish — xavfsizlik teshigi
"isinstance(x, int) butun sonni tekshiradi" True ham o'tadi; type(x) is int
"API sxemani majburlasa, validatsiya kerak emas" Shakl kafolatlanadi, ma'no va to'liqlik emas
"Qayta so'rash ko'p bo'lsa — yaxshi" T = 0 da bir xil prompt — bir xil javob; foyda tez to'yinadi, xarajat o'sadi
"Bo'sh maydonni standart qiymat bilan to'ldirgan ma'qul" Bu — uydirma; None va "aniqlanmadi" halolroq
"Extraction aniqligi = yozuvlarning qat'iy mosligi" Maydon bo'yicha, normallangan qiymatlarda; qat'iy taqqoslash 43.5 punkt past ko'rsatdi
"Manbaga bog'lash hamma uydirmani ushlaydi" Matnda bor, lekin noto'g'ri qiymatni (shahar) ushlamaydi

6. Keng tarqalgan xatolar va yechimlari

1. To'g'ridan-to'g'ri json.loads

python
d = json.loads(javob.content[0].text)                               # ⚠️
d, bosqich = ajrat(matn)                                            # ✅
if d is None:
    ...  # qayta so'rash yoki fallback

2. stop_reason tekshirilmagan

python
d = ajrat(matn)[0]                                                  # ⚠️
if javob.stop_reason == "max_tokens":                               # ✅
    raise RuntimeError("qirqilgan javob")

3. eval bilan o'qish

python
d = eval(matn)                                                      # ⚠️
d = json.loads(yengil_tuzat(nomzod))                                # ✅

4. bool butun son sifatida o'tib ketadi

python
if isinstance(x, int): ...                                          # ⚠️
if type(x) is int: ...                                              # ✅

5. Faqat sxema

python
xatolar = tekshir(d, SXEMA)                                         # ⚠️
xatolar = tekshir(d, SXEMA) or biznes_qoidalari(d)                  # ✅

6. Bir xil prompt bilan qayta so'rash

python
for _ in range(5):
    javob = llm(prompt, temperature=0)                              # ⚠️
for _ in range(3):
    javob = llm(prompt, temperature=0)
    d, xato = tekshir(javob)
    if d is not None:
        break
    prompt = asosiy + "\nOldingi javobingiz: " + javob + "\nXATO: " + xato   # ✅

7. Qat'iy satr taqqoslash bilan baholash

python
aniq = np.mean([xom[k] == h[k] for k in MAYDONLAR])                 # ⚠️
aniq = np.mean([NORM[k](xom[k]) == h[k] for k in MAYDONLAR])        # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 24.9-dars (o'tilgan): generatsiya sikli va logitlar — sxema bo'yicha niqoblash (constrained decoding) aynan shu siklga qo'shiladi; T = 0 da determinizm
  • 25.4-dars (o'tilgan): prompt engineering — chiqish formatini promptda aniq tasvirlash, misollar bilan ko'rsatish
  • 18-qism (o'tilgan): juftlashgan taqqoslash va SE — 3- va 4-misollarda
  • Keyingi darslar: LLM API bilan ishlash — qayta urinishlar, xatolar turlari, xarajat hisobi; RAG — ajratilgan maydonlar metadata sifatida; LLM ni baholash — maydon bo'yicha baho va avtomatik baholovchilar; Agentlar va tool calling — asbob parametrlari aynan shu sxemalar bilan; LLM xavfsizligi — model chiqishiga ishonmaslik, eval va injeksiya

8. Eng yaxshi amaliyotlar

  1. Chiqish sxemasini bir joyda saqlang va uni ham promptda, ham validatsiyada ishlating.

  2. Imkon bo'lsa API darajasidagi sxema majburlashni (structured outputs, strict tool use) yoqing — lekin validatsiyani olib tashlamang.

  3. Ajratishni bosqichli va konservativ qiling; ko'p ma'noli buzilishni rad eting.

  4. Jim xato sonini alohida o'lchang va loglang.

  5. Sxemadan tashqari maydonlararo biznes qoidalarini yozing.

  6. Moslashtirish qoidalarini faqat bir ma'noli holatlar uchun qo'shing.

  7. Qayta so'rashda aniq xato matnini bering, urinishlarni 2-3 bilan cheklang, fallback ni loyihalang.

  8. Extraction ni maydon bo'yicha, normallangan qiymatlarda baholang; uydirma ulushini alohida ko'rsating.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # json.loads(UCH + 'json\n{"a": 1}\n' + UCH) natijasi? (UCH - kod bloki belgisi)
2.  # {'a': True, 'b': None} ni qaysi uch almashtirish JSON ga aylantiradi?
3.  # isinstance(False, int) nima qaytaradi?
4.  # "Misol: {"x": 0}. Javob: {"x": 5}" dan birinchi obyektni olish xavfi?
5.  # stop_reason == "max_tokens" bo'lsa, qavsni yopib o'qish to'g'rimi?
6.  # T = 0 da bir xil prompt bilan 5 marta qayta so'rash foydasi?
7.  # "03.04" ni sanaga moslashtirish xavfsizmi?
8.  # sxema majburlash "jami == sum(soni * narx)" ni kafolatlaydimi?
9.  # qat'iy satr taqqoslashda "145 ming so'm" va 145000?
10. # manbaga bog'lash qaysi xatoni ushlamaydi?
11. # nega bo'sh maydonni standart qiymat bilan to'ldirmaslik kerak?
12. # qaysi holatda qayta so'rash ham yordam bermaydi?
Javoblar
  1. JSONDecodeError — kod bloki belgilari JSON emas; avval blok ichini ajratish kerak
  2. Bitta qo'shtirnoq → qo'shtirnoq, True → true, None → null (faqat satrlardan tashqarida)
  3. True — bool int ning quyi sinfi
  4. Jim xato: noto'g'ri obyekt (x: 0) muvaffaqiyatli o'qilgandek ko'rinadi; rad etish kerak
  5. Yo'q — yetishmagan qism noma'lum; max_tokens ni oshirib qayta so'rang
  6. Yo'q — deterministik dekodlashda aynan bir xil javob (3-misol: 70.7% 1 va 5 urinishda)
  7. Yo'q — 3-aprel yoki 4-mart; noaniq
  8. Yo'q — faqat shakl; bu biznes qoidasi
  9. Teng emas deb sanaladi — format farqi xato bo'lib ko'rinadi
  10. Matnda bor, lekin noto'g'ri maydonga qo'yilgan qiymat (jo'natuvchi va yetkazish shahri)
  11. Bu uydirma: noma'lum qiymat bilan ma'lum qiymatni ajratib bo'lmay qoladi
  12. Doimiy qiyin holatlar (3-misol: yiqilganlarning 76% i) — fallback kerak

Vazifa 2: Xatolarni tuzating

python
1.  d = json.loads(javob.content[0].text)
    baza.saqla(d)

2.  if isinstance(d["soni"], int) and d["soni"] >= 1:
        qabul(d)

3.  for _ in range(10):
        javob = llm(prompt, temperature=0)
        try:
            return json.loads(javob)
        except json.JSONDecodeError:
            pass

4.  d["telefon"] = d.get("telefon") or "+998000000000"

5.  aniqlik = np.mean([javob[k] == haqiqat[k] for k in MAYDONLAR])
Javoblar
python
1.  if javob.stop_reason == "max_tokens":
        raise RuntimeError("qirqilgan")
    d, _ = ajrat(next(b.text for b in javob.content if b.type == "text"))
    xatolar = tekshir(d, SXEMA) if d is not None else ["JSON topilmadi"]
    xatolar = xatolar or biznes_qoidalari(d)
    if not xatolar:
        baza.saqla(Buyurtma.yarat(d))

2.  if type(d["soni"]) is int and d["soni"] >= 1:          # True/False o'tmaydi
        qabul(d)

3.  prompt_i = prompt
    for _ in range(3):                                        # chegarali
        javob = llm(prompt_i, temperature=0)
        d, xato = tekshir(javob)
        if d is not None:
            return d
        prompt_i = prompt + "\nOldingi javobingiz: " + javob + "\nXATO: " + xato
    return None                                               # fallback

4.  d["telefon"] = norm_telefon(d.get("telefon")) if manbada_bormi(d.get("telefon"), matn) else None

5.  aniqlik = {k: np.mean([NORM[k](j[k]) == h[k] for j, h in juftlar]) for k in MAYDONLAR}
    uydirma = {k: np.mean([j[k] is not None for j, h in juftlar if h[k] is None])
               for k in MAYDONLAR}

Vazifa 3: Ajratuvchi

Modellang:

  1. Stub bilan 8 xil buzilish turi
  2. Bosqichli ajrat (to'g'ridan, kod bloki, qavs skaneri, tuzatish)
  3. Tur bo'yicha muvaffaqiyat va jim xato soni
  4. Chekka holatlar: satr ichidagi }, //, apostrof, ikki obyekt; qo'shimcha: massiv ([...]) qaytaradigan javoblar

Vazifa 4: Validator

Modellang:

  1. JSON Schema kichik qismi: type, required, enum, minimum, pattern, items
  2. Yo'l bilan xato xabarlari
  3. Biznes qoidasi va dataclass
  4. Qat'iy / moslashtirish / moslash + biznes siyosatlari: qabul va noto'g'ri qabul

Vazifa 5: Qayta so'rash sikli

Modellang:

  1. Stub: T = 0 da prompt xeshidan deterministik javob
  2. Uch strategiya, K = 1, 2, 3, 5
  3. Muvaffaqiyat, chaqiruvlar, kirish tokenlari
  4. Yiqilganlar tahlili va fallback

Vazifa 6: Extraction bahosi

Modellang:

  1. O'zbekcha xabarlar generatori va haqiqiy maydonlar
  2. Normallashtirish funksiyalari (summa, telefon, sana, son so'zlari)
  3. Qat'iy / normallangan / quvur baholari maydon bo'yicha
  4. Uydirma ulushi va manbaga bog'lash; qolgan xatolar tahlili

Vazifa 7: O'ylash

Jamoadoshingiz aytdi: "Endi API da structured outputs bor — javob har doim sxemaga mos JSON. Demak, validator, biznes qoidalari va qayta so'rash kodini o'chirib tashlaymiz, ular ortiqcha."

Javob

Qisqa javob: sxema majburlash — katta yutuq va uni yoqish kerak, lekin u faqat bitta qatlamni (shakl) yopadi. Qolgan qatlamlarni o'chirish xavfli.

1. Kafolat nimani qamraydi. Kalitlar, turlar, enum, ortiqcha maydonlar yo'qligi — ha. 1-misoldagi buzilishlarning ko'pchiligi (kod bloki, izoh, ortiqcha vergul) haqiqatan yo'qoladi. Lekin qirqilish qoladi: max_tokens yetmasa, javob to'liq emas — stop_reason ni tekshirish baribir kerak.

2. Ma'no kafolatlanmaydi. 2-misoldagi "jami" xatosi shaklan mukammal edi: sxema xatolari 0, faqat biznes qoidasi ushladi. 4-misoldagi uydirma telefon raqami ham sxemaga mos (^\+998\d{9}$) — uni faqat manbaga bog'lash ushladi. Noto'g'ri shahar esa bog'lashdan ham o'tdi.

3. Qayta so'rash hali ham kerak — endi format xatolari uchun emas, balki biznes qoidalari xatolari uchun: "jami hisoblangan summaga teng emas, tuzating". Urinishlar chegarasi va fallback o'zgarmaydi.

4. Validator — himoyaning ikkinchi chizig'i. Provayder, model yoki sxema versiyasi o'zgarganda, yoki kelajakda boshqa (majburlashsiz) model ulanganda, validator jim xatolarni to'xtatadi. Uning narxi — mikrosekundlar.

Tavsiya:

python
# 1. structured outputs / strict tool use ni yoqing
# 2. stop_reason ni tekshiring
# 3. sxema validatori qoladi (arzon, ikkinchi himoya)
# 4. biznes qoidalari va manbaga bog'lash - albatta
# 5. qayta so'rash - biznes xatolari uchun, K = 2, keyin fallback
# 6. loglar: bosqich statistikasi, rad etilganlar, uydirma ulushi

Jamoadoshga javob: "Structured outputs ni yoqamiz — format xatolari yo'qoladi va ajratish kodi soddalashadi. Lekin validator va biznes qoidalari qoladi: sxemaga mos JSON ichida ham noto'g'ri summa va uydirma telefon bo'lishi mumkin, buni bizning test to'plamimiz ko'rsatdi."

Nimani mustahkamlaydi: 2.2, 2.3, 2.5, 2.6, 2.7-bo'limlar.


Xulosa

Bu darsda LLM javobini dastur o'qishi uchun kerak bo'ladigan qatlamlarni qurdik: JSON ni xavfsiz ajratish, sxema va biznes qoidalari bilan validatsiya, xato bilan qayta so'rash va extraction ni maydon bo'yicha baholash. Barcha misollarda LLM o'rnida stub ishlatildi — raqamlar farazlarning oqibati, lekin kod tuzilishi haqiqiy API bilan bir xil.

Eng muhim uch fikr:

  1. Ajratish konservativ, validatsiya ikki qavatli. 1-misolda bosqichli ajrat 600 ta stub javobning 92.8% ini o'qidi (naiv json.loads — 39.0%), jim xato 0; ko'p ma'noli holatlar (ichki qo'shtirnoq, qirqilish, ikki obyekt) ataylab rad etildi. 2-misolda sxema shaklni tekshirdi, lekin "jami" xatosini faqat biznes qoidasi ushladi: moslashtirish qabulni 74.0% dan 98.8% ga oshirdi va 16 ta noto'g'ri javobni o'tkazdi, biznes qoidasi bilan esa 94.8% xatosiz qabul.

  2. Qayta so'rash promptni o'zgartirishi kerak va cheklangan bo'lishi kerak. 3-misolda T = 0 va bir xil prompt bilan 5 urinish 1 urinishdan yaxshi emas (70.7%), lekin 2.17 barobar ko'p chaqiruv. Xato matni bilan qayta so'rash K = 3 da 96.7%, K = 5 da 96.9% — foyda to'yinadi, tokenlar o'sadi; yiqilganlarning 76% i doimiy qiyin holatlar — ularga fallback kerak (stub farazlari ostida).

  3. Extraction ni maydon bo'yicha, normallangan qiymatlarda baholang. 4-misolda qat'iy satr taqqoslash aniqlikni 43.5 foiz punkt past ko'rsatdi (51.0% va 94.4%); manbaga bog'lash uydirmalarni 105 dan 0 ga tushirdi, lekin matnda bor noto'g'ri qiymatni (shahar, 87.2%) ushlamadi.

Keyingi darsda LLM API bilan ishlash: SDK tuzilishi va xabarlar formati, xatolar turlari (429, 529, 400, timeout), eksponensial backoff va jitter, kesh va idempotentlik, streaming, batch va usage dan xarajat hisobini ko'ramiz.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
25.5-dars: Strukturali chiqish va validatsiya — IlmHamroh