Skip to content

Commit dbf371c

Browse files
committed
docs: coding benchmarks (MBPP+) section
1 parent ae6f061 commit dbf371c

1 file changed

Lines changed: 37 additions & 1 deletion

File tree

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ To verify the install: `./test_setup.sh`, then `python test_optimization.py` wit
5151
**With a dataset** of input/output pairs:
5252

5353
1. Candidates are built: the original prompt, the rewrite, and each of those with selected few-shot examples.
54-
2. Each candidate is scored on held-out samples with the chosen metric: `exact`, `contains`, or `llm_judge` (a local model judges free-text answers). `auto` picks `contains` for short answers and the judge for longer ones.
54+
2. Each candidate is scored on held-out samples with the chosen metric: `exact`, `contains`, `llm_judge` (a local model judges free-text answers), or `tests` (the answer's code is run against the sample's asserts, see [Coding benchmarks](#coding-benchmarks-mbpp)). `auto` picks `tests` when every sample carries asserts, `contains` for short answers and the judge for longer ones.
5555
3. Evaluation is a fixed 80/20 split by default, or k-fold, where every sample is held out once. Splits and folds are stratified so every class appears in every fold.
5656
4. Few-shot examples are selected for coverage of the training inputs, so they are representative rather than lucky.
5757
5. The winner is whichever candidate scores highest held-out.
@@ -68,6 +68,36 @@ PromptCraft supplies the feedback metric, the split and budget controls, the sep
6868

6969
**Budgets.** A run uses at most 40 train and 20 held-out samples, 5 folds and 8 few-shot examples by default; GEPA gets 60 scored calls. That keeps a run to minutes on a laptop. Raise the `EVAL_MAX_*` values in `API/.env` and the GEPA budget in the run settings for stricter numbers.
7070

71+
## Coding benchmarks (MBPP+)
72+
73+
PromptCraft can optimize a prompt for Python code generation and score it by running tests. The dataset is MBPP+ from [EvalPlus](https://github.com/evalplus/evalplus): 378 short programming tasks, each with a function name and three asserts.
74+
75+
**Warning.** The `tests` metric executes code written by the model on your machine, inside a subprocess with a timeout, a memory cap and a guard that disables `os.system`, file deletion, process spawning and sockets. It is not a container. Run it on a machine you don't mind, and set `CODE_EVAL_ENABLED=false` in `API/.env` to switch it off.
76+
77+
**Build and import the dataset.**
78+
79+
```bash
80+
pip install -r API/requirements-bench.txt
81+
python scripts/build_mbppplus_dataset.py --out docs/benchmarks/mbppplus --seed 1234
82+
```
83+
84+
This writes `train.jsonl` (120 tasks), `val.jsonl` (60) and `test.jsonl` (198) plus `split.json` with the task ids, and checks that every canonical solution passes its own asserts in the sandbox. Import `train.jsonl` through the Import dialog (JSON Lines): `input` and `output` map as usual and `task_id`, `entry_point`, `test_imports` and `tests` land in each sample's extra data, where the metric reads them. The app holds part of the imported dataset out as its own dev split. `test.jsonl` is never used during optimization; it is only for the cross-check below.
85+
86+
**How it scores.** The metric extracts the first fenced code block (or the `def`) from the answer, checks that it defines the task's function, and runs each assert in its own interpreter. A sample passes only when every assert passes, which is pass@1 as MBPP+ defines it. GEPA's feedback metric additionally scores the fraction of asserts passed and says why the rest failed (no code, syntax error, wrong function name, the failing assert, an exception, or a timeout), so the reflection model has something to act on. The reported score of a run is always the binary pass@1, never the fraction.
87+
88+
**Recommended run settings for code.** Temperature 0, thinking off, a `max_tokens` of about 512, and few-shot examples placed before the task input (the default rendering). Raise `EVAL_MAX_TRAIN_SAMPLES` to use all 120 training tasks; the default caps keep a run to 60 samples.
89+
90+
**Cross-check with EvalPlus.** Export the optimized prompt's completions on the fixed test split and score them with the official harness:
91+
92+
```bash
93+
python scripts/export_evalplus_samples.py --session <session id> --split test --out samples.jsonl
94+
evalplus.evaluate --dataset mbpp --samples samples.jsonl
95+
```
96+
97+
The export prints PromptCraft's own pass@1 on the base asserts; EvalPlus adds its extended tests, so its number is usually lower. Report both and the agreement between them.
98+
99+
**Contamination.** llama3.2 and most models have seen MBPP during training. Absolute scores say little; compare within one model, before and after optimization, on the fixed test split.
100+
71101
## Results
72102

73103
All runs: llama3.2 3B via Ollama on a MacBook Pro (Apple M4 Pro, 48 GB), on the 18-sample support-ticket priority dataset in [`docs/examples/support-tickets.csv`](docs/examples/support-tickets.csv) (12 hand-written tickets plus 6 generated in the app). Prompt: *Classify the priority of this support ticket as high, medium or low.* Metric: `contains`, with half credit when the right label is present but buried in a longer answer.
@@ -101,6 +131,10 @@ EVAL_MAX_DEV_SAMPLES=20
101131
EVAL_MAX_FOLDS=5
102132
EVAL_MAX_DEMOS=8
103133
134+
CODE_EVAL_ENABLED=true
135+
CODE_EVAL_TIMEOUT_SECONDS=10
136+
CODE_EVAL_MEMORY_MB=1024
137+
104138
DATABASE_URL=sqlite:///./app.db
105139
LOG_LEVEL=INFO
106140
```
@@ -114,6 +148,7 @@ API/ FastAPI + DSPy + SQLAlchemy/SQLite
114148
app/services/optimization_service.py rewrite, heuristic score, candidate assembly
115149
app/services/eval_service.py splits, folds, metrics, held-out scoring
116150
app/services/gepa_service.py feedback metric, dspy.GEPA run, lineage tracking
151+
app/services/code_eval_service.py sandboxed test runner behind the "tests" metric
117152
app/services/embedding_service.py coverage-based example selection, dedup
118153
app/services/training_service.py datasets, import/export, synthetic samples
119154
app/services/try_service.py side-by-side "Try it"
@@ -128,6 +163,7 @@ DSPy sits between the optimizer and the model: signatures define the input/outpu
128163
- The no-dataset score is a rubric about the prompt's shape. It says nothing about whether the prompt works.
129164
- `llm_judge` is a small local model judging free text. Expect noise; prefer `exact` or `contains` whenever answers are short.
130165
- Budgets are capped by default. Raise them for real runs and expect longer wall-clock time.
166+
- The `tests` metric runs model-written code in a subprocess, not a container. Its guard blocks the obvious damage, not a determined adversary.
131167

132168
## Contributing
133169

0 commit comments

Comments
 (0)