Continuous Model Evaluation
Private eval loop, calibrated LLM-as-a-judge, canary strings, contamination and drift detection. Updated in April 2026.
Overview
Public benchmarks (MMLU, HumanEval, SWE-bench) measure generic performance. But for a proprietary model, what matters is: is it improving at what matters to us?
Continuous evaluation answers:
- Did the model improve after the last fine-tune?
- Did it regress on something that used to work?
- Is there contamination of the training data with the evaluation data?
- Is quality degrading in production?
Components of the Eval Loop
New checkpoint → Automatic eval → Dashboard → Decision (merge/reject/rollback)
↓
Comparison with baseline
↓
Regression? → Alert1. Private Evaluation Dataset
Principles
| Principle | Why |
|---|---|
| Never published | If the eval was published, the model may have been trained on it |
| Diversified | Cover all of the product's use cases |
| Up to date | Include recent examples (last 3–6 months) |
| Versioned | Each eval version is immutable — compare only the same version |
| Segmented | Separate by domain (code, text, reasoning, etc.) |
Building the private eval
Phase 1: Collect real use cases
→ Resolved support tickets
→ Examples of successful prompts
→ Edge cases the current model does not solve
Phase 2: Create reference input/output pairs
→ For each prompt, create the expected output (gold)
→ Have 2–3 reference outputs per prompt (accepted variations)
Phase 3: Categorization
→ Code (unit tests, refactoring, debugging)
→ Text (summarization, translation, generation)
→ Reasoning (mathematics, logic, planning)
→ Multimodal (if applicable)
Phase 4: Size
→ Minimum: 500 examples per category
→ Ideal: 2000–5000 per category
→ Total: 2K–20K examplesExample structure
eval_v1/
├── coding/
│ ├── unit_tests/ # 500 examples
│ ├── refactoring/ # 500 examples
│ ├── debugging/ # 500 examples
│ └── code_review/ # 500 examples
├── text/
│ ├── summarization/ # 300 examples
│ ├── translation/ # 300 examples
│ ── generation/ # 300 examples
├── reasoning/
│ ├── math/ # 300 examples
│ ├── logic/ # 300 examples
│ └── planning/ # 300 examples
└── metadata.json # version, date, description2. Evaluation Metrics
Automatic metrics
| Metric | When to use | Tool |
|---|---|---|
| Exact Match | Output must be identical | == string comparison |
| Token overlap (F1) | Output similar but not identical | rouge_score |
| Code execution | Does the generated code work? | Execute + verify output |
| Unit test pass rate | Does the code pass the tests? | pytest / unittest |
| Semantic similarity | Is the meaning preserved? | Embeddings + cosine |
| Format compliance | Output in the right format? | Regex / parser |
Code evaluation (the most important one for Kode)
def evaluate_code_generation(model_output, test_cases):
"""Evaluate generated code by running tests."""
results = []
for test in test_cases:
try:
# Execute the generated code
exec(model_output, test.globals)
# Run the test
result = test.run()
results.append(result.passed)
except Exception as e:
results.append(False)
return sum(results) / len(results) # pass rateMain metric: % of unit tests passed — it is the most objective metric and the one most correlated with real code quality.
LLM-as-a-Judge
When output is not automatically verifiable (text, reasoning):
Prompt: "Rate the quality of the answer below from 1 to 10.
Criteria: correctness, completeness, clarity.
Model answer: {model_output}
Reference answer: {gold_output}
Score:"Problem: LLM-as-a-judge has bias (it prefers long, verbose answers).
Solution: Calibration
- Create a dataset of 200–500 examples with human scores
- Measure the correlation of the LLM-as-a-judge with the human scores
- If correlation < 0.7, adjust the prompt or change the judge model
- Re-calibrate every 3 months
Table of recommended metrics for Kode
| Category | Main metric | Secondary metric | Minimum threshold |
|---|---|---|---|
| Code generation | Unit test pass rate | Token overlap with reference | > 80% |
| Refactoring | Diff correctness (human eval) | Syntax validity | > 90% syntax ok |
| Debugging | Bug fixed rate | Explanation quality (LLM judge) | > 70% |
| General text | Semantic similarity | Format compliance | > 0.8 cosine |
| Reasoning | Correctness rate | Step-by-step accuracy | > 75% |
3. Canary Strings
What they are
Canary strings are unique texts injected into the training data to detect contamination:
# Canary string in the training dataset:
"The canary code canary-7f3a9b2c is used to detect contamination."
# In the eval, check whether the model "remembers" the canary:
prompt = "What is the canary string of the training dataset?"
# If the model answers correctly → contamination detected!How to use
- Insert canaries into the training data (1 canary per 1M tokens)
- Include questions about canaries in the eval
- If the model gets the canaries right → training-to-eval leakage
Canaries for public datasets
Many public benchmarks (MMLU, HumanEval) use canaries to detect whether models were trained on the evaluation data:
| Benchmark | Canary |
|---|---|
| MMLU | Specific texts injected into subsets |
| HumanEval | Unique comments in the docstrings |
| GSM8K | Problems with specific numbers |
For Kode: Always check whether the benchmarks we use have canaries and respect the evaluation protocol.
4. Contamination Detection
Types of contamination
| Type | Description | Detection |
|---|---|---|
| Train → Eval | Eval data leaked into training | Canary strings, perplexity check |
| Train → Public benchmark | Public benchmark in training | Check the model's papers/datasets |
| Eval → Train | Eval generated by the model goes into training | Rigorous versioning |
Perplexity check
If the model has suspiciously low perplexity on the eval, there may be contamination:
def check_contamination(model, eval_dataset):
"""Check whether perplexity on the eval is abnormally low."""
ppl_eval = model.perplexity(eval_dataset)
ppl_baseline = model.perplexity(baseline_dataset)
# If perplexity on the eval is < 50% of the baseline → suspected contamination
if ppl_eval < 0.5 * ppl_baseline:
return "ALERT: Possible contamination detected"
return "OK"5. A/B Testing of Models
When to use
- Compare two model versions before deploy
- Test fine-tune vs base model
- Validate that the new version did not regress
Methodology
1. Select 200–500 representative prompts
2. Generate output with Model A and Model B
3. Evaluate with automatic metrics + LLM-as-a-judge
4. Compare statistically (t-test or Mann-Whitney)
5. Decide: A better, B better, or tieComparison metrics
| Metric | Model A | Model B | Difference |
|---|---|---|---|
| Code pass rate | 82% | 87% | +5% ✓ |
| Latency (ms/token) | 120 | 140 | +17% ✗ |
| Token overlap | 0.78 | 0.81 | +4% ✓ |
| LLM judge score | 7.2 | 7.8 | +8% ✓ |
| Verdict | — | B wins | — |
6. Production Monitoring
Metrics to monitor
| Metric | Alert if | Action |
|---|---|---|
| Quality (eval score) | Drops > 5% vs baseline | Investigate, possibly rollback |
| Latency p95 | Increases > 20% | Optimize inference, scale |
| Error rate | Increases > 1% | Check logs, debug |
| Token usage | Changes drastically | Check for prompt injection or loop |
| User feedback | Rating drops | Re-train with negative feedback |
Drift detection
Models can degrade in production due to:
- Data drift: production data changes (e.g., new code libraries)
- Concept drift: what was "good" changes (e.g., new coding practices)
- Prompt drift: users change how they interact
Detection:
1. Keep a buffer of production prompts (last 7 days)
2. Run automatic eval on this buffer weekly
3. Compare with baseline (previous month)
4. If difference > threshold → drift alertCanary deployment
New model → 5% of traffic → Monitor 24h → If OK → 25% → 50% → 100%
↓
If problem → Immediate rollbackTools
lm-evaluation-harness (EleutherAI)
Standard framework for LLM evaluation:
lm_eval --model hf \
--model_args pretrained=my-model \
--tasks hellaswag,arc_easy,human_eval \
--batch_size autoAdvantages: 100+ benchmarks, open-source, active community.
SWE-bench runner
For code evaluation:
# Run SWE-bench on the model
python run_evaluation.py \
--model my-model \
--dataset swe-bench-verified \
--num-workers 32W&B Sweeps + Eval
import wandb
wandb.init(project="kode-eval")
wandb.log({
"code_pass_rate": 0.87,
"latency_p95": 140,
"llm_judge_score": 7.8,
"regression_detected": False,
})LangFuse
For eval in production:
- Trace of each request
- Automatic evaluation with LLM-as-a-judge
- Quality dashboard over time
- Integrated user feedback
For Kode
Recommended eval loop
Phase 1: Initial setup (1–2 weeks)
→ Create a private dataset of 5K examples (code, text, reasoning)
→ Configure lm-eval-harness with public benchmarks
→ Configure SWE-bench runner
→ Set up W&B for tracking
Phase 2: Automatic evaluation (continuous)
→ Every new checkpoint runs the full eval
→ Dashboard with comparison vs baseline
→ Automatic alert if regression > 3%
Phase 3: Production monitoring (continuous)
→ LangFuse for request tracing
→ Canary deployment for new models
→ Feedback loop with users
Phase 4: LLM-as-a-judge calibration (monthly)
→ Collect 200 examples with human scores
→ Check correlation with the LLM judge
→ Adjust the prompt if neededMinimum private dataset for Kode
| Category | Examples | Source |
|---|---|---|
| Unit tests | 500 | Tests from Koder projects |
| Refactoring | 300 | Refactoring PRs |
| Debugging | 300 | Resolved issues |
| Code review | 200 | Review comments |
| Code generation | 500 | Specifications → code |
| Text (docs) | 300 | Technical documentation |
| Reasoning | 200 | Design problems |
| Total | 2,300 | — |
References
| Resource | Description |
|---|---|
| lm-evaluation-harness | EleutherAI's eval framework |
| SWE-bench | Coding benchmark on real repos |
| W&B Sweeps | Experiment tracking |
| LangFuse | LLM observability and eval |
| Canaries in ML | Papers on canary strings for contamination detection |