Spaces:
Sleeping
Sleeping
Promotion Space deploy (2026-03-25 05:46 UTC)
Browse files- configs/math_conjecture_sota.yaml +21 -4
- configs/qwen25_math_sota.yaml +21 -4
- scripts/eval_sota.py +155 -0
- scripts/train_sota.py +112 -0
- tests/test_core_utils.py +63 -3
configs/math_conjecture_sota.yaml
CHANGED
|
@@ -40,6 +40,13 @@ data:
|
|
| 40 |
formal_proof: 2.7
|
| 41 |
competition: 1.2
|
| 42 |
structured_reasoning: 1.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
system_prompt: |
|
| 44 |
You are NorthernTribe Research's math-conjecture solver model.
|
| 45 |
Prioritize solved-conjecture answer recovery, proof-quality reasoning,
|
|
@@ -75,14 +82,19 @@ stages:
|
|
| 75 |
num_train_epochs: 1
|
| 76 |
learning_rate: 2.0e-5
|
| 77 |
|
| 78 |
-
- name:
|
| 79 |
-
max_train_samples:
|
| 80 |
max_eval_samples: 3000
|
| 81 |
filters:
|
| 82 |
include_families:
|
| 83 |
- conjecture_core
|
| 84 |
- formal_proof
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
training:
|
| 87 |
num_train_epochs: 2
|
| 88 |
learning_rate: 8.0e-6
|
|
@@ -90,12 +102,14 @@ stages:
|
|
| 90 |
eval_steps: 220
|
| 91 |
|
| 92 |
- name: formal_consistency_refinement
|
| 93 |
-
max_train_samples:
|
| 94 |
max_eval_samples: 2200
|
| 95 |
filters:
|
| 96 |
include_families:
|
|
|
|
| 97 |
- conjecture_core
|
| 98 |
require_conjecture_id: true
|
|
|
|
| 99 |
training:
|
| 100 |
num_train_epochs: 2
|
| 101 |
learning_rate: 4.5e-6
|
|
@@ -109,6 +123,9 @@ stages:
|
|
| 109 |
include_families:
|
| 110 |
- conjecture_core
|
| 111 |
- formal_proof
|
|
|
|
|
|
|
|
|
|
| 112 |
require_conjecture_id: true
|
| 113 |
min_sample_weight: 3.0
|
| 114 |
training:
|
|
|
|
| 40 |
formal_proof: 2.7
|
| 41 |
competition: 1.2
|
| 42 |
structured_reasoning: 1.1
|
| 43 |
+
difficulty_boost:
|
| 44 |
+
simple: 1.35
|
| 45 |
+
intermediate: 1.2
|
| 46 |
+
advanced: 1.05
|
| 47 |
+
lean_formal: 1.5
|
| 48 |
+
general: 1.0
|
| 49 |
+
lean_proof_bonus: 1.15
|
| 50 |
system_prompt: |
|
| 51 |
You are NorthernTribe Research's math-conjecture solver model.
|
| 52 |
Prioritize solved-conjecture answer recovery, proof-quality reasoning,
|
|
|
|
| 82 |
num_train_epochs: 1
|
| 83 |
learning_rate: 2.0e-5
|
| 84 |
|
| 85 |
+
- name: simple_to_lean_alignment
|
| 86 |
+
max_train_samples: 120000
|
| 87 |
max_eval_samples: 3000
|
| 88 |
filters:
|
| 89 |
include_families:
|
| 90 |
- conjecture_core
|
| 91 |
- formal_proof
|
| 92 |
+
- structured_reasoning
|
| 93 |
+
include_difficulty_bands:
|
| 94 |
+
- simple
|
| 95 |
+
- intermediate
|
| 96 |
+
- lean_formal
|
| 97 |
+
min_sample_weight: 1.0
|
| 98 |
training:
|
| 99 |
num_train_epochs: 2
|
| 100 |
learning_rate: 8.0e-6
|
|
|
|
| 102 |
eval_steps: 220
|
| 103 |
|
| 104 |
- name: formal_consistency_refinement
|
| 105 |
+
max_train_samples: 80000
|
| 106 |
max_eval_samples: 2200
|
| 107 |
filters:
|
| 108 |
include_families:
|
| 109 |
+
- formal_proof
|
| 110 |
- conjecture_core
|
| 111 |
require_conjecture_id: true
|
| 112 |
+
require_lean_formal: true
|
| 113 |
training:
|
| 114 |
num_train_epochs: 2
|
| 115 |
learning_rate: 4.5e-6
|
|
|
|
| 123 |
include_families:
|
| 124 |
- conjecture_core
|
| 125 |
- formal_proof
|
| 126 |
+
include_difficulty_bands:
|
| 127 |
+
- advanced
|
| 128 |
+
- lean_formal
|
| 129 |
require_conjecture_id: true
|
| 130 |
min_sample_weight: 3.0
|
| 131 |
training:
|
configs/qwen25_math_sota.yaml
CHANGED
|
@@ -40,6 +40,13 @@ data:
|
|
| 40 |
formal_proof: 2.2
|
| 41 |
competition: 1.35
|
| 42 |
structured_reasoning: 1.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
system_prompt: |
|
| 44 |
You are a frontier mathematical reasoning model focused on unsolved
|
| 45 |
conjectures. Prioritize formal consistency, proof structure, and explicit
|
|
@@ -74,14 +81,19 @@ stages:
|
|
| 74 |
num_train_epochs: 1
|
| 75 |
learning_rate: 2.0e-5
|
| 76 |
|
| 77 |
-
- name:
|
| 78 |
-
max_train_samples:
|
| 79 |
max_eval_samples: 2500
|
| 80 |
filters:
|
| 81 |
include_families:
|
| 82 |
- conjecture_core
|
| 83 |
- formal_proof
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
training:
|
| 86 |
num_train_epochs: 2
|
| 87 |
learning_rate: 8.0e-6
|
|
@@ -89,12 +101,14 @@ stages:
|
|
| 89 |
eval_steps: 220
|
| 90 |
|
| 91 |
- name: conjecture_alignment
|
| 92 |
-
max_train_samples:
|
| 93 |
max_eval_samples: 2000
|
| 94 |
filters:
|
| 95 |
include_families:
|
|
|
|
| 96 |
- conjecture_core
|
| 97 |
require_conjecture_id: true
|
|
|
|
| 98 |
training:
|
| 99 |
num_train_epochs: 2
|
| 100 |
learning_rate: 5.0e-6
|
|
@@ -108,6 +122,9 @@ stages:
|
|
| 108 |
include_families:
|
| 109 |
- conjecture_core
|
| 110 |
- formal_proof
|
|
|
|
|
|
|
|
|
|
| 111 |
require_conjecture_id: true
|
| 112 |
min_sample_weight: 3.0
|
| 113 |
training:
|
|
|
|
| 40 |
formal_proof: 2.2
|
| 41 |
competition: 1.35
|
| 42 |
structured_reasoning: 1.1
|
| 43 |
+
difficulty_boost:
|
| 44 |
+
simple: 1.35
|
| 45 |
+
intermediate: 1.2
|
| 46 |
+
advanced: 1.05
|
| 47 |
+
lean_formal: 1.45
|
| 48 |
+
general: 1.0
|
| 49 |
+
lean_proof_bonus: 1.12
|
| 50 |
system_prompt: |
|
| 51 |
You are a frontier mathematical reasoning model focused on unsolved
|
| 52 |
conjectures. Prioritize formal consistency, proof structure, and explicit
|
|
|
|
| 81 |
num_train_epochs: 1
|
| 82 |
learning_rate: 2.0e-5
|
| 83 |
|
| 84 |
+
- name: simple_to_lean_specialization
|
| 85 |
+
max_train_samples: 110000
|
| 86 |
max_eval_samples: 2500
|
| 87 |
filters:
|
| 88 |
include_families:
|
| 89 |
- conjecture_core
|
| 90 |
- formal_proof
|
| 91 |
+
- structured_reasoning
|
| 92 |
+
include_difficulty_bands:
|
| 93 |
+
- simple
|
| 94 |
+
- intermediate
|
| 95 |
+
- lean_formal
|
| 96 |
+
min_sample_weight: 1.0
|
| 97 |
training:
|
| 98 |
num_train_epochs: 2
|
| 99 |
learning_rate: 8.0e-6
|
|
|
|
| 101 |
eval_steps: 220
|
| 102 |
|
| 103 |
- name: conjecture_alignment
|
| 104 |
+
max_train_samples: 70000
|
| 105 |
max_eval_samples: 2000
|
| 106 |
filters:
|
| 107 |
include_families:
|
| 108 |
+
- formal_proof
|
| 109 |
- conjecture_core
|
| 110 |
require_conjecture_id: true
|
| 111 |
+
require_lean_formal: true
|
| 112 |
training:
|
| 113 |
num_train_epochs: 2
|
| 114 |
learning_rate: 5.0e-6
|
|
|
|
| 122 |
include_families:
|
| 123 |
- conjecture_core
|
| 124 |
- formal_proof
|
| 125 |
+
include_difficulty_bands:
|
| 126 |
+
- advanced
|
| 127 |
+
- lean_formal
|
| 128 |
require_conjecture_id: true
|
| 129 |
min_sample_weight: 3.0
|
| 130 |
training:
|
scripts/eval_sota.py
CHANGED
|
@@ -103,6 +103,92 @@ def as_int(value: Any, default: int) -> int:
|
|
| 103 |
return default
|
| 104 |
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
def load_config(path: Path) -> Dict[str, Any]:
|
| 107 |
cfg = yaml.safe_load(path.read_text(encoding="utf-8"))
|
| 108 |
if not isinstance(cfg, dict):
|
|
@@ -296,6 +382,10 @@ def build_user_block(row: Dict[str, Any], data_cfg: Dict[str, Any]) -> str:
|
|
| 296 |
value = as_text(row.get(key))
|
| 297 |
if value:
|
| 298 |
lines.append(f"{label}: {value}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
if lines:
|
| 300 |
return f"{prompt}\n\nMetadata:\n" + "\n".join(lines)
|
| 301 |
return prompt
|
|
@@ -396,6 +486,18 @@ def finalize_bucket(bucket: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 396 |
}
|
| 397 |
|
| 398 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
def resolve_eval_file(arg_eval_file: Optional[Path], cfg: Dict[str, Any]) -> Path:
|
| 400 |
if arg_eval_file is not None:
|
| 401 |
return arg_eval_file
|
|
@@ -457,6 +559,8 @@ def run_evaluation(args: argparse.Namespace) -> Dict[str, Any]:
|
|
| 457 |
totals = make_bucket()
|
| 458 |
family_buckets: Dict[str, Dict[str, Any]] = {}
|
| 459 |
difficulty_buckets: Dict[str, Dict[str, Any]] = {}
|
|
|
|
|
|
|
| 460 |
|
| 461 |
processed_rows = 0
|
| 462 |
skipped_no_expected = 0
|
|
@@ -525,6 +629,30 @@ def run_evaluation(args: argparse.Namespace) -> Dict[str, Any]:
|
|
| 525 |
boxedk=boxedk,
|
| 526 |
)
|
| 527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
processed_rows += 1
|
| 529 |
if args.progress_every > 0 and processed_rows % args.progress_every == 0:
|
| 530 |
print(f"Progress: evaluated_rows={processed_rows} latest_family={family}")
|
|
@@ -535,6 +663,8 @@ def run_evaluation(args: argparse.Namespace) -> Dict[str, Any]:
|
|
| 535 |
"uid": as_text(row.get("uid")),
|
| 536 |
"family": family,
|
| 537 |
"difficulty": difficulty,
|
|
|
|
|
|
|
| 538 |
"prompt": as_text(row.get(prompt_field)),
|
| 539 |
"expected_values": expected_values[:5],
|
| 540 |
"candidates": candidates,
|
|
@@ -554,6 +684,22 @@ def run_evaluation(args: argparse.Namespace) -> Dict[str, Any]:
|
|
| 554 |
|
| 555 |
composite_score = 0.30 * pass_at_1 + 0.50 * pass_at_k + 0.20 * exact_at_k
|
| 556 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
report: Dict[str, Any] = {
|
| 558 |
"base_model": base_model,
|
| 559 |
"adapter_path": str(args.adapter_path) if args.adapter_path is not None else None,
|
|
@@ -582,6 +728,15 @@ def run_evaluation(args: argparse.Namespace) -> Dict[str, Any]:
|
|
| 582 |
key: finalize_bucket(difficulty_buckets[key])
|
| 583 |
for key in sorted(difficulty_buckets.keys())
|
| 584 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
"samples": samples,
|
| 586 |
}
|
| 587 |
|
|
|
|
| 103 |
return default
|
| 104 |
|
| 105 |
|
| 106 |
+
def canonical_difficulty(value: Any) -> str:
|
| 107 |
+
raw = as_text(value).lower()
|
| 108 |
+
if not raw or raw in {"unknown", "null", "none", "nan", "n/a"}:
|
| 109 |
+
return "general"
|
| 110 |
+
if any(key in raw for key in ("lean", "formal", "theorem", "proof", "mathlib", "proofnet")):
|
| 111 |
+
return "lean_formal"
|
| 112 |
+
if any(
|
| 113 |
+
key in raw
|
| 114 |
+
for key in (
|
| 115 |
+
"basic",
|
| 116 |
+
"easy",
|
| 117 |
+
"elementary",
|
| 118 |
+
"arithmetic",
|
| 119 |
+
"level 1",
|
| 120 |
+
"level1",
|
| 121 |
+
"level 2",
|
| 122 |
+
"level2",
|
| 123 |
+
)
|
| 124 |
+
):
|
| 125 |
+
return "simple"
|
| 126 |
+
if any(key in raw for key in ("intermediate", "medium", "basic_to_intermediate", "level 3", "level3")):
|
| 127 |
+
return "intermediate"
|
| 128 |
+
if any(
|
| 129 |
+
key in raw
|
| 130 |
+
for key in (
|
| 131 |
+
"advanced",
|
| 132 |
+
"hard",
|
| 133 |
+
"frontier",
|
| 134 |
+
"olympiad",
|
| 135 |
+
"competition",
|
| 136 |
+
"level 4",
|
| 137 |
+
"level4",
|
| 138 |
+
"level 5",
|
| 139 |
+
"level5",
|
| 140 |
+
"geometry",
|
| 141 |
+
"algebra",
|
| 142 |
+
"calculus",
|
| 143 |
+
"number theory",
|
| 144 |
+
"combinatorics",
|
| 145 |
+
"inequalities",
|
| 146 |
+
)
|
| 147 |
+
):
|
| 148 |
+
return "advanced"
|
| 149 |
+
return "general"
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def is_lean_formal_row(row: Dict[str, Any]) -> bool:
|
| 153 |
+
family = as_text(row.get("family")).lower()
|
| 154 |
+
if family == "formal_proof":
|
| 155 |
+
return True
|
| 156 |
+
task_type = as_text(row.get("task_type")).lower()
|
| 157 |
+
if any(token in task_type for token in ("lean", "formal", "theorem", "proof")):
|
| 158 |
+
return True
|
| 159 |
+
if as_text(row.get("proof_formal")):
|
| 160 |
+
return True
|
| 161 |
+
return canonical_difficulty(row.get("difficulty")) == "lean_formal"
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def infer_response_profile(row: Dict[str, Any]) -> str:
|
| 165 |
+
if is_lean_formal_row(row):
|
| 166 |
+
return "lean_formal"
|
| 167 |
+
band = canonical_difficulty(row.get("difficulty"))
|
| 168 |
+
if band != "general":
|
| 169 |
+
return band
|
| 170 |
+
family = as_text(row.get("family")).lower()
|
| 171 |
+
if family == "structured_reasoning":
|
| 172 |
+
return "intermediate"
|
| 173 |
+
if family in {"conjecture_core", "competition"}:
|
| 174 |
+
return "advanced"
|
| 175 |
+
return "general"
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def response_contract(profile: str) -> str:
|
| 179 |
+
if profile == "simple":
|
| 180 |
+
return "Use plain language, short steps, and end with one clear final answer."
|
| 181 |
+
if profile == "intermediate":
|
| 182 |
+
return "Give a compact derivation with key equations and a clean final conclusion."
|
| 183 |
+
if profile == "advanced":
|
| 184 |
+
return "Provide rigorous reasoning, explicit assumptions, and uncertainty when unresolved."
|
| 185 |
+
if profile == "lean_formal":
|
| 186 |
+
return (
|
| 187 |
+
"Provide a proof sketch plus a Lean-oriented theorem/lemma structure and verification plan."
|
| 188 |
+
)
|
| 189 |
+
return "Provide technically correct reasoning and separate facts from conjectural claims."
|
| 190 |
+
|
| 191 |
+
|
| 192 |
def load_config(path: Path) -> Dict[str, Any]:
|
| 193 |
cfg = yaml.safe_load(path.read_text(encoding="utf-8"))
|
| 194 |
if not isinstance(cfg, dict):
|
|
|
|
| 382 |
value = as_text(row.get(key))
|
| 383 |
if value:
|
| 384 |
lines.append(f"{label}: {value}")
|
| 385 |
+
profile = infer_response_profile(row)
|
| 386 |
+
lines.append(f"Difficulty band: {canonical_difficulty(row.get('difficulty'))}")
|
| 387 |
+
lines.append(f"Response profile: {profile}")
|
| 388 |
+
lines.append(f"Response contract: {response_contract(profile)}")
|
| 389 |
if lines:
|
| 390 |
return f"{prompt}\n\nMetadata:\n" + "\n".join(lines)
|
| 391 |
return prompt
|
|
|
|
| 486 |
}
|
| 487 |
|
| 488 |
|
| 489 |
+
def merge_buckets(buckets: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
|
| 490 |
+
merged = make_bucket()
|
| 491 |
+
for bucket in buckets:
|
| 492 |
+
merged["evaluated_rows"] += int(bucket.get("evaluated_rows", 0))
|
| 493 |
+
merged["pass_at_1_hits"] += int(bucket.get("pass_at_1_hits", 0))
|
| 494 |
+
merged["pass_at_k_hits"] += int(bucket.get("pass_at_k_hits", 0))
|
| 495 |
+
merged["exact_at_1_hits"] += int(bucket.get("exact_at_1_hits", 0))
|
| 496 |
+
merged["exact_at_k_hits"] += int(bucket.get("exact_at_k_hits", 0))
|
| 497 |
+
merged["boxed_at_k_hits"] += int(bucket.get("boxed_at_k_hits", 0))
|
| 498 |
+
return merged
|
| 499 |
+
|
| 500 |
+
|
| 501 |
def resolve_eval_file(arg_eval_file: Optional[Path], cfg: Dict[str, Any]) -> Path:
|
| 502 |
if arg_eval_file is not None:
|
| 503 |
return arg_eval_file
|
|
|
|
| 559 |
totals = make_bucket()
|
| 560 |
family_buckets: Dict[str, Dict[str, Any]] = {}
|
| 561 |
difficulty_buckets: Dict[str, Dict[str, Any]] = {}
|
| 562 |
+
difficulty_band_buckets: Dict[str, Dict[str, Any]] = {}
|
| 563 |
+
response_profile_buckets: Dict[str, Dict[str, Any]] = {}
|
| 564 |
|
| 565 |
processed_rows = 0
|
| 566 |
skipped_no_expected = 0
|
|
|
|
| 629 |
boxedk=boxedk,
|
| 630 |
)
|
| 631 |
|
| 632 |
+
difficulty_band = canonical_difficulty(row.get("difficulty"))
|
| 633 |
+
if difficulty_band not in difficulty_band_buckets:
|
| 634 |
+
difficulty_band_buckets[difficulty_band] = make_bucket()
|
| 635 |
+
update_bucket(
|
| 636 |
+
difficulty_band_buckets[difficulty_band],
|
| 637 |
+
hit1=hit1,
|
| 638 |
+
hitk=hitk,
|
| 639 |
+
exact1=exact1,
|
| 640 |
+
exactk=exactk,
|
| 641 |
+
boxedk=boxedk,
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
response_profile = infer_response_profile(row)
|
| 645 |
+
if response_profile not in response_profile_buckets:
|
| 646 |
+
response_profile_buckets[response_profile] = make_bucket()
|
| 647 |
+
update_bucket(
|
| 648 |
+
response_profile_buckets[response_profile],
|
| 649 |
+
hit1=hit1,
|
| 650 |
+
hitk=hitk,
|
| 651 |
+
exact1=exact1,
|
| 652 |
+
exactk=exactk,
|
| 653 |
+
boxedk=boxedk,
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
processed_rows += 1
|
| 657 |
if args.progress_every > 0 and processed_rows % args.progress_every == 0:
|
| 658 |
print(f"Progress: evaluated_rows={processed_rows} latest_family={family}")
|
|
|
|
| 663 |
"uid": as_text(row.get("uid")),
|
| 664 |
"family": family,
|
| 665 |
"difficulty": difficulty,
|
| 666 |
+
"difficulty_band": difficulty_band,
|
| 667 |
+
"response_profile": response_profile,
|
| 668 |
"prompt": as_text(row.get(prompt_field)),
|
| 669 |
"expected_values": expected_values[:5],
|
| 670 |
"candidates": candidates,
|
|
|
|
| 684 |
|
| 685 |
composite_score = 0.30 * pass_at_1 + 0.50 * pass_at_k + 0.20 * exact_at_k
|
| 686 |
|
| 687 |
+
simple_bundle = merge_buckets(
|
| 688 |
+
[response_profile_buckets[key] for key in ("simple", "intermediate") if key in response_profile_buckets]
|
| 689 |
+
)
|
| 690 |
+
lean_bundle = merge_buckets(
|
| 691 |
+
[response_profile_buckets[key] for key in ("lean_formal",) if key in response_profile_buckets]
|
| 692 |
+
)
|
| 693 |
+
simple_metrics = finalize_bucket(simple_bundle)
|
| 694 |
+
lean_metrics = finalize_bucket(lean_bundle)
|
| 695 |
+
simple_to_lean = {
|
| 696 |
+
"simple_rows": simple_metrics["evaluated_rows"],
|
| 697 |
+
"simple_pass_at_k": simple_metrics["pass_at_k"],
|
| 698 |
+
"lean_formal_rows": lean_metrics["evaluated_rows"],
|
| 699 |
+
"lean_formal_pass_at_k": lean_metrics["pass_at_k"],
|
| 700 |
+
"pass_at_k_gap_simple_minus_lean": simple_metrics["pass_at_k"] - lean_metrics["pass_at_k"],
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
report: Dict[str, Any] = {
|
| 704 |
"base_model": base_model,
|
| 705 |
"adapter_path": str(args.adapter_path) if args.adapter_path is not None else None,
|
|
|
|
| 728 |
key: finalize_bucket(difficulty_buckets[key])
|
| 729 |
for key in sorted(difficulty_buckets.keys())
|
| 730 |
},
|
| 731 |
+
"difficulty_band_metrics": {
|
| 732 |
+
key: finalize_bucket(difficulty_band_buckets[key])
|
| 733 |
+
for key in sorted(difficulty_band_buckets.keys())
|
| 734 |
+
},
|
| 735 |
+
"response_profile_metrics": {
|
| 736 |
+
key: finalize_bucket(response_profile_buckets[key])
|
| 737 |
+
for key in sorted(response_profile_buckets.keys())
|
| 738 |
+
},
|
| 739 |
+
"simple_to_lean": simple_to_lean,
|
| 740 |
"samples": samples,
|
| 741 |
}
|
| 742 |
|
scripts/train_sota.py
CHANGED
|
@@ -127,6 +127,93 @@ def as_bool(value: Any, default: bool = False) -> bool:
|
|
| 127 |
return default
|
| 128 |
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
def load_config(path: Path) -> Dict[str, Any]:
|
| 131 |
if not path.exists():
|
| 132 |
raise FileNotFoundError(f"Config not found: {path}")
|
|
@@ -239,6 +326,10 @@ def build_user_block(row: Dict[str, Any], data_cfg: Dict[str, Any]) -> str:
|
|
| 239 |
tag_text = ", ".join(as_text(tag) for tag in tags if as_text(tag))
|
| 240 |
if tag_text:
|
| 241 |
meta_lines.append(f"Tags: {tag_text}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
if not meta_lines:
|
| 243 |
return prompt
|
| 244 |
return f"{prompt}\n\nMetadata:\n" + "\n".join(meta_lines)
|
|
@@ -291,6 +382,13 @@ def compute_loss_weight(row: Dict[str, Any], data_cfg: Dict[str, Any]) -> float:
|
|
| 291 |
family_boost = data_cfg.get("family_boost", {})
|
| 292 |
if isinstance(family_boost, dict):
|
| 293 |
base *= as_float(family_boost.get(family), 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
min_w = as_float(data_cfg.get("min_loss_weight"), 0.1)
|
| 295 |
max_w = as_float(data_cfg.get("max_loss_weight"), 8.0)
|
| 296 |
if min_w > max_w:
|
|
@@ -331,7 +429,14 @@ def apply_filters(dataset: Dataset, filter_cfg: Dict[str, Any]) -> Dataset:
|
|
| 331 |
exclude_families = set(filter_cfg.get("exclude_families", []) or [])
|
| 332 |
include_task_types = set(filter_cfg.get("include_task_types", []) or [])
|
| 333 |
source_datasets = set(filter_cfg.get("source_datasets", []) or [])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
require_conjecture_id = bool(filter_cfg.get("require_conjecture_id", False))
|
|
|
|
| 335 |
min_sample_weight = filter_cfg.get("min_sample_weight")
|
| 336 |
min_sample_weight = as_float(min_sample_weight, 0.0) if min_sample_weight is not None else None
|
| 337 |
|
|
@@ -349,10 +454,17 @@ def apply_filters(dataset: Dataset, filter_cfg: Dict[str, Any]) -> Dataset:
|
|
| 349 |
source = as_text(row.get("source_dataset"))
|
| 350 |
if source not in source_datasets:
|
| 351 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
if require_conjecture_id:
|
| 353 |
conjecture_id = as_text(row.get("conjecture_id"))
|
| 354 |
if not conjecture_id or conjecture_id.lower() == "null":
|
| 355 |
return False
|
|
|
|
|
|
|
| 356 |
if min_sample_weight is not None:
|
| 357 |
sample_weight = as_float(row.get("sample_weight"), 0.0)
|
| 358 |
if sample_weight < min_sample_weight:
|
|
|
|
| 127 |
return default
|
| 128 |
|
| 129 |
|
| 130 |
+
def canonical_difficulty(value: Any) -> str:
|
| 131 |
+
raw = as_text(value).lower()
|
| 132 |
+
if not raw or raw in {"unknown", "null", "none", "nan", "n/a"}:
|
| 133 |
+
return "general"
|
| 134 |
+
|
| 135 |
+
if any(key in raw for key in ("lean", "formal", "theorem", "proof", "mathlib", "proofnet")):
|
| 136 |
+
return "lean_formal"
|
| 137 |
+
if any(
|
| 138 |
+
key in raw
|
| 139 |
+
for key in (
|
| 140 |
+
"basic",
|
| 141 |
+
"easy",
|
| 142 |
+
"elementary",
|
| 143 |
+
"arithmetic",
|
| 144 |
+
"level 1",
|
| 145 |
+
"level1",
|
| 146 |
+
"level 2",
|
| 147 |
+
"level2",
|
| 148 |
+
)
|
| 149 |
+
):
|
| 150 |
+
return "simple"
|
| 151 |
+
if any(key in raw for key in ("intermediate", "medium", "basic_to_intermediate", "level 3", "level3")):
|
| 152 |
+
return "intermediate"
|
| 153 |
+
if any(
|
| 154 |
+
key in raw
|
| 155 |
+
for key in (
|
| 156 |
+
"advanced",
|
| 157 |
+
"hard",
|
| 158 |
+
"frontier",
|
| 159 |
+
"olympiad",
|
| 160 |
+
"competition",
|
| 161 |
+
"level 4",
|
| 162 |
+
"level4",
|
| 163 |
+
"level 5",
|
| 164 |
+
"level5",
|
| 165 |
+
"geometry",
|
| 166 |
+
"algebra",
|
| 167 |
+
"calculus",
|
| 168 |
+
"number theory",
|
| 169 |
+
"combinatorics",
|
| 170 |
+
"inequalities",
|
| 171 |
+
)
|
| 172 |
+
):
|
| 173 |
+
return "advanced"
|
| 174 |
+
return "general"
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def is_lean_formal_row(row: Dict[str, Any]) -> bool:
|
| 178 |
+
family = as_text(row.get("family")).lower()
|
| 179 |
+
if family == "formal_proof":
|
| 180 |
+
return True
|
| 181 |
+
task_type = as_text(row.get("task_type")).lower()
|
| 182 |
+
if any(token in task_type for token in ("lean", "formal", "theorem", "proof")):
|
| 183 |
+
return True
|
| 184 |
+
if as_text(row.get("proof_formal")):
|
| 185 |
+
return True
|
| 186 |
+
return canonical_difficulty(row.get("difficulty")) == "lean_formal"
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def infer_response_profile(row: Dict[str, Any]) -> str:
|
| 190 |
+
if is_lean_formal_row(row):
|
| 191 |
+
return "lean_formal"
|
| 192 |
+
band = canonical_difficulty(row.get("difficulty"))
|
| 193 |
+
if band != "general":
|
| 194 |
+
return band
|
| 195 |
+
family = as_text(row.get("family")).lower()
|
| 196 |
+
if family == "structured_reasoning":
|
| 197 |
+
return "intermediate"
|
| 198 |
+
if family in {"conjecture_core", "competition"}:
|
| 199 |
+
return "advanced"
|
| 200 |
+
return "general"
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def response_contract(profile: str) -> str:
|
| 204 |
+
if profile == "simple":
|
| 205 |
+
return "Use plain language, short steps, and end with one clear final answer."
|
| 206 |
+
if profile == "intermediate":
|
| 207 |
+
return "Give a compact derivation with key equations and a clean final conclusion."
|
| 208 |
+
if profile == "advanced":
|
| 209 |
+
return "Provide rigorous reasoning, explicit assumptions, and uncertainty when unresolved."
|
| 210 |
+
if profile == "lean_formal":
|
| 211 |
+
return (
|
| 212 |
+
"Provide a proof sketch plus a Lean-oriented theorem/lemma structure and verification plan."
|
| 213 |
+
)
|
| 214 |
+
return "Provide technically correct reasoning and separate facts from conjectural claims."
|
| 215 |
+
|
| 216 |
+
|
| 217 |
def load_config(path: Path) -> Dict[str, Any]:
|
| 218 |
if not path.exists():
|
| 219 |
raise FileNotFoundError(f"Config not found: {path}")
|
|
|
|
| 326 |
tag_text = ", ".join(as_text(tag) for tag in tags if as_text(tag))
|
| 327 |
if tag_text:
|
| 328 |
meta_lines.append(f"Tags: {tag_text}")
|
| 329 |
+
profile = infer_response_profile(row)
|
| 330 |
+
meta_lines.append(f"Difficulty band: {canonical_difficulty(row.get('difficulty'))}")
|
| 331 |
+
meta_lines.append(f"Response profile: {profile}")
|
| 332 |
+
meta_lines.append(f"Response contract: {response_contract(profile)}")
|
| 333 |
if not meta_lines:
|
| 334 |
return prompt
|
| 335 |
return f"{prompt}\n\nMetadata:\n" + "\n".join(meta_lines)
|
|
|
|
| 382 |
family_boost = data_cfg.get("family_boost", {})
|
| 383 |
if isinstance(family_boost, dict):
|
| 384 |
base *= as_float(family_boost.get(family), 1.0)
|
| 385 |
+
difficulty_boost = data_cfg.get("difficulty_boost", {})
|
| 386 |
+
if isinstance(difficulty_boost, dict):
|
| 387 |
+
band = canonical_difficulty(row.get("difficulty"))
|
| 388 |
+
base *= as_float(difficulty_boost.get(band), 1.0)
|
| 389 |
+
lean_bonus = as_float(data_cfg.get("lean_proof_bonus"), 1.0)
|
| 390 |
+
if lean_bonus > 0 and is_lean_formal_row(row):
|
| 391 |
+
base *= lean_bonus
|
| 392 |
min_w = as_float(data_cfg.get("min_loss_weight"), 0.1)
|
| 393 |
max_w = as_float(data_cfg.get("max_loss_weight"), 8.0)
|
| 394 |
if min_w > max_w:
|
|
|
|
| 429 |
exclude_families = set(filter_cfg.get("exclude_families", []) or [])
|
| 430 |
include_task_types = set(filter_cfg.get("include_task_types", []) or [])
|
| 431 |
source_datasets = set(filter_cfg.get("source_datasets", []) or [])
|
| 432 |
+
include_difficulty_bands = {
|
| 433 |
+
as_text(value).lower() for value in (filter_cfg.get("include_difficulty_bands", []) or []) if as_text(value)
|
| 434 |
+
}
|
| 435 |
+
exclude_difficulty_bands = {
|
| 436 |
+
as_text(value).lower() for value in (filter_cfg.get("exclude_difficulty_bands", []) or []) if as_text(value)
|
| 437 |
+
}
|
| 438 |
require_conjecture_id = bool(filter_cfg.get("require_conjecture_id", False))
|
| 439 |
+
require_lean_formal = bool(filter_cfg.get("require_lean_formal", False))
|
| 440 |
min_sample_weight = filter_cfg.get("min_sample_weight")
|
| 441 |
min_sample_weight = as_float(min_sample_weight, 0.0) if min_sample_weight is not None else None
|
| 442 |
|
|
|
|
| 454 |
source = as_text(row.get("source_dataset"))
|
| 455 |
if source not in source_datasets:
|
| 456 |
return False
|
| 457 |
+
difficulty_band = canonical_difficulty(row.get("difficulty"))
|
| 458 |
+
if include_difficulty_bands and difficulty_band not in include_difficulty_bands:
|
| 459 |
+
return False
|
| 460 |
+
if exclude_difficulty_bands and difficulty_band in exclude_difficulty_bands:
|
| 461 |
+
return False
|
| 462 |
if require_conjecture_id:
|
| 463 |
conjecture_id = as_text(row.get("conjecture_id"))
|
| 464 |
if not conjecture_id or conjecture_id.lower() == "null":
|
| 465 |
return False
|
| 466 |
+
if require_lean_formal and not is_lean_formal_row(row):
|
| 467 |
+
return False
|
| 468 |
if min_sample_weight is not None:
|
| 469 |
sample_weight = as_float(row.get("sample_weight"), 0.0)
|
| 470 |
if sample_weight < min_sample_weight:
|
tests/test_core_utils.py
CHANGED
|
@@ -10,15 +10,32 @@ import unittest
|
|
| 10 |
from unittest import mock
|
| 11 |
from pathlib import Path
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
ROOT = Path(__file__).resolve().parents[1]
|
| 14 |
if str(ROOT) not in sys.path:
|
| 15 |
sys.path.insert(0, str(ROOT))
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
|
|
|
|
| 22 |
class AppUtilityTests(unittest.TestCase):
|
| 23 |
def test_validate_repo_id_accepts_valid(self) -> None:
|
| 24 |
self.assertEqual(
|
|
@@ -90,6 +107,7 @@ class AppUtilityTests(unittest.TestCase):
|
|
| 90 |
self.assertTrue((records_dir / "run-20260102-030405.json").exists())
|
| 91 |
|
| 92 |
|
|
|
|
| 93 |
class EvalUtilityTests(unittest.TestCase):
|
| 94 |
def test_parse_numeric_fraction(self) -> None:
|
| 95 |
value = eval_sota.parse_numeric_value("3/4")
|
|
@@ -102,7 +120,15 @@ class EvalUtilityTests(unittest.TestCase):
|
|
| 102 |
self.assertTrue(result["match"])
|
| 103 |
self.assertTrue(result["boxed"] or result["exact"])
|
| 104 |
|
|
|
|
|
|
|
|
|
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
class TrainUtilityTests(unittest.TestCase):
|
| 107 |
def test_as_bool_conversions(self) -> None:
|
| 108 |
self.assertTrue(train_sota.as_bool("yes"))
|
|
@@ -110,6 +136,38 @@ class TrainUtilityTests(unittest.TestCase):
|
|
| 110 |
self.assertTrue(train_sota.as_bool(True))
|
| 111 |
self.assertFalse(train_sota.as_bool(None, default=False))
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
def test_build_tokenizer_falls_back_when_protobuf_missing(self) -> None:
|
| 114 |
class DummyTokenizer:
|
| 115 |
def __init__(self) -> None:
|
|
@@ -135,6 +193,7 @@ class TrainUtilityTests(unittest.TestCase):
|
|
| 135 |
self.assertEqual(tok.pad_token, "<eos>")
|
| 136 |
|
| 137 |
|
|
|
|
| 138 |
class EvalTokenizerFallbackTests(unittest.TestCase):
|
| 139 |
def test_eval_tokenizer_falls_back_when_protobuf_missing(self) -> None:
|
| 140 |
class DummyTokenizer:
|
|
@@ -171,6 +230,7 @@ class EvalTokenizerFallbackTests(unittest.TestCase):
|
|
| 171 |
self.assertEqual(tok.pad_token, "<eos>")
|
| 172 |
|
| 173 |
|
|
|
|
| 174 |
class ContinuousModeSafetyTests(unittest.TestCase):
|
| 175 |
def test_continuous_mode_halts_after_consecutive_failures(self) -> None:
|
| 176 |
original_max = app.CONTINUOUS_MAX_CONSECUTIVE_FAILURES
|
|
|
|
| 10 |
from unittest import mock
|
| 11 |
from pathlib import Path
|
| 12 |
|
| 13 |
+
try:
|
| 14 |
+
from datasets import Dataset
|
| 15 |
+
except ModuleNotFoundError: # pragma: no cover - optional test dependency in this environment
|
| 16 |
+
Dataset = None
|
| 17 |
+
|
| 18 |
ROOT = Path(__file__).resolve().parents[1]
|
| 19 |
if str(ROOT) not in sys.path:
|
| 20 |
sys.path.insert(0, str(ROOT))
|
| 21 |
|
| 22 |
+
try:
|
| 23 |
+
import app
|
| 24 |
+
except Exception: # pragma: no cover - optional test dependency in this environment
|
| 25 |
+
app = None
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
from scripts import eval_sota
|
| 29 |
+
except Exception: # pragma: no cover - optional test dependency in this environment
|
| 30 |
+
eval_sota = None
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
from scripts import train_sota
|
| 34 |
+
except Exception: # pragma: no cover - optional test dependency in this environment
|
| 35 |
+
train_sota = None
|
| 36 |
|
| 37 |
|
| 38 |
+
@unittest.skipUnless(app is not None, "app runtime dependencies are not installed")
|
| 39 |
class AppUtilityTests(unittest.TestCase):
|
| 40 |
def test_validate_repo_id_accepts_valid(self) -> None:
|
| 41 |
self.assertEqual(
|
|
|
|
| 107 |
self.assertTrue((records_dir / "run-20260102-030405.json").exists())
|
| 108 |
|
| 109 |
|
| 110 |
+
@unittest.skipUnless(eval_sota is not None, "eval_sota runtime dependencies are not installed")
|
| 111 |
class EvalUtilityTests(unittest.TestCase):
|
| 112 |
def test_parse_numeric_fraction(self) -> None:
|
| 113 |
value = eval_sota.parse_numeric_value("3/4")
|
|
|
|
| 120 |
self.assertTrue(result["match"])
|
| 121 |
self.assertTrue(result["boxed"] or result["exact"])
|
| 122 |
|
| 123 |
+
def test_infer_response_profile_handles_formal_and_non_formal_rows(self) -> None:
|
| 124 |
+
formal_row = {"family": "formal_proof", "difficulty": "formal_proof"}
|
| 125 |
+
simple_row = {"family": "problem_solving", "difficulty": "basic"}
|
| 126 |
|
| 127 |
+
self.assertEqual(eval_sota.infer_response_profile(formal_row), "lean_formal")
|
| 128 |
+
self.assertEqual(eval_sota.infer_response_profile(simple_row), "simple")
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@unittest.skipUnless(train_sota is not None, "train_sota runtime dependencies are not installed")
|
| 132 |
class TrainUtilityTests(unittest.TestCase):
|
| 133 |
def test_as_bool_conversions(self) -> None:
|
| 134 |
self.assertTrue(train_sota.as_bool("yes"))
|
|
|
|
| 136 |
self.assertTrue(train_sota.as_bool(True))
|
| 137 |
self.assertFalse(train_sota.as_bool(None, default=False))
|
| 138 |
|
| 139 |
+
def test_canonical_difficulty_mappings(self) -> None:
|
| 140 |
+
self.assertEqual(train_sota.canonical_difficulty("basic_to_intermediate"), "simple")
|
| 141 |
+
self.assertEqual(train_sota.canonical_difficulty("formal_proof"), "lean_formal")
|
| 142 |
+
self.assertEqual(train_sota.canonical_difficulty("olympiad"), "advanced")
|
| 143 |
+
|
| 144 |
+
def test_apply_filters_include_bands_and_require_lean_formal(self) -> None:
|
| 145 |
+
if Dataset is None:
|
| 146 |
+
self.skipTest("datasets is not installed")
|
| 147 |
+
|
| 148 |
+
dataset = Dataset.from_dict(
|
| 149 |
+
{
|
| 150 |
+
"family": ["formal_proof", "problem_solving", "competition"],
|
| 151 |
+
"task_type": ["theorem_proving", "word_problem", "olympiad"],
|
| 152 |
+
"source_dataset": ["src-a", "src-b", "src-c"],
|
| 153 |
+
"difficulty": ["formal_proof", "basic_to_intermediate", "olympiad"],
|
| 154 |
+
"conjecture_id": ["c1", "c2", "c3"],
|
| 155 |
+
"sample_weight": [1.0, 1.0, 1.0],
|
| 156 |
+
}
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
filtered = train_sota.apply_filters(
|
| 160 |
+
dataset,
|
| 161 |
+
{
|
| 162 |
+
"include_difficulty_bands": ["lean_formal", "simple"],
|
| 163 |
+
"require_lean_formal": True,
|
| 164 |
+
},
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
self.assertEqual(len(filtered), 1)
|
| 168 |
+
self.assertEqual(filtered[0]["family"], "formal_proof")
|
| 169 |
+
self.assertEqual(filtered[0]["difficulty"], "formal_proof")
|
| 170 |
+
|
| 171 |
def test_build_tokenizer_falls_back_when_protobuf_missing(self) -> None:
|
| 172 |
class DummyTokenizer:
|
| 173 |
def __init__(self) -> None:
|
|
|
|
| 193 |
self.assertEqual(tok.pad_token, "<eos>")
|
| 194 |
|
| 195 |
|
| 196 |
+
@unittest.skipUnless(eval_sota is not None, "eval_sota runtime dependencies are not installed")
|
| 197 |
class EvalTokenizerFallbackTests(unittest.TestCase):
|
| 198 |
def test_eval_tokenizer_falls_back_when_protobuf_missing(self) -> None:
|
| 199 |
class DummyTokenizer:
|
|
|
|
| 230 |
self.assertEqual(tok.pad_token, "<eos>")
|
| 231 |
|
| 232 |
|
| 233 |
+
@unittest.skipUnless(app is not None, "app runtime dependencies are not installed")
|
| 234 |
class ContinuousModeSafetyTests(unittest.TestCase):
|
| 235 |
def test_continuous_mode_halts_after_consecutive_failures(self) -> None:
|
| 236 |
original_max = app.CONTINUOUS_MAX_CONSECUTIVE_FAILURES
|