File size: 11,435 Bytes
00ee773
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
"""Benchmark suites, built once and frozen to bench/data/suites.jsonl.

The "jev.*" and "app.*" suites reproduce Laya's research/scripts/bench_apps.py build()
case-for-case (same datasets, same filtering, same order, same seed 13, N=400), so Laya
and Nirnay answer byte-identical states and questions. "extra.*" suites are additional
held-out tasks (neither model trains on them):
  extra.sst5       ordinal 5-level sentiment (the `score` type; Laya's documented weak spot)
  extra.sms_spam   SMS spam (an independent Jev figure exists: 0.930)

  python bench\\suites.py
"""
import json
import os
import random
import sys

from datasets import load_dataset

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "data", "suites.jsonl")
SEED = 13
N = int(os.environ.get("BENCH_N", "400"))

SUITES = []


def register(name, cases, gold, **meta):
    for i, ((state, qs), g) in enumerate(zip(cases, gold)):
        SUITES.append({"suite": name, "i": i, "state": state, "questions": qs, "gold": g, "meta": meta})
    print("   %-28s %4d cases  %s" % (name, len(cases), meta.get("note", "")), flush=True)


def choice_q(qid, instructions, keys, gold_key):
    crit = {k: None for k in keys}
    return {qid: {"type": "choice", "instructions": instructions, "criteria": crit}}, keys.index(gold_key)


def email_state(subject, body):
    from laya import email_state as es  # the exact cleaning Laya's own benchmark applies
    return es(subject, body)


def build_laya_suites():
    rng = random.Random(SEED)

    d = load_dataset("fancyzhx/ag_news", split="test")
    crit = {"world": "world news and international politics", "sports": "sports",
            "business": "business and economy", "sci_tech": "science and technology"}
    keys = list(crit)
    cases, gold = [], []
    for r in list(d)[:N]:
        cases.append(({"article": r["text"]},
                      {"topic": {"type": "choice", "instructions": "What is the topic of `article`?",
                                 "criteria": dict(crit)}}))
        gold.append(keys.index(keys[int(r["label"])]))
    register("jev.ag_news", cases, gold, note="Jev 0.910", jev="ag_news", laya_in_training=True)

    d = load_dataset("dair-ai/emotion", "split", split="test")
    names = ["sadness", "joy", "love", "anger", "fear", "surprise"]
    cases, gold = [], []
    for r in list(d)[:N]:
        cases.append(({"text": r["text"]},
                      {"emotion": {"type": "choice",
                                   "instructions": "Which emotion is most strongly expressed in `text`?",
                                   "criteria": {n: None for n in names}}}))
        gold.append(int(r["label"]))
    register("jev.emotion", cases, gold, note="Jev 0.480", jev="emotion", laya_in_training=False)

    d = load_dataset("mteb/banking77", split="test")
    labels = sorted(set(d["label_text"]))
    cases, gold = [], []
    for r in list(d)[:N]:
        qs, gi = choice_q("intent", "Which banking intent does `message` express?",
                          [x.replace("_", " ") for x in labels], r["label_text"].replace("_", " "))
        cases.append(({"message": r["text"]}, qs))
        gold.append(gi)
    register("jev.banking77_full", cases, gold, note="%d labels | Jev 0.870 (72 labels)" % len(labels),
             jev="banking77", laya_in_training=False, n_labels=len(labels))

    d = load_dataset("Tobi-Bueck/customer-support-tickets", split="train")
    QUEUES = {"Technical Support": "technical problems, bugs, outages, integrations",
              "Product Support": "help using a product or feature",
              "Customer Service": "general account or service questions",
              "IT Support": "internal IT, devices, access, networks",
              "Billing and Payments": "invoices, charges, refunds, payment methods",
              "Returns and Exchanges": "returning or exchanging an item",
              "Service Outages and Maintenance": "downtime, outages, scheduled maintenance",
              "Sales and Pre-Sales": "pricing, quotes, buying",
              "Human Resources": "employment, payroll, leave, hiring",
              "General Inquiry": "anything else"}
    keys = list(QUEUES)
    cases, gold = [], []
    for r in d:
        if r.get("language") != "en" or r.get("queue") not in QUEUES or not r.get("body"):
            continue
        cases.append(({"subject": r["subject"] or "", "body": r["body"].replace("\\n", "\n")[:3000]},
                      {"queue": {"type": "choice", "instructions": "Which support queue should handle this ticket?",
                                 "criteria": dict(QUEUES)}}))
        gold.append(keys.index(r["queue"]))
        if len(cases) >= N:
            break
    register("app.support_triage", cases, gold, note="10-way queue routing", laya_in_training=True)

    d = load_dataset("SetFit/enron_spam", split="test")
    cases, gold = [], []
    for r in list(d)[:N]:
        st = email_state(r.get("subject") or "", (r.get("message") or "")[:3000])
        cases.append((st, {"is_spam": {"type": "noul", "instructions": "Is this email unsolicited spam or bulk marketing?"}}))
        gold.append(int(r["label"]))
    register("app.email_spam", cases, gold, note="enron spam", laya_in_training=True)

    d = load_dataset("zefang-liu/phishing-email-dataset", split="train")
    rows = [r for r in list(d)[:6000]
            if (r.get("Email Text") or "").strip() and r.get("Email Type") in ("Safe Email", "Phishing Email")]
    rng.shuffle(rows)
    cases, gold = [], []
    for r in rows[:N]:
        cases.append(({"email": r["Email Text"][:3000]},
                      {"is_phishing": {"type": "noul",
                                       "instructions": "Is this email a phishing or scam attempt to steal money, credentials, or personal data?",
                                       "criteria": {"true": "phishing, scam, or fraud",
                                                    "false": "a legitimate email (even if promotional)"}}}))
        gold.append(int(r["Email Type"] == "Phishing Email"))
    register("app.phishing", cases, gold, note="phishing emails", laya_in_training=True)

    d = load_dataset("lmsys/toxic-chat", "toxicchat0124", split="test")
    rows = [r for r in d if (r.get("user_input") or "").strip()]
    jb = [r for r in rows if int(r.get("jailbreaking", 0)) == 1][:N // 2]
    nj = [r for r in rows if int(r.get("jailbreaking", 0)) == 0][:N - len(jb)]
    mix = jb + nj
    rng.shuffle(mix)
    cases, gold = [], []
    for r in mix:
        cases.append(({"prompt": r["user_input"][:3000]},
                      {"jailbreak": {"type": "noul",
                                     "instructions": "Does `prompt` try to make an AI assistant ignore its rules, policies or system instructions?"}}))
        gold.append(int(r["jailbreaking"]))
    register("app.guardrails_jailbreak", cases, gold, note="toxic-chat jailbreaking (held out)", laya_in_training=False)
    tox = [r for r in rows if int(r.get("toxicity", 0)) == 1][:N // 2]
    ntox = [r for r in rows if int(r.get("toxicity", 0)) == 0][:N - len(tox)]
    mix2 = tox + ntox
    rng.shuffle(mix2)
    cases, gold = [], []
    for r in mix2:
        cases.append(({"post": r["user_input"][:3000]},
                      {"toxic": {"type": "noul",
                                 "instructions": "Is `post` toxic: rude, disrespectful or likely to make someone leave the discussion?"}}))
        gold.append(int(r["toxicity"]))
    register("app.moderation_toxicity", cases, gold, note="toxic-chat toxicity (held out)", laya_in_training=False)

    d = load_dataset("microsoft/ms_marco", "v1.1", split="validation")
    cases, gold = [], []
    for r in d:
        texts, sel = r["passages"]["passage_text"], r["passages"]["is_selected"]
        pos = [t for t, s in zip(texts, sel) if s == 1]
        neg = [t for t, s in zip(texts, sel) if s == 0]
        if not pos or not neg:
            continue
        take_pos = len(cases) % 2 == 0
        p = rng.choice(pos if take_pos else neg)
        cases.append(({"query": r["query"], "passage": p},
                      {"relevant": {"type": "noul", "instructions": "Does `passage` help answer `query`?"}}))
        gold.append(1 if take_pos else 0)
        if len(cases) >= N:
            break
    register("app.rag_relevance", cases, gold, note="MS MARCO relevance", laya_in_training=True)

    DOM = {"code": "software engineering, programming, refactoring, architecture, debugging",
           "math_or_logic": "mathematics, logic puzzles, proofs, complex calculation",
           "writing": "creative writing, essays, emails, blog posts, copywriting",
           "factual_lookup": "facts, definitions, trivia, history",
           "data_analysis": "statistics, SQL, data manipulation, metrics",
           "chitchat": "casual conversation, greetings, small talk"}
    keys = list(DOM)
    pool = []
    g = load_dataset("openai/gsm8k", "main", split="test")
    pool += [(r["question"], "math_or_logic") for r in list(g)[:N // 3]]
    m = load_dataset("google-research-datasets/mbpp", "full", split="test")
    pool += [(r["text"], "code") for r in list(m)[:N // 3]]
    t = load_dataset("fancyzhx/ag_news", split="test")
    pool += [(r["text"][:400], "factual_lookup") for r in list(t)[:N // 3]]
    rng.shuffle(pool)
    cases, gold = [], []
    for text, dom in pool[:N]:
        cases.append(({"request": text},
                      {"domain": {"type": "choice", "instructions": "What domain does `request` belong to?",
                                  "criteria": dict(DOM)}}))
        gold.append(keys.index(dom))
    register("app.model_routing_domain", cases, gold, note="gsm8k/mbpp/ag_news -> domain", laya_in_training=False)


def build_extra_suites():
    rng = random.Random(SEED + 1)
    d = load_dataset("SetFit/sst5", split="test")
    levels = ["very negative", "negative", "neutral", "positive", "very positive"]
    rows = list(d)
    rng.shuffle(rows)
    cases, gold = [], []
    for r in rows[:N]:
        cases.append(({"review": r["text"]},
                      {"sentiment": {"type": "score", "instructions": "How positive is the sentiment of `review`?",
                                     "criteria": list(levels)}}))
        gold.append(int(r["label"]))
    register("extra.sst5", cases, gold, note="ordinal 5-level (score type, held out)", laya_in_training=False)

    d = load_dataset("ucirvine/sms_spam", split="train")
    rows = list(d)
    rng.shuffle(rows)
    spam = [r for r in rows if int(r["label"]) == 1][:N // 2]
    ham = [r for r in rows if int(r["label"]) == 0][:N - len(spam)]
    mix = spam + ham
    rng.shuffle(mix)
    cases, gold = [], []
    for r in mix:
        cases.append(({"sms": r["sms"].strip()},
                      {"is_spam": {"type": "noul", "instructions": "Is `sms` spam?"}}))
        gold.append(int(r["label"]))
    register("extra.sms_spam", cases, gold, note="SMS spam (held out) | independent Jev 0.930", laya_in_training=False)


def main():
    os.makedirs(os.path.dirname(OUT), exist_ok=True)
    print("=== building suites (N=%d) ===" % N, flush=True)
    build_laya_suites()
    build_extra_suites()
    with open(OUT, "w", encoding="utf-8") as f:
        for r in SUITES:
            f.write(json.dumps(r, ensure_ascii=False) + "\n")
    print("wrote %d cases -> %s" % (len(SUITES), OUT))


if __name__ == "__main__":
    sys.exit(main())