NorthernTribe-Research commited on
Commit
73f3887
·
verified ·
1 Parent(s): 1ed1d4f

Promotion Space deploy (2026-03-25 05:46 UTC)

Browse files
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: conjecture_answer_alignment
79
- max_train_samples: 110000
80
  max_eval_samples: 3000
81
  filters:
82
  include_families:
83
  - conjecture_core
84
  - formal_proof
85
- min_sample_weight: 2.0
 
 
 
 
 
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: 70000
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: conjecture_specialization
78
- max_train_samples: 100000
79
  max_eval_samples: 2500
80
  filters:
81
  include_families:
82
  - conjecture_core
83
  - formal_proof
84
- min_sample_weight: 2.0
 
 
 
 
 
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: 60000
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
- import app
18
- from scripts import eval_sota
19
- from scripts import train_sota
 
 
 
 
 
 
 
 
 
 
 
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