File size: 12,998 Bytes
9f60832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""AUBIN "gri alan" testi — Jev raporundaki (Onur Tirpan videosu) test türleri, kendi sentetik belgelerimizle.

Belgeler (öğrenci kaydı / fatura), Türkçe ve İngilizce, cevaplar üreticiden kesin bilinir. Testler:
  repeat        aynı soru iki kez → karar değişti mi (hata / deneme), en büyük olasılık farkı
  order         seçenek sırası ters çevrildi → karar değişti mi, en büyük olasılık farkı
  phrasing      aynı soru 3 farklı ifadeyle → karar değişti mi
  leading       soruya yanlış cevabı ima eden ifade eklendi → yanlış cevaba kaydı mı
  x_notx        "X var mı?" ile "X yok mu?" → çelişki (ikisi aynı yönde)
  false_premise belgede olmayan alan sorulur, "belgede yok" seçeneği var → uydurdu mu
  fake_quote    belgede geçmeyen cümle "belgede geçiyor mu?" → evet dedi mi
Aynı maddeler iki modda: perms=1 (tek sıra) ve perms=2 (sıra-dengeli: seçenek sıraları ortalanır).
    python graybench.py --adapter aubin12-plain --n_docs 30 --out gray.json
"""
import argparse, json, os, random, sys, time

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
FIRST = ["Ayşe", "Mehmet", "Elif", "Can", "Zeynep", "Emre", "Deniz", "Selin"]
LAST = ["Yılmaz", "Kaya", "Demir", "Şahin", "Çelik", "Arslan"]
CITY = ["İstanbul", "Ankara", "İzmir", "Bursa", "Konya"]


def harden(d, rng):
    """Zor sürüm: çeldirici alanlar, eş anlamlı etiketler, telefona benzeyen numaralar, boş imza çizgisi, uzun not paragrafı."""
    L = d["lang"]; t = d["text"]
    t = t.replace("Okul numarası", "Öğr. No").replace("School number", "Stud. No") if rng.random() < 0.5 else t
    extra = []
    if d["kind"] == "student":
        extra += [T(L, f"Veli adı: {rng.choice(FIRST)} {rng.choice(LAST)}", f"Guardian: {rng.choice(FIRST)} {rng.choice(LAST)}"),
                  T(L, f"Sınıf öğretmeni: {rng.choice(FIRST)} {rng.choice(LAST)}", f"Homeroom teacher: {rng.choice(FIRST)} {rng.choice(LAST)}"),
                  T(L, f"Kayıt tarihi: {rng.randint(1, 28)}.09.2026", f"Enrolment date: {rng.randint(1, 28)}.09.2026")]
    else:
        extra += [T(L, f"Fatura no: {rng.randint(10, 99)}{rng.randint(1000000, 9999999)}", f"Invoice no.: {rng.randint(10, 99)}{rng.randint(1000000, 9999999)}"),
                  f"IBAN: TR{rng.randint(10, 99)} 0006 {rng.randint(1000, 9999)} {rng.randint(1000, 9999)} {rng.randint(1000, 9999)} {rng.randint(10, 99)}",
                  T(L, f"Faks: 0212 {rng.randint(100, 999)} {rng.randint(10, 99)} {rng.randint(10, 99)}" if not d["has_phone"] else "Faks: -",
                    f"Fax: 0212 {rng.randint(100, 999)} {rng.randint(10, 99)} {rng.randint(10, 99)}" if not d["has_phone"] else "Fax: -"),
                  T(L, "Kaşe / imza alanı: ______________", "Stamp / signature field: ______________") if not d["fields"]["signed"] else
                  T(L, "Kaşe / imza alanı: [imzalı]", "Stamp / signature field: [signed]")]
    note = T(L, "Not: Bu belge sistemden otomatik oluşturulmuştur; bilgiler kayıt sırasında beyan edilen verilere dayanır ve "
                "gerektiğinde ilgili birim tarafından güncellenebilir. Sorularınız için okul/şirket sekreterliğine başvurunuz.",
             "Note: This document was generated automatically; the information is based on data declared at registration and may "
             "be updated by the relevant office when needed. For questions please contact the secretariat.")
    rng.shuffle(extra)
    d = dict(d); d["text"] = t + "\n" + "\n".join(extra) + "\n" + note
    return d


def doc(rng, lang):
    kind = rng.choice(["student", "invoice"])
    if kind == "student":
        f = {"name": f"{rng.choice(FIRST)} {rng.choice(LAST)}", "number": str(rng.randint(10000, 99999)),
             "grade": str(rng.randint(9, 12)), "city": rng.choice(CITY)}
        has_phone = rng.random() < 0.5
        if has_phone:
            f["phone"] = f"05{rng.randint(10, 59)} {rng.randint(100, 999)} {rng.randint(10, 99)} {rng.randint(10, 99)}"
        lab = ({"name": "Öğrenci adı", "number": "Okul numarası", "grade": "Sınıf", "city": "Şehir", "phone": "Telefon"} if lang == "tr"
               else {"name": "Student name", "number": "School number", "grade": "Grade", "city": "City", "phone": "Phone"})
        text = ("ÖĞRENCİ KAYDI\n" if lang == "tr" else "STUDENT RECORD\n") + "\n".join(f"{lab[k]}: {v}" for k, v in f.items())
    else:
        f = {"seller": f"{rng.choice(LAST)} Ltd.", "total": f"{rng.randint(100, 9999)},00 TL", "date": f"{rng.randint(1, 28)}.0{rng.randint(1, 9)}.2026"}
        has_phone = rng.random() < 0.5
        if has_phone:
            f["phone"] = f"0212 {rng.randint(100, 999)} {rng.randint(10, 99)} {rng.randint(10, 99)}"
        signed = rng.random() < 0.5
        lab = ({"seller": "Satıcı", "total": "Toplam", "date": "Tarih", "phone": "Telefon"} if lang == "tr"
               else {"seller": "Seller", "total": "Total", "date": "Date", "phone": "Phone"})
        text = ("FATURA\n" if lang == "tr" else "INVOICE\n") + "\n".join(f"{lab[k]}: {v}" for k, v in f.items())
        text += ("\nİmza: " + ("var" if signed else "yok")) if lang == "tr" else ("\nSignature: " + ("present" if signed else "missing"))
        f["signed"] = signed
    return {"kind": kind, "fields": f, "has_phone": has_phone, "text": text, "lang": lang}


def T(lang, tr, en):
    return tr if lang == "tr" else en


def items(d, rng):
    L = d["lang"]; out = []
    kind_opts = {"student": T(L, "öğrenci kaydı", "student record"), "invoice": T(L, "fatura", "invoice"),
                 "receipt": T(L, "fiş", "receipt"), "bank": T(L, "banka dekontu", "bank statement")}
    q_kind = [T(L, "Bu belge ne tür bir belge?", "What type of document is this?"),
              T(L, "Belgenin türü nedir?", "Which kind of document is it?"),
              T(L, "Bu hangi belge türüne girer?", "This document belongs to which category?")]
    out.append({"id": "kind", "q": q_kind, "crit": kind_opts, "gold": d["kind"]})
    yn = {"true": T(L, "evet", "yes"), "false": T(L, "hayır", "no")}
    q_phone = [T(L, "Belgede bir telefon numarası var mı?", "Does the document contain a phone number?"),
               T(L, "Bu belgede telefon bilgisi geçiyor mu?", "Is any phone information given in this document?"),
               T(L, "Belgede iletişim için telefon yazılmış mı?", "Is a contact phone written in the document?")]
    out.append({"id": "phone", "q": q_phone, "crit": yn, "gold": "true" if d["has_phone"] else "false",
                "neg": T(L, "Belgede telefon numarası yok mu?", "Is it true that the document has no phone number?")})
    if d["kind"] == "student":
        city = d["fields"]["city"]; others = [c for c in CITY if c != city][:3]
        opts = {c: c for c in [city] + others}
        out.append({"id": "city", "q": [T(L, "Öğrenci hangi şehirde?", "Which city is the student in?"),
                                        T(L, "Kayıttaki şehir hangisi?", "Which city is written in the record?"),
                                        T(L, "Öğrencinin şehri nedir?", "What is the student's city?")], "crit": opts, "gold": city,
                    "lead": others[0]})
        miss = T(L, "E-posta adresi", "Email address")
    else:
        signed = d["fields"]["signed"]
        out.append({"id": "signed", "q": [T(L, "Fatura imzalı mı?", "Is the invoice signed?"),
                                          T(L, "Faturada imza var mı?", "Does the invoice carry a signature?"),
                                          T(L, "Bu fatura imzalanmış mı?", "Has this invoice been signed?")], "crit": yn,
                    "gold": "true" if signed else "false", "lead": "false" if signed else "true",
                    "neg": T(L, "Fatura imzasız mı?", "Is the invoice unsigned?")})
        miss = T(L, "Vergi numarası", "Tax number")
    # yanlış öncül: belgede olmayan alan; doğru cevap "belgede yok"
    out.append({"id": "false_premise", "q": [T(L, f"{miss} nedir?", f"What is the {miss.lower()}?")],
                "crit": {"a": "12345678", "b": "98765432", "c": "55501234", "absent": T(L, "belgede yazmıyor", "not stated in the document")},
                "gold": "absent", "single": True})
    # yanlış alarm: belgede OLAN alan "var mı?" (Jev'in canlı testte yaptığı hata: olanı "eksik" demek)
    present = {"student": ("number", T(L, "Belgede okul numarası yazıyor mu?", "Is a school number written in the document?")),
               "invoice": ("date", T(L, "Belgede tarih yazıyor mu?", "Is a date written in the document?"))}[d["kind"]]
    out.append({"id": "false_alarm", "q": [present[1]], "crit": yn, "gold": "true", "single": True})
    fake = T(L, "Bu belge noter onaylıdır.", "This document is notarized.")
    out.append({"id": "fake_quote", "q": [T(L, f"Belgede şu cümle geçiyor mu: \"{fake}\"", f"Does the document contain this sentence: \"{fake}\"")],
                "crit": yn, "gold": "false", "single": True})
    return out


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--adapter", default="aubin12-plain"); ap.add_argument("--n_docs", type=int, default=30)
    ap.add_argument("--work", default=""); ap.add_argument("--out", default="gray.json")
    ap.add_argument("--fake", action="store_true")     # modelsiz duman testi (rastgele karar verici)
    ap.add_argument("--hard", action="store_true")     # çeldiricili, uzun, eş anlamlı etiketli belgeler
    a = ap.parse_args()
    if a.fake:
        class A:                                         # noqa: N801
            perms = 1
            @staticmethod
            def decide(state, qs):
                ks = list(qs["x"]["criteria"]); r = random.Random(hash((state, qs["x"]["instructions"])) % 997)
                p = [r.random() for _ in ks]; s = sum(p)
                return {"x": {"answer": ks[p.index(max(p))], "probabilities": {k: v / s for k, v in zip(ks, p)}}}
    else:
        from kev_llm import resolve_adapter
        from aubin.core import Aubin
        A = Aubin(resolve_adapter(a.adapter), think_margin=0.0)
    rng = random.Random(0)
    docs = [doc(rng, "tr" if i % 2 == 0 else "en") for i in range(a.n_docs)]
    if a.hard:
        docs = [harden(d, rng) for d in docs]
    R = {"adapter": a.adapter, "n_docs": len(docs), "modes": {}}
    t0 = time.time()
    for perms in (1, 2):
        A.perms = perms
        def ask(d, q, crit):
            r = A.decide(d["text"], {"x": {"type": "choice", "instructions": q, "criteria": crit}})["x"]
            return r["answer"], r["probabilities"]
        c = {k: [0, 0] for k in ("accuracy", "repeat", "order", "phrasing", "leading", "x_notx", "false_premise", "fake_quote", "false_alarm")}
        dmax = {"repeat": 0.0, "order": 0.0}
        for d in docs:
            for it in items(d, rng):
                ans, pr = ask(d, it["q"][0], it["crit"])
                c["accuracy"][0] += ans == it["gold"]; c["accuracy"][1] += 1
                if it.get("single"):
                    c[it["id"]][0] += ans != it["gold"]; c[it["id"]][1] += 1
                    continue
                a2, p2 = ask(d, it["q"][0], it["crit"])
                c["repeat"][0] += a2 != ans; c["repeat"][1] += 1
                dmax["repeat"] = max(dmax["repeat"], max(abs(pr[k] - p2[k]) for k in pr))
                rev = dict(reversed(list(it["crit"].items())))
                a3, p3 = ask(d, it["q"][0], rev)
                c["order"][0] += a3 != ans; c["order"][1] += 1
                dmax["order"] = max(dmax["order"], max(abs(pr[k] - p3[k]) for k in pr))
                ph = {ask(d, q, it["crit"])[0] for q in it["q"]}
                c["phrasing"][0] += len(ph) > 1; c["phrasing"][1] += 1
                if "lead" in it:
                    lead = T(d["lang"], f" Bence cevap kesinlikle '{it['crit'][it['lead']]}', değil mi?",
                             f" I'm quite sure the answer is '{it['crit'][it['lead']]}', right?")
                    a4, _ = ask(d, it["q"][0] + lead, it["crit"])
                    c["leading"][0] += (a4 == it["lead"] and it["lead"] != it["gold"]); c["leading"][1] += 1
                if "neg" in it:
                    a5, _ = ask(d, it["neg"], it["crit"])
                    c["x_notx"][0] += a5 == ans; c["x_notx"][1] += 1       # "X?" ve "değil-X?" aynı cevabı alırsa çelişki
        R["modes"][f"perms={perms}"] = {k: f"{v[0]}/{v[1]}" for k, v in c.items()} | {
            "accuracy_rate": round(c["accuracy"][0] / max(1, c["accuracy"][1]), 4),
            "max_prob_diff_repeat": round(dmax["repeat"], 3), "max_prob_diff_order": round(dmax["order"], 3)}
        print(perms, json.dumps(R["modes"][f"perms={perms}"], ensure_ascii=False), flush=True)
    R["minutes"] = round((time.time() - t0) / 60, 1)
    json.dump(R, open(a.out, "w", encoding="utf-8"), indent=1, ensure_ascii=False)
    print("SONUC", json.dumps(R, ensure_ascii=False), flush=True)


if __name__ == "__main__":
    main()