"""LLM deneyi: makaledeki hakem eleştirilerini kapatmak için tek komutluk, tekrarlanabilir protokol. Çalıştırılan koşullar (aynı TR-TabloSQL soruları, aynı veritabanları, sıcaklık 0, hibrit geri düşme kapalı): _tr Türkçe normalizasyon + statik denetim + geri beslemeli döngü (K = 3) -> ana sonuç _tr_K1 aynı, döngü kapalı (tek deneme) -> döngünün katkısı _tr_statiksiz aynı, statik denetim kapalı (yalnızca yürütme/sonuç geri beslemesi) -> statik denetimin katkısı _metin sayılar metin olarak saklanmış veritabanı -> LLM metni kurtarabilir mi? _en İngilizce yerel ayar varsayımlı veritabanı -> geri döndürülemez kayıp stres testi pilot/belgeler (görülmemiş düzenler), cevap düzeyinde değerlendirme Kullanım (model adını sabit sürüm olarak verin; 'auto' yalnızca --auto-izin ile): python scripts/llm_deneyi.py --saglayici claude --model claude-sonnet-4-5-20250929 --kuru # maliyet tahmini python scripts/llm_deneyi.py --saglayici claude --model claude-sonnet-4-5-20250929 python scripts/llm_deneyi.py --saglayici openai --model gpt-4o-2024-08-06 python scripts/llm_deneyi.py --saglayici ollama --model qwen2.5-coder:7b # yerel, ücretsiz Anahtarlar .env dosyasından ya da ortam değişkenlerinden okunur (ANTHROPIC_API_KEY, OPENAI_API_KEY). Çıktılar: outputs/llm//ozet.json, rapor.md, sonuclar_*.jsonl, stres/pilot_sonuclari.json """ from __future__ import annotations import argparse import json import re import subprocess import sys import time import warnings from math import comb from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) warnings.filterwarnings("ignore") from t2s.errors import ErrorLog # noqa: E402 from t2s.evaluate import SystemSpec, evaluate_system, load_benchmark # noqa: E402 from t2s.llm import USAGE, load_dotenv, make_backend # noqa: E402 from t2s.pipeline import build_database, build_reference_db # noqa: E402 DB = ROOT / "outputs" / "db" def wilson(k, n, z=1.96): if not n: return None p = k / n d = 1 + z * z / n c = (p + z * z / (2 * n)) / d h = z * ((p * (1 - p) / n + z * z / (4 * n * n)) ** 0.5) / d return [round(100 * (c - h), 1), round(100 * (c + h), 1)] def mcnemar(b, c): n = b + c if n == 0: return 1.0 k = min(b, c) return min(1.0, 2 * sum(comb(n, i) for i in range(k + 1)) / 2 ** n) def load_recs(path: Path) -> dict: return {r["id"]: r for r in (json.loads(l) for l in path.read_text(encoding="utf-8").splitlines() if l.strip())} def compare(a: dict, b: dict): ids = sorted(set(a) & set(b)) bb = sum(1 for i in ids if a[i]["dogru"] and not b[i]["dogru"]) cc = sum(1 for i in ids if b[i]["dogru"] and not a[i]["dogru"]) return bb, cc, mcnemar(bb, cc) def dry_run(items, n_systems): from t2s.llm import SYSTEM_PROMPT, build_user_prompt from t2s.schema import connect, describe_schema con = connect(DB / "sistem_tr.db", readonly=True) schema = describe_schema(con) chars = sum(len(SYSTEM_PROMPT) + len(build_user_prompt(it["soru"], schema, it.get("kanit"))) for it in items) con.close() tok_in = chars / 3.2 * 1.35 * n_systems # ~3,2 karakter/token (Türkçe), ortalama 1,35 deneme tok_out = len(items) * 220 * 1.35 * n_systems return int(tok_in), int(tok_out) def main(): ap = argparse.ArgumentParser() ap.add_argument("--saglayici", required=True, choices=["claude", "openai", "ollama"]) ap.add_argument("--model", required=True, help="sabit model sürümü (ör. claude-sonnet-4-5-20250929)") ap.add_argument("--anahtar", default=None) ap.add_argument("--taban-url", default=None) ap.add_argument("--etiket", default=None, help="çıktı klasörü adı (varsayılan: sağlayıcı_model)") ap.add_argument("--yalniz", choices=["hepsi", "ana", "stres"], default="hepsi") ap.add_argument("--kuru", action="store_true", help="çağrı yapmadan token/maliyet tahmini") ap.add_argument("--auto-izin", action="store_true", help="model adı 'auto' olabilir (önerilmez)") args = ap.parse_args() load_dotenv(ROOT / ".env") if args.model == "auto" and not args.auto_izin: raise SystemExit("Tekrarlanabilirlik için sabit bir model sürümü verin (ör. claude-sonnet-4-5-20250929).") etiket = args.etiket or re.sub(r"[^A-Za-z0-9._-]+", "_", f"{args.saglayici}_{args.model}") out = ROOT / "outputs" / "llm" / etiket items = load_benchmark() for m in ("tr", "en", "text"): if not (DB / f"sistem_{m}.db").exists(): build_database(DB / f"sistem_{m}.db", mode=m) if not (DB / "referans.db").exists(): build_reference_db(DB / "referans.db") db_paths = {m: DB / f"sistem_{m}.db" for m in ("tr", "en", "text")} p = args.saglayici specs_def = [(f"{p}_tr", "tr", True, True), (f"{p}_tr_K1", "tr", False, True), (f"{p}_tr_statiksiz", "tr", True, False), (f"{p}_metin", "text", True, True), (f"{p}_en", "en", True, True)] if args.kuru: ti, to = dry_run(items, len(specs_def)) pilot_q = len(json.loads((ROOT / "pilot" / "sorular.json").read_text(encoding="utf-8"))["sorular"]) print(f"Tahmini çağrı sayısı : ~{int(len(items) * 1.35 * len(specs_def)) + int(pilot_q * 1.35)}") print(f"Tahmini giriş tokenı : ~{ti:,} (+ stres testi ~{int(ti / len(specs_def) * pilot_q / len(items)):,})") print(f"Tahmini çıkış tokenı : ~{to:,}") print("Ek cevap düzeyinde kümeler: stres kümesi (307 soru) ve ağır küme hızlı alt kümesi (200 soru) için") print(" yaklaşık 2,5 milyon giriş tokenı daha (tablo seçimi ve düzeltme denemeleriyle değişir).") print("Maliyet = giriş × (sağlayıcının giriş fiyatı) + çıkış × (çıkış fiyatı); Ollama ücretsizdir.") return out.mkdir(parents=True, exist_ok=True) # anahtar/adres verilmezse uygulamanın ayar dosyasından (Model bağlantıları) ya da .env'den okunur from t2s.settings import Settings cfg = Settings() key = args.anahtar or cfg.key(p) or None base = args.taban_url or cfg.base(p) backend = make_backend(p, args.model, key, base, device=cfg.conn("ollama").get("cihaz") if p == "ollama" else None) print("Bağlantı denemesi…", end=" ", flush=True) backend.chat("Kısa yanıt ver.", [{"role": "user", "content": "ping"}], max_tokens=5) print("tamam") meta = {"saglayici": p, "model": args.model, "sicaklik": 0.0, "K": 3, "hibrit_geri_dusme": False, "baslangic": time.strftime("%Y-%m-%dT%H:%M:%S"), "kosullar": {}} if args.yalniz in ("hepsi", "ana"): elog = ErrorLog(out / "hata_gunlugu.jsonl") for name, mode, sc, st in specs_def: t0 = time.time() spec = SystemSpec(name, backend, mode, self_correct=sc, static_check=st) s = evaluate_system(spec, items, db_paths, DB / "referans.db", out, elog, progress=lambda k, n, r: print(f"\r {name}: {k}/{n}", end="", flush=True)) n = s["n"] k = round(s["EX"] * n / 100) s["EX_ga95"] = wilson(k, n) s["sure_dk"] = round((time.time() - t0) / 60, 1) meta["kosullar"][name] = s print(f"\r {name:24} EX=%{s['EX']} test=%{s['bolum'].get('test')} ilk=%{s['EX_ilk_deneme']} " f"red=%{s['cevapsiz_dogru_red']} yanlış red=%{s['yanlis_red_orani']} ({s['sure_dk']} dk)") rule = ROOT / "outputs" / "sonuclar" / "sonuclar_kural_tr.jsonl" R = {name: load_recs(out / f"sonuclar_{name}.jsonl") for name, *_ in specs_def} pairs = [(f"{p}_tr", f"{p}_tr_K1", "geri beslemeli döngünün katkısı"), (f"{p}_tr", f"{p}_tr_statiksiz", "statik denetimin katkısı"), (f"{p}_tr", f"{p}_metin", "Türkçe normalizasyonun katkısı (LLM, metin kipine karşı)"), (f"{p}_tr", f"{p}_en", "Türkçe normalizasyonun katkısı (LLM, en kipine karşı)")] if rule.exists(): R["kural_tr"] = load_recs(rule) pairs.append((f"{p}_tr", "kural_tr", "LLM ve kural tabanlı üretici")) meta["mcnemar"] = [{"a": a, "b": b, "anlam": t, **dict(zip(("a_dogru_b_yanlis", "b_dogru_a_yanlis", "p"), compare(R[a], R[b])))} for a, b, t in pairs] if args.yalniz in ("hepsi", "stres"): flag = {"claude": "--claude", "openai": "--openai", "ollama": "--ollama"}[p] extra = (["--anahtar", args.anahtar] if args.anahtar else []) + \ (["--taban-url", args.taban_url] if args.taban_url else []) for kume, ad in (("pilot", "stres"), ("stres", "stres_kumesi"), ("agir_hizli", "agir_hizli")): cmd = [sys.executable, str(ROOT / "pilot" / "pilot_calistir.py"), "--kume", kume, flag, args.model, "--cikti", str(out / ad)] + extra print(f"Cevap düzeyinde küme: {kume}…") subprocess.run(cmd, check=True) meta[ad] = json.loads((out / ad / "sonuclar.json").read_text(encoding="utf-8"))["ozet"] meta[ad].pop("cikarim", None) meta["kullanim"] = {"cagri": USAGE.calls, "hata": USAGE.errors, "giris_token": USAGE.input_tokens, "cikis_token": USAGE.output_tokens} meta["bitis"] = time.strftime("%Y-%m-%dT%H:%M:%S") (out / "ozet.json").write_text(json.dumps(meta, ensure_ascii=False, indent=1, default=str), encoding="utf-8") L = [f"# LLM deneyi: {p} / {args.model}", "", f"Sıcaklık 0 · K = 3 · hibrit geri düşme kapalı · {meta['baslangic']}", ""] if meta["kosullar"]: L += ["| Koşul | EX (%95 GA) | Ayrılmış test | İlk deneme | Cevapsızda red | Yanlış red | Ort. deneme |", "|---|---|---|---|---|---|---|"] for n_, s in meta["kosullar"].items(): L.append(f"| {n_} | {s['EX']} ({s['EX_ga95'][0]}–{s['EX_ga95'][1]}) | {s['bolum'].get('test')} | " f"{s['EX_ilk_deneme']} | {s['cevapsiz_dogru_red']} | {s['yanlis_red_orani']} | {s['ort_deneme']} |") L += ["", "| Karşılaştırma | A doğru B yanlış | B doğru A yanlış | McNemar p |", "|---|---|---|---|"] for m in meta["mcnemar"]: L.append(f"| {m['a']} – {m['b']} ({m['anlam']}) | {m['a_dogru_b_yanlis']} | {m['b_dogru_a_yanlis']} | " f"{m['p']:.3g} |") for ad, baslik in (("stres", "Pilot (3 belge)"), ("stres_kumesi", "Stres kümesi (33 belge)"), ("agir_hizli", "Ağır küme, hızlı alt küme (tek veritabanı)")): if ad in meta: st = meta[ad] lv = st.get("seviyeye_gore") or {} lvs = " · ".join(f"{k} %{v['basari']}" for k, v in sorted(lv.items())) L += ["", f"**{baslik}:** genel %{st['basari']}, cevaplanabilir %{st['cevaplanabilir_basari']}, " f"cevapsızda red %{st['cevapsiz_dogru_red']}, yanlış red %{st['yanlis_red']}, " f"sessiz yanlış %{st.get('sessiz_yanlis')}" + (f" ({lvs})" if lvs else "")] u = meta["kullanim"] L += ["", f"Kullanım: {u['cagri']} çağrı, {u['giris_token']:,} giriş / {u['cikis_token']:,} çıkış tokenı, " f"{u['hata']} hata."] (out / "rapor.md").write_text("\n".join(L) + "\n", encoding="utf-8") print("\n".join(L)) print(f"\nÇıktılar: {out}") if __name__ == "__main__": main()