Portfolio Projects ยท Project 6 of 10

Project 6: LLM & RAG Evaluation Harness

๐Ÿ› ๏ธ Advancedโฑ 2โ€“3 weekends๐Ÿ“„ Resume lines included

Most candidates build a chatbot; very few can prove it works. This project builds a reusable evaluation harness for RAG and LLM apps: golden datasets, retrieval and generation metrics, LLM-as-judge, regression reports and a CI gate. It's a strong signal for senior and LLMOps roles.

What you'll build

Architecture

configs/*.yaml (retriever, k, prompt, model) โ†’ runner โ†’ for each question: retrieve โ†’ generate โ†’ log
   โ†’ metrics: retrieval (gold IDs) + generation (LLM judge with rubric, calibrated on 30 human labels)
   โ†’ results.parquet โ†’ report.md (scores, deltas vs baseline, worst 10 failures by category)

Tech stack

Python, Pydantic, pandas, an LLM judge (with a fixed rubric and temperature 0), RAGAS or DeepEval (optional), Typer (CLI), Jinja2 (report), GitHub Actions.

Step by step

  1. Golden set: write 50โ€“100 realistic questions with domain experts; include unanswerable ones (expected: "I don't know").
  2. Retrieval metrics: recall@k and MRR against the gold document IDs.
  3. Judge prompt: score faithfulness (are all claims supported by the context?) and correctness (vs the reference) on a 1โ€“5 scale with reasons; output JSON.
  4. Calibrate the judge: label 30 answers yourself; check agreement (Spearman/ฮบ) and adjust the rubric.
  5. Cache LLM calls (hash of prompt + model) so re-runs are cheap.
  6. Report: a table of metrics, deltas vs baseline, and failure buckets (retrieval miss / wrong synthesis / refusal error).
  7. CI gate: run on pull requests that change prompts or configs.

Starter code

import json, hashlib, statistics as st
JUDGE = """Rate the ANSWER for faithfulness to CONTEXT (1-5): 5 = every claim supported, 1 = mostly unsupported.
Return JSON {"faithfulness": int, "unsupported_claims": [..]}."""
def recall_at_k(retrieved, gold, k=4): return len(set(retrieved[:k]) & set(gold)) / max(1, len(gold))
def mrr(retrieved, gold): return next((1 / (i + 1) for i, d in enumerate(retrieved) if d in gold), 0.0)
def judge(context, answer, cache={}):
    key = hashlib.sha1((context + answer).encode()).hexdigest()
    if key not in cache: cache[key] = json.loads(llm(system=JUDGE, user=f"CONTEXT:\n{context}\n\nANSWER:\n{answer}", temperature=0))
    return cache[key]
def run(dataset, pipeline):
    rows = []
    for ex in dataset:
        docs, ans = pipeline(ex["question"])
        rows.append({"r@4": recall_at_k([d.id for d in docs], ex["gold_ids"]), "mrr": mrr([d.id for d in docs], ex["gold_ids"]),
                     "faith": judge("\n".join(d.text for d in docs), ans)["faithfulness"]})
    return {k: round(st.mean(r[k] for r in rows), 3) for k in rows[0]}

Deploy it

Publish the harness as a pip-installable package or a GitHub template repo, and add a sample report to the README. Write a LinkedIn post showing "how chunk size changed faithfulness".

Put it on your resume

Interview questions you'll get

Practise these in Mock Interview โ†’ System Design & MLOps.

Stretch goals

Created by Ajeetkumar