sha6th commited on
Commit
3b92dff
·
1 Parent(s): 66306ab

Add RAG evaluators - context relevance and recall + updated main

Browse files
llm-eval-dashboard ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 30b0f17dd666e144887bcfe8ebd564aaa537b2fc
main.py CHANGED
@@ -1,51 +1,52 @@
1
  from fastapi import FastAPI, HTTPException
2
  from pydantic import BaseModel
 
3
  from src.aggregator import evaluate_all
4
  from src.database import init_db, save_evaluation
5
 
6
  app = FastAPI(
7
  title="LLM Evaluation & Hallucination Detection Framework",
8
- version="1.0.0"
9
  )
10
- init_db()
11
 
 
12
 
13
- # Define what the request should look like
14
  class EvalRequest(BaseModel):
15
- context: str
16
  question: str
 
17
  llm_response: str
18
 
19
- # Define what the response will look like
20
  class EvalResponse(BaseModel):
21
  final_verdict: str
22
- cosine: dict
23
- fluency: dict
24
- bert_score: dict
25
- nli: dict
26
 
27
  @app.get("/")
28
  def home():
29
- return {"message": "LLM Evaluation Framework is running"}
30
 
31
  @app.post("/evaluate", response_model=EvalResponse)
32
  def evaluate(request: EvalRequest):
33
 
34
- # Edge case — empty inputs
35
- if not request.context.strip():
36
- raise HTTPException(status_code=400, detail="Context cannot be empty")
37
  if not request.question.strip():
38
  raise HTTPException(status_code=400, detail="Question cannot be empty")
 
 
39
  if not request.llm_response.strip():
40
  raise HTTPException(status_code=400, detail="LLM response cannot be empty")
41
 
42
- # Run evaluation
43
  result = evaluate_all(
44
- context=request.context,
45
  question=request.question,
 
46
  llm_response=request.llm_response
47
  )
48
- save_evaluation(request.context, request.question, request.llm_response, result)
 
 
 
 
 
 
49
 
50
  return result
51
 
@@ -54,16 +55,13 @@ from src.database import get_all_evaluations
54
  @app.get("/history")
55
  def history():
56
  rows = get_all_evaluations()
57
-
58
  results = []
59
  for row in rows:
60
  results.append({
61
  "id": row[0],
62
- "context": row[1],
63
  "question": row[2],
64
  "llm_response": row[3],
65
  "final_verdict": row[4],
66
  "created_at": row[11]
67
  })
68
-
69
  return {"total": len(results), "evaluations": results}
 
1
  from fastapi import FastAPI, HTTPException
2
  from pydantic import BaseModel
3
+ from typing import List
4
  from src.aggregator import evaluate_all
5
  from src.database import init_db, save_evaluation
6
 
7
  app = FastAPI(
8
  title="LLM Evaluation & Hallucination Detection Framework",
9
+ version="2.0.0"
10
  )
 
11
 
12
+ init_db()
13
 
 
14
  class EvalRequest(BaseModel):
 
15
  question: str
16
+ retrieved_contexts: List[str]
17
  llm_response: str
18
 
 
19
  class EvalResponse(BaseModel):
20
  final_verdict: str
21
+ retrieval_evaluation: dict
22
+ generation_evaluation: dict
 
 
23
 
24
  @app.get("/")
25
  def home():
26
+ return {"message": "LLM Evaluation Framework v2.0 is running"}
27
 
28
  @app.post("/evaluate", response_model=EvalResponse)
29
  def evaluate(request: EvalRequest):
30
 
 
 
 
31
  if not request.question.strip():
32
  raise HTTPException(status_code=400, detail="Question cannot be empty")
33
+ if not request.retrieved_contexts:
34
+ raise HTTPException(status_code=400, detail="Retrieved contexts cannot be empty")
35
  if not request.llm_response.strip():
36
  raise HTTPException(status_code=400, detail="LLM response cannot be empty")
37
 
 
38
  result = evaluate_all(
 
39
  question=request.question,
40
+ retrieved_contexts=request.retrieved_contexts,
41
  llm_response=request.llm_response
42
  )
43
+
44
+ save_evaluation(
45
+ context=" ".join(request.retrieved_contexts),
46
+ question=request.question,
47
+ llm_response=request.llm_response,
48
+ result=result
49
+ )
50
 
51
  return result
52
 
 
55
  @app.get("/history")
56
  def history():
57
  rows = get_all_evaluations()
 
58
  results = []
59
  for row in rows:
60
  results.append({
61
  "id": row[0],
 
62
  "question": row[2],
63
  "llm_response": row[3],
64
  "final_verdict": row[4],
65
  "created_at": row[11]
66
  })
 
67
  return {"total": len(results), "evaluations": results}
src/evaluators/context_recall_evaluator.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sentence_transformers import SentenceTransformer
2
+ from sklearn.metrics.pairwise import cosine_similarity
3
+
4
+ model = None
5
+
6
+ def evaluate_context_recall(question: str, retrieved_contexts: list) -> dict:
7
+ global model
8
+ if model is None:
9
+ model = SentenceTransformer("all-MiniLM-L6-v2")
10
+
11
+ if not retrieved_contexts:
12
+ return {
13
+ "score": 0.0,
14
+ "verdict": "No Context Retrieved"
15
+ }
16
+
17
+ combined_context = " ".join(retrieved_contexts)
18
+
19
+ question_embedding = model.encode([question])
20
+ context_embedding = model.encode([combined_context])
21
+
22
+ score = cosine_similarity(question_embedding, context_embedding)[0][0]
23
+ score = round(float(score), 4)
24
+
25
+ if score >= 0.6:
26
+ verdict = "High Recall"
27
+ elif score >= 0.35:
28
+ verdict = "Partial Recall"
29
+ else:
30
+ verdict = "Low Recall"
31
+
32
+ return {
33
+ "score": score,
34
+ "verdict": verdict
35
+ }
src/evaluators/context_relevance_evaluator.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sentence_transformers import SentenceTransformer
2
+ from sklearn.metrics.pairwise import cosine_similarity
3
+ import numpy as np
4
+
5
+ model = None
6
+
7
+ def evaluate_context_relevance(question: str, retrieved_contexts: list) -> dict:
8
+ global model
9
+ if model is None:
10
+ model = SentenceTransformer("all-MiniLM-L6-v2")
11
+
12
+ if not retrieved_contexts:
13
+ return {
14
+ "scores": [],
15
+ "average_score": 0.0,
16
+ "verdict": "No Context Retrieved"
17
+ }
18
+
19
+ question_embedding = model.encode([question])
20
+
21
+ scores = []
22
+ for chunk in retrieved_contexts:
23
+ chunk_embedding = model.encode([chunk])
24
+ score = cosine_similarity(question_embedding, chunk_embedding)[0][0]
25
+ scores.append(round(float(score), 4))
26
+
27
+ average_score = round(float(np.mean(scores)), 4)
28
+
29
+ if average_score >= 0.6:
30
+ verdict = "Highly Relevant Context"
31
+ elif average_score >= 0.4:
32
+ verdict = "Partially Relevant Context"
33
+ else:
34
+ verdict = "Irrelevant Context Retrieved"
35
+
36
+ return {
37
+ "scores": scores,
38
+ "average_score": average_score,
39
+ "verdict": verdict
40
+ }