Score LLM answers on verifiable correctness; keep fluency separate so confident-but-wrong models can't win your eval
Translation pending
Developer toolsPythonGitHub @mgupta646OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Score LLM answers on verifiable correctness, and keep fluency as a separate signal, so a confident-but-wrong model can't win your evaluation.
When I chose the model behind NYU Stern's V-Lab research assistant, I benchmarked six LLM providers on a 40-point rubric covering accuracy, depth and reasoning. Partway through, I found the rubric couldn't separate a correct answer from a fluent one. Two of the three criteria were judged from how an answer read, so confident phrasing was earning points that correctness should have earned. I had to rebuild the rubric and re-score everything.Read the full README ↗Score LLM answers on verifiable correctness, and keep fluency as a separate signal, so a confident-but-wrong model can't win your evaluation.
When I chose the model behind NYU Stern's V-Lab research assistant, I benchmarked six LLM providers on a 40-point rubric covering accuracy, depth and reasoning. Partway through, I found the rubric couldn't separate a correct answer from a fluent one. Two of the three criteria were judged from how an answer read, so confident phrasing was earning points that correctness should have earned. I had to rebuild the rubric and re-score everything.
This tool makes that failure impossible to miss:
In the bundled sample run, the confident model is last on correctness (0.14) but would be #2 under a style-weighted rubric.
git clone https://github.com/mgupta646/llm-rubric-eval && cd llm-rubric-eval
pip install -e ".[dev]"
# score the bundled sample answers and write an HTML report
rubriceval score --questions examples/questions.json --answers examples/answers --out report.html
| Rank | Model | Correctness | Fluency | Naive score | Confidently wrong |
|---:|---|---:|---:|---:|---:|
| 1 | sample-a (accurate) | 1.00 | 0.71 | 0.80 (#1) | 0 |
| 2 | sample-c (hedged) | 0.95 | 0.34 | 0.55 (#3) | 0 |
| 3 | sample-d (mixed) | 0.60 | 0.50 | 0.53 (#4) | 0 |
| 4 | sample-b (confident) | 0.14 | 0.96 | 0.69 (#2) | 11 |
! A style-weighted rubric would rank these models differently.
The four sample "models" are hand-written synthetic answers designed to show distinct behaviours (accurate, confident-but-wrong, hedged, mixed). They are not outputs of real models; see
examples/make_sample_answers.py.
export GROQ_API_KEY=... # or OPENAI_API_KEY / ANTHROPIC_API_KEY
rubriceval collect --questions examples/questions.json --provider groq --out answers/groq.jsonl
rubriceval collect --questions examples/questions.json --provider anthropic --out answers/claude.jsonl
rubriceval score --questions examples/questions.json --answers answers --out report.html
Keys are read from environment variables only. They are never written to disk or passed as arguments.
Each question carries its own criteria. Points come only from checks you could verify by hand:
{
"id": "q02_var",
"question": "A portfolio's 1-day 99% Value at Risk is $1 million. What does that mean?",
"criteria": [
{"id": "tail_probability", "type": "any_of", "points": 1, "any_of": ["1%", "99%"]},
{"id": "exceedance", "type": "any_of", "points": 1, "any_of": ["exceed", "more than", "greater than"]},
{"id": "about_losses", "type": "regex", "points": 1, "pattern": "\\bloss"},
{"id": "not_a_max", "type": "forbidden","penalty": 1.5, "phrases": ["maximum loss", "worst case"]}
]
}
| Type | Passes when | Notes |
|---|---|---|
any_of | any listed phrase appears | case, accent and dash-insensitive |
all_of | every listed phrase appears | |
number | a number in the answer is within abs_tol (or rel_tol) of value | parses $1,157.63, 12%, -3.5 |
regex | the pattern matches the normalized answer | |
forbidden | none of the known-wrong claims appear | a hit subtracts penalty |
forbidden matters more than it looks. In the sample, one answer swaps precision and recall but still contains both correct formulas. Only the forbidden-claim check catches it.
questions.json ─┐
├─► checks.py verifiable criteria → correctness (0–1)
answers/*.jsonl ┤
├─► fluency.py length, sentence shape, assertive vs hedging words → fluency, confidence
│
└─► scorer.py flags: confidently_wrong · hedged_but_right · known_wrong_claim
leaderboard: rank by correctness vs style-weighted "naive" rank
└─► report.py self-contained HTML (inline SVG, no JS)
forbidden entries for the mistakes you expect.any_of lists, or a human pass.pytest -q # 20 tests
Built by Mehul Gupta · Portfolio · LinkedIn