Last active
April 21, 2026 01:27
-
-
Save Hassan-Naeem-code/d72109eb2a27178acd5b7942138d77ea to your computer and use it in GitHub Desktop.
Python: LLM-as-judge evaluation harness — rubric scoring with aggregate metrics
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| LLM-as-judge evaluation harness. | |
| Given a list of (input, generated_output, expected_output) tuples, ask a | |
| strong model to score each generation on a rubric and produce aggregate metrics. | |
| Install: | |
| pip install openai | |
| Env: | |
| export OPENAI_API_KEY=sk-... | |
| """ | |
| import os | |
| import json | |
| from openai import OpenAI | |
| from dataclasses import dataclass | |
| from statistics import mean | |
| client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) | |
| JUDGE_MODEL = "gpt-4o" # use your strongest model to judge | |
| RUBRIC = """ | |
| Score the candidate answer on three axes, each 1-5: | |
| - correctness: Does the answer factually match the reference? | |
| - completeness: Does it cover everything the reference covers? | |
| - clarity: Is it clear, well-structured, free of filler? | |
| Return ONLY valid JSON of the form: | |
| {"correctness": int, "completeness": int, "clarity": int, "reason": "one-sentence justification"} | |
| """.strip() | |
| @dataclass | |
| class Sample: | |
| input: str | |
| generated: str | |
| expected: str | |
| def judge_one(sample: Sample) -> dict: | |
| prompt = f"""{RUBRIC} | |
| INPUT: | |
| {sample.input} | |
| REFERENCE ANSWER: | |
| {sample.expected} | |
| CANDIDATE ANSWER: | |
| {sample.generated} | |
| """ | |
| res = client.chat.completions.create( | |
| model=JUDGE_MODEL, | |
| response_format={"type": "json_object"}, | |
| messages=[{"role": "user", "content": prompt}], | |
| ) | |
| raw = res.choices[0].message.content.strip() | |
| return json.loads(raw) | |
| def evaluate(samples: list[Sample]) -> dict: | |
| scored = [judge_one(s) for s in samples] | |
| agg = { | |
| "n": len(scored), | |
| "correctness": round(mean(s["correctness"] for s in scored), 2), | |
| "completeness": round(mean(s["completeness"] for s in scored), 2), | |
| "clarity": round(mean(s["clarity"] for s in scored), 2), | |
| "per_sample": scored, | |
| } | |
| return agg | |
| if __name__ == "__main__": | |
| samples = [ | |
| Sample( | |
| input="Capital of France?", | |
| generated="Paris is the capital of France.", | |
| expected="Paris", | |
| ), | |
| Sample( | |
| input="Explain photosynthesis in one sentence.", | |
| generated="Plants eat sunlight.", | |
| expected="Photosynthesis is how plants convert sunlight, water, and CO2 into glucose and oxygen.", | |
| ), | |
| ] | |
| results = evaluate(samples) | |
| print(json.dumps(results, indent=2)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment