Spaces:
Sleeping
Sleeping
Smoke-train deployment update
Browse files- CODEBASE_STRUCTURE_MAIN_V5.md +1564 -0
- env/adapt_env.py +6 -1
- env/dataset_loader.py +316 -0
- env/generator.py +74 -3
- env/test_cases.py +7 -1
- pyproject.toml +1 -0
- requirements.txt +1 -0
- scripts/test_dataset_mode.py +93 -0
- scripts/test_verifier.py +22 -0
- server/app.py +3 -0
- test.py +2 -0
- training/train_grpo.py +52 -7
- verifier/complexity.py +177 -2
- verifier/verifier.py +22 -1
CODEBASE_STRUCTURE_MAIN_V5.md
ADDED
|
@@ -0,0 +1,1564 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADAPT Codebase Structure (LLM Working Map)
|
| 2 |
+
|
| 3 |
+
## Scope And Assumption
|
| 4 |
+
|
| 5 |
+
This document was generated from the repository rooted at `/Users/dishagoyal/Documents/meta-rl-dsa-solver`.
|
| 6 |
+
|
| 7 |
+
Important assumption:
|
| 8 |
+
- There is no literal `main-v5/` directory in this workspace.
|
| 9 |
+
- This document therefore treats the current repository as the codebase you meant by "main-v5".
|
| 10 |
+
|
| 11 |
+
Goal of this document:
|
| 12 |
+
- give an LLM enough structure to navigate, modify, and extend the codebase safely
|
| 13 |
+
- explain not just file names, but runtime flow, ownership boundaries, data models, and cross-module dependencies
|
| 14 |
+
- surface invariants and hidden coupling points that matter when changing the code
|
| 15 |
+
|
| 16 |
+
Repository size snapshot:
|
| 17 |
+
- Python/source/documentation lines inspected: about 6,981 lines across the main tracked source files
|
| 18 |
+
- Core subsystems: `env`, `verifier`, `training`, `server`, top-level client/inference/test entrypoints, and `scripts`
|
| 19 |
+
- Empty or currently unused directories: `rewards/`, `outputs/` in this snapshot
|
| 20 |
+
|
| 21 |
+
## One-Screen Summary
|
| 22 |
+
|
| 23 |
+
This repo implements **ADAPT**, an adversarial DSA tutoring environment for reinforcement learning and evaluation of code-generating models.
|
| 24 |
+
|
| 25 |
+
The system is organized around one central loop:
|
| 26 |
+
|
| 27 |
+
1. A problem family is chosen by the generator.
|
| 28 |
+
2. The environment exposes only the public statement plus two visible examples.
|
| 29 |
+
3. A model submits Python code.
|
| 30 |
+
4. The verifier statically checks the code, runs it in a subprocess sandbox, scores visible and hidden tests, and computes correctness/efficiency signals.
|
| 31 |
+
5. The environment converts those signals into repair-oriented feedback and an episode reward.
|
| 32 |
+
6. Training uses this environment as a reward source inside a GRPO loop.
|
| 33 |
+
7. The server layer exposes reset/step/state plus training/model management endpoints.
|
| 34 |
+
|
| 35 |
+
The three most important files in the repo are:
|
| 36 |
+
- `env/adapt_env.py`: the runtime environment and episode state machine
|
| 37 |
+
- `env/generator.py`: the full problem-family registry plus deterministic problem generation
|
| 38 |
+
- `training/train_grpo.py`: prompt construction, curriculum logic, reward hookup, evaluation, and GRPO training
|
| 39 |
+
|
| 40 |
+
## High-Level Architecture
|
| 41 |
+
|
| 42 |
+
```text
|
| 43 |
+
Problem Generator -> AdaptEnvironment -> Verifier -> Reward/Feedback
|
| 44 |
+
| | | |
|
| 45 |
+
| | | |
|
| 46 |
+
v v v v
|
| 47 |
+
Problem family Session state Complexity + Repair-aware
|
| 48 |
+
sampling + cases + observation sandboxing training signal
|
| 49 |
+
|
| 50 |
+
Server API / Space Runtime
|
| 51 |
+
|
|
| 52 |
+
v
|
| 53 |
+
Training jobs, model loading, code generation
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Top-Level Files And Directories
|
| 57 |
+
|
| 58 |
+
### Root
|
| 59 |
+
|
| 60 |
+
- `README.md`
|
| 61 |
+
Project narrative, architecture overview, run instructions, and problem-family summary.
|
| 62 |
+
|
| 63 |
+
- `pyproject.toml`
|
| 64 |
+
Main packaging and dependency definition.
|
| 65 |
+
Registers the installable packages: `env`, `server`, `training`, `verifier`.
|
| 66 |
+
Exposes script entrypoint `server = "server.app:main"`.
|
| 67 |
+
|
| 68 |
+
- `requirements.txt`
|
| 69 |
+
Inference/server/runtime dependencies only.
|
| 70 |
+
|
| 71 |
+
- `openenv.yaml`
|
| 72 |
+
OpenEnv metadata:
|
| 73 |
+
- runtime: `fastapi`
|
| 74 |
+
- app: `server.app:app`
|
| 75 |
+
- port: `7860`
|
| 76 |
+
|
| 77 |
+
- `Dockerfile`
|
| 78 |
+
Python 3.11 slim image, installs package with training extras, runs `uvicorn server.app:app`.
|
| 79 |
+
|
| 80 |
+
- `app.py`
|
| 81 |
+
Thin re-export of `server.app`.
|
| 82 |
+
|
| 83 |
+
- `client.py`
|
| 84 |
+
Local Python HTTP client for the environment and training endpoints.
|
| 85 |
+
|
| 86 |
+
- `models.py`
|
| 87 |
+
Pydantic schemas for actions, observations, and state.
|
| 88 |
+
|
| 89 |
+
- `inference.py`
|
| 90 |
+
Benchmark/evaluation runner that loops through tasks, calls an OpenAI-compatible API, and logs standardized `[START]/[STEP]/[END]` traces.
|
| 91 |
+
|
| 92 |
+
- `test.py`
|
| 93 |
+
Repository smoke-test aggregator; calls several script-level tests plus a verifier check.
|
| 94 |
+
|
| 95 |
+
### `env/`
|
| 96 |
+
|
| 97 |
+
Owns problem generation, environment state, subprocess execution, and the local Gradio demo.
|
| 98 |
+
|
| 99 |
+
- `env/adapt_env.py`
|
| 100 |
+
- `env/generator.py`
|
| 101 |
+
- `env/executor.py`
|
| 102 |
+
- `env/test_cases.py`
|
| 103 |
+
- `env/app.py`
|
| 104 |
+
|
| 105 |
+
### `verifier/`
|
| 106 |
+
|
| 107 |
+
Owns submission validation, execution scoring, reward metrics, and lightweight complexity analysis.
|
| 108 |
+
|
| 109 |
+
- `verifier/verifier.py`
|
| 110 |
+
- `verifier/sandbox.py`
|
| 111 |
+
- `verifier/metrics.py`
|
| 112 |
+
- `verifier/complexity.py`
|
| 113 |
+
|
| 114 |
+
### `training/`
|
| 115 |
+
|
| 116 |
+
Owns prompt formatting, curriculum and generator control, GRPO training, evaluation, trace artifacts, and plotting.
|
| 117 |
+
|
| 118 |
+
- `training/train_grpo.py`
|
| 119 |
+
- `training/trace_logging.py`
|
| 120 |
+
- `training/plot_results.py`
|
| 121 |
+
|
| 122 |
+
### `server/`
|
| 123 |
+
|
| 124 |
+
Owns the FastAPI service layer and Hugging Face Space runtime/training orchestration.
|
| 125 |
+
|
| 126 |
+
- `server/app.py`
|
| 127 |
+
- `server/runtime.py`
|
| 128 |
+
- `server/requirements.txt`
|
| 129 |
+
|
| 130 |
+
### `scripts/`
|
| 131 |
+
|
| 132 |
+
Operational and smoke-test scripts.
|
| 133 |
+
|
| 134 |
+
- `scripts/deploy_and_smoke_train.py`
|
| 135 |
+
- `scripts/test_env.py`
|
| 136 |
+
- `scripts/test_space_api.py`
|
| 137 |
+
- `scripts/test_trace_logging.py`
|
| 138 |
+
- `scripts/test_training_config.py`
|
| 139 |
+
- `scripts/test_verifier.py`
|
| 140 |
+
|
| 141 |
+
### Other
|
| 142 |
+
|
| 143 |
+
- `notebooks/colab_kaggle_smoke_train.ipynb`
|
| 144 |
+
Notebook-based smoke training support.
|
| 145 |
+
|
| 146 |
+
- `Qwen2.5_(3B)-GRPO.ipynb`
|
| 147 |
+
Large notebook artifact related to GRPO experimentation.
|
| 148 |
+
|
| 149 |
+
- `rewards/`
|
| 150 |
+
Present but empty in this snapshot.
|
| 151 |
+
|
| 152 |
+
- `outputs/`
|
| 153 |
+
Present but empty in this snapshot.
|
| 154 |
+
|
| 155 |
+
## Core Runtime Concepts
|
| 156 |
+
|
| 157 |
+
### 1. Problem
|
| 158 |
+
|
| 159 |
+
A generated problem is a dictionary produced mainly by `GeneratorAgent.generate_problem(...)` in `env/generator.py`.
|
| 160 |
+
|
| 161 |
+
Expected keys:
|
| 162 |
+
- `problem_id`
|
| 163 |
+
- `problem_type`
|
| 164 |
+
- `difficulty`
|
| 165 |
+
- `difficulty_label`
|
| 166 |
+
- `problem`
|
| 167 |
+
- `input_format`
|
| 168 |
+
- `constraints`
|
| 169 |
+
- `test_cases`
|
| 170 |
+
- `visible_problem`
|
| 171 |
+
- `generation_mode`
|
| 172 |
+
- `validity_bonus`
|
| 173 |
+
|
| 174 |
+
Important split:
|
| 175 |
+
- `test_cases` contains both visible and hidden tests
|
| 176 |
+
- `visible_problem` is the sanitized public-facing subset
|
| 177 |
+
- hidden tests must never leak into the observation payload
|
| 178 |
+
|
| 179 |
+
### 2. Episode
|
| 180 |
+
|
| 181 |
+
An episode is one problem solved over up to `MAX_STEPS_PER_EPISODE = 3` attempts.
|
| 182 |
+
|
| 183 |
+
Attempt flow:
|
| 184 |
+
- attempt 1: initial submission
|
| 185 |
+
- attempt 2/3: repair attempt based on feedback
|
| 186 |
+
- episode ends early when hidden correctness reaches 1.0 and efficiency target is met, or after step 3
|
| 187 |
+
|
| 188 |
+
### 3. Reward
|
| 189 |
+
|
| 190 |
+
There are two related reward layers:
|
| 191 |
+
|
| 192 |
+
- verifier-level reward in `verifier.metrics.compute_pass_rate(...)`
|
| 193 |
+
Used as raw execution-derived correctness reward.
|
| 194 |
+
|
| 195 |
+
- environment episode reward in `verifier.metrics.compute_episode_reward(...)`
|
| 196 |
+
This is the more important repair-aware reward used by `AdaptEnvironment`.
|
| 197 |
+
It accounts for:
|
| 198 |
+
- hidden pass rate
|
| 199 |
+
- step discount
|
| 200 |
+
- improvement over previous attempt
|
| 201 |
+
- efficiency score
|
| 202 |
+
- optimization completion target
|
| 203 |
+
|
| 204 |
+
### 4. Generator Reward Signal
|
| 205 |
+
|
| 206 |
+
`env.generator.generator_reward(...)` measures how educational a generated problem is, targeting pass rates near 0.5.
|
| 207 |
+
|
| 208 |
+
This signal is used by training-side reward-aware family sampling:
|
| 209 |
+
- productive families become more likely to be sampled
|
| 210 |
+
- implemented by `GeneratorController.family_productivity`
|
| 211 |
+
|
| 212 |
+
## Shared Schemas (`models.py`)
|
| 213 |
+
|
| 214 |
+
### `AdaptAction`
|
| 215 |
+
|
| 216 |
+
Fields:
|
| 217 |
+
- `session_id`
|
| 218 |
+
- `code`
|
| 219 |
+
|
| 220 |
+
Meaning:
|
| 221 |
+
- server-routed step requests require a session id
|
| 222 |
+
- local environment calls may omit it because the environment can self-bootstrap
|
| 223 |
+
|
| 224 |
+
### `AdaptObservation`
|
| 225 |
+
|
| 226 |
+
This is the main environment-facing payload.
|
| 227 |
+
|
| 228 |
+
Key fields:
|
| 229 |
+
- identity/context:
|
| 230 |
+
- `session_id`
|
| 231 |
+
- `problem_id`
|
| 232 |
+
- `problem_type`
|
| 233 |
+
- `difficulty`
|
| 234 |
+
- episode progress:
|
| 235 |
+
- `attempt_number`
|
| 236 |
+
- `max_steps`
|
| 237 |
+
- `done`
|
| 238 |
+
- public task content:
|
| 239 |
+
- `problem`
|
| 240 |
+
- `input_format`
|
| 241 |
+
- `constraints`
|
| 242 |
+
- `feedback`
|
| 243 |
+
- evaluation signals:
|
| 244 |
+
- `pass_rate`
|
| 245 |
+
- `visible_pass_rate`
|
| 246 |
+
- `hidden_pass_rate`
|
| 247 |
+
- `syntax_valid`
|
| 248 |
+
- `execution_status`
|
| 249 |
+
- `timeout_count`
|
| 250 |
+
- `runtime_error_count`
|
| 251 |
+
- `invalid_output_count`
|
| 252 |
+
- `wrong_answer_count`
|
| 253 |
+
- `format_compliance`
|
| 254 |
+
- `reward_components`
|
| 255 |
+
- `generator_reward_signal`
|
| 256 |
+
- `reward`
|
| 257 |
+
|
| 258 |
+
### `AdaptState`
|
| 259 |
+
|
| 260 |
+
Server/state snapshot object; broader than the observation.
|
| 261 |
+
|
| 262 |
+
Important fields:
|
| 263 |
+
- active problem metadata
|
| 264 |
+
- generator mode
|
| 265 |
+
- generated public problem
|
| 266 |
+
- last reward / pass rate / feedback / execution status
|
| 267 |
+
- attempt history
|
| 268 |
+
- recent metrics
|
| 269 |
+
- generator reward signal
|
| 270 |
+
|
| 271 |
+
## Subsystem 1: Environment (`env/`)
|
| 272 |
+
|
| 273 |
+
### `env/adapt_env.py`
|
| 274 |
+
|
| 275 |
+
This is the environment state machine and the single most important runtime module.
|
| 276 |
+
|
| 277 |
+
#### Main class: `AdaptEnvironment`
|
| 278 |
+
|
| 279 |
+
Inheritance:
|
| 280 |
+
- attempts to inherit from `openenv.core.env_server.interfaces.Environment`
|
| 281 |
+
- falls back to a local generic stub if OpenEnv is unavailable
|
| 282 |
+
|
| 283 |
+
Important constants:
|
| 284 |
+
- `MAX_STEPS_PER_EPISODE = 3`
|
| 285 |
+
- `TARGET_EFFICIENCY_SCORE = 0.95`
|
| 286 |
+
|
| 287 |
+
Internal mutable state:
|
| 288 |
+
- `self.generator`
|
| 289 |
+
- `self.generator_mode`
|
| 290 |
+
- `self.session_id`
|
| 291 |
+
- `self.problem`
|
| 292 |
+
- `self.test_cases`
|
| 293 |
+
- `self.last_results`
|
| 294 |
+
- `self.history`
|
| 295 |
+
- `self.attempt_history`
|
| 296 |
+
- `self.previous_execution_status`
|
| 297 |
+
- `self.episode_done`
|
| 298 |
+
- `self._state` (`AdaptState`)
|
| 299 |
+
|
| 300 |
+
#### `reset(...)`
|
| 301 |
+
|
| 302 |
+
Responsibilities:
|
| 303 |
+
- optionally update session id / generator mode / difficulty
|
| 304 |
+
- choose or load a problem
|
| 305 |
+
- copy test cases into the environment
|
| 306 |
+
- clear prior attempt/results state
|
| 307 |
+
- initialize `AdaptState`
|
| 308 |
+
- return an initial observation with public problem text and instructions
|
| 309 |
+
|
| 310 |
+
Supports:
|
| 311 |
+
- forced `problem_id`
|
| 312 |
+
- forced `difficulty`
|
| 313 |
+
- externally injected `generated_problem`
|
| 314 |
+
- family weighting via `family_weights`
|
| 315 |
+
|
| 316 |
+
#### `step(action, ...)`
|
| 317 |
+
|
| 318 |
+
Responsibilities:
|
| 319 |
+
- lazy-reset if no problem is loaded
|
| 320 |
+
- return terminal "call reset" observation if episode already ended
|
| 321 |
+
- increment attempt number
|
| 322 |
+
- call `_verify_submission`
|
| 323 |
+
- compute hidden/visible pass rates and execution status
|
| 324 |
+
- compute repair-aware reward via `compute_episode_reward`
|
| 325 |
+
- build human-readable feedback
|
| 326 |
+
- record metrics/state
|
| 327 |
+
- finalize the episode if terminal
|
| 328 |
+
|
| 329 |
+
Terminal conditions:
|
| 330 |
+
- hidden pass rate is 1.0 and efficiency score >= 0.95
|
| 331 |
+
- or step number reaches 3
|
| 332 |
+
|
| 333 |
+
Important nuance:
|
| 334 |
+
- a fully correct but less efficient solution may produce `pass_rate == 1.0` but `done == False`
|
| 335 |
+
- the agent is then nudged to optimize further
|
| 336 |
+
|
| 337 |
+
#### `_verify_submission(...)`
|
| 338 |
+
|
| 339 |
+
Delegates to `verifier.verify(...)`.
|
| 340 |
+
|
| 341 |
+
Then augments the verifier metadata with:
|
| 342 |
+
- diversity bonus
|
| 343 |
+
- validity bonus
|
| 344 |
+
- generator reward signal
|
| 345 |
+
|
| 346 |
+
#### `_format_feedback(...)`
|
| 347 |
+
|
| 348 |
+
Produces repair-facing natural language feedback.
|
| 349 |
+
|
| 350 |
+
Behavior depends on status:
|
| 351 |
+
- syntax/safety violations get short static feedback
|
| 352 |
+
- other cases get:
|
| 353 |
+
- attempt number
|
| 354 |
+
- previous status
|
| 355 |
+
- current status
|
| 356 |
+
- hidden and visible pass rates
|
| 357 |
+
- efficiency score
|
| 358 |
+
- failed test summaries
|
| 359 |
+
- optional optimization hints
|
| 360 |
+
|
| 361 |
+
#### `_public_problem_view(...)`
|
| 362 |
+
|
| 363 |
+
Critical leakage-control function.
|
| 364 |
+
|
| 365 |
+
It uses:
|
| 366 |
+
- `visible_problem`
|
| 367 |
+
- formatted visible examples from `_format_examples()`
|
| 368 |
+
|
| 369 |
+
This is what gets surfaced to the agent.
|
| 370 |
+
Hidden tests stay internal.
|
| 371 |
+
|
| 372 |
+
#### History and curriculum-relevant state
|
| 373 |
+
|
| 374 |
+
`AdaptEnvironment` tracks:
|
| 375 |
+
- recent pass rates
|
| 376 |
+
- recent problem families
|
| 377 |
+
- generator rewards
|
| 378 |
+
- problem signatures
|
| 379 |
+
- episode index
|
| 380 |
+
|
| 381 |
+
This history influences future generation diversity and can be used by higher-level curriculum logic.
|
| 382 |
+
|
| 383 |
+
### `env/generator.py`
|
| 384 |
+
|
| 385 |
+
This file is the full problem bank, generator, case synthesis engine, and reference-solver library.
|
| 386 |
+
It is the largest single file in the repo.
|
| 387 |
+
|
| 388 |
+
#### Core constants
|
| 389 |
+
|
| 390 |
+
- `VISIBLE_TEST_COUNT = 2`
|
| 391 |
+
- `HIDDEN_TEST_COUNT = 8`
|
| 392 |
+
- `TOTAL_TEST_CASES = 10`
|
| 393 |
+
- `MIN_TEST_CASES = 10`
|
| 394 |
+
|
| 395 |
+
#### `ProblemTemplate`
|
| 396 |
+
|
| 397 |
+
Dataclass fields:
|
| 398 |
+
- `problem_type`
|
| 399 |
+
- `difficulty_tier`
|
| 400 |
+
- `title`
|
| 401 |
+
- `input_format`
|
| 402 |
+
- `constraints`
|
| 403 |
+
- `statement_builder`
|
| 404 |
+
- `solver`
|
| 405 |
+
- `case_builder`
|
| 406 |
+
|
| 407 |
+
This is the canonical template unit from which all problems are built.
|
| 408 |
+
|
| 409 |
+
#### `generator_reward(pass_rate, diversity_bonus, validity_bonus)`
|
| 410 |
+
|
| 411 |
+
Educational-value reward:
|
| 412 |
+
- highest when pass rate is near 0.5
|
| 413 |
+
- clipped into `[0.0, 1.5]`
|
| 414 |
+
- later used by the reward-aware family sampler in training
|
| 415 |
+
|
| 416 |
+
#### `validate_problem(problem_dict)`
|
| 417 |
+
|
| 418 |
+
Hard validation gate for generated problems.
|
| 419 |
+
|
| 420 |
+
Checks:
|
| 421 |
+
- required keys exist
|
| 422 |
+
- public text fields are non-empty
|
| 423 |
+
- scalar difficulty is between 0 and 1
|
| 424 |
+
- exactly 10 test cases
|
| 425 |
+
- visible/hidden ordering and flags are correct
|
| 426 |
+
- test inputs are unique
|
| 427 |
+
- output diversity is high enough to avoid degenerate problems
|
| 428 |
+
|
| 429 |
+
This function is critical if changing generator behavior.
|
| 430 |
+
|
| 431 |
+
#### `normalize_problem(problem_dict)`
|
| 432 |
+
|
| 433 |
+
Normalizes string fields and shallow copies test cases / visible problem data.
|
| 434 |
+
|
| 435 |
+
#### `GeneratorAgent`
|
| 436 |
+
|
| 437 |
+
Dependency-free deterministic generator by default.
|
| 438 |
+
|
| 439 |
+
Main behaviors:
|
| 440 |
+
- `generate_problem(...)`
|
| 441 |
+
- `generate(...)` thin alias
|
| 442 |
+
- `_choose_template(...)`
|
| 443 |
+
- `_rng_for(...)`
|
| 444 |
+
- `_problem_signature(...)`
|
| 445 |
+
- `_tier_to_scalar(...)`
|
| 446 |
+
|
| 447 |
+
Determinism strategy:
|
| 448 |
+
- hashes a seed material bundle containing difficulty, recent pass rates, recent problem types, episode index, and family weights
|
| 449 |
+
- creates a `random.Random` instance from that hash
|
| 450 |
+
|
| 451 |
+
This means generation is reproducible for the same history and settings.
|
| 452 |
+
|
| 453 |
+
#### Template registry
|
| 454 |
+
|
| 455 |
+
`_build_templates()` registers 20 problem families:
|
| 456 |
+
|
| 457 |
+
Easy:
|
| 458 |
+
- `sum_even_numbers`
|
| 459 |
+
- `range_span`
|
| 460 |
+
- `count_vowels`
|
| 461 |
+
- `max_consecutive_ones`
|
| 462 |
+
- `fizzbuzz_variant`
|
| 463 |
+
- `running_total`
|
| 464 |
+
|
| 465 |
+
Medium:
|
| 466 |
+
- `count_local_peaks`
|
| 467 |
+
- `longest_non_decreasing_run`
|
| 468 |
+
- `two_sum_count`
|
| 469 |
+
- `max_subarray_sum`
|
| 470 |
+
- `group_anagrams_count`
|
| 471 |
+
- `balanced_brackets`
|
| 472 |
+
- `matrix_diagonal_sum`
|
| 473 |
+
|
| 474 |
+
Hard:
|
| 475 |
+
- `smallest_most_frequent`
|
| 476 |
+
- `reverse_words`
|
| 477 |
+
- `longest_common_subsequence`
|
| 478 |
+
- `word_ladder_steps`
|
| 479 |
+
- `merge_intervals`
|
| 480 |
+
- `min_coins`
|
| 481 |
+
- `rotate_matrix_90`
|
| 482 |
+
|
| 483 |
+
For each family, the file contains:
|
| 484 |
+
- statement text
|
| 485 |
+
- input format
|
| 486 |
+
- constraints
|
| 487 |
+
- visible/hidden randomized case builders
|
| 488 |
+
- a reference solver used to derive expected outputs
|
| 489 |
+
|
| 490 |
+
#### Bottom half of the file
|
| 491 |
+
|
| 492 |
+
The remainder of `env/generator.py` contains:
|
| 493 |
+
- case factory helpers for each family
|
| 494 |
+
- solver implementations for each family
|
| 495 |
+
- parsers for input formats
|
| 496 |
+
- random instance builders for bracket/path/word-ladder style tasks
|
| 497 |
+
- formatting helpers for arrays, targets, intervals, matrices, coins, etc.
|
| 498 |
+
|
| 499 |
+
Practical meaning:
|
| 500 |
+
- if you add a new problem family, most of the work belongs here
|
| 501 |
+
- the generator is intentionally self-contained; it does not depend on external datasets
|
| 502 |
+
|
| 503 |
+
### `env/executor.py`
|
| 504 |
+
|
| 505 |
+
Owns actual subprocess execution of user code.
|
| 506 |
+
|
| 507 |
+
Execution strategy:
|
| 508 |
+
- writes submission code to a temporary file under `.adapt_tmp` or `ADAPT_TMP_DIR`
|
| 509 |
+
- runs `python -I -S submission.py`
|
| 510 |
+
- feeds stdin
|
| 511 |
+
- captures stdout/stderr
|
| 512 |
+
- enforces timeout
|
| 513 |
+
- truncates outputs to `OUTPUT_LIMIT_BYTES`
|
| 514 |
+
|
| 515 |
+
Important constants:
|
| 516 |
+
- timeout: `1.0s`
|
| 517 |
+
- memory limit: `512 MB`
|
| 518 |
+
- output limit: `256 KB`
|
| 519 |
+
|
| 520 |
+
Linux-only hardening:
|
| 521 |
+
- RLIMIT for address space, CPU, file size, and file descriptors
|
| 522 |
+
|
| 523 |
+
Portable hardening:
|
| 524 |
+
- isolated Python mode
|
| 525 |
+
- stripped environment variables
|
| 526 |
+
- temp HOME/TMP
|
| 527 |
+
|
| 528 |
+
### `env/test_cases.py`
|
| 529 |
+
|
| 530 |
+
Convenience layer over the generator.
|
| 531 |
+
|
| 532 |
+
Main functions:
|
| 533 |
+
- `load_problem_bank()`
|
| 534 |
+
Builds one representative problem per template for task listing.
|
| 535 |
+
- `load_problem(problem_id=None, difficulty=None)`
|
| 536 |
+
Loads or samples a single problem.
|
| 537 |
+
- `get_test_cases(...)`
|
| 538 |
+
Returns copied test cases.
|
| 539 |
+
- `split_test_cases(...)`
|
| 540 |
+
Splits visible vs hidden tests.
|
| 541 |
+
|
| 542 |
+
Server and inference logic depend on this file for task enumeration.
|
| 543 |
+
|
| 544 |
+
### `env/app.py`
|
| 545 |
+
|
| 546 |
+
Gradio demo app, separate from the FastAPI/OpenEnv server.
|
| 547 |
+
|
| 548 |
+
Global state:
|
| 549 |
+
- `TRAINING_MANAGER = SpaceTrainingManager()`
|
| 550 |
+
- `SESSIONS: dict[str, AdaptEnvironment]`
|
| 551 |
+
|
| 552 |
+
Main interactive functions:
|
| 553 |
+
- `_get_env(...)`
|
| 554 |
+
- `_problem_markdown(...)`
|
| 555 |
+
- `sample_problem(...)`
|
| 556 |
+
- `evaluate_submission(...)`
|
| 557 |
+
- `model_attempt(...)`
|
| 558 |
+
|
| 559 |
+
Use case:
|
| 560 |
+
- manual exploration of sampled problems
|
| 561 |
+
- manual verification of code
|
| 562 |
+
- comparison against the current loaded model's generated code
|
| 563 |
+
|
| 564 |
+
Notable design choice:
|
| 565 |
+
- `model_attempt(...)` calls `TRAINING_MANAGER.generate_code(...)`, then immediately feeds that code back through `evaluate_submission(...)`
|
| 566 |
+
- so the UI path uses the exact same verifier/environment loop as manual submissions
|
| 567 |
+
|
| 568 |
+
## Subsystem 2: Verification (`verifier/`)
|
| 569 |
+
|
| 570 |
+
### `verifier/verifier.py`
|
| 571 |
+
|
| 572 |
+
Central verification orchestration.
|
| 573 |
+
|
| 574 |
+
Function: `verify(code, test_cases, step_number=1)`
|
| 575 |
+
|
| 576 |
+
Pipeline:
|
| 577 |
+
1. `validate_code(code)` from `verifier.sandbox`
|
| 578 |
+
2. `analyze_code_complexity(code)` from `verifier.complexity`
|
| 579 |
+
3. If syntax/safety fails:
|
| 580 |
+
- compute status/reward with empty results
|
| 581 |
+
- return immediately
|
| 582 |
+
4. Else execute against each test case via `verifier.sandbox.run_code(...)`
|
| 583 |
+
5. Build per-test result records
|
| 584 |
+
6. Compute pass-rate metrics with `compute_pass_rate(...)`
|
| 585 |
+
7. Build hidden-facing feedback summary
|
| 586 |
+
|
| 587 |
+
Per-test result record includes:
|
| 588 |
+
- index
|
| 589 |
+
- status
|
| 590 |
+
- passed
|
| 591 |
+
- format_ok
|
| 592 |
+
- stdout/stderr
|
| 593 |
+
- expected/input only for visible tests
|
| 594 |
+
- timed_out
|
| 595 |
+
- exit_code
|
| 596 |
+
- duration_ms
|
| 597 |
+
- sandbox metadata
|
| 598 |
+
- visibility
|
| 599 |
+
|
| 600 |
+
Security boundary:
|
| 601 |
+
- visible tests expose expected/actual/input in the result metadata
|
| 602 |
+
- hidden tests intentionally suppress those values
|
| 603 |
+
|
| 604 |
+
### `verifier/sandbox.py`
|
| 605 |
+
|
| 606 |
+
Static validation plus runtime delegation.
|
| 607 |
+
|
| 608 |
+
Forbidden imports:
|
| 609 |
+
- `ctypes`
|
| 610 |
+
- `os`
|
| 611 |
+
- `pathlib`
|
| 612 |
+
- `resource`
|
| 613 |
+
- `shutil`
|
| 614 |
+
- `signal`
|
| 615 |
+
- `socket`
|
| 616 |
+
- `subprocess`
|
| 617 |
+
|
| 618 |
+
Forbidden calls:
|
| 619 |
+
- `__import__`
|
| 620 |
+
- `breakpoint`
|
| 621 |
+
- `compile`
|
| 622 |
+
- `eval`
|
| 623 |
+
- `exec`
|
| 624 |
+
- `open`
|
| 625 |
+
|
| 626 |
+
Main functions:
|
| 627 |
+
- `validate_code(code)`
|
| 628 |
+
- `run_code(code, stdin, timeout=1)`
|
| 629 |
+
|
| 630 |
+
Important nuance:
|
| 631 |
+
- runtime isolation is actually implemented in `env.executor.run_code`
|
| 632 |
+
- this file adds AST-level policy checks before execution
|
| 633 |
+
|
| 634 |
+
### `verifier/metrics.py`
|
| 635 |
+
|
| 636 |
+
Owns reward math.
|
| 637 |
+
|
| 638 |
+
Constants:
|
| 639 |
+
- `STEP_DISCOUNTS = {1: 1.0, 2: 0.85, 3: 0.70}`
|
| 640 |
+
- `TERMINAL_ZERO_STATUSES = {"syntax_error", "safety_violation", "timeout"}`
|
| 641 |
+
|
| 642 |
+
Main functions:
|
| 643 |
+
- `step_discount(...)`
|
| 644 |
+
- `compute_reward(...)`
|
| 645 |
+
- `compute_pass_rate(...)`
|
| 646 |
+
- `compute_episode_reward(...)`
|
| 647 |
+
|
| 648 |
+
#### `compute_pass_rate(...)`
|
| 649 |
+
|
| 650 |
+
Produces:
|
| 651 |
+
- pass counts
|
| 652 |
+
- hidden/visible pass rates
|
| 653 |
+
- execution status classification
|
| 654 |
+
- format compliance
|
| 655 |
+
- reward components
|
| 656 |
+
- verifier components
|
| 657 |
+
|
| 658 |
+
Execution status priority order:
|
| 659 |
+
- syntax error
|
| 660 |
+
- safety violation
|
| 661 |
+
- precheck status
|
| 662 |
+
- timeout
|
| 663 |
+
- runtime error
|
| 664 |
+
- invalid output format
|
| 665 |
+
- wrong answer
|
| 666 |
+
- completed
|
| 667 |
+
|
| 668 |
+
#### `compute_episode_reward(...)`
|
| 669 |
+
|
| 670 |
+
This is the repair-aware environment reward.
|
| 671 |
+
|
| 672 |
+
Behavior:
|
| 673 |
+
- syntax/safety/timeout => `0.0`
|
| 674 |
+
- correct solution => discounted reward blended with efficiency score
|
| 675 |
+
- incorrect but non-terminal and improved => small progress reward (`0.1 * delta`)
|
| 676 |
+
- final failed attempt => `0.0`
|
| 677 |
+
|
| 678 |
+
This function is one of the most behavior-critical points in the codebase.
|
| 679 |
+
|
| 680 |
+
### `verifier/complexity.py`
|
| 681 |
+
|
| 682 |
+
Static efficiency heuristic.
|
| 683 |
+
|
| 684 |
+
Signals tracked:
|
| 685 |
+
- nested loop depth
|
| 686 |
+
- list/set/dict comprehensions
|
| 687 |
+
- generator expressions
|
| 688 |
+
- sorting calls
|
| 689 |
+
- materialized builtin inputs
|
| 690 |
+
|
| 691 |
+
Outputs:
|
| 692 |
+
- `time_complexity_score`
|
| 693 |
+
- `space_complexity_score`
|
| 694 |
+
- `efficiency_score`
|
| 695 |
+
- `optimization_hints`
|
| 696 |
+
- `complexity_signals`
|
| 697 |
+
|
| 698 |
+
Role in the system:
|
| 699 |
+
- not a formal complexity analyzer
|
| 700 |
+
- acts as a lightweight shaping mechanism for "correct but inefficient" solutions
|
| 701 |
+
|
| 702 |
+
## Subsystem 3: Training (`training/`)
|
| 703 |
+
|
| 704 |
+
### `training/train_grpo.py`
|
| 705 |
+
|
| 706 |
+
This is the second-most important file after `env/generator.py`.
|
| 707 |
+
It owns the full RL training path.
|
| 708 |
+
|
| 709 |
+
#### Global constants
|
| 710 |
+
|
| 711 |
+
- `SYSTEM_PROMPT`
|
| 712 |
+
Shared solver instruction string.
|
| 713 |
+
|
| 714 |
+
- `CRITICAL_PROJECTION_NAMES`
|
| 715 |
+
Used during precision audits.
|
| 716 |
+
|
| 717 |
+
- `SMOKE_PREFERRED_PRECISION = "fp16"`
|
| 718 |
+
|
| 719 |
+
#### `TrainingConfig`
|
| 720 |
+
|
| 721 |
+
Dataclass with the full trainable configuration surface.
|
| 722 |
+
|
| 723 |
+
Important fields:
|
| 724 |
+
- model/runtime:
|
| 725 |
+
- `model_name`
|
| 726 |
+
- `output_dir`
|
| 727 |
+
- `load_in_4bit`
|
| 728 |
+
- `gradient_checkpointing`
|
| 729 |
+
- `bf16`
|
| 730 |
+
- `save_merged_model`
|
| 731 |
+
- data/training:
|
| 732 |
+
- `dataset_size`
|
| 733 |
+
- `max_steps`
|
| 734 |
+
- `batch_size`
|
| 735 |
+
- `gradient_accumulation_steps`
|
| 736 |
+
- `num_generations`
|
| 737 |
+
- `learning_rate`
|
| 738 |
+
- token lengths:
|
| 739 |
+
- `max_seq_length`
|
| 740 |
+
- `max_prompt_length`
|
| 741 |
+
- `max_completion_length`
|
| 742 |
+
- LoRA:
|
| 743 |
+
- `lora_rank`
|
| 744 |
+
- `lora_alpha`
|
| 745 |
+
- evaluation:
|
| 746 |
+
- `baseline_eval`
|
| 747 |
+
- `evaluation_episodes`
|
| 748 |
+
- `eval_max_new_tokens`
|
| 749 |
+
- logging/control:
|
| 750 |
+
- `disable_wandb`
|
| 751 |
+
- `wandb_project`
|
| 752 |
+
- `wandb_run_name`
|
| 753 |
+
- `generator_mode`
|
| 754 |
+
- `non_deterministic_generator`
|
| 755 |
+
- `trace_logging_enabled`
|
| 756 |
+
- `checkpoint_log_interval_steps`
|
| 757 |
+
|
| 758 |
+
#### Training presets
|
| 759 |
+
|
| 760 |
+
Defined in `TRAINING_PRESETS`:
|
| 761 |
+
- `smoke`
|
| 762 |
+
- `l4`
|
| 763 |
+
- `default`
|
| 764 |
+
|
| 765 |
+
`smoke`:
|
| 766 |
+
- tiny, CPU/fallback-friendly
|
| 767 |
+
- no 4-bit
|
| 768 |
+
- no gradient checkpointing
|
| 769 |
+
- short run
|
| 770 |
+
|
| 771 |
+
`l4` and `default`:
|
| 772 |
+
- intended for real LoRA + 4-bit training
|
| 773 |
+
- default model: `Qwen/Qwen2.5-3B-Instruct`
|
| 774 |
+
|
| 775 |
+
#### Prompt helpers
|
| 776 |
+
|
| 777 |
+
- `extract_code(...)`
|
| 778 |
+
- `format_examples(...)`
|
| 779 |
+
- `build_solver_prompt(...)`
|
| 780 |
+
- `build_prompt_from_problem(...)`
|
| 781 |
+
|
| 782 |
+
Important prompt shape:
|
| 783 |
+
- problem id/family/difficulty
|
| 784 |
+
- attempt number
|
| 785 |
+
- problem statement with examples
|
| 786 |
+
- input format
|
| 787 |
+
- constraints
|
| 788 |
+
- feedback
|
| 789 |
+
|
| 790 |
+
This exact shape is reused by training, evaluation, and server-side model generation.
|
| 791 |
+
|
| 792 |
+
#### Config helpers
|
| 793 |
+
|
| 794 |
+
- `build_training_config(...)`
|
| 795 |
+
- `namespace_to_config(...)`
|
| 796 |
+
|
| 797 |
+
These are used both by CLI and by the server runtime manager.
|
| 798 |
+
|
| 799 |
+
#### `CurriculumManager`
|
| 800 |
+
|
| 801 |
+
Adaptive difficulty controller.
|
| 802 |
+
|
| 803 |
+
Defaults:
|
| 804 |
+
- difficulties: `easy`, `medium`, `hard`
|
| 805 |
+
- moving window: `10`
|
| 806 |
+
- promote threshold: `0.70`
|
| 807 |
+
- demote threshold: `0.30`
|
| 808 |
+
|
| 809 |
+
Behavior:
|
| 810 |
+
- tracks recent episode pass rates
|
| 811 |
+
- promotes/demotes based on moving average
|
| 812 |
+
- clears history after each level change
|
| 813 |
+
|
| 814 |
+
#### `GeneratorController`
|
| 815 |
+
|
| 816 |
+
Training-side wrapper over `GeneratorAgent`.
|
| 817 |
+
|
| 818 |
+
Responsibilities:
|
| 819 |
+
- sample problems for a target difficulty
|
| 820 |
+
- register prompt -> problem mapping
|
| 821 |
+
- compute reward-aware family weights
|
| 822 |
+
- track family productivity EMA
|
| 823 |
+
- update generator-side history after each episode
|
| 824 |
+
|
| 825 |
+
Key feature:
|
| 826 |
+
- in `reward_aware` mode, family selection is biased by a softmax over `family_productivity / temperature`
|
| 827 |
+
|
| 828 |
+
#### `GeneratorRolloutDataset`
|
| 829 |
+
|
| 830 |
+
Minimal dataset adapter used by GRPO.
|
| 831 |
+
|
| 832 |
+
Each item:
|
| 833 |
+
- samples a new problem at the current curriculum difficulty
|
| 834 |
+
- returns a dict with only `{"prompt": prompt}`
|
| 835 |
+
|
| 836 |
+
This means the dataset is not static; prompts are generated online.
|
| 837 |
+
|
| 838 |
+
#### `TrainingLogger`
|
| 839 |
+
|
| 840 |
+
Owns:
|
| 841 |
+
- in-memory reward/event rows
|
| 842 |
+
- optional Weights & Biases logging
|
| 843 |
+
- optional trace artifacts through `TraceArtifactLogger`
|
| 844 |
+
- CSV writing
|
| 845 |
+
|
| 846 |
+
Every logged row includes:
|
| 847 |
+
- phase
|
| 848 |
+
- episode reward
|
| 849 |
+
- pass rate
|
| 850 |
+
- visible pass rate
|
| 851 |
+
- difficulty
|
| 852 |
+
- family
|
| 853 |
+
- curriculum level
|
| 854 |
+
- execution status
|
| 855 |
+
- attempt number
|
| 856 |
+
- family productivity columns
|
| 857 |
+
- extra metadata like prompt/completion/code/feedback
|
| 858 |
+
|
| 859 |
+
#### `build_reward_func(...)`
|
| 860 |
+
|
| 861 |
+
This is the bridge between GRPOTrainer and the environment.
|
| 862 |
+
|
| 863 |
+
For each `(prompt, completion)` pair:
|
| 864 |
+
- resolve original problem from prompt registry
|
| 865 |
+
- spin up `AdaptEnvironment`
|
| 866 |
+
- reset with the exact generated problem
|
| 867 |
+
- run one environment step using extracted code
|
| 868 |
+
- record reward
|
| 869 |
+
- update generator controller
|
| 870 |
+
- update curriculum
|
| 871 |
+
- log event
|
| 872 |
+
- emit progress callback payload
|
| 873 |
+
|
| 874 |
+
This function is the key glue layer for RL.
|
| 875 |
+
|
| 876 |
+
#### `generate_completion(...)`
|
| 877 |
+
|
| 878 |
+
Shared inference helper for local model objects.
|
| 879 |
+
|
| 880 |
+
Responsibilities:
|
| 881 |
+
- render chat template if tokenizer supports it
|
| 882 |
+
- choose generation device from HF device map or model device
|
| 883 |
+
- call `model.generate(...)`
|
| 884 |
+
- decode only newly generated tokens
|
| 885 |
+
|
| 886 |
+
Used by:
|
| 887 |
+
- evaluation
|
| 888 |
+
- server runtime generation
|
| 889 |
+
|
| 890 |
+
#### `run_policy_evaluation(...)`
|
| 891 |
+
|
| 892 |
+
Offline evaluator over a schedule of easy/medium/hard episodes.
|
| 893 |
+
|
| 894 |
+
Loop:
|
| 895 |
+
- sample a problem
|
| 896 |
+
- run up to 3 repair attempts
|
| 897 |
+
- log final result
|
| 898 |
+
- aggregate pass rates by tier
|
| 899 |
+
|
| 900 |
+
Outputs summary:
|
| 901 |
+
- easy
|
| 902 |
+
- medium
|
| 903 |
+
- hard
|
| 904 |
+
- overall
|
| 905 |
+
|
| 906 |
+
#### Precision/runtime utilities
|
| 907 |
+
|
| 908 |
+
- `get_runtime_versions(...)`
|
| 909 |
+
- `validate_runtime_versions(...)`
|
| 910 |
+
- `resolve_precision_policy(...)`
|
| 911 |
+
- `normalize_model_precision(...)`
|
| 912 |
+
- `audit_critical_module_precision(...)`
|
| 913 |
+
|
| 914 |
+
These guardrails exist because the training path is tuned around Unsloth + GRPO + mixed precision.
|
| 915 |
+
|
| 916 |
+
#### `run_training(...)`
|
| 917 |
+
|
| 918 |
+
The full training entrypoint.
|
| 919 |
+
|
| 920 |
+
Major stages:
|
| 921 |
+
1. Normalize config
|
| 922 |
+
2. Import `torch`, `trl`, `unsloth`, and transformers callback support
|
| 923 |
+
3. Create output directory
|
| 924 |
+
4. Patch Unsloth RL stack
|
| 925 |
+
5. Resolve runtime versions and precision policy
|
| 926 |
+
6. Load base model/tokenizer via `FastLanguageModel.from_pretrained`
|
| 927 |
+
7. Apply LoRA with `FastLanguageModel.get_peft_model`
|
| 928 |
+
8. Run precision audit(s)
|
| 929 |
+
9. Create curriculum, generator controller, and logger
|
| 930 |
+
10. Optionally run baseline evaluation
|
| 931 |
+
11. Build `GRPOConfig`
|
| 932 |
+
12. Build `GRPOTrainer` with online prompt dataset and environment reward function
|
| 933 |
+
13. Train
|
| 934 |
+
14. Save adapter or merged model
|
| 935 |
+
15. Optionally run trained evaluation
|
| 936 |
+
16. Write reward CSV and finalize trace artifacts
|
| 937 |
+
17. Return a summary payload containing paths and metrics
|
| 938 |
+
|
| 939 |
+
Output summary includes:
|
| 940 |
+
- config
|
| 941 |
+
- runtime versions
|
| 942 |
+
- precision mode/policy/audits
|
| 943 |
+
- `output_dir`
|
| 944 |
+
- `reward_curve_csv`
|
| 945 |
+
- trace artifact paths
|
| 946 |
+
- baseline/trained summaries
|
| 947 |
+
- completed steps
|
| 948 |
+
|
| 949 |
+
#### CLI
|
| 950 |
+
|
| 951 |
+
`build_parser()` exposes the training surface as a command-line interface.
|
| 952 |
+
|
| 953 |
+
Entrypoint:
|
| 954 |
+
- `python training/train_grpo.py ...`
|
| 955 |
+
|
| 956 |
+
### `training/trace_logging.py`
|
| 957 |
+
|
| 958 |
+
Structured artifact logger for Space/server integration and post-run analysis.
|
| 959 |
+
|
| 960 |
+
Main class: `TraceArtifactLogger`
|
| 961 |
+
|
| 962 |
+
Artifacts under `output_dir/logs/`:
|
| 963 |
+
- `run_manifest.json`
|
| 964 |
+
- `events.jsonl`
|
| 965 |
+
- `latest_checkpoint.json`
|
| 966 |
+
- `checkpoint_step_XXXXX.json`
|
| 967 |
+
- `run_summary.json`
|
| 968 |
+
|
| 969 |
+
Tracked rolling metrics:
|
| 970 |
+
- average reward
|
| 971 |
+
- average pass rate
|
| 972 |
+
- average efficiency score
|
| 973 |
+
|
| 974 |
+
Used by:
|
| 975 |
+
- `TrainingLogger` in `training/train_grpo.py`
|
| 976 |
+
- surfaced through `server.runtime.SpaceTrainingManager`
|
| 977 |
+
|
| 978 |
+
### `training/plot_results.py`
|
| 979 |
+
|
| 980 |
+
Offline plotting tool for `reward_curve.csv`.
|
| 981 |
+
|
| 982 |
+
Outputs:
|
| 983 |
+
- `reward_curve.png`
|
| 984 |
+
- `pass_rate_by_difficulty.png`
|
| 985 |
+
- `family_productivity.png`
|
| 986 |
+
|
| 987 |
+
Important note:
|
| 988 |
+
- `plot_pass_rate_by_difficulty(...)` groups rows by `difficulty_tier`
|
| 989 |
+
- the function then iterates over `"easy"`, `"medium"`, `"hard"`
|
| 990 |
+
- this assumes the CSV contains string difficulty labels, which it currently does
|
| 991 |
+
|
| 992 |
+
## Subsystem 4: Server And Space Runtime (`server/`)
|
| 993 |
+
|
| 994 |
+
### `server/app.py`
|
| 995 |
+
|
| 996 |
+
FastAPI surface for OpenEnv plus training/model utilities.
|
| 997 |
+
|
| 998 |
+
Global state:
|
| 999 |
+
- `SESSIONS`
|
| 1000 |
+
- `SESSION_LAST_ACCESSED`
|
| 1001 |
+
- `TRAINING_MANAGER = SpaceTrainingManager()`
|
| 1002 |
+
- `TASKS` generated from `env.test_cases.load_problem_bank()`
|
| 1003 |
+
|
| 1004 |
+
Session policy:
|
| 1005 |
+
- TTL: 30 minutes
|
| 1006 |
+
- unknown/expired sessions raise 404
|
| 1007 |
+
|
| 1008 |
+
#### Request models
|
| 1009 |
+
|
| 1010 |
+
- `ResetRequest`
|
| 1011 |
+
- `TrainRequest`
|
| 1012 |
+
- `RunTrainedPolicyRequest`
|
| 1013 |
+
- `GenerateCodeRequest`
|
| 1014 |
+
|
| 1015 |
+
#### Informational endpoints
|
| 1016 |
+
|
| 1017 |
+
- `GET /`
|
| 1018 |
+
Root metadata plus training/model status and active session count
|
| 1019 |
+
- `GET /health`
|
| 1020 |
+
- `GET /metadata`
|
| 1021 |
+
- `GET /tasks`
|
| 1022 |
+
- `GET /schema`
|
| 1023 |
+
- `GET /train/status`
|
| 1024 |
+
- `GET /model/status`
|
| 1025 |
+
|
| 1026 |
+
#### Environment endpoints
|
| 1027 |
+
|
| 1028 |
+
- `POST /reset`
|
| 1029 |
+
Creates a new `AdaptEnvironment`, stores it in `SESSIONS`, returns initial observation.
|
| 1030 |
+
|
| 1031 |
+
- `POST /step`
|
| 1032 |
+
Accepts either raw action JSON or `{ "action": ... }`.
|
| 1033 |
+
Requires `session_id`.
|
| 1034 |
+
Returns:
|
| 1035 |
+
- `observation`
|
| 1036 |
+
- `reward`
|
| 1037 |
+
- `done`
|
| 1038 |
+
- compact `info`
|
| 1039 |
+
|
| 1040 |
+
- `GET /state`
|
| 1041 |
+
Returns `AdaptState`.
|
| 1042 |
+
|
| 1043 |
+
#### Model/training endpoints
|
| 1044 |
+
|
| 1045 |
+
- `POST /train`
|
| 1046 |
+
Starts async training via `SpaceTrainingManager.start_training(...)`
|
| 1047 |
+
|
| 1048 |
+
- `POST /run-trained-policy`
|
| 1049 |
+
Runs current model through a full 3-attempt environment episode
|
| 1050 |
+
|
| 1051 |
+
- `POST /generate-code`
|
| 1052 |
+
One-shot generation for a supplied problem payload
|
| 1053 |
+
|
| 1054 |
+
#### MCP endpoint
|
| 1055 |
+
|
| 1056 |
+
- `POST /mcp`
|
| 1057 |
+
Stub only; always returns "not implemented"
|
| 1058 |
+
|
| 1059 |
+
### `server/runtime.py`
|
| 1060 |
+
|
| 1061 |
+
Owns asynchronous training and model-loading behavior for a Space-like deployment.
|
| 1062 |
+
|
| 1063 |
+
This file has two major classes:
|
| 1064 |
+
- `SpaceModelRegistry`
|
| 1065 |
+
- `SpaceTrainingManager`
|
| 1066 |
+
|
| 1067 |
+
It also defines two dataclasses used as persisted status:
|
| 1068 |
+
- `ModelState`
|
| 1069 |
+
- `TrainingJobState`
|
| 1070 |
+
|
| 1071 |
+
#### `ModelState`
|
| 1072 |
+
|
| 1073 |
+
Tracks:
|
| 1074 |
+
- whether a model is loaded
|
| 1075 |
+
- whether the active generation source is `trained`, `base`, or `unavailable`
|
| 1076 |
+
- repo id / local path / revision
|
| 1077 |
+
- base model name
|
| 1078 |
+
- load time
|
| 1079 |
+
- last error
|
| 1080 |
+
|
| 1081 |
+
#### `TrainingJobState`
|
| 1082 |
+
|
| 1083 |
+
Tracks:
|
| 1084 |
+
- job status and run id
|
| 1085 |
+
- config
|
| 1086 |
+
- timestamps
|
| 1087 |
+
- artifact paths
|
| 1088 |
+
- uploaded model revision
|
| 1089 |
+
- logs location
|
| 1090 |
+
- current phase and progress
|
| 1091 |
+
- precision/runtime audit metadata
|
| 1092 |
+
- last seen train metrics
|
| 1093 |
+
- baseline/trained summaries
|
| 1094 |
+
- error and traceback
|
| 1095 |
+
|
| 1096 |
+
This is what backs `/train/status`.
|
| 1097 |
+
|
| 1098 |
+
#### `SpaceModelRegistry`
|
| 1099 |
+
|
| 1100 |
+
Primary responsibilities:
|
| 1101 |
+
- load base model when no trained model is available
|
| 1102 |
+
- load trained artifact from local disk or Hugging Face Hub
|
| 1103 |
+
- choose generation stack
|
| 1104 |
+
- fallback from trained model to base model if trained generation fails
|
| 1105 |
+
- run policy episodes and one-shot code generation
|
| 1106 |
+
|
| 1107 |
+
Important methods:
|
| 1108 |
+
- `_require_runtime_dependencies(...)`
|
| 1109 |
+
- `load_base_model()`
|
| 1110 |
+
- `load_from_local(...)`
|
| 1111 |
+
- `load_latest_from_hub()`
|
| 1112 |
+
- `run_policy(...)`
|
| 1113 |
+
- `generate_code(...)`
|
| 1114 |
+
|
| 1115 |
+
Environment variables it cares about:
|
| 1116 |
+
- `HF_MODEL_REPO_ID`
|
| 1117 |
+
- `HF_TOKEN`
|
| 1118 |
+
- `BASE_MODEL_NAME`
|
| 1119 |
+
- `MODEL_NAME`
|
| 1120 |
+
|
| 1121 |
+
Fallback behavior:
|
| 1122 |
+
- if no trained model is loadable, base-model generation can still work
|
| 1123 |
+
- if trained-model generation errors, it can fall back to base generation and record the fallback reason
|
| 1124 |
+
|
| 1125 |
+
#### `SpaceTrainingManager`
|
| 1126 |
+
|
| 1127 |
+
Primary responsibilities:
|
| 1128 |
+
- persist training job state to disk
|
| 1129 |
+
- prevent concurrent training runs
|
| 1130 |
+
- build output directory for each run
|
| 1131 |
+
- spawn background training thread
|
| 1132 |
+
- upload artifacts to the Hub
|
| 1133 |
+
- delete local logs after upload
|
| 1134 |
+
- refresh active model after successful upload
|
| 1135 |
+
|
| 1136 |
+
State files:
|
| 1137 |
+
- status persisted in `training_status.json` under `SPACE_OUTPUT_ROOT` or `/tmp/adapt-space`
|
| 1138 |
+
|
| 1139 |
+
Key methods:
|
| 1140 |
+
- `_restore_status()`
|
| 1141 |
+
- `_persist_status()`
|
| 1142 |
+
- `_update_progress(...)`
|
| 1143 |
+
- `status_payload()`
|
| 1144 |
+
- `start_training(...)`
|
| 1145 |
+
- `_run_training_job(...)`
|
| 1146 |
+
- `_upload_artifacts(...)`
|
| 1147 |
+
- `_cleanup_local_logs(...)`
|
| 1148 |
+
- `load_latest_model()`
|
| 1149 |
+
- `run_trained_policy(...)`
|
| 1150 |
+
- `generate_code(...)`
|
| 1151 |
+
|
| 1152 |
+
Background training behavior:
|
| 1153 |
+
- `start_training(...)` creates a `TrainingConfig` using `build_training_config(...)`
|
| 1154 |
+
- output dir is namespaced by `run_id`
|
| 1155 |
+
- training happens on a daemon thread
|
| 1156 |
+
- progress updates come from `run_training(..., progress_callback=...)`
|
| 1157 |
+
|
| 1158 |
+
Successful completion path:
|
| 1159 |
+
1. run training
|
| 1160 |
+
2. upload artifacts to HF model repo
|
| 1161 |
+
3. delete local logs
|
| 1162 |
+
4. load latest trained model from Hub
|
| 1163 |
+
5. mark status as `succeeded`
|
| 1164 |
+
|
| 1165 |
+
Failure path:
|
| 1166 |
+
- mark status as `failed`
|
| 1167 |
+
- persist traceback
|
| 1168 |
+
- optionally clean logs first
|
| 1169 |
+
|
| 1170 |
+
## Top-Level Utility Entrypoints
|
| 1171 |
+
|
| 1172 |
+
### `client.py`
|
| 1173 |
+
|
| 1174 |
+
`AdaptEnvClient` wraps HTTP operations for:
|
| 1175 |
+
- `reset`
|
| 1176 |
+
- `step`
|
| 1177 |
+
- `state`
|
| 1178 |
+
- `train`
|
| 1179 |
+
- `train_status`
|
| 1180 |
+
- `model_status`
|
| 1181 |
+
- `run_trained_policy`
|
| 1182 |
+
- `generate_code`
|
| 1183 |
+
|
| 1184 |
+
Useful for:
|
| 1185 |
+
- local automation
|
| 1186 |
+
- notebooks
|
| 1187 |
+
- scripted experimentation against the FastAPI app
|
| 1188 |
+
|
| 1189 |
+
### `inference.py`
|
| 1190 |
+
|
| 1191 |
+
OpenAI-compatible external inference runner.
|
| 1192 |
+
|
| 1193 |
+
Key behaviors:
|
| 1194 |
+
- loads tasks from `env.test_cases.load_problem_bank()`
|
| 1195 |
+
- resets an `AdaptEnvironment` per task
|
| 1196 |
+
- builds a JSON user payload from the observation
|
| 1197 |
+
- calls `OpenAI(...).chat.completions.create(...)`
|
| 1198 |
+
- extracts code
|
| 1199 |
+
- runs up to 3 repair attempts
|
| 1200 |
+
- logs machine-readable stdout lines
|
| 1201 |
+
|
| 1202 |
+
Important env vars:
|
| 1203 |
+
- `HF_TOKEN`
|
| 1204 |
+
- `API_BASE_URL` default `https://router.huggingface.co/v1`
|
| 1205 |
+
- `MODEL_NAME` default `openai/gpt-oss-120b`
|
| 1206 |
+
|
| 1207 |
+
This file is separate from the local model runtime in `server/runtime.py`.
|
| 1208 |
+
It is for remote API-based evaluation, not local HF model objects.
|
| 1209 |
+
|
| 1210 |
+
### `test.py`
|
| 1211 |
+
|
| 1212 |
+
Simple "run several smokes in sequence" script.
|
| 1213 |
+
|
| 1214 |
+
Calls:
|
| 1215 |
+
- `scripts.test_env.main`
|
| 1216 |
+
- `scripts.test_space_api.main`
|
| 1217 |
+
- `scripts.test_training_config.main`
|
| 1218 |
+
- `scripts.test_trace_logging.main`
|
| 1219 |
+
- then a direct verifier correctness check
|
| 1220 |
+
|
| 1221 |
+
## Scripts (`scripts/`)
|
| 1222 |
+
|
| 1223 |
+
### `scripts/deploy_and_smoke_train.py`
|
| 1224 |
+
|
| 1225 |
+
Operational helper for pushing to a Space and smoke-running training remotely.
|
| 1226 |
+
|
| 1227 |
+
Capabilities:
|
| 1228 |
+
- optionally auto-commit and push repo changes
|
| 1229 |
+
- wait for `/health`
|
| 1230 |
+
- start `/train`
|
| 1231 |
+
- poll `/train/status`
|
| 1232 |
+
- follow an already-running job if requested
|
| 1233 |
+
|
| 1234 |
+
Key modes/flags:
|
| 1235 |
+
- `--skip-push`
|
| 1236 |
+
- `--skip-health-check`
|
| 1237 |
+
- `--trigger-only`
|
| 1238 |
+
- `--status-only`
|
| 1239 |
+
- `--follow-running`
|
| 1240 |
+
|
| 1241 |
+
This is a deployment/ops script, not part of core runtime.
|
| 1242 |
+
|
| 1243 |
+
### `scripts/test_env.py`
|
| 1244 |
+
|
| 1245 |
+
Best environment behavior reference in the repo.
|
| 1246 |
+
|
| 1247 |
+
It verifies:
|
| 1248 |
+
- public examples are exposed
|
| 1249 |
+
- hidden tests are not exposed
|
| 1250 |
+
- correct first-attempt solution gets full reward
|
| 1251 |
+
- repair trajectory produces discounted reward
|
| 1252 |
+
- correct-but-less-efficient solution remains open for optimization
|
| 1253 |
+
- syntax/runtime/timeout/safety states behave as expected
|
| 1254 |
+
|
| 1255 |
+
If you need to understand intended environment semantics, start here after `adapt_env.py`.
|
| 1256 |
+
|
| 1257 |
+
### `scripts/test_space_api.py`
|
| 1258 |
+
|
| 1259 |
+
FastAPI smoke tests with patched training manager behavior.
|
| 1260 |
+
|
| 1261 |
+
Verifies:
|
| 1262 |
+
- root/model/train status endpoints
|
| 1263 |
+
- reset/step/state flow
|
| 1264 |
+
- expected 409 when no trained model is available for policy run
|
| 1265 |
+
- generate-code path
|
| 1266 |
+
- training start and conflict handling
|
| 1267 |
+
|
| 1268 |
+
### `scripts/test_trace_logging.py`
|
| 1269 |
+
|
| 1270 |
+
Smoke tests trace artifact creation.
|
| 1271 |
+
|
| 1272 |
+
Checks:
|
| 1273 |
+
- manifest creation
|
| 1274 |
+
- event JSONL writing
|
| 1275 |
+
- checkpoint writing
|
| 1276 |
+
- summary finalization
|
| 1277 |
+
|
| 1278 |
+
### `scripts/test_training_config.py`
|
| 1279 |
+
|
| 1280 |
+
Tests:
|
| 1281 |
+
- preset construction
|
| 1282 |
+
- precision policy resolution under fake torch/cuda conditions
|
| 1283 |
+
- reward behavior for fully correct but sub-target efficiency case
|
| 1284 |
+
|
| 1285 |
+
### `scripts/test_verifier.py`
|
| 1286 |
+
|
| 1287 |
+
Manual verification sanity suite over:
|
| 1288 |
+
- correct
|
| 1289 |
+
- wrong
|
| 1290 |
+
- less optimized
|
| 1291 |
+
- invalid output
|
| 1292 |
+
- timeout
|
| 1293 |
+
- runtime error
|
| 1294 |
+
- safety violation
|
| 1295 |
+
|
| 1296 |
+
Also confirms the complexity/efficiency signal differentiates optimized from less-optimized code.
|
| 1297 |
+
|
| 1298 |
+
## End-To-End Flows
|
| 1299 |
+
|
| 1300 |
+
### Flow A: API Environment Interaction
|
| 1301 |
+
|
| 1302 |
+
1. Client calls `POST /reset`
|
| 1303 |
+
2. `server.app.reset(...)` creates a new `AdaptEnvironment`
|
| 1304 |
+
3. `AdaptEnvironment.reset(...)` loads/generates a problem and returns public observation
|
| 1305 |
+
4. Client submits code via `POST /step`
|
| 1306 |
+
5. `server.app.step(...)` validates `AdaptAction`
|
| 1307 |
+
6. `AdaptEnvironment.step(...)` calls `verify(...)`
|
| 1308 |
+
7. `verify(...)` validates AST safety, runs sandboxed code on tests, computes metrics
|
| 1309 |
+
8. Environment converts metrics into feedback + reward + updated observation
|
| 1310 |
+
9. `GET /state` returns broader session state if needed
|
| 1311 |
+
|
| 1312 |
+
### Flow B: Local Training
|
| 1313 |
+
|
| 1314 |
+
1. CLI or server builds `TrainingConfig`
|
| 1315 |
+
2. `run_training(...)` loads base model and applies LoRA
|
| 1316 |
+
3. `GeneratorRolloutDataset` emits prompts for current curriculum difficulty
|
| 1317 |
+
4. GRPO generates completions
|
| 1318 |
+
5. `build_reward_func(...)` routes completions through `AdaptEnvironment`
|
| 1319 |
+
6. Environment returns reward
|
| 1320 |
+
7. `CurriculumManager` updates difficulty
|
| 1321 |
+
8. `GeneratorController` updates family productivity
|
| 1322 |
+
9. `TrainingLogger` writes CSV/events/checkpoints
|
| 1323 |
+
10. Model and tokenizer are saved
|
| 1324 |
+
11. Optional trained evaluation runs
|
| 1325 |
+
|
| 1326 |
+
### Flow C: Server-Side Training On Space
|
| 1327 |
+
|
| 1328 |
+
1. `POST /train`
|
| 1329 |
+
2. `SpaceTrainingManager.start_training(...)`
|
| 1330 |
+
3. Background thread calls `_run_training_job(...)`
|
| 1331 |
+
4. `run_training(...)` emits progress updates
|
| 1332 |
+
5. Status persisted to `training_status.json`
|
| 1333 |
+
6. Artifacts uploaded to HF repo
|
| 1334 |
+
7. Local logs optionally deleted
|
| 1335 |
+
8. Latest model reloaded from Hub
|
| 1336 |
+
9. `/train/status` and `/model/status` reflect final state
|
| 1337 |
+
|
| 1338 |
+
### Flow D: Model-Powered Generation In Demo Or API
|
| 1339 |
+
|
| 1340 |
+
1. A problem payload is turned into a solver prompt with `build_solver_prompt(...)`
|
| 1341 |
+
2. `SpaceModelRegistry.generate_code(...)` picks trained or base model
|
| 1342 |
+
3. `generate_completion(...)` runs local generation
|
| 1343 |
+
4. Returned completion is cleaned with `extract_code(...)`
|
| 1344 |
+
5. In Gradio demo, generated code is immediately evaluated in the same environment loop
|
| 1345 |
+
|
| 1346 |
+
## Important Invariants And Hidden Couplings
|
| 1347 |
+
|
| 1348 |
+
### Hidden tests must never leak
|
| 1349 |
+
|
| 1350 |
+
Critical files:
|
| 1351 |
+
- `env/adapt_env.py`
|
| 1352 |
+
- `verifier/verifier.py`
|
| 1353 |
+
- `scripts/test_env.py`
|
| 1354 |
+
|
| 1355 |
+
If you change observation formatting, server payloads, or debug output, verify that hidden tests are still suppressed.
|
| 1356 |
+
|
| 1357 |
+
### Prompt text is reused across training, evaluation, and generation
|
| 1358 |
+
|
| 1359 |
+
Critical file:
|
| 1360 |
+
- `training/train_grpo.py`
|
| 1361 |
+
|
| 1362 |
+
If you change `SYSTEM_PROMPT`, `build_solver_prompt(...)`, or `extract_code(...)`, you affect:
|
| 1363 |
+
- GRPO training
|
| 1364 |
+
- baseline/trained evaluation
|
| 1365 |
+
- `server.runtime` generation
|
| 1366 |
+
- `env.app` model attempt path
|
| 1367 |
+
|
| 1368 |
+
### Reward semantics are distributed
|
| 1369 |
+
|
| 1370 |
+
Files:
|
| 1371 |
+
- `verifier/metrics.py`
|
| 1372 |
+
- `env/adapt_env.py`
|
| 1373 |
+
- `env/generator.py`
|
| 1374 |
+
|
| 1375 |
+
There are three conceptually different signals:
|
| 1376 |
+
- verifier correctness reward
|
| 1377 |
+
- episode reward
|
| 1378 |
+
- generator reward signal
|
| 1379 |
+
|
| 1380 |
+
Do not change one assuming it is the only reward in the system.
|
| 1381 |
+
|
| 1382 |
+
### The generator is both data source and answer key
|
| 1383 |
+
|
| 1384 |
+
`env/generator.py` does all of these:
|
| 1385 |
+
- defines task families
|
| 1386 |
+
- generates inputs
|
| 1387 |
+
- defines reference solvers
|
| 1388 |
+
- defines expected outputs
|
| 1389 |
+
|
| 1390 |
+
If a reference solver is wrong, the entire environment silently becomes wrong.
|
| 1391 |
+
|
| 1392 |
+
### Correctness is based on hidden tests, not visible tests
|
| 1393 |
+
|
| 1394 |
+
Visible tests are for examples and repair feedback.
|
| 1395 |
+
Core reward/curriculum decisions are driven by hidden pass rate.
|
| 1396 |
+
|
| 1397 |
+
### Efficiency can block episode completion
|
| 1398 |
+
|
| 1399 |
+
An all-tests-passing answer can still have `done == False` if efficiency target is not met.
|
| 1400 |
+
This is intentional and affects reward, feedback, and repair behavior.
|
| 1401 |
+
|
| 1402 |
+
### Space runtime is designed to degrade gracefully
|
| 1403 |
+
|
| 1404 |
+
`server/runtime.py` allows:
|
| 1405 |
+
- base model use when trained model is unavailable
|
| 1406 |
+
- trained-to-base fallback on generation error
|
| 1407 |
+
|
| 1408 |
+
This is useful operationally, but it means "generation works" does not always imply "trained model is active".
|
| 1409 |
+
|
| 1410 |
+
## Change Impact Guide
|
| 1411 |
+
|
| 1412 |
+
### If you want to add a new problem family
|
| 1413 |
+
|
| 1414 |
+
Primary file:
|
| 1415 |
+
- `env/generator.py`
|
| 1416 |
+
|
| 1417 |
+
You will usually need to add:
|
| 1418 |
+
- template entry in `_build_templates()`
|
| 1419 |
+
- case builder(s)
|
| 1420 |
+
- reference solver
|
| 1421 |
+
- any parser/helper utilities
|
| 1422 |
+
|
| 1423 |
+
Then verify:
|
| 1424 |
+
- `validate_problem(...)` still passes
|
| 1425 |
+
- `load_problem_bank()` includes the new family
|
| 1426 |
+
- Gradio dropdown in `env/app.py` optionally includes it
|
| 1427 |
+
- README family list stays aligned
|
| 1428 |
+
|
| 1429 |
+
### If you want to change reward behavior
|
| 1430 |
+
|
| 1431 |
+
Primary files:
|
| 1432 |
+
- `verifier/metrics.py`
|
| 1433 |
+
- `env/adapt_env.py`
|
| 1434 |
+
- maybe `env/generator.py` if generator curriculum shaping should change
|
| 1435 |
+
|
| 1436 |
+
Then re-check:
|
| 1437 |
+
- `scripts/test_env.py`
|
| 1438 |
+
- `scripts/test_training_config.py`
|
| 1439 |
+
- training summaries and trace logging assumptions
|
| 1440 |
+
|
| 1441 |
+
### If you want to change prompt style or model behavior
|
| 1442 |
+
|
| 1443 |
+
Primary file:
|
| 1444 |
+
- `training/train_grpo.py`
|
| 1445 |
+
|
| 1446 |
+
Also impacts:
|
| 1447 |
+
- `server/runtime.py`
|
| 1448 |
+
- `inference.py`
|
| 1449 |
+
- `env/app.py`
|
| 1450 |
+
|
| 1451 |
+
### If you want to harden execution/security
|
| 1452 |
+
|
| 1453 |
+
Primary files:
|
| 1454 |
+
- `verifier/sandbox.py`
|
| 1455 |
+
- `env/executor.py`
|
| 1456 |
+
|
| 1457 |
+
Be careful to preserve:
|
| 1458 |
+
- valid competitive-programming style solutions
|
| 1459 |
+
- output capture behavior
|
| 1460 |
+
- timeout and isolation semantics
|
| 1461 |
+
|
| 1462 |
+
### If you want to modify server payloads or API
|
| 1463 |
+
|
| 1464 |
+
Primary files:
|
| 1465 |
+
- `models.py`
|
| 1466 |
+
- `server/app.py`
|
| 1467 |
+
- `client.py`
|
| 1468 |
+
- possibly `scripts/test_space_api.py`
|
| 1469 |
+
|
| 1470 |
+
## Recommended Reading Order For A New LLM
|
| 1471 |
+
|
| 1472 |
+
1. `README.md`
|
| 1473 |
+
2. `models.py`
|
| 1474 |
+
3. `env/adapt_env.py`
|
| 1475 |
+
4. `verifier/verifier.py`
|
| 1476 |
+
5. `verifier/metrics.py`
|
| 1477 |
+
6. `env/generator.py`
|
| 1478 |
+
7. `training/train_grpo.py`
|
| 1479 |
+
8. `server/app.py`
|
| 1480 |
+
9. `server/runtime.py`
|
| 1481 |
+
10. `scripts/test_env.py`
|
| 1482 |
+
11. `scripts/test_space_api.py`
|
| 1483 |
+
|
| 1484 |
+
Why this order:
|
| 1485 |
+
- it starts with conceptual context
|
| 1486 |
+
- then schemas
|
| 1487 |
+
- then the core environment loop
|
| 1488 |
+
- then generation and reward internals
|
| 1489 |
+
- then training and serving
|
| 1490 |
+
- then tests as executable specification
|
| 1491 |
+
|
| 1492 |
+
## File-By-File Responsibility Index
|
| 1493 |
+
|
| 1494 |
+
### Root
|
| 1495 |
+
|
| 1496 |
+
- `README.md`: project rationale, architecture, usage docs
|
| 1497 |
+
- `pyproject.toml`: package/dependency metadata
|
| 1498 |
+
- `requirements.txt`: lighter runtime dependencies
|
| 1499 |
+
- `openenv.yaml`: OpenEnv deployment metadata
|
| 1500 |
+
- `Dockerfile`: container entrypoint
|
| 1501 |
+
- `app.py`: re-export server app
|
| 1502 |
+
- `client.py`: Python HTTP client wrapper
|
| 1503 |
+
- `models.py`: shared action/observation/state schemas
|
| 1504 |
+
- `inference.py`: remote API-based benchmark runner
|
| 1505 |
+
- `test.py`: combined smoke test launcher
|
| 1506 |
+
|
| 1507 |
+
### `env/`
|
| 1508 |
+
|
| 1509 |
+
- `env/__init__.py`: re-exports generator utilities
|
| 1510 |
+
- `env/adapt_env.py`: main environment state machine
|
| 1511 |
+
- `env/app.py`: Gradio demo app
|
| 1512 |
+
- `env/executor.py`: subprocess execution sandbox
|
| 1513 |
+
- `env/generator.py`: problem registry, generators, solvers, helpers
|
| 1514 |
+
- `env/test_cases.py`: problem bank and split helpers
|
| 1515 |
+
|
| 1516 |
+
### `verifier/`
|
| 1517 |
+
|
| 1518 |
+
- `verifier/__init__.py`: re-exports `verify`
|
| 1519 |
+
- `verifier/complexity.py`: static complexity heuristics
|
| 1520 |
+
- `verifier/metrics.py`: reward and scoring math
|
| 1521 |
+
- `verifier/sandbox.py`: AST safety checks and runtime bridge
|
| 1522 |
+
- `verifier/verifier.py`: verification orchestration
|
| 1523 |
+
|
| 1524 |
+
### `training/`
|
| 1525 |
+
|
| 1526 |
+
- `training/__init__.py`: package marker
|
| 1527 |
+
- `training/plot_results.py`: offline plotting utilities
|
| 1528 |
+
- `training/trace_logging.py`: structured logs/checkpoints/summary artifacts
|
| 1529 |
+
- `training/train_grpo.py`: training, evaluation, prompts, config, curriculum
|
| 1530 |
+
|
| 1531 |
+
### `server/`
|
| 1532 |
+
|
| 1533 |
+
- `server/__init__.py`: re-export app/main
|
| 1534 |
+
- `server/app.py`: FastAPI routes and session handling
|
| 1535 |
+
- `server/requirements.txt`: server/runtime dependency subset
|
| 1536 |
+
- `server/runtime.py`: model registry and training job manager
|
| 1537 |
+
|
| 1538 |
+
### `scripts/`
|
| 1539 |
+
|
| 1540 |
+
- `scripts/deploy_and_smoke_train.py`: deploy-and-train automation
|
| 1541 |
+
- `scripts/test_env.py`: environment smoke spec
|
| 1542 |
+
- `scripts/test_space_api.py`: API smoke spec
|
| 1543 |
+
- `scripts/test_trace_logging.py`: trace logger smoke spec
|
| 1544 |
+
- `scripts/test_training_config.py`: config/precision/reward smoke spec
|
| 1545 |
+
- `scripts/test_verifier.py`: verifier behavior sanity script
|
| 1546 |
+
|
| 1547 |
+
## Known Gaps Or Non-Core Artifacts
|
| 1548 |
+
|
| 1549 |
+
- No `main-v5/` directory exists in this workspace.
|
| 1550 |
+
- `rewards/` is empty in this snapshot.
|
| 1551 |
+
- `outputs/` is empty in this snapshot, so no completed run artifacts were available to inspect.
|
| 1552 |
+
- The notebook artifacts were not treated as the source of truth; the Python modules are the canonical implementation.
|
| 1553 |
+
|
| 1554 |
+
## Bottom Line
|
| 1555 |
+
|
| 1556 |
+
If another LLM needs to work effectively in this repo, it should think of the codebase as five tightly linked layers:
|
| 1557 |
+
|
| 1558 |
+
1. `env/generator.py` defines what the tasks are.
|
| 1559 |
+
2. `verifier/*` defines how code is checked and scored.
|
| 1560 |
+
3. `env/adapt_env.py` turns scoring into an interactive repair environment.
|
| 1561 |
+
4. `training/train_grpo.py` plugs that environment into GRPO and curriculum learning.
|
| 1562 |
+
5. `server/*` makes the environment, model runtime, and training lifecycle available over HTTP and in a Hugging Face Space.
|
| 1563 |
+
|
| 1564 |
+
That is the true structural spine of the repository.
|
env/adapt_env.py
CHANGED
|
@@ -33,10 +33,15 @@ class AdaptEnvironment(Environment[AdaptAction, AdaptObservation, AdaptState]):
|
|
| 33 |
self,
|
| 34 |
generator: GeneratorAgent | None = None,
|
| 35 |
generator_mode: str = "heuristic",
|
|
|
|
|
|
|
| 36 |
session_id: str | None = None,
|
| 37 |
) -> None:
|
| 38 |
super().__init__()
|
| 39 |
-
self.generator = generator or GeneratorAgent(
|
|
|
|
|
|
|
|
|
|
| 40 |
self.generator_mode = generator_mode
|
| 41 |
self.session_id = session_id or str(uuid4())
|
| 42 |
self.problem: dict[str, Any] = {}
|
|
|
|
| 33 |
self,
|
| 34 |
generator: GeneratorAgent | None = None,
|
| 35 |
generator_mode: str = "heuristic",
|
| 36 |
+
use_dataset: bool = False,
|
| 37 |
+
dataset_kwargs: dict[str, Any] | None = None,
|
| 38 |
session_id: str | None = None,
|
| 39 |
) -> None:
|
| 40 |
super().__init__()
|
| 41 |
+
self.generator = generator or GeneratorAgent(
|
| 42 |
+
use_dataset=use_dataset,
|
| 43 |
+
dataset_kwargs=dataset_kwargs or {},
|
| 44 |
+
)
|
| 45 |
self.generator_mode = generator_mode
|
| 46 |
self.session_id = session_id or str(uuid4())
|
| 47 |
self.problem: dict[str, Any] = {}
|
env/dataset_loader.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import random
|
| 5 |
+
import re
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from env.generator import HIDDEN_TEST_COUNT, TOTAL_TEST_CASES, VISIBLE_TEST_COUNT
|
| 9 |
+
|
| 10 |
+
MAX_IO_CHARS = 4096
|
| 11 |
+
DEFAULT_DATASET_NAME = "deepmind/code_contests"
|
| 12 |
+
DEFAULT_SPLIT = "train"
|
| 13 |
+
DEFAULT_MAX_PROBLEMS = 5000
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _load_raw_dataset(
|
| 17 |
+
dataset_name: str,
|
| 18 |
+
split: str = DEFAULT_SPLIT,
|
| 19 |
+
max_problems: int = DEFAULT_MAX_PROBLEMS,
|
| 20 |
+
) -> list[dict[str, Any]]:
|
| 21 |
+
from datasets import load_dataset
|
| 22 |
+
|
| 23 |
+
dataset = load_dataset(dataset_name, split=split, trust_remote_code=True)
|
| 24 |
+
rows: list[dict[str, Any]] = []
|
| 25 |
+
for raw_row in dataset:
|
| 26 |
+
row = dict(raw_row)
|
| 27 |
+
statement = str(row.get("description") or row.get("question") or "").strip()
|
| 28 |
+
if not statement:
|
| 29 |
+
continue
|
| 30 |
+
if not _extract_pairs(row.get("public_tests")):
|
| 31 |
+
continue
|
| 32 |
+
if not (_extract_pairs(row.get("private_tests")) or _extract_pairs(row.get("generated_tests"))):
|
| 33 |
+
continue
|
| 34 |
+
rows.append(row)
|
| 35 |
+
if len(rows) >= int(max_problems):
|
| 36 |
+
break
|
| 37 |
+
return rows
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _normalise_row(raw_row: dict[str, Any], dataset_name: str) -> dict[str, Any] | None:
|
| 41 |
+
statement = _extract_problem_statement(raw_row)
|
| 42 |
+
if not statement:
|
| 43 |
+
return None
|
| 44 |
+
|
| 45 |
+
public_pairs = _extract_pairs(raw_row.get("public_tests"))
|
| 46 |
+
private_pairs = _extract_pairs(raw_row.get("private_tests"))
|
| 47 |
+
generated_pairs = _extract_pairs(raw_row.get("generated_tests"))
|
| 48 |
+
visible_pairs = public_pairs[:VISIBLE_TEST_COUNT]
|
| 49 |
+
hidden_pairs = (private_pairs + generated_pairs)[:HIDDEN_TEST_COUNT]
|
| 50 |
+
if len(visible_pairs) != VISIBLE_TEST_COUNT or len(hidden_pairs) != HIDDEN_TEST_COUNT:
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
test_cases: list[dict[str, Any]] = []
|
| 54 |
+
seen_inputs: set[str] = set()
|
| 55 |
+
for index, (raw_input, raw_output) in enumerate(visible_pairs + hidden_pairs):
|
| 56 |
+
normalized_input = _normalize_io_text(raw_input, ensure_trailing_newline=True)
|
| 57 |
+
normalized_output = _normalize_io_text(raw_output, ensure_trailing_newline=False)
|
| 58 |
+
if not normalized_input or normalized_input in seen_inputs:
|
| 59 |
+
return None
|
| 60 |
+
seen_inputs.add(normalized_input)
|
| 61 |
+
test_cases.append(
|
| 62 |
+
{
|
| 63 |
+
"input": normalized_input,
|
| 64 |
+
"output": normalized_output,
|
| 65 |
+
"is_visible": index < VISIBLE_TEST_COUNT,
|
| 66 |
+
}
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
if len(test_cases) != TOTAL_TEST_CASES:
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
difficulty_label, difficulty_value = _difficulty_fields(raw_row, dataset_name)
|
| 73 |
+
input_format = _extract_section(statement, "input") or "Read from stdin."
|
| 74 |
+
constraints = _extract_constraints(statement)
|
| 75 |
+
problem_type = _infer_problem_type(raw_row, statement)
|
| 76 |
+
problem_id = _problem_id(raw_row, dataset_name)
|
| 77 |
+
|
| 78 |
+
visible_examples = [dict(test_case) for test_case in test_cases[:VISIBLE_TEST_COUNT]]
|
| 79 |
+
return {
|
| 80 |
+
"problem_id": problem_id,
|
| 81 |
+
"problem_type": problem_type,
|
| 82 |
+
"difficulty": difficulty_value,
|
| 83 |
+
"difficulty_label": difficulty_label,
|
| 84 |
+
"problem": statement,
|
| 85 |
+
"input_format": input_format,
|
| 86 |
+
"constraints": constraints,
|
| 87 |
+
"test_cases": test_cases,
|
| 88 |
+
"visible_problem": {
|
| 89 |
+
"problem": statement,
|
| 90 |
+
"input_format": input_format,
|
| 91 |
+
"constraints": constraints,
|
| 92 |
+
"examples": visible_examples,
|
| 93 |
+
},
|
| 94 |
+
"generation_mode": "dataset",
|
| 95 |
+
"validity_bonus": 1.0,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class DatasetProblemBank:
|
| 100 |
+
def __init__(
|
| 101 |
+
self,
|
| 102 |
+
dataset_name: str = DEFAULT_DATASET_NAME,
|
| 103 |
+
split: str = DEFAULT_SPLIT,
|
| 104 |
+
max_problems: int = DEFAULT_MAX_PROBLEMS,
|
| 105 |
+
) -> None:
|
| 106 |
+
self.dataset_name = dataset_name
|
| 107 |
+
self.split = split
|
| 108 |
+
self.max_problems = int(max_problems)
|
| 109 |
+
self._by_difficulty: dict[str, list[dict[str, Any]]] = {
|
| 110 |
+
"easy": [],
|
| 111 |
+
"medium": [],
|
| 112 |
+
"hard": [],
|
| 113 |
+
}
|
| 114 |
+
self._by_id: dict[str, dict[str, Any]] = {}
|
| 115 |
+
|
| 116 |
+
raw_rows = _load_raw_dataset(dataset_name=dataset_name, split=split, max_problems=max_problems)
|
| 117 |
+
for raw_row in raw_rows:
|
| 118 |
+
normalized = _normalise_row(raw_row, dataset_name)
|
| 119 |
+
if normalized is None:
|
| 120 |
+
continue
|
| 121 |
+
problem_id = str(normalized["problem_id"])
|
| 122 |
+
if problem_id in self._by_id:
|
| 123 |
+
continue
|
| 124 |
+
difficulty = str(normalized.get("difficulty_label", "medium")).lower()
|
| 125 |
+
if difficulty not in self._by_difficulty:
|
| 126 |
+
difficulty = "medium"
|
| 127 |
+
normalized["difficulty_label"] = difficulty
|
| 128 |
+
stored = _copy_problem(normalized)
|
| 129 |
+
self._by_difficulty[difficulty].append(stored)
|
| 130 |
+
self._by_id[problem_id] = stored
|
| 131 |
+
|
| 132 |
+
if not self._by_id:
|
| 133 |
+
raise ValueError(
|
| 134 |
+
f"No usable problems were found in dataset `{dataset_name}` split `{split}` with max_problems={max_problems}."
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
def sample(self, difficulty: str, rng: random.Random, recent_types: list[str]) -> dict[str, Any] | None:
|
| 138 |
+
requested = str(difficulty).strip().lower()
|
| 139 |
+
candidates = list(self._by_difficulty.get(requested, []))
|
| 140 |
+
if not candidates:
|
| 141 |
+
candidates = [problem for bucket in self._by_difficulty.values() for problem in bucket]
|
| 142 |
+
if not candidates:
|
| 143 |
+
return None
|
| 144 |
+
|
| 145 |
+
recent = {problem_type for problem_type in recent_types[-3:] if problem_type}
|
| 146 |
+
diverse = [problem for problem in candidates if str(problem.get("problem_type", "")) not in recent]
|
| 147 |
+
pool = diverse or candidates
|
| 148 |
+
return _copy_problem(rng.choice(pool))
|
| 149 |
+
|
| 150 |
+
def all_problem_ids(self) -> list[str]:
|
| 151 |
+
return sorted(self._by_id)
|
| 152 |
+
|
| 153 |
+
def get_by_id(self, problem_id: str) -> dict[str, Any]:
|
| 154 |
+
return _copy_problem(self._by_id[str(problem_id)])
|
| 155 |
+
|
| 156 |
+
def problem_types_for_difficulty(self, difficulty: str) -> list[str]:
|
| 157 |
+
requested = str(difficulty).strip().lower()
|
| 158 |
+
candidates = self._by_difficulty.get(requested, [])
|
| 159 |
+
return sorted({str(problem.get("problem_type", "")) for problem in candidates if problem.get("problem_type")})
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
_BANK: DatasetProblemBank | None = None
|
| 163 |
+
_BANK_CONFIG: tuple[str, str, int] | None = None
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def get_problem_bank(**kwargs: Any) -> DatasetProblemBank:
|
| 167 |
+
global _BANK, _BANK_CONFIG
|
| 168 |
+
|
| 169 |
+
dataset_name = str(kwargs.get("dataset_name", DEFAULT_DATASET_NAME))
|
| 170 |
+
split = str(kwargs.get("split", DEFAULT_SPLIT))
|
| 171 |
+
max_problems = int(kwargs.get("max_problems", DEFAULT_MAX_PROBLEMS))
|
| 172 |
+
config = (dataset_name, split, max_problems)
|
| 173 |
+
if _BANK is None or _BANK_CONFIG != config:
|
| 174 |
+
_BANK = DatasetProblemBank(
|
| 175 |
+
dataset_name=dataset_name,
|
| 176 |
+
split=split,
|
| 177 |
+
max_problems=max_problems,
|
| 178 |
+
)
|
| 179 |
+
_BANK_CONFIG = config
|
| 180 |
+
return _BANK
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def _extract_problem_statement(raw_row: dict[str, Any]) -> str:
|
| 184 |
+
value = raw_row.get("description") or raw_row.get("question") or raw_row.get("problem")
|
| 185 |
+
return str(value or "").strip()
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _extract_pairs(raw_value: Any) -> list[tuple[str, str]]:
|
| 189 |
+
pairs: list[tuple[str, str]] = []
|
| 190 |
+
if raw_value is None:
|
| 191 |
+
return pairs
|
| 192 |
+
|
| 193 |
+
if isinstance(raw_value, dict):
|
| 194 |
+
inputs = raw_value.get("input") or raw_value.get("inputs") or raw_value.get("stdin") or []
|
| 195 |
+
outputs = raw_value.get("output") or raw_value.get("outputs") or raw_value.get("stdout") or []
|
| 196 |
+
if isinstance(inputs, str):
|
| 197 |
+
inputs = [inputs]
|
| 198 |
+
if isinstance(outputs, str):
|
| 199 |
+
outputs = [outputs]
|
| 200 |
+
for raw_input, raw_output in zip(list(inputs), list(outputs)):
|
| 201 |
+
pairs.append((str(raw_input), str(raw_output)))
|
| 202 |
+
return pairs
|
| 203 |
+
|
| 204 |
+
if isinstance(raw_value, list):
|
| 205 |
+
for item in raw_value:
|
| 206 |
+
if isinstance(item, dict):
|
| 207 |
+
raw_input = item.get("input") or item.get("stdin") or item.get("in")
|
| 208 |
+
raw_output = item.get("output") or item.get("stdout") or item.get("out")
|
| 209 |
+
if raw_input is None or raw_output is None:
|
| 210 |
+
continue
|
| 211 |
+
pairs.append((str(raw_input), str(raw_output)))
|
| 212 |
+
elif isinstance(item, (list, tuple)) and len(item) >= 2:
|
| 213 |
+
pairs.append((str(item[0]), str(item[1])))
|
| 214 |
+
return pairs
|
| 215 |
+
|
| 216 |
+
return pairs
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _normalize_io_text(value: Any, *, ensure_trailing_newline: bool) -> str:
|
| 220 |
+
text = str(value or "")[:MAX_IO_CHARS]
|
| 221 |
+
if ensure_trailing_newline:
|
| 222 |
+
text = text.rstrip("\n")
|
| 223 |
+
return f"{text}\n" if text else "\n"
|
| 224 |
+
return text.strip()
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def _difficulty_fields(raw_row: dict[str, Any], dataset_name: str) -> tuple[str, float]:
|
| 228 |
+
dataset_key = dataset_name.lower()
|
| 229 |
+
if "code_contests" in dataset_key or "code-contests" in dataset_key:
|
| 230 |
+
raw_difficulty = raw_row.get("difficulty")
|
| 231 |
+
try:
|
| 232 |
+
rating = float(raw_difficulty)
|
| 233 |
+
except (TypeError, ValueError):
|
| 234 |
+
return "medium", 0.5
|
| 235 |
+
normalized = max(0.0, min((rating - 800.0) / (3500.0 - 800.0), 1.0))
|
| 236 |
+
if rating <= 1200:
|
| 237 |
+
label = "easy"
|
| 238 |
+
elif rating <= 1800:
|
| 239 |
+
label = "medium"
|
| 240 |
+
else:
|
| 241 |
+
label = "hard"
|
| 242 |
+
return label, round(normalized, 4)
|
| 243 |
+
|
| 244 |
+
raw_difficulty = str(raw_row.get("difficulty") or "").strip().lower()
|
| 245 |
+
if raw_difficulty in {"easy", "medium", "hard"}:
|
| 246 |
+
return raw_difficulty, {"easy": 0.25, "medium": 0.5, "hard": 0.75}[raw_difficulty]
|
| 247 |
+
return "medium", 0.5
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def _problem_id(raw_row: dict[str, Any], dataset_name: str) -> str:
|
| 251 |
+
prefix = "cc" if "code_contests" in dataset_name.lower() else "ds"
|
| 252 |
+
for key in ("problem_id", "id", "name", "source"):
|
| 253 |
+
value = raw_row.get(key)
|
| 254 |
+
if value is not None and str(value).strip():
|
| 255 |
+
candidate = re.sub(r"[^a-zA-Z0-9_]+", "_", str(value).strip()).strip("_")
|
| 256 |
+
if candidate:
|
| 257 |
+
return f"{prefix}_{candidate}"
|
| 258 |
+
digest = hashlib.sha256(repr(sorted(raw_row.items())).encode("utf-8")).hexdigest()
|
| 259 |
+
return f"{prefix}_{digest[:12]}"
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _extract_section(statement: str, heading: str) -> str:
|
| 263 |
+
pattern = re.compile(
|
| 264 |
+
rf"{heading}\s*:?[\r\n]+(.*?)(?=\n[A-Z][A-Za-z ]{{1,30}}:?[\r\n]|\Z)",
|
| 265 |
+
flags=re.IGNORECASE | re.DOTALL,
|
| 266 |
+
)
|
| 267 |
+
match = pattern.search(statement)
|
| 268 |
+
return match.group(1).strip() if match else ""
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def _extract_constraints(statement: str) -> str:
|
| 272 |
+
constraints = _extract_section(statement, "constraints")
|
| 273 |
+
return constraints or "See problem statement."
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def _infer_problem_type(raw_row: dict[str, Any], statement: str) -> str:
|
| 277 |
+
parts: list[str] = [statement]
|
| 278 |
+
tags = raw_row.get("tags")
|
| 279 |
+
if isinstance(tags, list):
|
| 280 |
+
parts.extend(str(tag) for tag in tags)
|
| 281 |
+
elif isinstance(tags, str):
|
| 282 |
+
parts.append(tags)
|
| 283 |
+
for key in ("source", "name"):
|
| 284 |
+
value = raw_row.get(key)
|
| 285 |
+
if value:
|
| 286 |
+
parts.append(str(value))
|
| 287 |
+
text = " ".join(parts).lower()
|
| 288 |
+
|
| 289 |
+
keyword_map = {
|
| 290 |
+
"graph": "graph",
|
| 291 |
+
"tree": "tree",
|
| 292 |
+
"dynamic programming": "dp",
|
| 293 |
+
" dp ": "dp",
|
| 294 |
+
"string": "string",
|
| 295 |
+
"array": "array",
|
| 296 |
+
"greedy": "greedy",
|
| 297 |
+
"math": "math",
|
| 298 |
+
"sort": "sorting",
|
| 299 |
+
"binary search": "search",
|
| 300 |
+
}
|
| 301 |
+
padded = f" {text} "
|
| 302 |
+
for needle, problem_type in keyword_map.items():
|
| 303 |
+
haystack = padded if needle.startswith(" ") and needle.endswith(" ") else text
|
| 304 |
+
if needle in haystack:
|
| 305 |
+
return problem_type
|
| 306 |
+
return "implementation"
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def _copy_problem(problem: dict[str, Any]) -> dict[str, Any]:
|
| 310 |
+
copied = dict(problem)
|
| 311 |
+
copied["test_cases"] = [dict(test_case) for test_case in problem.get("test_cases", [])]
|
| 312 |
+
copied["visible_problem"] = dict(problem.get("visible_problem", {}))
|
| 313 |
+
examples = copied["visible_problem"].get("examples")
|
| 314 |
+
if isinstance(examples, list):
|
| 315 |
+
copied["visible_problem"]["examples"] = [dict(example) for example in examples]
|
| 316 |
+
return copied
|
env/generator.py
CHANGED
|
@@ -109,17 +109,43 @@ def normalize_problem(problem_dict: dict[str, Any]) -> dict[str, Any]:
|
|
| 109 |
normalized["problem"] = str(problem_dict.get("problem", "")).strip()
|
| 110 |
normalized["input_format"] = str(problem_dict.get("input_format", "")).strip()
|
| 111 |
normalized["constraints"] = str(problem_dict.get("constraints", "")).strip()
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
return normalized
|
| 115 |
|
| 116 |
|
| 117 |
class GeneratorAgent:
|
| 118 |
"""Deterministic, dependency-free generator for DSA-style problems."""
|
| 119 |
|
| 120 |
-
def __init__(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
self.deterministic = deterministic
|
|
|
|
|
|
|
| 122 |
self.templates = _build_templates()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
def generate_problem(
|
| 125 |
self,
|
|
@@ -130,6 +156,30 @@ class GeneratorAgent:
|
|
| 130 |
) -> dict[str, Any]:
|
| 131 |
history = history or {}
|
| 132 |
target_tier = _difficulty_to_tier(difficulty_level)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
rng = self._rng_for(target_tier, history, problem_id, family_weights or {})
|
| 134 |
template = self._choose_template(
|
| 135 |
target_tier,
|
|
@@ -250,6 +300,27 @@ class GeneratorAgent:
|
|
| 250 |
def _tier_to_scalar(self, tier: int) -> float:
|
| 251 |
return {1: 0.25, 2: 0.5, 3: 0.75}.get(tier, 0.5)
|
| 252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
def _difficulty_to_tier(difficulty_level: int | float | str) -> int:
|
| 255 |
if isinstance(difficulty_level, str):
|
|
|
|
| 109 |
normalized["problem"] = str(problem_dict.get("problem", "")).strip()
|
| 110 |
normalized["input_format"] = str(problem_dict.get("input_format", "")).strip()
|
| 111 |
normalized["constraints"] = str(problem_dict.get("constraints", "")).strip()
|
| 112 |
+
normalized_cases: list[dict[str, Any]] = []
|
| 113 |
+
for test_case in problem_dict.get("test_cases", []):
|
| 114 |
+
case = dict(test_case)
|
| 115 |
+
if "output" not in case and "expected_output" in case:
|
| 116 |
+
case["output"] = case.get("expected_output")
|
| 117 |
+
if "is_visible" not in case and "visible" in case:
|
| 118 |
+
case["is_visible"] = bool(case.get("visible"))
|
| 119 |
+
normalized_cases.append(case)
|
| 120 |
+
normalized["test_cases"] = normalized_cases
|
| 121 |
+
normalized_visible = dict(problem_dict.get("visible_problem", {}))
|
| 122 |
+
examples = normalized_visible.get("examples")
|
| 123 |
+
if isinstance(examples, list):
|
| 124 |
+
normalized_visible["examples"] = [dict(example) for example in examples]
|
| 125 |
+
normalized["visible_problem"] = normalized_visible
|
| 126 |
return normalized
|
| 127 |
|
| 128 |
|
| 129 |
class GeneratorAgent:
|
| 130 |
"""Deterministic, dependency-free generator for DSA-style problems."""
|
| 131 |
|
| 132 |
+
def __init__(
|
| 133 |
+
self,
|
| 134 |
+
deterministic: bool = True,
|
| 135 |
+
use_dataset: bool = False,
|
| 136 |
+
dataset_kwargs: dict[str, Any] | None = None,
|
| 137 |
+
) -> None:
|
| 138 |
self.deterministic = deterministic
|
| 139 |
+
self._use_dataset = use_dataset
|
| 140 |
+
self._dataset_kwargs = dict(dataset_kwargs or {})
|
| 141 |
self.templates = _build_templates()
|
| 142 |
+
self._bank = None
|
| 143 |
+
|
| 144 |
+
def _ensure_bank(self) -> None:
|
| 145 |
+
if self._bank is None:
|
| 146 |
+
from env.dataset_loader import get_problem_bank
|
| 147 |
+
|
| 148 |
+
self._bank = get_problem_bank(**self._dataset_kwargs)
|
| 149 |
|
| 150 |
def generate_problem(
|
| 151 |
self,
|
|
|
|
| 156 |
) -> dict[str, Any]:
|
| 157 |
history = history or {}
|
| 158 |
target_tier = _difficulty_to_tier(difficulty_level)
|
| 159 |
+
|
| 160 |
+
if self._use_dataset:
|
| 161 |
+
self._ensure_bank()
|
| 162 |
+
if problem_id:
|
| 163 |
+
try:
|
| 164 |
+
problem = self._bank.get_by_id(problem_id)
|
| 165 |
+
except KeyError:
|
| 166 |
+
problem = None
|
| 167 |
+
if problem is not None:
|
| 168 |
+
normalized_problem = normalize_problem(problem)
|
| 169 |
+
if validate_problem(normalized_problem):
|
| 170 |
+
return normalized_problem
|
| 171 |
+
|
| 172 |
+
rng = self._rng_for(target_tier, history, problem_id, family_weights or {})
|
| 173 |
+
problem = self._bank.sample(
|
| 174 |
+
self._difficulty_to_label(self._tier_to_scalar(target_tier)),
|
| 175 |
+
rng,
|
| 176 |
+
list(history.get("problem_types", [])),
|
| 177 |
+
)
|
| 178 |
+
if problem is not None:
|
| 179 |
+
normalized_problem = normalize_problem(problem)
|
| 180 |
+
if validate_problem(normalized_problem):
|
| 181 |
+
return normalized_problem
|
| 182 |
+
|
| 183 |
rng = self._rng_for(target_tier, history, problem_id, family_weights or {})
|
| 184 |
template = self._choose_template(
|
| 185 |
target_tier,
|
|
|
|
| 300 |
def _tier_to_scalar(self, tier: int) -> float:
|
| 301 |
return {1: 0.25, 2: 0.5, 3: 0.75}.get(tier, 0.5)
|
| 302 |
|
| 303 |
+
def _difficulty_to_label(self, difficulty: float) -> str:
|
| 304 |
+
if difficulty < 0.35:
|
| 305 |
+
return "easy"
|
| 306 |
+
if difficulty < 0.70:
|
| 307 |
+
return "medium"
|
| 308 |
+
return "hard"
|
| 309 |
+
|
| 310 |
+
def problem_types_for_difficulty(self, difficulty: str) -> list[str]:
|
| 311 |
+
if self._use_dataset:
|
| 312 |
+
self._ensure_bank()
|
| 313 |
+
return self._bank.problem_types_for_difficulty(difficulty)
|
| 314 |
+
|
| 315 |
+
normalized = str(difficulty).strip().lower()
|
| 316 |
+
return sorted(
|
| 317 |
+
{
|
| 318 |
+
template.problem_type
|
| 319 |
+
for template in self.templates
|
| 320 |
+
if DIFFICULTY_LABELS.get(template.difficulty_tier) == normalized
|
| 321 |
+
}
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
|
| 325 |
def _difficulty_to_tier(difficulty_level: int | float | str) -> int:
|
| 326 |
if isinstance(difficulty_level, str):
|
env/test_cases.py
CHANGED
|
@@ -5,7 +5,13 @@ from typing import Any
|
|
| 5 |
from env.generator import DIFFICULTY_LABELS, GeneratorAgent, VISIBLE_TEST_COUNT
|
| 6 |
|
| 7 |
|
| 8 |
-
def load_problem_bank() -> list[dict[str, Any]]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
generator = GeneratorAgent()
|
| 10 |
bank = []
|
| 11 |
for template in generator.templates:
|
|
|
|
| 5 |
from env.generator import DIFFICULTY_LABELS, GeneratorAgent, VISIBLE_TEST_COUNT
|
| 6 |
|
| 7 |
|
| 8 |
+
def load_problem_bank(use_dataset: bool = False) -> list[dict[str, Any]]:
|
| 9 |
+
if use_dataset:
|
| 10 |
+
from env.dataset_loader import get_problem_bank
|
| 11 |
+
|
| 12 |
+
bank = get_problem_bank()
|
| 13 |
+
return [bank.get_by_id(problem_id) for problem_id in bank.all_problem_ids()[:200]]
|
| 14 |
+
|
| 15 |
generator = GeneratorAgent()
|
| 16 |
bank = []
|
| 17 |
for template in generator.templates:
|
pyproject.toml
CHANGED
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
| 16 |
"uvicorn>=0.24.0",
|
| 17 |
"httpx>=0.28.0",
|
| 18 |
"huggingface_hub>=0.34.0,<0.36",
|
|
|
|
| 19 |
"transformers>=4.51.3,<5.7",
|
| 20 |
"peft>=0.18.0,<0.20",
|
| 21 |
]
|
|
|
|
| 16 |
"uvicorn>=0.24.0",
|
| 17 |
"httpx>=0.28.0",
|
| 18 |
"huggingface_hub>=0.34.0,<0.36",
|
| 19 |
+
"datasets>=2.18.0",
|
| 20 |
"transformers>=4.51.3,<5.7",
|
| 21 |
"peft>=0.18.0,<0.20",
|
| 22 |
]
|
requirements.txt
CHANGED
|
@@ -4,5 +4,6 @@ fastapi>=0.104.0
|
|
| 4 |
uvicorn>=0.24.0
|
| 5 |
pydantic>=2.0.0
|
| 6 |
huggingface_hub>=0.34.0,<0.36
|
|
|
|
| 7 |
transformers>=4.51.3,<5.7
|
| 8 |
peft>=0.18.0,<0.20
|
|
|
|
| 4 |
uvicorn>=0.24.0
|
| 5 |
pydantic>=2.0.0
|
| 6 |
huggingface_hub>=0.34.0,<0.36
|
| 7 |
+
datasets>=2.18.0
|
| 8 |
transformers>=4.51.3,<5.7
|
| 9 |
peft>=0.18.0,<0.20
|
scripts/test_dataset_mode.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
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:
|
| 8 |
+
sys.path.insert(0, str(ROOT))
|
| 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 |
+
|
| 16 |
+
|
| 17 |
+
class FakeBank:
|
| 18 |
+
def __init__(self, problem: dict) -> None:
|
| 19 |
+
self.problem = problem
|
| 20 |
+
|
| 21 |
+
def sample(self, difficulty: str, rng, recent_types: list[str]) -> dict:
|
| 22 |
+
del difficulty, rng, recent_types
|
| 23 |
+
return _copy_problem(self.problem)
|
| 24 |
+
|
| 25 |
+
def all_problem_ids(self) -> list[str]:
|
| 26 |
+
return [self.problem["problem_id"]]
|
| 27 |
+
|
| 28 |
+
def get_by_id(self, problem_id: str) -> dict:
|
| 29 |
+
if problem_id != self.problem["problem_id"]:
|
| 30 |
+
raise KeyError(problem_id)
|
| 31 |
+
return _copy_problem(self.problem)
|
| 32 |
+
|
| 33 |
+
def problem_types_for_difficulty(self, difficulty: str) -> list[str]:
|
| 34 |
+
del difficulty
|
| 35 |
+
return [self.problem["problem_type"]]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _copy_problem(problem: dict) -> dict:
|
| 39 |
+
copied = dict(problem)
|
| 40 |
+
copied["test_cases"] = [dict(test_case) for test_case in problem.get("test_cases", [])]
|
| 41 |
+
copied["visible_problem"] = dict(problem.get("visible_problem", {}))
|
| 42 |
+
examples = copied["visible_problem"].get("examples")
|
| 43 |
+
if isinstance(examples, list):
|
| 44 |
+
copied["visible_problem"]["examples"] = [dict(example) for example in examples]
|
| 45 |
+
return copied
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def main() -> None:
|
| 49 |
+
template_problem = GeneratorAgent().generate_problem(1, {}, problem_id="sum_even_numbers")
|
| 50 |
+
dataset_problem = _copy_problem(template_problem)
|
| 51 |
+
dataset_problem["problem_id"] = "cc_stub_sum_even_numbers"
|
| 52 |
+
dataset_problem["generation_mode"] = "dataset"
|
| 53 |
+
dataset_problem["validity_bonus"] = 1.0
|
| 54 |
+
|
| 55 |
+
fake_bank = FakeBank(dataset_problem)
|
| 56 |
+
original_bank = dataset_loader._BANK
|
| 57 |
+
original_config = dataset_loader._BANK_CONFIG
|
| 58 |
+
dataset_loader._BANK = fake_bank
|
| 59 |
+
dataset_loader._BANK_CONFIG = ("deepmind/code_contests", "train", 5000)
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
loaded_bank = load_problem_bank(use_dataset=True)
|
| 63 |
+
assert loaded_bank
|
| 64 |
+
assert validate_problem(loaded_bank[0])
|
| 65 |
+
|
| 66 |
+
generated = GeneratorAgent(use_dataset=True).generate_problem("easy", {})
|
| 67 |
+
assert generated["problem_id"] == dataset_problem["problem_id"]
|
| 68 |
+
assert generated["generation_mode"] == "dataset"
|
| 69 |
+
assert validate_problem(generated)
|
| 70 |
+
|
| 71 |
+
env = AdaptEnvironment(use_dataset=True)
|
| 72 |
+
observation = env.reset(difficulty="easy")
|
| 73 |
+
assert env.problem["generation_mode"] == "dataset"
|
| 74 |
+
assert observation.problem_type == "sum_even_numbers"
|
| 75 |
+
|
| 76 |
+
result = env.step(
|
| 77 |
+
AdaptAction(
|
| 78 |
+
code=(
|
| 79 |
+
"n=int(input())\n"
|
| 80 |
+
"nums=list(map(int,input().split()))\n"
|
| 81 |
+
"print(sum(x for x in nums if x % 2 == 0))"
|
| 82 |
+
)
|
| 83 |
+
)
|
| 84 |
+
)
|
| 85 |
+
assert result.pass_rate == 1.0
|
| 86 |
+
print("Dataset mode smoke tests passed")
|
| 87 |
+
finally:
|
| 88 |
+
dataset_loader._BANK = original_bank
|
| 89 |
+
dataset_loader._BANK_CONFIG = original_config
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
if __name__ == "__main__":
|
| 93 |
+
main()
|
scripts/test_verifier.py
CHANGED
|
@@ -18,6 +18,12 @@ test_cases = [
|
|
| 18 |
{"input": "5\n8 8 8 8 8\n", "output": "40", "is_visible": False},
|
| 19 |
]
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
correct_code = """
|
| 22 |
n = int(input())
|
| 23 |
nums = list(map(int, input().split()))
|
|
@@ -90,3 +96,19 @@ assert info_optimal["verifier_components"]["hidden_correctness"] == 1.0
|
|
| 90 |
assert info_optimal["verifier_components"]["anti_cheat_compliance"] == 1.0
|
| 91 |
assert reward_safety == 0.0
|
| 92 |
assert info_safety["execution_status"] == "safety_violation"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
{"input": "5\n8 8 8 8 8\n", "output": "40", "is_visible": False},
|
| 19 |
]
|
| 20 |
|
| 21 |
+
empirical_test_cases = [
|
| 22 |
+
{"input": "10\n", "output": "45", "is_visible": False},
|
| 23 |
+
{"input": "200\n", "output": "19900", "is_visible": False},
|
| 24 |
+
{"input": "1000\n", "output": "499500", "is_visible": False},
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
correct_code = """
|
| 28 |
n = int(input())
|
| 29 |
nums = list(map(int, input().split()))
|
|
|
|
| 96 |
assert info_optimal["verifier_components"]["anti_cheat_compliance"] == 1.0
|
| 97 |
assert reward_safety == 0.0
|
| 98 |
assert info_safety["execution_status"] == "safety_violation"
|
| 99 |
+
|
| 100 |
+
linear_code = "n = int(input()); print(sum(range(n)))"
|
| 101 |
+
quadratic_code = "n = int(input()); print(sum(i*j for i in range(n) for j in range(n)))"
|
| 102 |
+
_, linear_result = verify(linear_code, empirical_test_cases)
|
| 103 |
+
_, quadratic_result = verify(quadratic_code, empirical_test_cases)
|
| 104 |
+
assert linear_result["efficiency_score"] >= quadratic_result["efficiency_score"], (
|
| 105 |
+
"Empirical complexity: O(n) should score >= O(n^2)"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
small_alloc = "n = int(input()); print(n)"
|
| 109 |
+
large_alloc = "n = int(input()); x = [0] * (n * 10000); print(len(x))"
|
| 110 |
+
_, small_result = verify(small_alloc, empirical_test_cases)
|
| 111 |
+
_, large_result = verify(large_alloc, empirical_test_cases)
|
| 112 |
+
assert small_result["efficiency_score"] >= large_result["efficiency_score"], (
|
| 113 |
+
"Empirical complexity: small allocation should score >= large allocation"
|
| 114 |
+
)
|
server/app.py
CHANGED
|
@@ -59,6 +59,9 @@ class TrainRequest(BaseModel):
|
|
| 59 |
evaluation_episodes: Optional[int] = None
|
| 60 |
baseline_eval: Optional[bool] = None
|
| 61 |
generator_mode: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
| 62 |
disable_wandb: Optional[bool] = None
|
| 63 |
save_merged_model: Optional[bool] = None
|
| 64 |
|
|
|
|
| 59 |
evaluation_episodes: Optional[int] = None
|
| 60 |
baseline_eval: Optional[bool] = None
|
| 61 |
generator_mode: Optional[str] = None
|
| 62 |
+
use_dataset: bool = False
|
| 63 |
+
dataset_name: str = "deepmind/code_contests"
|
| 64 |
+
dataset_max_problems: int = 5000
|
| 65 |
disable_wandb: Optional[bool] = None
|
| 66 |
save_merged_model: Optional[bool] = None
|
| 67 |
|
test.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
from scripts.test_env import main as run_env_smoke
|
| 4 |
from scripts.test_space_api import main as run_space_api_smoke
|
| 5 |
from scripts.test_training_config import main as run_training_config_smoke
|
|
@@ -9,6 +10,7 @@ from verifier.verifier import verify
|
|
| 9 |
|
| 10 |
|
| 11 |
def main() -> None:
|
|
|
|
| 12 |
run_env_smoke()
|
| 13 |
run_space_api_smoke()
|
| 14 |
run_training_config_smoke()
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
from scripts.test_dataset_mode import main as run_dataset_mode_smoke
|
| 4 |
from scripts.test_env import main as run_env_smoke
|
| 5 |
from scripts.test_space_api import main as run_space_api_smoke
|
| 6 |
from scripts.test_training_config import main as run_training_config_smoke
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
def main() -> None:
|
| 13 |
+
run_dataset_mode_smoke()
|
| 14 |
run_env_smoke()
|
| 15 |
run_space_api_smoke()
|
| 16 |
run_training_config_smoke()
|
training/train_grpo.py
CHANGED
|
@@ -59,6 +59,9 @@ class TrainingConfig:
|
|
| 59 |
wandb_run_name: str | None = None
|
| 60 |
generator_mode: str = "reward_aware"
|
| 61 |
non_deterministic_generator: bool = False
|
|
|
|
|
|
|
|
|
|
| 62 |
trace_logging_enabled: bool = True
|
| 63 |
checkpoint_log_interval_steps: int = 10
|
| 64 |
save_merged_model: bool = False
|
|
@@ -207,6 +210,9 @@ def namespace_to_config(args: argparse.Namespace) -> TrainingConfig:
|
|
| 207 |
wandb_run_name=args.wandb_run_name,
|
| 208 |
generator_mode=args.generator_mode,
|
| 209 |
non_deterministic_generator=args.non_deterministic_generator,
|
|
|
|
|
|
|
|
|
|
| 210 |
trace_logging_enabled=args.trace_logging_enabled,
|
| 211 |
checkpoint_log_interval_steps=args.checkpoint_log_interval_steps,
|
| 212 |
save_merged_model=getattr(args, "save_merged_model", False),
|
|
@@ -258,6 +264,8 @@ class GeneratorController:
|
|
| 258 |
mode: str = "heuristic"
|
| 259 |
deterministic: bool = True
|
| 260 |
temperature: float = 0.5
|
|
|
|
|
|
|
| 261 |
generator: GeneratorAgent = field(init=False)
|
| 262 |
history: dict[str, Any] = field(
|
| 263 |
default_factory=lambda: {
|
|
@@ -272,10 +280,14 @@ class GeneratorController:
|
|
| 272 |
family_productivity: dict[str, float] = field(default_factory=dict)
|
| 273 |
|
| 274 |
def __post_init__(self) -> None:
|
| 275 |
-
self.generator = GeneratorAgent(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
if not self.family_productivity:
|
| 277 |
self.family_productivity = {
|
| 278 |
-
|
| 279 |
}
|
| 280 |
|
| 281 |
@property
|
|
@@ -306,11 +318,14 @@ class GeneratorController:
|
|
| 306 |
if self.mode != "reward_aware":
|
| 307 |
return None
|
| 308 |
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
|
|
|
|
|
|
|
|
|
| 314 |
if not eligible:
|
| 315 |
return None
|
| 316 |
|
|
@@ -319,6 +334,14 @@ class GeneratorController:
|
|
| 319 |
exp_values = [math.exp(logit - max_logit) for logit in logits]
|
| 320 |
return {family: value for family, value in zip(eligible, exp_values)}
|
| 321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
def update(
|
| 323 |
self,
|
| 324 |
problem: dict[str, Any],
|
|
@@ -695,6 +718,8 @@ def run_policy_evaluation(
|
|
| 695 |
tokenizer: Any,
|
| 696 |
generator_mode: str,
|
| 697 |
deterministic_generator: bool,
|
|
|
|
|
|
|
| 698 |
episodes: int,
|
| 699 |
logger: TrainingLogger,
|
| 700 |
phase: str,
|
|
@@ -703,6 +728,8 @@ def run_policy_evaluation(
|
|
| 703 |
controller = GeneratorController(
|
| 704 |
mode=generator_mode,
|
| 705 |
deterministic=deterministic_generator,
|
|
|
|
|
|
|
| 706 |
)
|
| 707 |
schedule = ["easy"] * (episodes // 3 + (1 if episodes % 3 > 0 else 0))
|
| 708 |
schedule += ["medium"] * (episodes // 3 + (1 if episodes % 3 > 1 else 0))
|
|
@@ -1050,6 +1077,11 @@ def run_training(
|
|
| 1050 |
controller = GeneratorController(
|
| 1051 |
mode="reward_aware" if config.generator_mode == "reward_aware" else "heuristic",
|
| 1052 |
deterministic=not config.non_deterministic_generator,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
)
|
| 1054 |
logger = TrainingLogger(
|
| 1055 |
output_dir=output_dir,
|
|
@@ -1110,6 +1142,11 @@ def run_training(
|
|
| 1110 |
tokenizer=tokenizer,
|
| 1111 |
generator_mode=controller.mode,
|
| 1112 |
deterministic_generator=not config.non_deterministic_generator,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1113 |
episodes=config.evaluation_episodes,
|
| 1114 |
logger=logger,
|
| 1115 |
phase="baseline_eval",
|
|
@@ -1206,6 +1243,11 @@ def run_training(
|
|
| 1206 |
tokenizer=tokenizer,
|
| 1207 |
generator_mode=controller.mode,
|
| 1208 |
deterministic_generator=not config.non_deterministic_generator,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1209 |
episodes=config.evaluation_episodes,
|
| 1210 |
logger=logger,
|
| 1211 |
phase="trained_eval",
|
|
@@ -1290,6 +1332,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
| 1290 |
action="store_true",
|
| 1291 |
help="Disable deterministic fallback seeding for generator rollouts.",
|
| 1292 |
)
|
|
|
|
|
|
|
|
|
|
| 1293 |
return parser
|
| 1294 |
|
| 1295 |
|
|
|
|
| 59 |
wandb_run_name: str | None = None
|
| 60 |
generator_mode: str = "reward_aware"
|
| 61 |
non_deterministic_generator: bool = False
|
| 62 |
+
use_dataset: bool = False
|
| 63 |
+
dataset_name: str = "deepmind/code_contests"
|
| 64 |
+
dataset_max_problems: int = 5000
|
| 65 |
trace_logging_enabled: bool = True
|
| 66 |
checkpoint_log_interval_steps: int = 10
|
| 67 |
save_merged_model: bool = False
|
|
|
|
| 210 |
wandb_run_name=args.wandb_run_name,
|
| 211 |
generator_mode=args.generator_mode,
|
| 212 |
non_deterministic_generator=args.non_deterministic_generator,
|
| 213 |
+
use_dataset=args.use_dataset,
|
| 214 |
+
dataset_name=args.dataset_name,
|
| 215 |
+
dataset_max_problems=args.dataset_max_problems,
|
| 216 |
trace_logging_enabled=args.trace_logging_enabled,
|
| 217 |
checkpoint_log_interval_steps=args.checkpoint_log_interval_steps,
|
| 218 |
save_merged_model=getattr(args, "save_merged_model", False),
|
|
|
|
| 264 |
mode: str = "heuristic"
|
| 265 |
deterministic: bool = True
|
| 266 |
temperature: float = 0.5
|
| 267 |
+
use_dataset: bool = False
|
| 268 |
+
dataset_kwargs: dict[str, Any] = field(default_factory=dict)
|
| 269 |
generator: GeneratorAgent = field(init=False)
|
| 270 |
history: dict[str, Any] = field(
|
| 271 |
default_factory=lambda: {
|
|
|
|
| 280 |
family_productivity: dict[str, float] = field(default_factory=dict)
|
| 281 |
|
| 282 |
def __post_init__(self) -> None:
|
| 283 |
+
self.generator = GeneratorAgent(
|
| 284 |
+
deterministic=self.deterministic,
|
| 285 |
+
use_dataset=self.use_dataset,
|
| 286 |
+
dataset_kwargs=self.dataset_kwargs,
|
| 287 |
+
)
|
| 288 |
if not self.family_productivity:
|
| 289 |
self.family_productivity = {
|
| 290 |
+
family: 0.0 for family in self._known_problem_families()
|
| 291 |
}
|
| 292 |
|
| 293 |
@property
|
|
|
|
| 318 |
if self.mode != "reward_aware":
|
| 319 |
return None
|
| 320 |
|
| 321 |
+
if self.use_dataset:
|
| 322 |
+
eligible = self.generator.problem_types_for_difficulty(difficulty)
|
| 323 |
+
else:
|
| 324 |
+
eligible = [
|
| 325 |
+
template.problem_type
|
| 326 |
+
for template in self.generator.templates
|
| 327 |
+
if DIFFICULTY_LABELS[template.difficulty_tier] == difficulty
|
| 328 |
+
]
|
| 329 |
if not eligible:
|
| 330 |
return None
|
| 331 |
|
|
|
|
| 334 |
exp_values = [math.exp(logit - max_logit) for logit in logits]
|
| 335 |
return {family: value for family, value in zip(eligible, exp_values)}
|
| 336 |
|
| 337 |
+
def _known_problem_families(self) -> list[str]:
|
| 338 |
+
if self.use_dataset:
|
| 339 |
+
families: set[str] = set()
|
| 340 |
+
for difficulty in ("easy", "medium", "hard"):
|
| 341 |
+
families.update(self.generator.problem_types_for_difficulty(difficulty))
|
| 342 |
+
return sorted(families)
|
| 343 |
+
return sorted({template.problem_type for template in self.generator.templates})
|
| 344 |
+
|
| 345 |
def update(
|
| 346 |
self,
|
| 347 |
problem: dict[str, Any],
|
|
|
|
| 718 |
tokenizer: Any,
|
| 719 |
generator_mode: str,
|
| 720 |
deterministic_generator: bool,
|
| 721 |
+
use_dataset: bool,
|
| 722 |
+
dataset_kwargs: dict[str, Any],
|
| 723 |
episodes: int,
|
| 724 |
logger: TrainingLogger,
|
| 725 |
phase: str,
|
|
|
|
| 728 |
controller = GeneratorController(
|
| 729 |
mode=generator_mode,
|
| 730 |
deterministic=deterministic_generator,
|
| 731 |
+
use_dataset=use_dataset,
|
| 732 |
+
dataset_kwargs=dataset_kwargs,
|
| 733 |
)
|
| 734 |
schedule = ["easy"] * (episodes // 3 + (1 if episodes % 3 > 0 else 0))
|
| 735 |
schedule += ["medium"] * (episodes // 3 + (1 if episodes % 3 > 1 else 0))
|
|
|
|
| 1077 |
controller = GeneratorController(
|
| 1078 |
mode="reward_aware" if config.generator_mode == "reward_aware" else "heuristic",
|
| 1079 |
deterministic=not config.non_deterministic_generator,
|
| 1080 |
+
use_dataset=config.use_dataset,
|
| 1081 |
+
dataset_kwargs={
|
| 1082 |
+
"dataset_name": config.dataset_name,
|
| 1083 |
+
"max_problems": config.dataset_max_problems,
|
| 1084 |
+
},
|
| 1085 |
)
|
| 1086 |
logger = TrainingLogger(
|
| 1087 |
output_dir=output_dir,
|
|
|
|
| 1142 |
tokenizer=tokenizer,
|
| 1143 |
generator_mode=controller.mode,
|
| 1144 |
deterministic_generator=not config.non_deterministic_generator,
|
| 1145 |
+
use_dataset=config.use_dataset,
|
| 1146 |
+
dataset_kwargs={
|
| 1147 |
+
"dataset_name": config.dataset_name,
|
| 1148 |
+
"max_problems": config.dataset_max_problems,
|
| 1149 |
+
},
|
| 1150 |
episodes=config.evaluation_episodes,
|
| 1151 |
logger=logger,
|
| 1152 |
phase="baseline_eval",
|
|
|
|
| 1243 |
tokenizer=tokenizer,
|
| 1244 |
generator_mode=controller.mode,
|
| 1245 |
deterministic_generator=not config.non_deterministic_generator,
|
| 1246 |
+
use_dataset=config.use_dataset,
|
| 1247 |
+
dataset_kwargs={
|
| 1248 |
+
"dataset_name": config.dataset_name,
|
| 1249 |
+
"max_problems": config.dataset_max_problems,
|
| 1250 |
+
},
|
| 1251 |
episodes=config.evaluation_episodes,
|
| 1252 |
logger=logger,
|
| 1253 |
phase="trained_eval",
|
|
|
|
| 1332 |
action="store_true",
|
| 1333 |
help="Disable deterministic fallback seeding for generator rollouts.",
|
| 1334 |
)
|
| 1335 |
+
parser.add_argument("--use-dataset", action="store_true")
|
| 1336 |
+
parser.add_argument("--dataset-name", default="deepmind/code_contests")
|
| 1337 |
+
parser.add_argument("--dataset-max-problems", type=int, default=5000)
|
| 1338 |
return parser
|
| 1339 |
|
| 1340 |
|
verifier/complexity.py
CHANGED
|
@@ -1,9 +1,17 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import ast
|
|
|
|
|
|
|
|
|
|
| 4 |
from dataclasses import dataclass
|
| 5 |
from typing import Any
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
@dataclass
|
| 9 |
class ComplexitySignals:
|
|
@@ -69,7 +77,109 @@ def _call_name(node: ast.AST) -> str:
|
|
| 69 |
return ""
|
| 70 |
|
| 71 |
|
| 72 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
try:
|
| 74 |
tree = ast.parse(code)
|
| 75 |
except SyntaxError:
|
|
@@ -78,7 +188,7 @@ def analyze_code_complexity(code: str) -> dict[str, Any]:
|
|
| 78 |
"space_complexity_score": 0.0,
|
| 79 |
"efficiency_score": 0.0,
|
| 80 |
"optimization_hints": [],
|
| 81 |
-
"complexity_signals": {},
|
| 82 |
}
|
| 83 |
|
| 84 |
visitor = ComplexityVisitor()
|
|
@@ -126,5 +236,70 @@ def analyze_code_complexity(code: str) -> dict[str, Any]:
|
|
| 126 |
"generator_expressions": signals.generator_expressions,
|
| 127 |
"sorting_calls": signals.sorting_calls,
|
| 128 |
"materialized_builtin_inputs": signals.materialized_builtin_inputs,
|
|
|
|
| 129 |
},
|
| 130 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import ast
|
| 4 |
+
import math
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
from dataclasses import dataclass
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
+
from env.executor import run_code as execute_submission
|
| 11 |
+
|
| 12 |
+
PROBE_TIMEOUT_SECONDS = 2.0
|
| 13 |
+
METRICS_PATTERN = re.compile(r"ADAPT_METRICS:\s*time_ms=([0-9.]+)\s+peak_kb=([0-9.]+)")
|
| 14 |
+
|
| 15 |
|
| 16 |
@dataclass
|
| 17 |
class ComplexitySignals:
|
|
|
|
| 77 |
return ""
|
| 78 |
|
| 79 |
|
| 80 |
+
def _probe_timeout() -> float:
|
| 81 |
+
raw_value = os.getenv("ADAPT_PROBE_TIMEOUT", str(PROBE_TIMEOUT_SECONDS))
|
| 82 |
+
try:
|
| 83 |
+
timeout = float(raw_value)
|
| 84 |
+
except ValueError:
|
| 85 |
+
return PROBE_TIMEOUT_SECONDS
|
| 86 |
+
return timeout if timeout > 0 else PROBE_TIMEOUT_SECONDS
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _build_measurement_harness(code: str) -> str:
|
| 90 |
+
return f"""
|
| 91 |
+
import sys as _adapt_sys
|
| 92 |
+
import time as _adapt_time
|
| 93 |
+
import tracemalloc as _adapt_tracemalloc
|
| 94 |
+
|
| 95 |
+
_adapt_globals = {{"__name__": "__main__"}}
|
| 96 |
+
_adapt_source = {code!r}
|
| 97 |
+
_adapt_tracemalloc.start()
|
| 98 |
+
_adapt_t0 = _adapt_time.perf_counter()
|
| 99 |
+
exec(compile(_adapt_source, "<submission>", "exec"), _adapt_globals, _adapt_globals)
|
| 100 |
+
_adapt_t1 = _adapt_time.perf_counter()
|
| 101 |
+
_adapt_peak_kb = _adapt_tracemalloc.get_traced_memory()[1] / 1024
|
| 102 |
+
_adapt_tracemalloc.stop()
|
| 103 |
+
print(
|
| 104 |
+
f"ADAPT_METRICS: time_ms={{(_adapt_t1 - _adapt_t0) * 1000:.3f}} peak_kb={{_adapt_peak_kb:.1f}}",
|
| 105 |
+
file=_adapt_sys.stderr,
|
| 106 |
+
)
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _parse_harness_output(stderr: str) -> tuple[float, float]:
|
| 111 |
+
match = METRICS_PATTERN.search(str(stderr or ""))
|
| 112 |
+
if match is None:
|
| 113 |
+
return 0.0, 0.0
|
| 114 |
+
try:
|
| 115 |
+
return float(match.group(1)), float(match.group(2))
|
| 116 |
+
except ValueError:
|
| 117 |
+
return 0.0, 0.0
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _fit_scaling_exponent(sizes: list[float], values: list[float]) -> float:
|
| 121 |
+
if len(sizes) < 2 or len(values) < 2:
|
| 122 |
+
return 1.0
|
| 123 |
+
log_n = [math.log(max(size, 1.0)) for size in sizes]
|
| 124 |
+
log_v = [math.log(max(value, 1e-6)) for value in values]
|
| 125 |
+
count = len(log_n)
|
| 126 |
+
mean_n = sum(log_n) / count
|
| 127 |
+
mean_v = sum(log_v) / count
|
| 128 |
+
numerator = sum((log_n[index] - mean_n) * (log_v[index] - mean_v) for index in range(count))
|
| 129 |
+
denominator = sum((log_n[index] - mean_n) ** 2 for index in range(count))
|
| 130 |
+
return numerator / denominator if denominator > 1e-9 else 1.0
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def _exponent_to_score(alpha: float) -> float:
|
| 134 |
+
if alpha < 0.1:
|
| 135 |
+
return 1.0
|
| 136 |
+
if alpha < 1.2:
|
| 137 |
+
return 0.85
|
| 138 |
+
if alpha < 1.6:
|
| 139 |
+
return 0.75
|
| 140 |
+
if alpha < 2.3:
|
| 141 |
+
return 0.50
|
| 142 |
+
if alpha < 3.2:
|
| 143 |
+
return 0.20
|
| 144 |
+
return 0.0
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _memory_to_score(peak_kb: float) -> float:
|
| 148 |
+
mb = peak_kb / 1024.0
|
| 149 |
+
if mb < 1:
|
| 150 |
+
return 1.0
|
| 151 |
+
if mb < 10:
|
| 152 |
+
return 0.85
|
| 153 |
+
if mb < 50:
|
| 154 |
+
return 0.65
|
| 155 |
+
if mb < 256:
|
| 156 |
+
return 0.40
|
| 157 |
+
return 0.10
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _hints_from_scores(time_score: float, space_score: float, time_alpha: float) -> list[str]:
|
| 161 |
+
hints: list[str] = []
|
| 162 |
+
if time_alpha >= 2.3:
|
| 163 |
+
hints.append("Reduce quadratic-or-worse work; measured runtime growth looks steep across larger inputs.")
|
| 164 |
+
elif time_score < 0.85:
|
| 165 |
+
hints.append("Consider a more scalable algorithm so runtime grows more gently with input size.")
|
| 166 |
+
if space_score < 0.85:
|
| 167 |
+
hints.append("Reduce peak memory usage by avoiding large intermediate containers when possible.")
|
| 168 |
+
return hints
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _merge_hints(*hint_groups: list[str]) -> list[str]:
|
| 172 |
+
merged: list[str] = []
|
| 173 |
+
seen: set[str] = set()
|
| 174 |
+
for group in hint_groups:
|
| 175 |
+
for hint in group:
|
| 176 |
+
if hint and hint not in seen:
|
| 177 |
+
seen.add(hint)
|
| 178 |
+
merged.append(hint)
|
| 179 |
+
return merged
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _heuristic_fallback(code: str) -> dict[str, Any]:
|
| 183 |
try:
|
| 184 |
tree = ast.parse(code)
|
| 185 |
except SyntaxError:
|
|
|
|
| 188 |
"space_complexity_score": 0.0,
|
| 189 |
"efficiency_score": 0.0,
|
| 190 |
"optimization_hints": [],
|
| 191 |
+
"complexity_signals": {"measurement_source": "heuristic"},
|
| 192 |
}
|
| 193 |
|
| 194 |
visitor = ComplexityVisitor()
|
|
|
|
| 236 |
"generator_expressions": signals.generator_expressions,
|
| 237 |
"sorting_calls": signals.sorting_calls,
|
| 238 |
"materialized_builtin_inputs": signals.materialized_builtin_inputs,
|
| 239 |
+
"measurement_source": "heuristic",
|
| 240 |
},
|
| 241 |
}
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _empirical_complexity(code: str, probe_inputs: list[str]) -> dict[str, Any]:
|
| 245 |
+
heuristic = _heuristic_fallback(code)
|
| 246 |
+
if len(probe_inputs) < 3:
|
| 247 |
+
return heuristic
|
| 248 |
+
|
| 249 |
+
harness = _build_measurement_harness(code)
|
| 250 |
+
sizes: list[float] = []
|
| 251 |
+
times: list[float] = []
|
| 252 |
+
mem_peaks: list[float] = []
|
| 253 |
+
for probe_input in probe_inputs:
|
| 254 |
+
result = execute_submission(harness, probe_input, timeout_seconds=_probe_timeout())
|
| 255 |
+
if bool(result.get("timed_out")) or int(result.get("exit_code", 0)) != 0:
|
| 256 |
+
return heuristic
|
| 257 |
+
wall_ms, peak_kb = _parse_harness_output(str(result.get("stderr", "")))
|
| 258 |
+
if wall_ms <= 0.0 and peak_kb <= 0.0:
|
| 259 |
+
return heuristic
|
| 260 |
+
sizes.append(float(len(probe_input)))
|
| 261 |
+
times.append(float(wall_ms))
|
| 262 |
+
mem_peaks.append(float(peak_kb))
|
| 263 |
+
|
| 264 |
+
if len(sizes) < 3:
|
| 265 |
+
return heuristic
|
| 266 |
+
|
| 267 |
+
empirical_time_score = _exponent_to_score(_fit_scaling_exponent(sizes, times))
|
| 268 |
+
empirical_space_score = _memory_to_score(max(mem_peaks))
|
| 269 |
+
time_alpha = _fit_scaling_exponent(sizes, times)
|
| 270 |
+
space_alpha = _fit_scaling_exponent(sizes, mem_peaks)
|
| 271 |
+
|
| 272 |
+
time_score = min(empirical_time_score, float(heuristic["time_complexity_score"]))
|
| 273 |
+
space_score = min(empirical_space_score, float(heuristic["space_complexity_score"]))
|
| 274 |
+
efficiency_score = round(min(0.7 * time_score + 0.3 * space_score, float(heuristic["efficiency_score"])), 4)
|
| 275 |
+
optimization_hints = _merge_hints(
|
| 276 |
+
_hints_from_scores(time_score, space_score, time_alpha),
|
| 277 |
+
list(heuristic.get("optimization_hints", [])),
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
complexity_signals = dict(heuristic.get("complexity_signals", {}))
|
| 281 |
+
complexity_signals.update(
|
| 282 |
+
{
|
| 283 |
+
"time_exponent": round(time_alpha, 4),
|
| 284 |
+
"space_exponent": round(space_alpha, 4),
|
| 285 |
+
"peak_memory_kb": round(max(mem_peaks), 1),
|
| 286 |
+
"measurement_source": "empirical",
|
| 287 |
+
}
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
return {
|
| 291 |
+
"time_complexity_score": round(time_score, 4),
|
| 292 |
+
"space_complexity_score": round(space_score, 4),
|
| 293 |
+
"efficiency_score": efficiency_score,
|
| 294 |
+
"optimization_hints": optimization_hints,
|
| 295 |
+
"complexity_signals": complexity_signals,
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def analyze_code_complexity(code: str, probe_inputs: list[str] | None = None) -> dict[str, Any]:
|
| 300 |
+
if probe_inputs and len(probe_inputs) >= 3:
|
| 301 |
+
try:
|
| 302 |
+
return _empirical_complexity(code, probe_inputs)
|
| 303 |
+
except Exception:
|
| 304 |
+
pass
|
| 305 |
+
return _heuristic_fallback(code)
|
verifier/verifier.py
CHANGED
|
@@ -14,9 +14,9 @@ def verify(
|
|
| 14 |
step_number: int = 1,
|
| 15 |
) -> tuple[float, dict[str, Any]]:
|
| 16 |
precheck = validate_code(code)
|
| 17 |
-
complexity = analyze_code_complexity(code)
|
| 18 |
|
| 19 |
if not precheck["syntax_ok"] or not precheck["safety_ok"]:
|
|
|
|
| 20 |
reward, metrics = compute_pass_rate(
|
| 21 |
[],
|
| 22 |
step_number=step_number,
|
|
@@ -33,6 +33,9 @@ def verify(
|
|
| 33 |
"error": str(precheck["error"]),
|
| 34 |
}
|
| 35 |
|
|
|
|
|
|
|
|
|
|
| 36 |
results: list[dict[str, Any]] = []
|
| 37 |
for index, test_case in enumerate(test_cases):
|
| 38 |
if isinstance(test_case, dict):
|
|
@@ -91,6 +94,24 @@ def verify(
|
|
| 91 |
}
|
| 92 |
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
def _build_feedback(metrics: dict[str, Any], *, error: str = "") -> str:
|
| 95 |
execution_status = str(metrics.get("execution_status", "unknown"))
|
| 96 |
if execution_status == "syntax_error":
|
|
|
|
| 14 |
step_number: int = 1,
|
| 15 |
) -> tuple[float, dict[str, Any]]:
|
| 16 |
precheck = validate_code(code)
|
|
|
|
| 17 |
|
| 18 |
if not precheck["syntax_ok"] or not precheck["safety_ok"]:
|
| 19 |
+
complexity = analyze_code_complexity(code)
|
| 20 |
reward, metrics = compute_pass_rate(
|
| 21 |
[],
|
| 22 |
step_number=step_number,
|
|
|
|
| 33 |
"error": str(precheck["error"]),
|
| 34 |
}
|
| 35 |
|
| 36 |
+
probe_inputs = _build_probe_inputs(test_cases)
|
| 37 |
+
complexity = analyze_code_complexity(code, probe_inputs=probe_inputs)
|
| 38 |
+
|
| 39 |
results: list[dict[str, Any]] = []
|
| 40 |
for index, test_case in enumerate(test_cases):
|
| 41 |
if isinstance(test_case, dict):
|
|
|
|
| 94 |
}
|
| 95 |
|
| 96 |
|
| 97 |
+
def _build_probe_inputs(test_cases: list[dict[str, Any]] | list[tuple[str, str]]) -> list[str]:
|
| 98 |
+
inputs: list[str] = []
|
| 99 |
+
for test_case in test_cases:
|
| 100 |
+
if isinstance(test_case, dict):
|
| 101 |
+
raw_input = test_case.get("input")
|
| 102 |
+
elif test_case:
|
| 103 |
+
raw_input = test_case[0]
|
| 104 |
+
else:
|
| 105 |
+
raw_input = None
|
| 106 |
+
if isinstance(raw_input, str):
|
| 107 |
+
inputs.append(raw_input)
|
| 108 |
+
|
| 109 |
+
inputs_sorted = sorted(set(inputs), key=len)
|
| 110 |
+
if len({len(item) for item in inputs_sorted}) < 3:
|
| 111 |
+
return []
|
| 112 |
+
return inputs_sorted
|
| 113 |
+
|
| 114 |
+
|
| 115 |
def _build_feedback(metrics: dict[str, Any], *, error: str = "") -> str:
|
| 116 |
execution_status = str(metrics.get("execution_status", "unknown"))
|
| 117 |
if execution_status == "syntax_error":
|