Spaces:
Sleeping
Sleeping
Make training resilient to bad problem templates
Browse files- env/generator.py +85 -33
- scripts/test_dataset_mode.py +22 -1
env/generator.py
CHANGED
|
@@ -181,47 +181,59 @@ class GeneratorAgent:
|
|
| 181 |
return normalized_problem
|
| 182 |
|
| 183 |
rng = self._rng_for(target_tier, history, problem_id, family_weights or {})
|
| 184 |
-
|
| 185 |
target_tier,
|
| 186 |
history,
|
| 187 |
rng,
|
| 188 |
forced_problem_type=problem_id,
|
| 189 |
family_weights=family_weights or {},
|
| 190 |
)
|
|
|
|
| 191 |
|
| 192 |
-
for
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
def generate(
|
| 227 |
self,
|
|
@@ -268,6 +280,46 @@ class GeneratorAgent:
|
|
| 268 |
|
| 269 |
return rng.choices(eligible, weights=weights, k=1)[0]
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
def _rng_for(
|
| 272 |
self,
|
| 273 |
tier: int,
|
|
|
|
| 181 |
return normalized_problem
|
| 182 |
|
| 183 |
rng = self._rng_for(target_tier, history, problem_id, family_weights or {})
|
| 184 |
+
templates_to_try = self._candidate_templates_for_generation(
|
| 185 |
target_tier,
|
| 186 |
history,
|
| 187 |
rng,
|
| 188 |
forced_problem_type=problem_id,
|
| 189 |
family_weights=family_weights or {},
|
| 190 |
)
|
| 191 |
+
failures: list[str] = []
|
| 192 |
|
| 193 |
+
for template in templates_to_try:
|
| 194 |
+
for _ in range(20):
|
| 195 |
+
try:
|
| 196 |
+
raw_cases = template.case_builder(rng)
|
| 197 |
+
test_cases = [
|
| 198 |
+
{
|
| 199 |
+
"input": case_input,
|
| 200 |
+
"output": template.solver(case_input),
|
| 201 |
+
"is_visible": index < VISIBLE_TEST_COUNT,
|
| 202 |
+
}
|
| 203 |
+
for index, case_input in enumerate(raw_cases)
|
| 204 |
+
]
|
| 205 |
+
signature = self._problem_signature(template.problem_type, test_cases)
|
| 206 |
+
statement = template.statement_builder()
|
| 207 |
+
problem = {
|
| 208 |
+
"problem_id": f"{template.problem_type}_{signature[:8]}",
|
| 209 |
+
"problem_type": template.problem_type,
|
| 210 |
+
"difficulty": round(self._tier_to_scalar(target_tier), 4),
|
| 211 |
+
"difficulty_label": DIFFICULTY_LABELS[target_tier],
|
| 212 |
+
"problem": statement,
|
| 213 |
+
"input_format": template.input_format,
|
| 214 |
+
"constraints": template.constraints,
|
| 215 |
+
"test_cases": test_cases,
|
| 216 |
+
"visible_problem": {
|
| 217 |
+
"problem": statement,
|
| 218 |
+
"input_format": template.input_format,
|
| 219 |
+
"constraints": template.constraints,
|
| 220 |
+
},
|
| 221 |
+
"generation_mode": "deterministic_fallback" if self.deterministic else "local_rule_based",
|
| 222 |
+
"validity_bonus": 0.15,
|
| 223 |
+
}
|
| 224 |
+
normalized_problem = normalize_problem(problem)
|
| 225 |
+
if validate_problem(normalized_problem):
|
| 226 |
+
return normalized_problem
|
| 227 |
+
except Exception as exc:
|
| 228 |
+
failures.append(f"{template.problem_type}: {exc}")
|
| 229 |
+
break
|
| 230 |
+
|
| 231 |
+
failures.append(f"{template.problem_type}: invalid after retries")
|
| 232 |
|
| 233 |
+
raise ValueError(
|
| 234 |
+
"Unable to generate a valid problem across candidate templates. "
|
| 235 |
+
f"Last failures: {failures[-5:]}"
|
| 236 |
+
)
|
| 237 |
|
| 238 |
def generate(
|
| 239 |
self,
|
|
|
|
| 280 |
|
| 281 |
return rng.choices(eligible, weights=weights, k=1)[0]
|
| 282 |
|
| 283 |
+
def _candidate_templates_for_generation(
|
| 284 |
+
self,
|
| 285 |
+
tier: int,
|
| 286 |
+
history: dict[str, Any],
|
| 287 |
+
rng: random.Random,
|
| 288 |
+
forced_problem_type: str | None = None,
|
| 289 |
+
family_weights: dict[str, float] | None = None,
|
| 290 |
+
) -> list[ProblemTemplate]:
|
| 291 |
+
if forced_problem_type:
|
| 292 |
+
forced_matches = [template for template in self.templates if template.problem_type == forced_problem_type]
|
| 293 |
+
if forced_matches:
|
| 294 |
+
return forced_matches
|
| 295 |
+
|
| 296 |
+
eligible = [template for template in self.templates if template.difficulty_tier == tier]
|
| 297 |
+
if not eligible:
|
| 298 |
+
eligible = list(self.templates)
|
| 299 |
+
|
| 300 |
+
primary = self._choose_template(
|
| 301 |
+
tier,
|
| 302 |
+
history,
|
| 303 |
+
rng,
|
| 304 |
+
forced_problem_type=forced_problem_type,
|
| 305 |
+
family_weights=family_weights or {},
|
| 306 |
+
)
|
| 307 |
+
remaining_eligible = [template for template in eligible if template != primary]
|
| 308 |
+
rng.shuffle(remaining_eligible)
|
| 309 |
+
|
| 310 |
+
fallback_templates = [template for template in self.templates if template not in eligible and template != primary]
|
| 311 |
+
rng.shuffle(fallback_templates)
|
| 312 |
+
|
| 313 |
+
ordered = [primary, *remaining_eligible, *fallback_templates]
|
| 314 |
+
deduped: list[ProblemTemplate] = []
|
| 315 |
+
seen_problem_types: set[str] = set()
|
| 316 |
+
for template in ordered:
|
| 317 |
+
if template.problem_type in seen_problem_types:
|
| 318 |
+
continue
|
| 319 |
+
seen_problem_types.add(template.problem_type)
|
| 320 |
+
deduped.append(template)
|
| 321 |
+
return deduped
|
| 322 |
+
|
| 323 |
def _rng_for(
|
| 324 |
self,
|
| 325 |
tier: int,
|
scripts/test_dataset_mode.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
import sys
|
| 4 |
from pathlib import Path
|
|
|
|
| 5 |
|
| 6 |
ROOT = Path(__file__).resolve().parents[1]
|
| 7 |
if str(ROOT) not in sys.path:
|
|
@@ -9,7 +10,7 @@ if str(ROOT) not in sys.path:
|
|
| 9 |
|
| 10 |
from env import dataset_loader
|
| 11 |
from env.adapt_env import AdaptEnvironment
|
| 12 |
-
from env.generator import GeneratorAgent, validate_problem
|
| 13 |
from env.test_cases import load_problem_bank
|
| 14 |
from models import AdaptAction
|
| 15 |
|
|
@@ -107,6 +108,26 @@ def main() -> None:
|
|
| 107 |
assert injected_result.execution_status == "completed"
|
| 108 |
assert injected_result.reward > 0.0
|
| 109 |
assert 0.0 <= injected_result.reward_components.get("efficiency_score", -1.0) <= 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
print("Dataset mode smoke tests passed")
|
| 111 |
finally:
|
| 112 |
dataset_loader._BANK = original_bank
|
|
|
|
| 2 |
|
| 3 |
import sys
|
| 4 |
from pathlib import Path
|
| 5 |
+
from unittest.mock import patch
|
| 6 |
|
| 7 |
ROOT = Path(__file__).resolve().parents[1]
|
| 8 |
if str(ROOT) not in sys.path:
|
|
|
|
| 10 |
|
| 11 |
from env import dataset_loader
|
| 12 |
from env.adapt_env import AdaptEnvironment
|
| 13 |
+
from env.generator import GeneratorAgent, ProblemTemplate, validate_problem
|
| 14 |
from env.test_cases import load_problem_bank
|
| 15 |
from models import AdaptAction
|
| 16 |
|
|
|
|
| 108 |
assert injected_result.execution_status == "completed"
|
| 109 |
assert injected_result.reward > 0.0
|
| 110 |
assert 0.0 <= injected_result.reward_components.get("efficiency_score", -1.0) <= 1.0
|
| 111 |
+
|
| 112 |
+
resilient_agent = GeneratorAgent()
|
| 113 |
+
good_template = next(template for template in resilient_agent.templates if template.problem_type == "sum_even_numbers")
|
| 114 |
+
bad_template = ProblemTemplate(
|
| 115 |
+
problem_type="always_bad",
|
| 116 |
+
difficulty_tier=good_template.difficulty_tier,
|
| 117 |
+
title="Always Bad",
|
| 118 |
+
input_format=good_template.input_format,
|
| 119 |
+
constraints=good_template.constraints,
|
| 120 |
+
statement_builder=lambda: "Bad template used for fallback testing.",
|
| 121 |
+
solver=good_template.solver,
|
| 122 |
+
case_builder=lambda rng: ["1\n1\n"] * 10,
|
| 123 |
+
)
|
| 124 |
+
resilient_agent.templates = [bad_template, *resilient_agent.templates]
|
| 125 |
+
|
| 126 |
+
with patch.object(resilient_agent, "_choose_template", return_value=bad_template):
|
| 127 |
+
resilient_problem = resilient_agent.generate_problem("easy", {})
|
| 128 |
+
assert resilient_problem["problem_type"] != "always_bad"
|
| 129 |
+
assert validate_problem(resilient_problem)
|
| 130 |
+
|
| 131 |
print("Dataset mode smoke tests passed")
|
| 132 |
finally:
|
| 133 |
dataset_loader._BANK = original_bank
|