Vineetiitg commited on
Commit
e7ea02e
·
1 Parent(s): b9537ed

test: add golden RAG evaluation benchmark

Browse files
app/tests/eval_rag.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ from datetime import datetime, timezone
3
+ from pathlib import Path
4
+ from time import perf_counter
5
+
6
+ from app.graph.workflow import compile_workflow
7
+
8
+
9
+ DATASET_PATH = Path("datasets/golden_qa.csv")
10
+ REPORT_PATH = Path("reports/eval_report.md")
11
+
12
+
13
+ def load_golden_questions(path: Path = DATASET_PATH) -> list[dict]:
14
+ with path.open(newline="", encoding="utf-8") as handle:
15
+ return list(csv.DictReader(handle))
16
+
17
+
18
+ def token_overlap(expected: str, actual: str) -> float:
19
+ expected_tokens = set(expected.lower().split())
20
+ actual_tokens = set(actual.lower().split())
21
+ if not expected_tokens:
22
+ return 0.0
23
+ return round(len(expected_tokens & actual_tokens) / len(expected_tokens), 3)
24
+
25
+
26
+ def source_hit(expected_sources: str, sources: list[dict]) -> bool:
27
+ expected = {source.strip() for source in expected_sources.split("|") if source.strip()}
28
+ actual = {source.get("source") for source in sources}
29
+ return bool(expected & actual)
30
+
31
+
32
+ def run_local_evaluation() -> dict:
33
+ agent = compile_workflow()
34
+ rows = load_golden_questions()
35
+ results = []
36
+
37
+ for row in rows:
38
+ started = perf_counter()
39
+ output_state = agent.invoke({"question": row["question"], "run_count": 0})
40
+ latency_ms = round((perf_counter() - started) * 1000, 2)
41
+ answer = output_state.get("generation", "")
42
+ sources = output_state.get("sources", [])
43
+ results.append(
44
+ {
45
+ "question": row["question"],
46
+ "answer": answer,
47
+ "latency_ms": latency_ms,
48
+ "answer_overlap": token_overlap(row["expected_answer"], answer),
49
+ "source_hit": source_hit(row["expected_sources"], sources),
50
+ "retrieved_contexts": len(output_state.get("documents", [])),
51
+ }
52
+ )
53
+
54
+ average_overlap = round(sum(result["answer_overlap"] for result in results) / max(len(results), 1), 3)
55
+ source_hit_rate = round(sum(1 for result in results if result["source_hit"]) / max(len(results), 1), 3)
56
+ average_latency_ms = round(sum(result["latency_ms"] for result in results) / max(len(results), 1), 2)
57
+ summary = {
58
+ "questions": len(results),
59
+ "answer_overlap": average_overlap,
60
+ "source_hit_rate": source_hit_rate,
61
+ "average_latency_ms": average_latency_ms,
62
+ "results": results,
63
+ }
64
+ write_report(summary)
65
+ return summary
66
+
67
+
68
+ def write_report(summary: dict) -> None:
69
+ REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
70
+ lines = [
71
+ "# RAG Evaluation Report",
72
+ "",
73
+ f"Generated: {datetime.now(timezone.utc).isoformat()}",
74
+ "",
75
+ "| Metric | Value |",
76
+ "| --- | ---: |",
77
+ f"| Questions | {summary['questions']} |",
78
+ f"| Answer overlap | {summary['answer_overlap']} |",
79
+ f"| Source hit rate | {summary['source_hit_rate']} |",
80
+ f"| Average latency ms | {summary['average_latency_ms']} |",
81
+ "",
82
+ "## Question Results",
83
+ "",
84
+ ]
85
+ for result in summary["results"]:
86
+ lines.extend(
87
+ [
88
+ f"### {result['question']}",
89
+ "",
90
+ f"- Answer overlap: {result['answer_overlap']}",
91
+ f"- Source hit: {result['source_hit']}",
92
+ f"- Retrieved contexts: {result['retrieved_contexts']}",
93
+ f"- Latency ms: {result['latency_ms']}",
94
+ "",
95
+ ]
96
+ )
97
+ REPORT_PATH.write_text("\n".join(lines), encoding="utf-8")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ print(run_local_evaluation())
datasets/golden_qa.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ question,expected_answer,expected_sources
2
+ What is Error Code 404?,Error Code 404 indicates that the requested server resource was not found and the router configuration should be checked.,sample_error.txt
3
+ What does this support copilot do?,The support copilot answers support questions using indexed documentation and retrieved context.,product_guide.txt
reports/eval_report.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # RAG Evaluation Report
2
+
3
+ Run the local benchmark with:
4
+
5
+ ```powershell
6
+ .\.venv\Scripts\python.exe -m app.tests.eval_rag
7
+ ```
8
+
9
+ This report is regenerated from `datasets/golden_qa.csv` and captures answer overlap, source hit rate, retrieved context count, and latency.