Skip to content

Commit 15b9b87

Browse files
docs: model evaluation results (single + multi-agent, with speed)
- docs/model_evaluation.md: full sweep on the fixed 80-item eval — per-model precision/recall/F1, parse errors, and real-prompt speed; latency caveat (trivial probe != real diff); multi-agent/consensus table + finding. - Default model updated devstral-2:123b -> gemma4:31b: clean isolated re-run confirmed F1 0.872 / precision 0.895 (near claude 0.93) / recall 0.85, 0 errors, reproducible; the earlier 73-error run was concurrent-sweep API contention. Multi-agent consensus did NOT beat gemma solo (correlated errors; devstral+qwen nested); AND(gemma,qwen) reaches 0.929 precision for a precision-max sub-tier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c29dcd8 commit 15b9b87

3 files changed

Lines changed: 114 additions & 10 deletions

File tree

collection/IMPROVEMENT_LOG.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,14 @@ nemotron got 2 runs each). Ranked:
135135
| openai · gemma4:31b |||| 73/80 JSON parse errors |
136136
| openai · kimi-k2.7-code |||| timeout (slow on real prompts) |
137137

138-
**Default = `devstral-2:123b`** (Mistral's code model): best F1 (0.84, *beats*
139-
claude's 0.78), consistent across two runs, 0 parse errors, high recall with
140-
threshold-tunable precision. `--model qwen3-coder:480b` for the highest-precision
141-
(0.90) variant. Slow/reasoning models (glm-5, qwen3.5:397b, deepseek-v3.2 ~20-45s
142-
/call) and gemma (parse-broken) are impractical. Note: n=80 has ~0.05 run
143-
variance, so sub-0.06 gaps aren't meaningful.
138+
**Default = `gemma4:31b`** (updated after a clean isolated re-run): F1 **0.872**,
139+
precision **0.895** (near claude's 0.93) AND recall 0.85 — two clean runs
140+
identical, 0 errors. Its earlier 73-error run was API contention from two
141+
concurrent sweeps, not a model flaw. `--model qwen3-coder:480b` for a
142+
precision-leaning variant. **Multi-agent consensus did NOT beat gemma solo**
143+
(correlated errors; devstral+qwen are nested → no synergy); only `AND(gemma,qwen)`
144+
is useful, reaching 0.929 precision for a precision-max sub-tier.
145+
📄 Full table (incl. speed + consensus) in **`docs/model_evaluation.md`**.
144146

145147
This is Ollama *Cloud* (hosted), not local hardware; a truly local model via
146148
`--engine ollama` on :11434 would need `ollama serve` + a pulled model (untested).

collection/llm_classify_fixes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,11 @@ def main() -> int:
274274
ap.add_argument("--api-key-env", default="", help="env var holding the API key (openai engine)")
275275
a = ap.parse_args()
276276
# Best default for the openai/Ollama-Cloud engine, chosen by an 80-item eval
277-
# sweep (see collection/IMPROVEMENT_LOG.md): devstral-2:123b — F1 0.84 (beats
278-
# claude's 0.78), high recall, consistent, 0 parse errors. Use
279-
# --model qwen3-coder:480b if you want the highest-precision (0.90) variant.
280-
model = a.model or ("devstral-2:123b" if a.engine == "openai" else "")
277+
# sweep (see docs/model_evaluation.md): gemma4:31b — F1 0.872, precision 0.895
278+
# (near claude's 0.93) AND recall 0.85, two clean runs identical, 0 errors.
279+
# --model qwen3-coder:480b for a precision-leaning variant; multi-agent
280+
# consensus did NOT beat this single model (correlated errors).
281+
model = a.model or ("gemma4:31b" if a.engine == "openai" else "")
281282
ENGINE.update(engine=a.engine, model=model, host=a.ollama_host,
282283
base_url=a.base_url, api_key=os.environ.get(a.api_key_env, "") if a.api_key_env else "")
283284
if a.engine == "ollama" and a.workers > 2:

docs/model_evaluation.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Silent-fix classifier — model evaluation
2+
3+
Evaluation of LLM backends for the training-free silent-fix classifier
4+
(`collection/llm_classify_fixes.py`). All numbers are on **one fixed 80-item
5+
eval set** (40 positive / 40 negative) so every model and ensemble is directly
6+
comparable. Runs on **Ollama Cloud** via the environment's `hermes-agent`
7+
(OpenAI-compatible `https://ollama.com/v1`); `claude -p` (Opus) is the baseline.
8+
9+
_Last updated: 2026-07-02._
10+
11+
## Method
12+
13+
- **Task:** given a code diff + dev artifacts (title/description) + a "graph-lite"
14+
hint (the security-sensitive subsystem touched), decide `is_security_fix`
15+
with a confidence, via a Chain-of-Thought prompt (LLM4VFD-style, arXiv
16+
2501.14983). No training / fine-tuning.
17+
- **Labels:** unambiguous title-based ground truth — positives are explicit
18+
"fix panic/crash/overflow/race/consensus…" changes; negatives are clear
19+
feature/refactor/perf. Vendored-dep, revert, and test/CI-only changes are
20+
excluded from positives. (The LLM often reasons *more* precisely than these
21+
labels — attacker-reachability, prod-vs-test — so measured F1 slightly
22+
understates true quality.)
23+
- **Discipline:** every candidate is judged on precision/recall/F1 **and** the
24+
applied-confidence ranking; a good aggregate that ranks features above real
25+
fixes is rejected. (A TF-IDF classifier with CV-AUC 0.97 was killed this way.)
26+
- **Caveat:** at n=80 there is ~±0.05 run-to-run variance, so gaps under ~0.06
27+
are not meaningful. "Speed" = wall-clock for all 80 items at the noted worker
28+
count against Ollama Cloud (network-bound, indicative not benchmark-grade).
29+
30+
## Single-model results
31+
32+
| engine · model | precision | recall | F1 | errors | speed (80 items) | notes |
33+
|---|---|---|---|---|---|---|
34+
| **openai · gemma4:31b****default** | **0.895** | 0.850 | **0.872** | 0 | ~137s (6w) | best F1; two clean runs identical (0.872) |
35+
| openai · devstral-2:123b | 0.78 | 0.90 | 0.84 / 0.84 | 0 | ~170s (8w) | consistent ×2, high recall |
36+
| openai · nemotron-3-nano:30b | 0.78 | 0.90 | 0.83 / 0.84 | 0 | ~150s (8w) | matches devstral, smaller |
37+
| _claude · opus (baseline)_ | 0.93 | 0.68 | 0.78 | 0 || highest precision, lower recall |
38+
| openai · qwen3-coder:480b | 0.90 | 0.65 | 0.75 | 0 | ~112s (8w) | precision-leaning |
39+
| openai · gpt-oss:120b | 0.74 | 0.78 | 0.76 | 1 | ~94s (8w) | balanced |
40+
| openai · qwen3-coder-next | 0.75 | 0.38 | 0.50 | 0 | ~55s (8w) | weak recall |
41+
| openai · gemma4:31b (run under contention) | 1.00 | 0.13 | 0.22 | **73** | ~45s | JSON truncated by concurrent-sweep API contention; not representative |
42+
| openai · kimi-k2.7-code ||||| **>320s timeout** | fast on trivial prompts, very slow on real diffs |
43+
| openai · glm-5 / qwen3.5:397b / deepseek-v3.2 ||||| ~20–45s **per call** | reasoning models, impractical at scale |
44+
45+
**Winner: `gemma4:31b`** — near-Claude precision (0.895 vs 0.93) *and* higher
46+
recall (0.85 vs 0.68), best F1 (0.872, beats Claude's 0.78), 0 parse errors,
47+
reproducible. `--model` empty resolves to it for the `openai` engine.
48+
49+
### Latency note (trivial probe vs real prompt)
50+
A one-line "reply ok" probe is misleading: gemma4:31b 0.5s, qwen3-coder-next
51+
0.7s, kimi 1.1s, gpt-oss:120b 2.1s — but on the full ~5k-char diff prompt kimi
52+
blows past 320s and glm-5/qwen3.5/deepseek run 20–45s **per call** (hours for a
53+
500-row production run). Pick the default on *real-prompt* speed × accuracy.
54+
55+
## Multi-agent / consensus
56+
57+
Computed offline by combining saved per-model predictions on the same eval set
58+
(no extra calls).
59+
60+
| strategy | precision | recall | F1 |
61+
|---|---|---|---|
62+
| gemma4:31b solo (best single) | 0.895 | 0.850 | **0.872** |
63+
| majority(gemma, devstral, qwen) | 0.872 | 0.850 | 0.861 |
64+
| majority(gemma, devstral, nemotron) | 0.818 | 0.900 | 0.857 |
65+
| majority(5 models) | 0.854 | 0.875 | 0.864 |
66+
| **AND(gemma, qwen)** — precision-max | **0.929** | 0.650 | 0.765 |
67+
| AND(gemma, devstral) | 0.895 | 0.850 | 0.872 |
68+
| OR(gemma, devstral) — recall-max | 0.771 | 0.925 | 0.841 |
69+
| devstral + qwen: AND | 0.897 | 0.650 | 0.754 |
70+
| devstral + qwen: OR | 0.771 | 0.925 | 0.841 |
71+
72+
**Finding: consensus does not beat the best single model here.** The models'
73+
errors are *correlated* (they read the same code signal), so ensembles only
74+
slide along the precision/recall curve a single confidence threshold already
75+
covers — majority votes score slightly *below* gemma solo. In particular
76+
**devstral + qwen are nested** (qwen's positives ⊂ devstral's), so their AND =
77+
qwen's operating point and OR = devstral's — zero synergy.
78+
79+
**The one useful ensemble:** `AND(gemma, qwen)` reaches **0.929 precision**
80+
(Claude-level) — worth a 2× cost only when you want a precision-max sub-tier
81+
(e.g. auto-promote near-certain fixes). For general use, gemma solo dominates on
82+
cost and F1.
83+
84+
## How to run
85+
86+
```bash
87+
export OLLAMA_API_KEY="$(grep ^OLLAMA_API_KEY= ~/.hermes/.env | cut -d= -f2-)"
88+
89+
# default engine (gemma4:31b) on the eval set
90+
uv run python collection/llm_classify_fixes.py --run \
91+
--engine openai --base-url https://ollama.com/v1 --api-key-env OLLAMA_API_KEY
92+
93+
# precision-leaning single model
94+
# ... --model qwen3-coder:480b
95+
# Anthropic baseline
96+
# ... --engine claude
97+
```
98+
99+
Diffs are served rate-limit-free by `collection/local_diffs.py` (bare blobless
100+
clone + persistent cache + delta `git fetch`), so a full off-Claude,
101+
cache-resumable classification run over the dataset is practical.

0 commit comments

Comments
 (0)