sys1bench 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sys1bench/__init__.py +3 -0
- sys1bench/adapters/__init__.py +19 -0
- sys1bench/adapters/base.py +159 -0
- sys1bench/adapters/embed_knn.py +61 -0
- sys1bench/adapters/encoder_finetuned.py +114 -0
- sys1bench/adapters/generic_http.py +161 -0
- sys1bench/adapters/hybrid_router.py +49 -0
- sys1bench/adapters/jev_openrouter.py +172 -0
- sys1bench/adapters/jev_typesafe.py +165 -0
- sys1bench/adapters/laya_local.py +192 -0
- sys1bench/adapters/llm_constrained.py +104 -0
- sys1bench/adapters/majority_prior.py +53 -0
- sys1bench/adapters/mock.py +77 -0
- sys1bench/adapters/nli_zeroshot.py +50 -0
- sys1bench/adapters/regex_keyword.py +70 -0
- sys1bench/analysis/__init__.py +4 -0
- sys1bench/analysis/arms.py +46 -0
- sys1bench/analysis/decision_value.py +43 -0
- sys1bench/analysis/decomposition.py +47 -0
- sys1bench/analysis/meta_eval.py +67 -0
- sys1bench/analysis/stats.py +76 -0
- sys1bench/cli.py +434 -0
- sys1bench/data/__init__.py +37 -0
- sys1bench/data/canary/canary.jsonl +200 -0
- sys1bench/data/framings/guardrail_intent.yaml +21 -0
- sys1bench/data/framings/log_triage.yaml +20 -0
- sys1bench/data/framings/phishing_email.yaml +25 -0
- sys1bench/data/framings/policy_compliance.yaml +18 -0
- sys1bench/data/framings/support_tickets.yaml +41 -0
- sys1bench/framing/__init__.py +10 -0
- sys1bench/framing/expand.py +214 -0
- sys1bench/framing/perturb.py +67 -0
- sys1bench/generators/__init__.py +10 -0
- sys1bench/generators/base.py +105 -0
- sys1bench/generators/guardrail_intent.py +95 -0
- sys1bench/generators/log_triage.py +100 -0
- sys1bench/generators/multilingual_tickets.py +84 -0
- sys1bench/generators/phishing_email.py +127 -0
- sys1bench/generators/policy_compliance.py +90 -0
- sys1bench/generators/rag_relevance.py +122 -0
- sys1bench/generators/support_tickets.py +212 -0
- sys1bench/metrics/__init__.py +9 -0
- sys1bench/metrics/calibration.py +164 -0
- sys1bench/metrics/consistency.py +71 -0
- sys1bench/metrics/decision_value.py +43 -0
- sys1bench/metrics/efficiency.py +35 -0
- sys1bench/metrics/ordinal.py +80 -0
- sys1bench/metrics/robustness.py +46 -0
- sys1bench/metrics/selective.py +67 -0
- sys1bench/report/__init__.py +1 -0
- sys1bench/report/dashboard.py +177 -0
- sys1bench/report/latex.py +40 -0
- sys1bench/report/plots.py +143 -0
- sys1bench/report/results_doc.py +237 -0
- sys1bench/report/scorecard.py +170 -0
- sys1bench/runners/__init__.py +2 -0
- sys1bench/runners/benchmark_runner.py +149 -0
- sys1bench/runners/cache.py +53 -0
- sys1bench/runners/canary.py +43 -0
- sys1bench/runners/hybrid_sweep.py +37 -0
- sys1bench/runners/noul_consistency.py +121 -0
- sys1bench/runners/ordinal_probes.py +90 -0
- sys1bench/runners/robustness.py +112 -0
- sys1bench/runners/suite.py +183 -0
- sys1bench/runners/sweeps.py +190 -0
- sys1bench/schemas.py +274 -0
- sys1bench-0.3.0.dist-info/METADATA +195 -0
- sys1bench-0.3.0.dist-info/RECORD +72 -0
- sys1bench-0.3.0.dist-info/WHEEL +5 -0
- sys1bench-0.3.0.dist-info/entry_points.txt +2 -0
- sys1bench-0.3.0.dist-info/licenses/LICENSE +202 -0
- sys1bench-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Log triage generator: JSON log records with injected distractor keys.
|
|
2
|
+
|
|
3
|
+
severity (score 0..3) -> from the log level and error class, per a stated policy
|
|
4
|
+
owning_team (choice) -> from the service name (a mapping is given in the state as `team_directory`)
|
|
5
|
+
page_oncall (noul) -> policy: severity >= 2 AND the environment is production
|
|
6
|
+
The team directory lives in the state, so the model must read a lookup table rather than guess; the paging rule
|
|
7
|
+
combines two fields. Distractors are irrelevant keys with plausible values.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
|
|
15
|
+
from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
|
|
16
|
+
|
|
17
|
+
SERVICES = {"checkout-api": "payments", "ledger": "payments", "search-indexer": "discovery", "ranker": "discovery", "auth-gateway": "identity",
|
|
18
|
+
"session-store": "identity", "notify-worker": "messaging", "mailer": "messaging", "cdn-edge": "platform", "k8s-autoscaler": "platform"}
|
|
19
|
+
TEAMS = [("payments", "Checkout, ledger, refunds"), ("discovery", "Search and ranking"), ("identity", "Auth, sessions, SSO"),
|
|
20
|
+
("messaging", "Email, push, in-app notifications"), ("platform", "Infra, CDN, orchestration")]
|
|
21
|
+
# (level, error class, base severity, message)
|
|
22
|
+
EVENTS = [
|
|
23
|
+
("INFO", "deploy", 0, "deployment {ver} rolled out to {pct}% of pods"),
|
|
24
|
+
("INFO", "cache", 0, "cache warmup complete in {ms} ms"),
|
|
25
|
+
("WARN", "latency", 1, "p99 latency {ms} ms exceeds SLO of 800 ms for {n} minutes"),
|
|
26
|
+
("WARN", "retry", 1, "upstream {dep} returned 503; retry {n}/5 succeeded"),
|
|
27
|
+
("ERROR", "exception", 2, "NullPointerException in {fn} handling request {rid}; {n} requests failed"),
|
|
28
|
+
("ERROR", "db", 2, "connection pool exhausted; {n} queries queued > 5 s"),
|
|
29
|
+
("CRITICAL", "outage", 3, "health check failing on all replicas for {n} minutes; traffic returning 5xx"),
|
|
30
|
+
("CRITICAL", "data", 3, "write path returned inconsistent results; {n} records may be lost"),
|
|
31
|
+
("ERROR", "auth", 2, "token validation failing for {pct}% of requests since {ver}"),
|
|
32
|
+
("WARN", "disk", 1, "disk usage at {pct}% on {n} nodes"),
|
|
33
|
+
]
|
|
34
|
+
ENVS = ["production", "production", "staging", "canary"]
|
|
35
|
+
DISTRACTORS = {"trace_sampling": "0.1", "build_sha": "a3f9c21", "region": "eu-central-1", "feature_flags": ["new-ranker", "dark-mode"],
|
|
36
|
+
"previous_incident": "INC-2041 (resolved)", "runbook_url": "https://runbooks.internal/generic", "request_headers": {"x-ab": "B"}}
|
|
37
|
+
SEV = [Level(level=0, description="Informational; no action."), Level(level=1, description="Degraded; investigate within the day."),
|
|
38
|
+
Level(level=2, description="Errors affecting users; fix within hours."), Level(level=3, description="Outage or data loss; all hands now.")]
|
|
39
|
+
SEV_INSTR = "Severity 0-3 from the log record. Policy: INFO=0, WARN=1, ERROR=2, CRITICAL=3."
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@register_generator("log_triage")
|
|
43
|
+
class LogTriageGenerator(BaseGenerator):
|
|
44
|
+
version = "1.0.0"
|
|
45
|
+
|
|
46
|
+
def generate(self) -> list[TaskItem]:
|
|
47
|
+
kb = self.knobs
|
|
48
|
+
rng, ctl = self.rng("items"), self.rng("controls")
|
|
49
|
+
items = []
|
|
50
|
+
for i in range(kb.n):
|
|
51
|
+
distract = ctl.random() < kb.distractor_density
|
|
52
|
+
noise_draw = ctl.random() < kb.label_noise
|
|
53
|
+
service = rng.choice(list(SERVICES))
|
|
54
|
+
level, cls, sev, msg = rng.choice(EVENTS)
|
|
55
|
+
env = rng.choice(ENVS)
|
|
56
|
+
msg = msg.format(ver=f"v{rng.randrange(2, 9)}.{rng.randrange(0, 30)}", pct=rng.randrange(5, 100), ms=rng.randrange(120, 4000),
|
|
57
|
+
n=rng.randrange(1, 60), dep=rng.choice(list(SERVICES)), fn=rng.choice(["chargeCard", "buildIndex", "verifyToken", "renderEmail"]),
|
|
58
|
+
rid=f"req-{rng.randrange(10**5, 10**6)}")
|
|
59
|
+
record = {"ts": f"2026-09-{rng.randrange(1, 28):02d}T{rng.randrange(0, 24):02d}:{rng.randrange(0, 60):02d}:00Z", "level": level, "service": service,
|
|
60
|
+
"env": env, "message": msg, "error_class": cls}
|
|
61
|
+
if distract:
|
|
62
|
+
for k in ctl.sample(list(DISTRACTORS), 3):
|
|
63
|
+
record[k] = DISTRACTORS[k]
|
|
64
|
+
state = {"log": record, "team_directory": SERVICES,
|
|
65
|
+
"paging_policy": "Page the on-call engineer only when severity is 2 or higher AND env is production."}
|
|
66
|
+
if kb.target_tokens:
|
|
67
|
+
state["context"] = pad_to_tokens(ctl, "", kb.target_tokens)
|
|
68
|
+
team = SERVICES[service]
|
|
69
|
+
page = sev >= 2 and env == "production"
|
|
70
|
+
noise = False
|
|
71
|
+
if kb.label_noise and noise_draw:
|
|
72
|
+
noise = True
|
|
73
|
+
team = ctl.choice([t for t, _ in TEAMS if t != team])
|
|
74
|
+
sev = ctl.choice([x for x in range(4) if x != sev])
|
|
75
|
+
page = not page
|
|
76
|
+
tid = f"logs_{kb.seed}_{i:05d}"
|
|
77
|
+
items.append(TaskItem(
|
|
78
|
+
task_id=tid, tier="G", domain="log_triage", language=kb.language, state=state,
|
|
79
|
+
state_tokens=estimate_tokens(json.dumps(state)),
|
|
80
|
+
questions={
|
|
81
|
+
"severity": Question(type="score", instructions=SEV_INSTR, criteria=SEV, ground_truth=sev, framing_group="logs.severity"),
|
|
82
|
+
"owning_team": Question(type="choice", instructions="Which team owns the service in `log.service`? Use `team_directory`.",
|
|
83
|
+
criteria=[Option(key=k, description=d) for k, d in TEAMS], ground_truth=team, framing_group="logs.owning_team"),
|
|
84
|
+
"page_oncall": Question(type="noul", instructions="Per `paging_policy`, should the on-call engineer be paged for this record?",
|
|
85
|
+
ground_truth=page, framing_group="logs.page_oncall"),
|
|
86
|
+
},
|
|
87
|
+
label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
|
|
88
|
+
controls=Controls(distractor_density=kb.distractor_density), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
|
|
89
|
+
metadata={"level": level, "env": env, "service": service},
|
|
90
|
+
))
|
|
91
|
+
return items
|
|
92
|
+
|
|
93
|
+
def regex_rules(self) -> dict:
|
|
94
|
+
return {"logs.severity": {"0": [r'"level": "INFO"'], "1": [r'"level": "WARN"'], "2": [r'"level": "ERROR"'], "3": [r'"level": "CRITICAL"']},
|
|
95
|
+
"logs.owning_team": {t: [rf'"service": "{s}"' for s, tt in SERVICES.items() if tt == t] for t, _ in TEAMS},
|
|
96
|
+
"logs.page_oncall": [r'"level": "(ERROR|CRITICAL)"[^}]*"env": "production"|"env": "production"[^}]*"level": "(ERROR|CRITICAL)"']}
|
|
97
|
+
|
|
98
|
+
def cost_matrices(self):
|
|
99
|
+
return {"page_oncall": {"true": {"true": 0.0, "false": 50.0}, "false": {"true": 5.0, "false": 0.0}},
|
|
100
|
+
"severity": {str(t): {str(p): float(abs(t - p) * (3 if p < t else 1)) for p in range(4)} for t in range(4)}}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Multilingual support tickets: the support_tickets task templated in German, Spanish, French and Hindi (Devanagari),
|
|
2
|
+
with English questions and criteria. Labels follow the same policy as the English generator. Used for Laya's language
|
|
3
|
+
router and for Jev's language behaviour; scripts other than Latin are the collapse case reported by Convai.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from ..schemas import Controls, LabelProvenance, Option, Question, TaskItem
|
|
9
|
+
from .base import BaseGenerator, canary_for, estimate_tokens, register_generator
|
|
10
|
+
from .support_tickets import PRIORITY_INSTRUCTIONS, PRIORITY_LEVELS, QUEUES
|
|
11
|
+
|
|
12
|
+
# queue, base urgency, {lang: template}
|
|
13
|
+
T = [
|
|
14
|
+
("billing", 1, {"de": "Mir wurde am {day} zweimal {amt} € für Bestellung #{order} abgebucht. Es darf nur eine Abbuchung geben.",
|
|
15
|
+
"es": "Me cobraron dos veces {amt} € el {day} por el pedido #{order}. Solo debería haber un cargo.",
|
|
16
|
+
"fr": "J'ai été débité deux fois de {amt} € le {day} pour la commande #{order}. Il ne devrait y avoir qu'un seul débit.",
|
|
17
|
+
"hi": "ऑर्डर #{order} के लिए {day} को मुझसे दो बार ₹{amt} लिए गए। केवल एक ही शुल्क होना चाहिए।"}),
|
|
18
|
+
("shipping", 1, {"de": "Bestellung #{order} gilt seit {day} als zugestellt, aber nichts ist angekommen.",
|
|
19
|
+
"es": "El pedido #{order} figura como entregado el {day}, pero no ha llegado nada.",
|
|
20
|
+
"fr": "La commande #{order} est indiquée livrée le {day}, mais rien n'est arrivé.",
|
|
21
|
+
"hi": "ऑर्डर #{order} {day} को डिलीवर दिखाया गया है, लेकिन कुछ भी नहीं आया।"}),
|
|
22
|
+
("account", 2, {"de": "Ich bin ausgesperrt. Die Zwei-Faktor-Codes gehen an eine alte Nummer.",
|
|
23
|
+
"es": "No puedo entrar. Los códigos de dos factores van a un número antiguo.",
|
|
24
|
+
"fr": "Je suis bloqué. Les codes à deux facteurs partent vers un ancien numéro.",
|
|
25
|
+
"hi": "मैं लॉक आउट हो गया हूँ। टू-फैक्टर कोड पुराने नंबर पर जा रहे हैं।"}),
|
|
26
|
+
("technical", 2, {"de": "Die App stürzt seit dem Update am {day} beim Start ab. Fehlercode {code}.",
|
|
27
|
+
"es": "La aplicación se cierra al abrirla desde la actualización del {day}. Código de error {code}.",
|
|
28
|
+
"fr": "L'application plante au démarrage depuis la mise à jour du {day}. Code d'erreur {code}.",
|
|
29
|
+
"hi": "{day} के अपडेट के बाद से ऐप खोलते ही क्रैश हो जाता है। एरर कोड {code}।"}),
|
|
30
|
+
("cancellation", 1, {"de": "Bitte kündigen Sie mein Abo vor der Verlängerung am {day}.",
|
|
31
|
+
"es": "Por favor cancelen mi suscripción antes de la renovación del {day}.",
|
|
32
|
+
"fr": "Veuillez annuler mon abonnement avant le renouvellement du {day}.",
|
|
33
|
+
"hi": "कृपया {day} के नवीनीकरण से पहले मेरी सदस्यता रद्द करें।"}),
|
|
34
|
+
("sales", 0, {"de": "Gibt es einen Rabatt bei jährlicher Zahlung für das Team-Paket?",
|
|
35
|
+
"es": "¿Hay descuento por pago anual en el plan de equipo?",
|
|
36
|
+
"fr": "Y a-t-il une remise pour un paiement annuel sur l'offre équipe ?",
|
|
37
|
+
"hi": "टीम प्लान पर वार्षिक भुगतान के लिए कोई छूट है?"}),
|
|
38
|
+
]
|
|
39
|
+
ANGRY = {"de": "Das ist völlig inakzeptabel, ich bin wütend.", "es": "Esto es completamente inaceptable, estoy furioso.",
|
|
40
|
+
"fr": "C'est totalement inacceptable, je suis furieux.", "hi": "यह पूरी तरह अस्वीकार्य है, मैं बहुत गुस्से में हूँ।"}
|
|
41
|
+
CALM = {"de": "Vielen Dank im Voraus.", "es": "Gracias de antemano.", "fr": "Merci d'avance.", "hi": "अग्रिम धन्यवाद।"}
|
|
42
|
+
CTX = {"de": "Kontext: Kundenstufe {tier}.", "es": "Contexto: nivel de cliente {tier}.", "fr": "Contexte : niveau client {tier}.", "hi": "संदर्भ: ग्राहक स्तर {tier}।"}
|
|
43
|
+
TIERS = ["free", "standard", "gold", "enterprise"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@register_generator("multilingual_tickets")
|
|
47
|
+
class MultilingualTicketsGenerator(BaseGenerator):
|
|
48
|
+
version = "1.0.0"
|
|
49
|
+
|
|
50
|
+
def generate(self) -> list[TaskItem]:
|
|
51
|
+
kb = self.knobs
|
|
52
|
+
rng = self.rng("items")
|
|
53
|
+
langs = [kb.language] if kb.language in ("de", "es", "fr", "hi") else ["de", "es", "fr", "hi"]
|
|
54
|
+
queues = [q for q in QUEUES if q[0] in {t[0] for t in T}]
|
|
55
|
+
items = []
|
|
56
|
+
for i in range(kb.n):
|
|
57
|
+
lang = langs[i % len(langs)]
|
|
58
|
+
queue, base, tmpl = rng.choice(T)
|
|
59
|
+
angry = rng.random() < 0.3
|
|
60
|
+
tier = rng.choice(TIERS)
|
|
61
|
+
body = tmpl[lang].format(day=rng.choice(["Montag", "3.", "gestern"]) if lang == "de" else rng.choice(["lunes", "3", "ayer"]) if lang == "es"
|
|
62
|
+
else rng.choice(["lundi", "3", "hier"]) if lang == "fr" else rng.choice(["सोमवार", "3 तारीख", "कल"]),
|
|
63
|
+
amt=rng.choice([19, 45, 89, 249]), order=rng.randrange(10000, 99999), code=f"E{rng.randrange(100, 999)}")
|
|
64
|
+
state = f"{body} {(ANGRY if angry else CALM)[lang]}\n{CTX[lang].format(tier=tier)}"
|
|
65
|
+
priority = min(4, base + (1 if angry else 0) + (1 if tier in ("gold", "enterprise") else 0))
|
|
66
|
+
tid = f"mltickets_{kb.seed}_{lang}_{i:05d}"
|
|
67
|
+
items.append(TaskItem(
|
|
68
|
+
task_id=tid, tier="G", domain="support_triage", language=lang, state=state, state_tokens=estimate_tokens(state),
|
|
69
|
+
questions={
|
|
70
|
+
"queue": Question(type="choice", instructions="Which queue should handle this ticket?", criteria=[Option(key=k, description=d) for k, d in queues],
|
|
71
|
+
ground_truth=queue, framing_group="tickets.queue"),
|
|
72
|
+
"is_angry": Question(type="noul", instructions="Is the customer expressing anger or hostility?", ground_truth=angry, framing_group="tickets.is_angry"),
|
|
73
|
+
"priority": Question(type="score", instructions=PRIORITY_INSTRUCTIONS, criteria=PRIORITY_LEVELS, ground_truth=priority, framing_group="tickets.priority"),
|
|
74
|
+
},
|
|
75
|
+
label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed), controls=Controls(),
|
|
76
|
+
canary=canary_for(tid), metadata={"lang": lang, "tier": tier, "angry": angry, "base_urgency": base},
|
|
77
|
+
))
|
|
78
|
+
return items
|
|
79
|
+
|
|
80
|
+
def regex_rules(self) -> dict:
|
|
81
|
+
return {"tickets.is_angry": [r"inakzeptabel|inaceptable|furieux|अस्वीकार्य"]}
|
|
82
|
+
|
|
83
|
+
def cost_matrices(self):
|
|
84
|
+
return {}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Phishing email generator (is_phishing: noul, attack_class: choice, urgency: score) with a built-in
|
|
2
|
+
5-way decomposition for Suite B (sender mismatch, credential request, urgency language, link/domain
|
|
3
|
+
mismatch, unexpected attachment) combined by a fixed rule.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
|
|
11
|
+
from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
|
|
12
|
+
|
|
13
|
+
BRANDS = ["Northwind Bank", "Contoso Cloud", "Fabrikam Shipping", "Tailspin Payroll", "Woodgrove HR"]
|
|
14
|
+
LEGIT_DOMAINS = {"Northwind Bank": "northwindbank.com", "Contoso Cloud": "contoso.com", "Fabrikam Shipping": "fabrikam.com",
|
|
15
|
+
"Tailspin Payroll": "tailspin.com", "Woodgrove HR": "woodgrove.com"}
|
|
16
|
+
LOOKALIKE = {"northwindbank.com": "northwind-bank-secure.com", "contoso.com": "contoso-cloud-login.net",
|
|
17
|
+
"fabrikam.com": "fabrikam-delivery.co", "tailspin.com": "tailspin-payroll.info", "woodgrove.com": "woodgrove-hr.org"}
|
|
18
|
+
|
|
19
|
+
CLASSES = [
|
|
20
|
+
("credential_harvest", "Asks the recipient to log in or verify credentials via a link"),
|
|
21
|
+
("invoice_fraud", "Fake invoice or payment redirection"),
|
|
22
|
+
("malware_attachment", "Unexpected attachment the recipient is urged to open"),
|
|
23
|
+
("ceo_fraud", "Impersonates an executive requesting an urgent transfer or gift cards"),
|
|
24
|
+
("benign", "Legitimate message with no attack"),
|
|
25
|
+
]
|
|
26
|
+
URGENCY = [Level(level=0, description="No time pressure"), Level(level=1, description="Mild: 'when you get a chance'"),
|
|
27
|
+
Level(level=2, description="Moderate: action requested within days"), Level(level=3, description="High: within 24 hours or account consequences")]
|
|
28
|
+
|
|
29
|
+
LEGIT_TEMPLATES = [
|
|
30
|
+
("Your {brand} statement is ready", "Hi {name}, your monthly statement is available in the app under Documents. No action is required.", 0, False),
|
|
31
|
+
("Delivery update for order {order}", "Hi {name}, your parcel is out for delivery today. Track it in your account.", 0, False),
|
|
32
|
+
("Team lunch Thursday", "Hi {name}, we're doing lunch Thursday at noon. Reply if you have dietary needs.", 1, False),
|
|
33
|
+
("Q3 timesheet reminder", "Hi {name}, timesheets are due Friday. Submit through the usual portal when you get a chance.", 1, False),
|
|
34
|
+
("Invoice {order} attached", "Hi {name}, attached is the invoice we discussed on the call. Let me know if anything looks off.", 1, True),
|
|
35
|
+
]
|
|
36
|
+
PHISH_TEMPLATES = [
|
|
37
|
+
("credential_harvest", "Urgent: verify your {brand} account", "Dear customer, unusual sign-in detected. Verify your identity within 24 hours at https://{fake}/verify or your account will be suspended.", 3, False),
|
|
38
|
+
("credential_harvest", "Password expires today", "Your {brand} password expires today. Reset now: https://{fake}/reset", 3, False),
|
|
39
|
+
("invoice_fraud", "Updated banking details for invoice {order}", "Hi {name}, please note our bank details have changed. Remit invoice {order} to the new account in the attached PDF by end of week.", 2, True),
|
|
40
|
+
("malware_attachment", "Scanned document", "Please see the attached scan (Document_{order}.zip). Open to review.", 1, True),
|
|
41
|
+
("ceo_fraud", "Quick favour", "{name}, I'm in a meeting and need you to buy 5 gift cards for a client today. Send the codes here. Keep it confidential. Sent from my iPhone", 3, False),
|
|
42
|
+
]
|
|
43
|
+
NAMES = ["Alex", "Priya", "Jordan", "Mei", "Sam", "Fatima", "Luca", "Noor"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@register_generator("phishing_email")
|
|
47
|
+
class PhishingEmailGenerator(BaseGenerator):
|
|
48
|
+
version = "1.0.1"
|
|
49
|
+
|
|
50
|
+
def generate(self) -> list[TaskItem]:
|
|
51
|
+
kb = self.knobs
|
|
52
|
+
rng = self.rng("items")
|
|
53
|
+
ctl = self.rng("controls")
|
|
54
|
+
items: list[TaskItem] = []
|
|
55
|
+
for i in range(kb.n):
|
|
56
|
+
distract = ctl.random() < kb.distractor_density
|
|
57
|
+
noise_draw = ctl.random() < kb.label_noise
|
|
58
|
+
brand = rng.choice(BRANDS)
|
|
59
|
+
legit_dom = LEGIT_DOMAINS[brand]
|
|
60
|
+
name = rng.choice(NAMES)
|
|
61
|
+
order = rng.randrange(10000, 99999)
|
|
62
|
+
is_phish = rng.random() < 0.5
|
|
63
|
+
if is_phish:
|
|
64
|
+
cls, subj, body, urg, attach = rng.choice(PHISH_TEMPLATES)
|
|
65
|
+
sender_dom = LOOKALIKE[legit_dom] if rng.random() < 0.8 else legit_dom # 20% spoof the real domain
|
|
66
|
+
fake = LOOKALIKE[legit_dom]
|
|
67
|
+
else:
|
|
68
|
+
subj, body, urg, attach = rng.choice(LEGIT_TEMPLATES)
|
|
69
|
+
cls, sender_dom, fake = "benign", legit_dom, legit_dom
|
|
70
|
+
body = body.format(brand=brand, name=name, order=order, fake=fake)
|
|
71
|
+
subj = subj.format(brand=brand, order=order)
|
|
72
|
+
state = {"from": f"{brand} <no-reply@{sender_dom}>", "to": f"{name.lower()}@example-corp.com", "subject": subj,
|
|
73
|
+
"body": body, "attachments": ([f"file_{order}.{'zip' if cls == 'malware_attachment' else 'pdf'}"] if attach else []),
|
|
74
|
+
"known_brand_domain": legit_dom}
|
|
75
|
+
if distract:
|
|
76
|
+
state["thread_history"] = "Earlier: 'Thanks for the quick turnaround last month!'"
|
|
77
|
+
if kb.target_tokens:
|
|
78
|
+
state["footer"] = pad_to_tokens(ctl, "", kb.target_tokens - estimate_tokens(body))
|
|
79
|
+
noise = False
|
|
80
|
+
truth_phish, truth_cls, truth_urg = is_phish, cls, urg
|
|
81
|
+
if kb.label_noise and noise_draw:
|
|
82
|
+
noise = True
|
|
83
|
+
truth_phish = not truth_phish
|
|
84
|
+
truth_cls = ctl.choice([c[0] for c in CLASSES if c[0] != cls])
|
|
85
|
+
tid = f"phish_{kb.seed}_{i:05d}"
|
|
86
|
+
# decomposition sub-questions (Suite B): each is a noul with rule-derived truth
|
|
87
|
+
sub = {
|
|
88
|
+
"sender_mismatch": ("Does the sender domain differ from the known brand domain?", sender_dom != legit_dom),
|
|
89
|
+
"asks_credentials": ("Does the message ask the recipient to log in, verify, or reset credentials via a link?", cls == "credential_harvest"),
|
|
90
|
+
"urgent_language": ("Does the message pressure the recipient to act within 24 hours or threaten consequences?", urg >= 3),
|
|
91
|
+
"payment_redirect": ("Does the message ask to change payment details or send money or gift cards?", cls in ("invoice_fraud", "ceo_fraud")),
|
|
92
|
+
# v1.0.1: invoice-fraud mails also push an unrequested attachment ("new account in the attached PDF"); the legit
|
|
93
|
+
# "Invoice attached" template was discussed on a call, so it stays false.
|
|
94
|
+
"unexpected_attachment": ("Does the message urge opening an attachment the recipient did not request?", cls in ("malware_attachment", "invoice_fraud")),
|
|
95
|
+
}
|
|
96
|
+
questions = {
|
|
97
|
+
"is_phishing": Question(type="noul", instructions="Is this email a phishing or social-engineering attack?",
|
|
98
|
+
ground_truth=truth_phish, framing_group="phish.is_phishing"),
|
|
99
|
+
"attack_class": Question(type="choice", instructions="Classify the email.", criteria=[Option(key=k, description=d) for k, d in CLASSES],
|
|
100
|
+
ground_truth=truth_cls, framing_group="phish.attack_class"),
|
|
101
|
+
"urgency": Question(type="score", instructions="Rate the time pressure the message applies, 0 (none) to 3 (within 24 hours or threats).",
|
|
102
|
+
criteria=URGENCY, ground_truth=truth_urg, framing_group="phish.urgency"),
|
|
103
|
+
}
|
|
104
|
+
for k, (instr, truth) in sub.items():
|
|
105
|
+
questions[f"sub_{k}"] = Question(type="noul", instructions=instr, ground_truth=truth,
|
|
106
|
+
framing_group=f"phish.sub.{k}", decomposition_of="is_phishing")
|
|
107
|
+
items.append(TaskItem(
|
|
108
|
+
task_id=tid, tier="G", domain="email_security", language=kb.language, state=state,
|
|
109
|
+
state_tokens=estimate_tokens(json.dumps(state)), questions=questions,
|
|
110
|
+
label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
|
|
111
|
+
controls=Controls(distractor_density=kb.distractor_density), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
|
|
112
|
+
metadata={"class": cls, "sender_domain": sender_dom, "decomposition_rule": "phishing if sender_mismatch or any of the other four"},
|
|
113
|
+
))
|
|
114
|
+
return items
|
|
115
|
+
|
|
116
|
+
def regex_rules(self) -> dict:
|
|
117
|
+
return {
|
|
118
|
+
"phish.is_phishing": [r"verify your", r"suspended", r"expires today", r"gift cards", r"bank details have changed", r"\.zip", r"-secure\.com|-login\.net|-delivery\.co|-payroll\.info|-hr\.org"],
|
|
119
|
+
"phish.attack_class": {"credential_harvest": [r"verify", r"reset now", r"password"], "invoice_fraud": [r"bank details", r"remit"],
|
|
120
|
+
"malware_attachment": [r"\.zip", r"scanned document"], "ceo_fraud": [r"gift cards", r"confidential"],
|
|
121
|
+
"benign": [r"no action is required", r"team lunch", r"timesheet", r"out for delivery"]},
|
|
122
|
+
"phish.urgency": {"3": [r"24 hours|today|suspended"], "2": [r"end of week"], "1": [r"when you get a chance|let me know"], "0": [r"no action"]},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def cost_matrices(self) -> dict[str, dict[str, dict[str, float]]]:
|
|
126
|
+
# missing a phish costs 20; false alarm costs 1
|
|
127
|
+
return {"is_phishing": {"true": {"true": 0.0, "false": 20.0}, "false": {"true": 1.0, "false": 0.0}}}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Policy compliance generator: a short policy (3 to 5 numbered clauses) plus a request.
|
|
2
|
+
|
|
3
|
+
compliant (noul) -> no clause is violated
|
|
4
|
+
violated_clause (choice over clause ids plus a required "none") -> the single violated clause, or none
|
|
5
|
+
severity (score 0..2) -> 0 compliant, 1 minor clause, 2 major clause (clause severities stated in the policy)
|
|
6
|
+
Requests are built from the same clause set, so the label is exact and the "none" option is correct for a stated
|
|
7
|
+
fraction of items. Nothing in the request text names the clause.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
|
|
15
|
+
from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
|
|
16
|
+
|
|
17
|
+
# clause: (id, text, severity 1|2, violating request templates, compliant request templates)
|
|
18
|
+
CLAUSES = [
|
|
19
|
+
("C1", "Refunds are only issued within 30 days of purchase.", 2,
|
|
20
|
+
["I bought this {days} days ago and want a full refund.", "Please refund order #{order}; it was placed {days} days back."],
|
|
21
|
+
["I bought this {sdays} days ago and want a full refund.", "Order #{order} from {sdays} days ago, please refund."]),
|
|
22
|
+
("C2", "Account details may only be shared with the verified account holder.", 2,
|
|
23
|
+
["I'm calling for my husband; can you read me his last four transactions?", "My roommate's account: what email is on file? He asked me to check."],
|
|
24
|
+
["Can you read me my own last four transactions? I've verified via the security question.", "What email is on my account? I'm the holder and verified."]),
|
|
25
|
+
("C3", "Discount codes cannot be combined with other promotions.", 1,
|
|
26
|
+
["Apply code SAVE20 on top of the current summer sale, please.", "Stack my loyalty coupon with the Black Friday price."],
|
|
27
|
+
["Apply code SAVE20 to my order; no other promotions are on it.", "Use my loyalty coupon on this full-price item."]),
|
|
28
|
+
("C4", "Shipping address changes are not possible after dispatch.", 1,
|
|
29
|
+
["My order shipped this morning; change the address to my office.", "Tracking says dispatched; please redirect it to my parents' house."],
|
|
30
|
+
["My order hasn't shipped yet; change the address to my office.", "Order still processing, please update the delivery address."]),
|
|
31
|
+
("C5", "Support agents must not provide legal or tax advice.", 2,
|
|
32
|
+
["Should I declare this refund as income on my tax return?", "Is it legal for my employer to deduct this from my pay?"],
|
|
33
|
+
["Can you send me the invoice so my accountant can handle the taxes?", "Where do I download my annual statement?"]),
|
|
34
|
+
]
|
|
35
|
+
SEV = [Level(level=0, description="Compliant: no clause is violated."), Level(level=1, description="Minor violation (a clause marked minor)."),
|
|
36
|
+
Level(level=2, description="Major violation (a clause marked major).")]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@register_generator("policy_compliance")
|
|
40
|
+
class PolicyComplianceGenerator(BaseGenerator):
|
|
41
|
+
version = "1.0.0"
|
|
42
|
+
|
|
43
|
+
def generate(self) -> list[TaskItem]:
|
|
44
|
+
kb = self.knobs
|
|
45
|
+
rng, ctl = self.rng("items"), self.rng("controls")
|
|
46
|
+
none_frac = kb.none_correct_frac if kb.none_correct_frac else 0.3
|
|
47
|
+
items = []
|
|
48
|
+
for i in range(kb.n):
|
|
49
|
+
noise_draw = ctl.random() < kb.label_noise
|
|
50
|
+
k = rng.randrange(3, 6)
|
|
51
|
+
clauses = rng.sample(CLAUSES, k)
|
|
52
|
+
policy = "\n".join(f"{cid}. {txt} [{'major' if sev == 2 else 'minor'}]" for cid, txt, sev, _, _ in clauses)
|
|
53
|
+
compliant = rng.random() < none_frac
|
|
54
|
+
target = rng.choice(clauses)
|
|
55
|
+
tmpl = rng.choice(target[4] if compliant else target[3])
|
|
56
|
+
request = tmpl.format(days=rng.randrange(31, 120), sdays=rng.randrange(1, 29), order=rng.randrange(10000, 99999))
|
|
57
|
+
state = {"policy": policy, "customer_request": request}
|
|
58
|
+
if kb.target_tokens:
|
|
59
|
+
state["history"] = pad_to_tokens(ctl, "", kb.target_tokens)
|
|
60
|
+
violated = "none" if compliant else target[0]
|
|
61
|
+
sev = 0 if compliant else target[2]
|
|
62
|
+
noise = False
|
|
63
|
+
if kb.label_noise and noise_draw:
|
|
64
|
+
noise = True
|
|
65
|
+
compliant = not compliant
|
|
66
|
+
violated = "none" if compliant else target[0]
|
|
67
|
+
sev = 0 if compliant else target[2]
|
|
68
|
+
options = [Option(key=cid, description=txt) for cid, txt, _, _, _ in clauses] + [Option(key="none", description="No clause is violated.")]
|
|
69
|
+
tid = f"policy_{kb.seed}_{i:05d}"
|
|
70
|
+
items.append(TaskItem(
|
|
71
|
+
task_id=tid, tier="G", domain="policy_compliance", language=kb.language, state=state, state_tokens=estimate_tokens(json.dumps(state)),
|
|
72
|
+
questions={
|
|
73
|
+
"compliant": Question(type="noul", instructions="Can the request be fulfilled without violating any clause of `policy`?",
|
|
74
|
+
ground_truth=compliant, framing_group="policy.compliant"),
|
|
75
|
+
"violated_clause": Question(type="choice", instructions="Which clause of `policy` would fulfilling `customer_request` violate? Choose none if it complies.",
|
|
76
|
+
criteria=options, ground_truth=violated, framing_group="policy.violated_clause"),
|
|
77
|
+
"severity": Question(type="score", instructions="Violation severity per the [minor]/[major] tags in `policy`; 0 if compliant.",
|
|
78
|
+
criteria=SEV, ground_truth=sev, framing_group="policy.severity"),
|
|
79
|
+
},
|
|
80
|
+
label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
|
|
81
|
+
controls=Controls(none_correct=compliant), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
|
|
82
|
+
metadata={"target_clause": target[0], "k": k},
|
|
83
|
+
))
|
|
84
|
+
return items
|
|
85
|
+
|
|
86
|
+
def regex_rules(self) -> dict:
|
|
87
|
+
return {"policy.compliant": [r"(?!)"]}
|
|
88
|
+
|
|
89
|
+
def cost_matrices(self):
|
|
90
|
+
return {"compliant": {"true": {"true": 0.0, "false": 2.0}, "false": {"true": 10.0, "false": 0.0}}}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""RAG relevance generator: query plus K candidate passages.
|
|
2
|
+
|
|
3
|
+
Questions:
|
|
4
|
+
best_passage (choice over K passage ids, K up to 255) -> the one passage that answers the query
|
|
5
|
+
is_relevant (noul) -> does passage `focus` answer the query (focus is a random candidate; 50% positives)
|
|
6
|
+
relevance (score 0..3) -> grade of `focus`: 0 unrelated, 1 same topic, 2 partial, 3 answers it
|
|
7
|
+
|
|
8
|
+
Passages are built from a fact table: each fact has a topic, an entity, an attribute and a value. Distractors are
|
|
9
|
+
(a) same topic different entity (hard), (b) same entity different attribute (hard), (c) unrelated (easy). Labels are
|
|
10
|
+
exact by construction, so this is the cardinality sweep generator for Suite C (K in 2..255).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import random
|
|
17
|
+
|
|
18
|
+
from ..schemas import Controls, LabelProvenance, Level, Option, Question, TaskItem
|
|
19
|
+
from .base import BaseGenerator, canary_for, estimate_tokens, pad_to_tokens, register_generator
|
|
20
|
+
|
|
21
|
+
TOPICS = {
|
|
22
|
+
"planets": (["Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"],
|
|
23
|
+
{"moons": lambda r: str(r.randrange(0, 90)), "day_length_hours": lambda r: str(r.randrange(10, 5900)),
|
|
24
|
+
"discovery_year": lambda r: str(r.randrange(1600, 1950)), "mean_temp_c": lambda r: str(r.randrange(-220, 470))}),
|
|
25
|
+
"companies": (["Northwind", "Contoso", "Fabrikam", "Tailspin", "Woodgrove", "Litware", "Adatum", "Proseware"],
|
|
26
|
+
{"headquarters": lambda r: r.choice(["Oslo", "Lisbon", "Austin", "Kyoto", "Nairobi", "Toronto"]),
|
|
27
|
+
"founded": lambda r: str(r.randrange(1950, 2022)), "employees": lambda r: str(r.randrange(20, 90000)),
|
|
28
|
+
"ceo": lambda r: r.choice(["A. Okafor", "M. Lindqvist", "R. Tanaka", "S. Mehta", "J. Alvarez"])}),
|
|
29
|
+
"recipes": (["ratatouille", "bibimbap", "moussaka", "feijoada", "pho", "tagine", "paella", "goulash"],
|
|
30
|
+
{"origin_country": lambda r: r.choice(["France", "Korea", "Greece", "Brazil", "Vietnam", "Morocco", "Spain", "Hungary"]),
|
|
31
|
+
"cook_time_minutes": lambda r: str(r.randrange(15, 240)), "main_protein": lambda r: r.choice(["beef", "pork", "chicken", "lamb", "none", "fish"]),
|
|
32
|
+
"servings": lambda r: str(r.randrange(2, 12))}),
|
|
33
|
+
"software": (["Kestrel", "Nimbus", "Quartz", "Vellum", "Marlin", "Orchid", "Tundra", "Zephyr"],
|
|
34
|
+
{"latest_version": lambda r: f"{r.randrange(1, 9)}.{r.randrange(0, 20)}", "license": lambda r: r.choice(["MIT", "Apache-2.0", "GPL-3.0", "BSD-3", "proprietary"]),
|
|
35
|
+
"language": lambda r: r.choice(["Rust", "Go", "Python", "TypeScript", "C++"]), "first_release": lambda r: str(r.randrange(2005, 2026))}),
|
|
36
|
+
}
|
|
37
|
+
TEMPLATES = ["{entity}'s {attr_h} is {value}.", "According to the {topic} handbook, the {attr_h} of {entity} is {value}.",
|
|
38
|
+
"Note: {entity} — {attr_h}: {value}.", "For {entity}, records list the {attr_h} as {value}."]
|
|
39
|
+
QUERY_TEMPLATES = ["What is the {attr_h} of {entity}?", "{entity}: {attr_h}?", "Tell me {entity}'s {attr_h}.", "Look up the {attr_h} for {entity}."]
|
|
40
|
+
REL_LEVELS = [Level(level=0, description="Unrelated to the query's topic or entity."),
|
|
41
|
+
Level(level=1, description="Same topic but about a different entity or attribute; does not answer the query."),
|
|
42
|
+
Level(level=2, description="About the right entity but a different attribute, or the right attribute for a different entity."),
|
|
43
|
+
Level(level=3, description="Directly states the value the query asks for.")]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _fact(rng: random.Random, topic: str, entity: str, attr: str) -> tuple[str, str]:
|
|
47
|
+
ents, attrs = TOPICS[topic]
|
|
48
|
+
value = attrs[attr](rng)
|
|
49
|
+
text = rng.choice(TEMPLATES).format(entity=entity, attr_h=attr.replace("_", " "), value=value, topic=topic)
|
|
50
|
+
return text, value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@register_generator("rag_relevance")
|
|
54
|
+
class RagRelevanceGenerator(BaseGenerator):
|
|
55
|
+
version = "1.0.0"
|
|
56
|
+
|
|
57
|
+
def generate(self) -> list[TaskItem]:
|
|
58
|
+
kb = self.knobs
|
|
59
|
+
rng = self.rng("items")
|
|
60
|
+
ctl = self.rng("controls")
|
|
61
|
+
K = max(2, min(kb.cardinality or 5, 255))
|
|
62
|
+
items: list[TaskItem] = []
|
|
63
|
+
for i in range(kb.n):
|
|
64
|
+
topic = rng.choice(list(TOPICS))
|
|
65
|
+
ents, attrs = TOPICS[topic]
|
|
66
|
+
entity, attr = rng.choice(ents), rng.choice(list(attrs))
|
|
67
|
+
query = rng.choice(QUERY_TEMPLATES).format(entity=entity, attr_h=attr.replace("_", " "))
|
|
68
|
+
gold_text, _ = _fact(rng, topic, entity, attr)
|
|
69
|
+
passages: list[tuple[str, int]] = [(gold_text, 3)] # (text, relevance grade)
|
|
70
|
+
# hard distractors
|
|
71
|
+
while len(passages) < K:
|
|
72
|
+
kind = rng.random()
|
|
73
|
+
if kind < 0.35: # same entity, other attribute
|
|
74
|
+
other_attr = rng.choice([a for a in attrs if a != attr])
|
|
75
|
+
passages.append((_fact(rng, topic, entity, other_attr)[0], 2))
|
|
76
|
+
elif kind < 0.7: # same attribute, other entity
|
|
77
|
+
other_ent = rng.choice([e for e in ents if e != entity])
|
|
78
|
+
passages.append((_fact(rng, topic, other_ent, attr)[0], 2))
|
|
79
|
+
elif kind < 0.85: # same topic, other entity and attribute
|
|
80
|
+
other_ent = rng.choice([e for e in ents if e != entity])
|
|
81
|
+
other_attr = rng.choice([a for a in attrs if a != attr])
|
|
82
|
+
passages.append((_fact(rng, topic, other_ent, other_attr)[0], 1))
|
|
83
|
+
else: # unrelated topic
|
|
84
|
+
ot = rng.choice([t for t in TOPICS if t != topic])
|
|
85
|
+
oe, oa = rng.choice(TOPICS[ot][0]), rng.choice(list(TOPICS[ot][1]))
|
|
86
|
+
passages.append((_fact(rng, ot, oe, oa)[0], 0))
|
|
87
|
+
rng.shuffle(passages)
|
|
88
|
+
ids = [f"p{j:03d}" for j in range(K)]
|
|
89
|
+
gold_id = ids[[g for _, g in passages].index(3)]
|
|
90
|
+
focus_idx = rng.randrange(K) if rng.random() < 0.5 else ids.index(gold_id)
|
|
91
|
+
focus_id, focus_grade = ids[focus_idx], passages[focus_idx][1]
|
|
92
|
+
state = {"query": query, "passages": {pid: txt for pid, (txt, _) in zip(ids, passages)}, "focus": focus_id}
|
|
93
|
+
noise_draw = ctl.random() < kb.label_noise
|
|
94
|
+
if kb.target_tokens:
|
|
95
|
+
state["notes"] = pad_to_tokens(ctl, "", kb.target_tokens)
|
|
96
|
+
noise = False
|
|
97
|
+
if kb.label_noise and noise_draw:
|
|
98
|
+
noise = True
|
|
99
|
+
gold_id = ctl.choice([p for p in ids if p != gold_id])
|
|
100
|
+
tid = f"rag_{kb.seed}_K{K}_{i:05d}"
|
|
101
|
+
items.append(TaskItem(
|
|
102
|
+
task_id=tid, tier="G", domain="rag_relevance", language=kb.language, state=state,
|
|
103
|
+
state_tokens=estimate_tokens(json.dumps(state)),
|
|
104
|
+
questions={
|
|
105
|
+
"best_passage": Question(type="choice", instructions="Which passage in `passages` directly answers `query`?",
|
|
106
|
+
criteria=[Option(key=pid, description="") for pid in ids], ground_truth=gold_id, framing_group="rag.best_passage"),
|
|
107
|
+
"is_relevant": Question(type="noul", instructions="Does the passage with id `focus` directly answer `query`?",
|
|
108
|
+
ground_truth=(focus_grade == 3), framing_group="rag.is_relevant"),
|
|
109
|
+
"relevance": Question(type="score", instructions="Grade how well the passage with id `focus` answers `query`.",
|
|
110
|
+
criteria=REL_LEVELS, ground_truth=focus_grade, framing_group="rag.relevance"),
|
|
111
|
+
},
|
|
112
|
+
label_provenance=LabelProvenance(source="generator", generator=self.generator_id, seed=kb.seed, noise_injected=noise),
|
|
113
|
+
controls=Controls(distractor_density=kb.distractor_density), cost_matrix=self.cost_matrices(), canary=canary_for(tid),
|
|
114
|
+
metadata={"topic": topic, "entity": entity, "attribute": attr, "K": K, "focus_grade": focus_grade},
|
|
115
|
+
))
|
|
116
|
+
return items
|
|
117
|
+
|
|
118
|
+
def regex_rules(self) -> dict:
|
|
119
|
+
return {"rag.is_relevant": [r"(?!)"]} # no sensible regex baseline: passages are ids; kept so the rule file exists
|
|
120
|
+
|
|
121
|
+
def cost_matrices(self) -> dict[str, dict[str, dict[str, float]]]:
|
|
122
|
+
return {"is_relevant": {"true": {"true": 0.0, "false": 5.0}, "false": {"true": 1.0, "false": 0.0}}}
|