Asosiy qismga o'tish

Ilova: engine, kalit va pipe

30-bob 10 kod bloki 2761 so'z

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

Uch qism

29-bobgacha Tez faqat benchmark ichida yashardi: savollar fayldan o'qilardi, javoblar faylga yozilardi. Tahlilchi esa savolni oynaga yozadi. Bu bob — ilovaning birinchi qurilgan qismi haqida. Ilova uch qismdan iborat:

python5 qator
oyna (Tauri)            fayl tashlash, oldindan ko'rish, savol, javob va tushuntirish
   |  pipe: stdin/stdout orqali JSON qatorlar
engine (Python)         fayl o'qish, tavsif, prompt, validator, darvoza, DuckDB, tushuntirish
   |  HTTP, faqat 127.0.0.1, tasodifiy port va kalit
llama-server + tez8     faqat JSON plan yozadi

Pastki ikki qism qurildi va sinaldi. Oyna — hali yo'q. Engine kodi loyihaning app/ papkasida:

python3 qator
for fayl in sorted((TEZ / "app").glob("*.py")):
    turi = "sinov" if fayl.name.startswith("test_") or fayl.name == "fake_llama.py" else "kod"
    print(f"{fayl.name:16s} {turi:6s} {len(fayl.read_text(encoding='utf-8').splitlines()):4d} qator")
Chiqish
endpoints.py     kod      24 qator
engine.py        kod      77 qator
fake_llama.py    sinov    41 qator
model.py         kod      62 qator
parity.py        kod      66 qator
pipe.py          kod      85 qator
table.py         kod      36 qator
test_model.py    sinov    81 qator
test_pipe.py     sinov    74 qator
test_session.py  sinov    94 qator
test_table.py    sinov    49 qator

Kod kam, chunki engine deyarli hech narsani yangidan yozmaydi.

Engine benchmark yo'lidan yuradi

Bu kitobdagi har bir raqam benchmark kodi bilan o'lchangan. Agar ilova savolni boshqa yo'l bilan qayta ishlasa — boshqa tavsif, boshqa prompt, boshqa darvoza — o'sha raqamlar ilovaga tegishli bo'lmaydi. Shuning uchun engine benchmark funksiyalarining o'zini chaqiradi:

python6 qator
import ast

for nom in ("table.py", "engine.py", "model.py"):
    for tugun in ast.parse((TEZ / "app" / nom).read_text(encoding="utf-8")).body:
        if isinstance(tugun, ast.ImportFrom) and tugun.module.split(".")[0] in ("accuracy", "prompt", "runtime", "training"):
            print(f"{nom:10s} <- {tugun.module}: {', '.join(n.name for n in tugun.names)}")
Chiqish
table.py   <- accuracy.load_excel: clean_sheet, read_sheet, write_csv
table.py   <- accuracy.run: BASE, open_data
table.py   <- accuracy.tools: ROW_ID, q
table.py   <- training.make_table: describe_table
engine.py  <- accuracy.execute: effective_steps, run_plan
engine.py  <- accuracy.run: BASE, guard, prompt_history
engine.py  <- accuracy.tools: ROW_ID, ToolError, q
engine.py  <- accuracy.validate: confidence
engine.py  <- prompt: build
engine.py  <- runtime.client: parse_answer
engine.py  <- training.describe: explain, load_texts
model.py   <- runtime: server
model.py   <- runtime.client: condition, payload
model.py   <- runtime.launch: launch

Fayl 11-bobdagi loader dan o'tadi, tavsifni o'sha tavsifchi yozadi (29-bobdagi 600 belgi qoidasi bilan), prompt benchmark dagi kod bilan quriladi, guard — benchmark dagi validator va darvozaning o'zi.

Bu da'voni tekshirish oson: ilova oltita real jadvalni ochsin, va uning tavsifi benchmark dagi tavsif bilan solishtirilsin. To'rtta jadval — CSV fayl. Hududlar jadvali va valyuta kurslari esa — asl Excel fayllar, 11-bobda ishlatilganlarning o'zi; ular loader dan o'tadi, sarlavha ustidagi matnlar bilan:

python21 qator
import json
import shutil
from app.table import open_table

def oqi(nisbiy):
    return json.loads((TEZ / nisbiy).read_text(encoding="utf-8"))

ilova = oqi("app/config.json")
qoidalar, sozlama, profil = oqi("accuracy/load.json"), oqi("accuracy/settings.json"), oqi("training/settings.json")
manbalar = {"real_banks": "/mnt/d/Tez_spikes/data/real_banks.csv",
            "real_credit_clients": "/mnt/d/Tez_spikes/data/real_credit_clients.csv",
            "real_industry": "/mnt/d/Tez_spikes/data/real_industry.csv",
            "real_retail": "/mnt/d/Tez_spikes/data/real_retail.csv",
            "real_regions": DATA / "regions_eng.xlsx", "real_fx_rates": DATA / "0009_eng.xlsx"}
for toplam, manba in manbalar.items():
    kutilgan = oqi(f"prompt/domains/{toplam}/table.json")
    nusxa = ISH / f"{kutilgan['name']}{Path(manba).suffix}"
    shutil.copyfile(manba, nusxa)
    baza, jadval = open_table(nusxa, ilova, qoidalar, sozlama, profil)
    print(f"{toplam:20s} {Path(manba).suffix:5s} {jadval['rows']:7,d} qator   tavsif bir xil: {jadval == kutilgan}")
    baza.close()
Chiqish
real_banks           .csv       36 qator   tavsif bir xil: True
real_credit_clients  .csv   30,000 qator   tavsif bir xil: True
real_industry        .csv        9 qator   tavsif bir xil: True
real_retail          .csv  541,909 qator   tavsif bir xil: True
real_regions         .xlsx      14 qator   tavsif bir xil: True
real_fx_rates        .xlsx      75 qator   tavsif bir xil: True

Oltitasi ham bir xil. Excel fayl uchun loader tozalagan jadval vaqtinchalik CSV ga yoziladi, DuckDB uni yuklaydi, va vaqtinchalik papka darhol o'chiriladi — benchmark ham Excel ni aynan shu yo'l bilan o'qigan.

Model serveri: xesh, port, kalit

Engine model serverini o'zi ishga tushiradi va o'zi to'xtatadi. Ishga tushirish kodi:

python4 qator
manba = (TEZ / "app/model.py").read_text(encoding="utf-8")
for tugun in ast.walk(ast.parse(manba)):
    if isinstance(tugun, ast.FunctionDef) and tugun.name == "__init__":
        print(ast.get_source_segment(manba, tugun))
Chiqish
def __init__(self, config, log_path):
        path = Path(config["model_dir"]) / config["model"]["file"]
        if file_hash(path) != config["model"]["sha256"]:
            raise ModelError(f"{path} does not match the expected SHA-256")
        if sys.platform not in config["binaries"]:
            raise ModelError(f"no llama-server configured for {sys.platform}")
        key = secrets.token_hex(config["key_bytes"])
        runtime = {**config["runtime"], "binaries": {"cpu": config["binaries"][sys.platform]},
                   "env": {**config["runtime"]["env"], config["key_env"]: key}}
        self.config = {"runtimes": {RUNTIME: runtime}, "default_runtime": RUNTIME, "model_dir": config["model_dir"],
                       "port": free_port(), "context": config["context"], "timeout": config["timeout"],
                       "max_tokens": config["max_tokens"]}
        self.headers = {"Authorization": f"Bearer {key}"}
        self.process, self.runtime = launch(self.config, config["model"], condition(f"cpu:{config['threads']}"), log_path, self.headers)

Tartib muhim. Avval model faylining xeshi tekshiriladi (28-bob) — mos kelmasa, server umuman ishga tushmaydi. Keyin bo'sh port tanlanadi va har safar yangi tasodifiy kalit yaratiladi. Kalit server jarayoniga muhit o'zgaruvchisi orqali beriladi, buyruq qatori orqali emas. Engine kalitni faqat o'z so'rovlarida yuboradi. Kalitsiz so'rovlarga server nima deyishi 27-bobda o'lchandi: faqat /health javob beradi.

Pipe

Oyna engine bilan qanday gaplashadi? Loyiha planida (04-architecture.md) bu yerda FastAPI va WebSocket turardi. Lekin bu ikkinchi mahalliy port degani — va 27-bobdagi teshik ikkinchi marta ochilishi mumkin edi. Shuning uchun boshqa yo'l tanlandi: oyna engine ni o'zining bola jarayoni sifatida ishga tushiradi va u bilan pipe (quvur) orqali — jarayonning standart kirishi va chiqishi orqali — gaplashadi. Har bir so'rov — bitta JSON qator, har bir javob — bitta JSON qator. Tarmoq porti yo'q, demak brauzer ham, boshqa dastur ham engine ga ulana olmaydi.

Protokolni ko'rish uchun engine ga model o'rniga oldindan yozilgan javob beramiz — bu yerda model ishlamaydi, faqat engine ning pipe qismi ishlaydi. Fayl — kitobdagi sintetik kreditlar jadvali (datasets/loans.csv):

python25 qator
import io
from app.pipe import Pipe, serve

class YozilganModel:
    def __init__(self, javob):
        self.javob = javob

    def body(self, context, schema, text, history):
        return {}

    def chat(self, body):
        return {"choices": [{"message": {"content": json.dumps(self.javob)}, "logprobs": {"content": [{"logprob": 0.0}]}}]}

plan = {"steps": [{"tool": "group_and_total", "args": {"group_by": ["viloyat"], "value_column": "qoldiq_uzs", "agg": "sum"}}]}
shutil.copyfile(DATA / "loans.csv", ISH / "kreditlar.csv")
sorovlar = [{"id": 1, "op": "open", "path": str(ISH / "kreditlar.csv")},
            {"id": 2, "op": "ask", "text": "viloyatlar bo'yicha qoldiq"},
            {"id": 3, "op": "ask_everything"}]
chiqish = io.StringIO()
serve([json.dumps(s) for s in sorovlar], chiqish, Pipe(YozilganModel(plan), ilova, lambda: sozlama["today"]))
for qator in chiqish.getvalue().splitlines():
    javob = json.loads(qator)
    if "rows" in javob:
        javob = {**javob, "columns": javob["columns"][:4], "rows": [r[:4] for r in javob["rows"][:2]]}
    print(json.dumps({k: v for k, v in javob.items() if k not in ("output",)}, ensure_ascii=False)[:420], "\n")
Chiqish
{"id": 1, "ok": true, "table": "kreditlar", "columns": ["kredit_raqami", "berilgan_sana", "filial", "viloyat"], "rows": [[500001, "20/11/2024", "Buxoro", "Buxoro"], [500002, "04.04.2025", "Toshkent", "Andijon"]], "total_rows": 20000} 

{"id": 2, "ok": true, "kind": "result", "text": "1. Har bir viloyat uchun qoldiq_uzs bo'yicha jami hisoblanadi.", "held": null, "columns": ["viloyat", "qoldiq_uzs"], "rows": [["Qashqadaryo", 1416469087152.8354], ["Namangan", 1442975847712.3447]], "total_rows": 6, "artifacts": [], "confidence": 1.0} 

{"id": 3, "ok": false, "error": "unknown op 'ask_everything'"}

Birinchi javob — fayl ochildi: jadval nomi, ustunlar, birinchi qatorlar va jami qatorlar soni (oyna buni oldindan ko'rish uchun ko'rsatadi). Ikkinchisi — natija: plan bajarilgan jadval va tahlilchi tilidagi tushuntirish (29-bob). Uchinchisi — noma'lum buyruq: engine yiqilmaydi, xato bilan javob beradi.

Pipe ning yana bir foydasi bor. Oyna yopilsa — yoki kutilmaganda o'lsa — engine ning standart kirishi yopiladi. Engine buni ko'radi va model serverini to'xtatadi: kompyuterda deyarli 5 GB xotira (26-bob) egallagan yetim server qolmaydi. Bu va boshqa hamma holatlar sinovlarda tekshiriladi — haqiqiy llama-server o'rniga uni taqlid qiladigan kichik server bilan:

python12 qator
import os
import subprocess
import sys

sinov = subprocess.run([sys.executable, "-B", "-m", "pytest", "-q", "-p", "no:cacheprovider", "app"],
                       cwd=TEZ, capture_output=True, text=True, env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"})
print(sinov.stdout.strip().splitlines()[-1])
for fayl in sorted((TEZ / "app").glob("test_*.py")):
    nomlar = [t.name for t in ast.parse(fayl.read_text(encoding="utf-8")).body if isinstance(t, ast.FunctionDef) and t.name.startswith("test_")]
    print(f"\n{fayl.name}:")
    for nom in nomlar:
        print(f"  {nom}")
Chiqish
20 passed in 7.92s

test_model.py:
  test_file_hash_matches_hashlib
  test_wrong_hash_refuses_to_start
  test_unknown_platform_refuses_to_start
  test_server_needs_the_key_and_stops_on_close
  test_each_start_gets_its_own_key

test_pipe.py:
  test_open_then_ask
  test_ask_before_open_is_an_error
  test_bad_requests_are_answered_not_fatal
  test_quit_stops_reading
  test_engine_process_stops_the_model_when_the_window_goes_away

test_session.py:
  test_plan_becomes_a_result_with_an_explanation
  test_low_confidence_asks_and_is_not_remembered
  test_validator_problem_asks
  test_model_question_is_passed_on
  test_unusable_output_fails_politely
  test_follow_up_sees_the_earlier_turn_and_restart_forgets_it

test_table.py:
  test_csv_is_described_like_the_benchmark_tables
  test_excel_keeps_titles_and_leaves_no_copy
  test_other_files_are_refused

Bitta chegara: agar engine ning o'zi majburan o'ldirilsa (masalan, vazifalar menejeridan), u serverni to'xtatishga ulgurmaydi. Buning uchun Windows da operatsion tizimning o'zi bola jarayonlarni birga yopadigan mexanizm kerak — bu o'rnatuvchi bilan birga qilinadi.

Windows da: o'sha javoblarmi

Engine benchmark kodini chaqiradi — lekin bu "javoblar bir xil" degani emasmi? Buni ham o'lchaymiz. Windows da, tahlilchi ishlatadigan llama.cpp build ida, haqiqiy tez8 bilan, ikkinchi yangi to'plamning (29-bob) besh jadvaldagi hamma savollari engine orqali o'tdi, va har bir chiqish o'sha build dagi benchmark javobi bilan solishtirildi. Hududlar jadvali bu o'tishga kirmadi: o'tish CSV nusxalar bilan qilingan, hududlar CSV sida esa Excel dagi sarlavhalar yo'q. Natija saqlangan:

python4 qator
natija = (TEZ / "results/app_parity_win.txt").read_text(encoding="utf-8").splitlines()
for qator in natija:
    if not qator.startswith("  #"):
        print(qator)
Chiqish
hash seconds 1.8
start seconds (hash + load) 3.8
/props without key: HTTP 401
/slots without key: HTTP 401
chat without key: HTTP 401
real_banks: description identical True
real_credit_clients: description identical True
real_fx_rates: description identical True
real_industry: description identical True
real_retail: description identical True
questions 72, identical output 71, same gate/validator decision 72
confidence gap median 0.00e+00 max 3.03e-03, median seconds 2.50
  real_retail #11: held None -> None
server still running: False

Xesh 1.8 soniya, server ishga tushishi bilan birga 3.8 soniya. Kalitsiz uchta so'rovning uchalasi rad etildi. Tavsiflar beshala jadvalda bir xil. 72 savoldan 71 tasida model aynan o'sha chiqishni berdi, va 72 tasining hammasida darvoza va validator bir xil qaror qildi. Farq qilgan bitta savol:

python8 qator
qism = "\n".join(natija).split("real_retail:")[1]
ilova_javobi = next(q for q in qism.splitlines() if q.startswith("  #11 "))
savol = next(s for s in oqi("accuracy/suites/real_retail/fresh2.json") if s["id"] == 11)
benchmark = next(j for j in map(json.loads, open(TEZ / "results/win8f2_cpu_real_retail.jsonl", encoding="utf-8")) if j["id"] == 11)
print("savol:      ", savol["text"])
print("kutilgan:   ", json.dumps(savol["expect"][0], ensure_ascii=False))
print("benchmark:  ", benchmark["output"], "->", benchmark["reason"])
print("ilova:      ", ilova_javobi.strip())
Chiqish
savol:       line chart of quantity by date
kutilgan:    {"steps": [{"tool": "make_chart", "args": {"chart": "line", "x": "InvoiceDate", "y": "Quantity", "agg": "sum"}}]}
benchmark:   {"steps":[{"tool":"make_chart","args":{"chart":"line","x":"InvoiceDate","y":"Quantity","agg":"none"}}]} -> different result
ilova:       #11 result: "1. Har bir InvoiceDate uchun Quantity bo'yicha jami ko'rsatilgan chiziqli diagramma chizil"

Benchmark da model kunlar bo'yicha xom nuqtalarni chizgan (agg: none) — noto'g'ri. Engine orqali u yig'indini tanladi — kutilgan javob. Bu safar farq foydali tomonga chiqdi, lekin buni yutuq deb hisoblamaymiz: sababi tekshirilmagan (ehtimol, prompt keshining holati ikki o'tishda boshqacha bo'lgan), va keyingi safar u teskari tomonga ham chiqishi mumkin. Bu — 29-bobdagi Windows va WSL farqi bilan bir turdagi shovqin.

Bankka: internetsiz papka

Bankda dastur internetdan hech narsa yuklab olmasligi kerak — na o'rnatishda, na ishlashda. Ko'p bank kompyuterlari tarmoqdan umuman uzilgan bo'lishi ham mumkin. Shuning uchun Tez bitta papka sifatida yetkaziladi (USB yoki ichki tarmoq): o'rnatuvchi, model fayli va o'rnatish skripti. Skript modelni foydalanuvchi papkasiga ko'chiradi va xeshini tekshiradi. Bu va oldingi qarorlar loyihaning qarorlar jurnalida:

python4 qator
for qator in (TEZ.parent.parent / "10-decisions-and-open-questions.md").read_text(encoding="utf-8").splitlines():
    if any(qator.startswith(f"| D-{n} |") for n in (18, 19, 20, 21)):
        katak = [k.strip() for k in qator.strip("|").split("|")]
        print(f"{katak[0]} ({katak[1]}): {katak[2]}\n")
Chiqish
D-18 (2026-11-03): Desktop window (Tauri) talks to the Python engine over stdin/stdout JSON lines, not FastAPI + WebSocket

D-19 (2026-11-03): Explanations and clarify messages follow one language chosen in settings, no per-question detection

D-20 (2026-11-03): Model file lives in the user folder (%LOCALAPPDATA%\Tez\models, macOS Application Support, Linux ~/.local/share/tez), checked by SHA-256

D-21 (2026-11-03): Tez ships as one offline folder (USB stick or network share): the app installer, the GGUF and one install script that copies the model and checks its hash. Every runtime is inside: Python engine with DuckDB and calamine, llama-server, and the WebView2 runtime. Nothing is downloaded at install or at run time

Model o'rnatuvchining ichida emas, yonida. Bizning bilishimizcha, keng tarqalgan o'rnatuvchi formatlari taxminan 2 GB lik chegaraga ega, model fayli esa ikki yarim gigabaytdan katta (28-bob). Bu chegara hali tekshirilmagan: u o'rnatuvchi qurilganda o'lchanadi, va agar model ichiga sig'sa — papka bitta faylga qisqaradi.

Bank xavfsizlik bo'limi, ehtimol, yana ikki narsani so'raydi: dasturning raqamli imzosi (imzosiz .exe fayllarni ko'p bank sozlamalari bloklaydi) va ichidagi hamma qismlarning ro'yxati — versiyalari, litsenziyalari va xeshlari bilan. Ikkalasi ham hali yo'q.

Hali yo'q

Qism Holat
Engine: fayl ochish, savol, natija, tushuntirish Bor, sinalgan
Model serveri: xesh, tasodifiy port va kalit, --offline Bor, Windows da o'lchangan
Pipe: oyna yopilsa server to'xtaydi Bor, sinalgan
Oyna (Tauri): fayl tashlash, oldindan ko'rish, javob Yo'q — keyingi qadam
Engine majburan o'ldirilsa ham serverni yopish Yo'q — o'rnatuvchi bilan
Internetsiz o'rnatuvchi papkasi va skript Yo'q — shakli tanlangan
Tarmoq to'liq yopiq holdagi sinov Yo'q
Haqiqiy brauzerdan kalit sinovi Yo'q
Tushuntirish tili Sozlamada bitta til; savol tilini aniqlash yo'q

Xulosa

  • Ilovaning engine qismi qurildi: u benchmark funksiyalarining o'zini chaqiradi — loader, tavsifchi, prompt, validator va darvoza.
  • Oltita real jadvalda — to'rtta CSV va ikkita asl Excel fayl — ilova yozgan tavsif benchmark dagisi bilan bir xil.
  • Model serveri har ishga tushishda xesh bilan tekshiriladi, tasodifiy port va yangi kalit oladi; kalitsiz faqat /health javob beradi.
  • Oyna engine bilan pipe orqali gaplashadi — ikkinchi port yo'q; oyna yopilsa, model serveri ham to'xtaydi.
  • Windows da 72 ko'rilmagan savoldan 71 tasida engine benchmark bilan aynan bir xil chiqish berdi, darvoza va validator qarorlari 72 tasida ham bir xil.
  • Bankka Tez internetsiz papka bo'lib boradi. Oyna, o'rnatuvchi va tarmoqsiz sinov — keyingi qadamlar.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar