Asosiy qismga o'tish

Arxitektura: nega model hisoblamaydi

3-bob 5 kod bloki 2087 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Ikki yo'l

AI ni jadval bilan ishlatishning ikki yo'li bor.

Birinchi yo'l: jadvalni modelga berasiz va so'raysiz: "filiallar bo'yicha jami qancha?" Model o'qiydi va raqam yozadi.

Ikkinchi yo'l: modelga jadvalni emas, faqat uning tavsifini berasiz: ustun nomlari, turlari, bir nechta namuna. Model raqam yozmaydi — plan yozadi: "filial bo'yicha guruhla, summa ustunini jamla". Planni engine bajaradi.

Birinchi yo'l oddiyroq ko'rinadi. Tez ikkinchisini tanladi. Bu bob — nega.

Model jadvalni ko'rmaydi ham

Eng oddiy sababdan boshlaymiz: hajm. Tezning testlarida ishlatiladigan sintetik kredit jadvalini olamiz. Bu jadval butunlay o'ylab topilgan: ismlar, summalar, sanalar — hammasini kompyuter yasagan. Lekin u ataylab haqiqiy bank fayliga o'xshatib "iflos" qilingan.

Model aynan nimani ko'rishini Tezning o'z kodi bilan yasaymiz — prompt/build.py:

python12 qator
from prompt import build

kontekst = build.load_context(TEZ / "prompt/domains/bank_loans")
jadval = kontekst["table"]
prompt = build.system_prompt(kontekst)
fayl = DATA / "loans.csv"

print(f"jadval: {jadval['name']}, {jadval['rows']:,} qator, {len(jadval['columns'])} ustun")
print(f"jadval fayli: {fayl.stat().st_size:,} bayt")
print(f"model ko'radigan prompt: {len(prompt):,} belgi")
print()
print(build.column_lines(jadval)[:900])
Chiqish
jadval: kreditlar, 20,000 qator, 22 ustun
jadval fayli: 4,074,219 bayt
model ko'radigan prompt: 7,283 belgi

- kredit_raqami (integer), samples: [500216, 500245, 500278]
- berilgan_sana (text), samples: ["2024-11-28", "02.04.2024", "14.04.2024"]
- filial (text), all values: ["Тошкент", "Namangan", "Samarqand ", "Samarqand", "Бухоро", "Toshkent", "Qarshi", "Andijon", "Buxoro"]
- viloyat (text), all values: ["Toshkent sh.", "Namangan", "Samarqand", "Andijon", "Buxoro", "Qashqadaryo"]
- qarzdor_id (integer), samples: [700127, 700018, 700054]
- qarzdor_ismi (text), all values: ["Saidova Gulnora", "Qodirov Sherzod", "Petrova Anna", "Nazarov Ulug'bek", "Мирзаев Анвар", "Toshpo'latova Nilufar", "Ergashev Javohir"]
- qarzdor_turi (text), all values: ["Jismoniy", "Yuridik", "YaTT"]
- kredit_turi (text), all values: ["Iste'mol krediti", "Ipoteka", "Biznes krediti", "Avtokredit", "Mikroqarz", "Ta'lim krediti"]
- valyuta (text), all values: ["UZS", "USD"]
- kredit_summasi (text), samples: ["102629561.14",

Yigirma ming qatorli fayl — to'rt million baytdan ortiq. Model ko'radigan butun prompt — bir necha ming belgi. Unda qatorlar yo'q. Har bir ustundan faqat uchta namuna, yoki ustunda qiymatlar kam bo'lsa — ularning ro'yxati.

Demak "filiallar bo'yicha jami qancha?" degan savolga model raqam yozsa, u raqam qayerdan keladi? Faqat bitta joydan: o'ylab topilgan bo'ladi. Model yigirma ming summaning birortasini ham ko'rmagan.

"Unda hamma qatorni promptga solaylik" — degan fikr kelishi mumkin. Tezdagi model serveri 8192 token kontekst bilan ishga tushadi (speed/config.json). 1-bobdagi bir million qatorli fayl esa 44.5 MB edi. Bu fayl hech qanday kichik modelning kontekstiga sig'maydi. Sig'ganda ham, kichik model million sonni xatosiz qo'sha olishiga hech qanday kafolat yo'q.

Namunalardagi bir narsaga e'tibor bering: filial ustunida "Тошкент" va "Toshkent", "Samarqand" va "Samarqand " (oxirida bo'sh joy bilan) — alohida qiymatlar. Haqiqiy bank fayllari ham xuddi shunday. Bunday iflos qiymatlarni tozalash ham hisoblash — demak u ham engine ning ishi (unify_spelling tooli, 10-bob).

Tezning qatlamlari

Endi butun yo'lni chizamiz. Rangli qutilar — bugun ishlaydigan kod. Kulrang quti — hali qurilmagan qism.

python21 qator
diagramma(
    {
        "So'rov\no'zbek / rus / ingliz": (0, 0, 2.6, 1.1, "manba"),
        "Prompt + JSON schema\nprompt/build.py": (3.4, 0, 2.8, 1.1, "qayta"),
        "Model\nllama-server, 127.0.0.1": (7.0, 0, 2.8, 1.1, "xizmat"),
        "JSON reja\nyoki savol": (7.0, 2.2, 2.8, 1.1, "kuzatuv"),
        "Dvigatel\naccuracy/execute.py": (3.4, 2.2, 2.8, 1.1, "qayta"),
        "DuckDB\nhaqiqiy raqamlar": (0, 2.2, 2.6, 1.1, "saqlash"),
        "Ilova: ko'rish, tasdiqlash,\nbekor qilish (hali yo'q)": (0, 4.4, 2.6, 1.1, "oddiy"),
    },
    [
        ("So'rov\no'zbek / rus / ingliz", "Prompt + JSON schema\nprompt/build.py"),
        ("Prompt + JSON schema\nprompt/build.py", "Model\nllama-server, 127.0.0.1"),
        ("Model\nllama-server, 127.0.0.1", "JSON reja\nyoki savol", "grammar ichida"),
        ("JSON reja\nyoki savol", "Dvigatel\naccuracy/execute.py"),
        ("Dvigatel\naccuracy/execute.py", "DuckDB\nhaqiqiy raqamlar", "SQL"),
        ("DuckDB\nhaqiqiy raqamlar", "Ilova: ko'rish, tasdiqlash,\nbekor qilish (hali yo'q)"),
    ],
    nom="qatlamlar.png",
    izoh="Tezning bugungi yo'li: so'rovdan raqamgacha",
)

Tezning bugungi yo'li: so'rovdan raqamgacha

Tezning bugungi yo'li: so'rovdan raqamgacha

Har bir qatlamning bitta vazifasi bor:

Qatlam Nima qiladi Nima qilmaydi
prompt/build.py Jadval tavsifi, toollar ro'yxati va qoidalardan prompt yasaydi; javob shaklini JSON schema bilan cheklaydi Qatorlarni modelga bermaydi
Model (llama-server) So'rovni plan yoki savolga aylantiradi Raqam yozmaydi, jadvalga tegmaydi
accuracy/execute.py Planni qadamma-qadam bajaradi, har bir toolni tekshiradi Model nima deganini "tuzatib" o'tirmaydi
DuckDB Hamma hisobni bajaradi O'zidan hech narsa qo'shmaydi — engine bergan SQL ni aniq bajaradi

Model serveri faqat 127.0.0.1 manzilida tinglaydi — ya'ni faqat shu kompyuterning o'zidan kirish mumkin. Tarmoqdagi boshqa kompyuter unga ulana olmaydi. Bu maxfiylikning birinchi qatori (27-bobda to'liq).

Birinchi to'siq: grammar

Model plan yozadi — lekin u plan noto'g'ri bo'lsa-chi? Masalan, jadvalda yo'q ustun nomini yozsa?

Tezda bunga ikki to'siq bor. Birinchisi — model hali yozayotgan paytda ishlaydi. build.py promptdan tashqari javobning aniq shaklini ham yasaydi — JSON schema. Model serveri bu schemani grammar ga aylantiradi va model har bir tokenni faqat shu grammar ruxsat bergan variantlardan tanlaydi.

Schema nimalarni cheklashini ko'raylik:

python11 qator
import json

schema = build.output_schema(kontekst)
reja_shakli, savol_shakli = schema["anyOf"]
variantlar = reja_shakli["properties"]["steps"]["items"]["anyOf"]
print(f"schema hajmi: {len(json.dumps(schema)):,} belgi")
print(f"bitta qadamning mumkin bo'lgan shakllari: {len(variantlar)}")
print(f"rejadagi qadamlar: {reja_shakli['properties']['steps']['minItems']} dan {reja_shakli['properties']['steps']['maxItems']} gacha")
print(f"savol uzunligi: {savol_shakli['properties']['ask']['maxLength']} belgigacha")
print()
print(json.dumps(variantlar[0], ensure_ascii=False, indent=1)[:700])
Chiqish
schema hajmi: 48,781 belgi
bitta qadamning mumkin bo'lgan shakllari: 85
rejadagi qadamlar: 1 dan 4 gacha
savol uzunligi: 300 belgigacha

{
 "type": "object",
 "properties": {
  "tool": {
   "const": "filter"
  },
  "args": {
   "type": "object",
   "properties": {
    "column": {
     "type": "string",
     "enum": [
      "filial"
     ]
    },
    "op": {
     "enum": [
      "=",
      "!="
     ]
    },
    "value": {
     "enum": [
      "Тошкент",
      "Namangan",
      "Samarqand ",
      "Samarqand",
      "Бухоро",
      "Toshkent",
      "Qarshi",
      "Andijon",
      "Buxoro"
     ]
    }
   },
   "required": [
    "column",
    "op",
    "value"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "tool",
  "args"
 ],
 "additionalProperties": false
}

Birinchi variantni o'qing: filter tooli, filial ustuni, = yoki != amali — va value uchun faqat jadvalda bor qiymatlar ro'yxati. = amalida model "Ташкент" yoki "Tashkent" deb yoza olmaydi, chunki grammar unga bunday harflar ketma-ketligini bermaydi. U faqat jadvalda haqiqatan bor "Toshkent" yoki "Тошкент" ni tanlay oladi. (contains yoki > kabi amallarda esa qiymat erkin — u yerda ro'yxat bilan cheklab bo'lmaydi.)

Ustun nomlari ham xuddi shunday: faqat jadvaldagi nomlar. Tool nomlari ham: faqat o'n beshta. Schema har bir jadval uchun qaytadan yasaladi — shuning uchun model har doim aynan shu jadvalning ustunlari bilan cheklanadi.

Grammar ham xato qilishi mumkin

Grammar bizni ko'p narsadan himoya qiladi, lekin uning o'zi ham tuzoq bo'lishi mumkin. tez9 modeli yetti soat o'qitilganidan keyin birinchi testda juda yomon chiqdi — va sababi model emas, grammar edi: llama.cpp ixtiyoriy kalitlarni majburiylaridan keyinga qo'yadi, model esa boshqa tartibda o'rgangan edi. Bu hikoya 9-bobda. (Grammar tuzatilgandan keyin ham tez9 eski modeldan yutmadi — lekin bu boshqa sabab, 21-bob.)

Ikkinchi to'siq: engine

Grammar shaklni tekshiradi. Lekin ba'zi xatolar shakl jihatidan to'g'ri bo'ladi. Masalan, ko'p qadamli planda ikkinchi qadam birinchi qadam natijasida yo'q ustunni so'rashi mumkin.

Ikkinchi to'siq — engine. Har bir tool ishga tushishdan oldin ustunlar haqiqatan borligini tekshiradi. Yo'q ustun bilan plan beramiz:

python13 qator
from accuracy.run import open_data, BASE
from accuracy.execute import run_plan
from accuracy.tools import ToolError

sozlamalar = json.load(open(TEZ / "accuracy/settings.json", encoding="utf-8"))
con = open_data(str(fayl))
print(f"bazaga yuklandi: {con.execute(f'select count(*) from {BASE}').fetchone()[0]:,} qator")

yomon_reja = [{"tool": "group_and_total", "args": {"group_by": ["mijoz"], "value_column": "qoldiq_uzs", "agg": "sum"}}]
try:
    run_plan(con, BASE, yomon_reja, sozlamalar)
except ToolError as xato:
    print(f"reja to'xtatildi: {xato}")
Chiqish
bazaga yuklandi: 20,000 qator
reja to'xtatildi: columns not in current result: mijoz

Plan bajarilmadi va sababi aniq aytildi. Hech qanday "taxminiy" natija chiqmadi. Bu muhim: noto'g'ri plan to'xtashi kerak, jim o'tib ketmasligi kerak.

Qanday bilamizki, plan to'g'ri?

Endi eng nozik savol. Model plan yozdi, engine uni xatosiz bajardi. Lekin natija so'ralgan narsami?

Testlarda biz buni shunday tekshiramiz: har bir test savoliga to'g'ri planni oldindan yozib qo'yganmiz. Model plani ham, to'g'ri plan ham bajariladi va natijalari solishtiriladi — accuracy/compare.py dagi same_result. Planlarning matni emas, natijalari.

Nega matn emas? Chunki bitta savolga bir nechta to'g'ri plan bo'lishi mumkin:

python15 qator
from accuracy.compare import same_result

def bajar(qadamlar):
    return run_plan(con, BASE, qadamlar, sozlamalar)

jamla = {"tool": "group_and_total", "args": {"group_by": ["viloyat"], "value_column": "qoldiq_uzs", "agg": "sum"}}
a = bajar([{"tool": "filter", "args": {"column": "valyuta", "op": "=", "value": "USD"}}, jamla])
b = bajar([{"tool": "filter", "args": {"column": "valyuta", "op": "in", "values": ["USD"]}}, jamla])
c = bajar([jamla])

print("A: valyuta = USD, keyin viloyat bo'yicha jami")
print("B: valyuta ro'yxatda [USD], keyin viloyat bo'yicha jami")
print("C: filtrsiz, viloyat bo'yicha jami")
print(f"A va B bir xilmi: {same_result(a, b, sozlamalar)}")
print(f"A va C bir xilmi: {same_result(a, c, sozlamalar)}")
Chiqish
A: valyuta = USD, keyin viloyat bo'yicha jami
B: valyuta ro'yxatda [USD], keyin viloyat bo'yicha jami
C: filtrsiz, viloyat bo'yicha jami
A va B bir xilmi: True
A va C bir xilmi: False

A va B — har xil yozilgan, lekin bir xil natija beradigan planlar. same_result ularni teng deb biladi. C esa filtrni tashlab yuborgan — natija boshqa, va same_result buni ushlaydi.

Bu usulning bir kamchiligi bor va uni ochiq aytish kerak: ikki xil plan tasodifan bir xil natija bersa, test xatoni sezmaydi. Masalan, jadvalda faqat USD kreditlari qolgan bo'lsa, filtrli va filtrsiz plan bir xil chiqadi. Shuning uchun test savollari va jadvallar shunday tanlanadiki, noto'g'ri plan ko'rinadigan farq bersin (23-bob).

Planda bor, kodda yo'q

Loyiha boshidagi arxitektura plani bundan kattaroq edi. Unda yana bir nechta qatlam bor:

  • Ilova (React va ECharts): oyna, jadval, jonli grafiklar.
  • API (FastAPI): ilova va engine orasidagi ko'prik.
  • Tarix va bekor qilish: har bir qadam yangi versiya yaratadi, hech narsa ustidan yozilmaydi, bir bosishda orqaga qaytish.
  • Avtomatik tekshiruvlar: har bir qadamdan keyin qator soni, jamilar, bo'sh kataklar manba bilan solishtiriladi; tekshiruv o'tmasa, qadam to'xtaydi.
  • Oldindan ko'rish va tasdiqlash: odam o'zgarishni ko'radi va "ha" deydi.

Bularning hech biri hali kod sifatida yo'q. Bugun bor narsa — diagrammadagi rangli qutilar: prompt, model, engine, DuckDB. Ular test to'plamlari orqali ishlatiladi, ilova orqali emas.

Biz bu tartibni ongli tanladik va 2-bobda sababini aytdik. Lekin arxitektura shunday qurilganki, ilova keyin ustiga qo'shiladi: engine planni qayerdan kelganini bilmaydi — testdanmi, ilovadanmi. Bir xil run_plan ikkalasiga ham xizmat qiladi.

Xulosa

  • Model jadvalni ko'rmaydi: u faqat ustun nomlari va bir nechta namunani ko'radi. Shuning uchun u raqam yozsa — bu o'ylab topilgan raqam bo'lardi.
  • Model plan yozadi, DuckDB hisoblaydi. Har bir qatlamning bitta vazifasi bor.
  • Noto'g'ri planga qarshi ikki to'siq: grammar (model faqat bor ustun, bor qiymat va o'n besh tooldan tanlaydi) va engine (yo'q ustun bo'lsa, plan to'xtaydi).
  • Planning to'g'riligini matni bilan emas, natijasi bilan tekshiramiz (same_result).
  • Ilova, tarix, avtomatik tekshiruvlar — hali planda.

Bu bilan birinchi qism tugadi: muammo, maqsad va arxitektura. Ikkinchi qismda bitta qator kod yozishdan oldin nimalarni o'lchaganimizni ko'ramiz — Excel o'qishdan boshlab.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar