rag-vietnamese / evaluate.py
thaidinhz1's picture
feat: add ColPali visual retrieval + contextual retrieval + PDF eval
af92b4d
Raw History Blame
4.95 kB
"""
RAGAS evaluation script.
Dùng LLM-as-judge thay vì ragas library để tránh dependency conflicts.
Metrics: Faithfulness, Answer Relevancy, Context Recall
"""
print("Loading evaluate.py...")
import json
print("json ok")
import time
print("time ok")
from groq import Groq
print("groq ok")
from dotenv import load_dotenv
import os
load_dotenv()
client = Groq(api_key=os.environ["GROQ_API_KEY"])
JUDGE_MODEL = "llama-3.3-70b-versatile"
print("groq client ok")
from src.rag import answer as rag_answer
print("rag ok")
def judge(prompt: str) -> float:
"""Gọi LLM judge, trả về score 0.0-1.0."""
response = client.chat.completions.create(
model=JUDGE_MODEL,
messages=[{"role": "user", "content": prompt}],
max_tokens=10,
temperature=0.0,
)
text = response.choices[0].message.content.strip()
try:
score = float(text.split()[0])
return max(0.0, min(1.0, score))
except Exception:
return 0.5
def eval_faithfulness(question: str, answer: str, contexts: list[str]) -> float:
context_text = "\n".join(contexts)
prompt = f"""Đánh giá xem câu trả lời có trung thực với ngữ cảnh không.
Chỉ trả về một số từ 0.0 đến 1.0 (1.0 = hoàn toàn trung thực, 0.0 = bịa đặt).
Ngữ cảnh: {context_text}
Câu trả lời: {answer}
Score:"""
return judge(prompt)
def eval_answer_relevancy(question: str, answer: str) -> float:
prompt = f"""Đánh giá xem câu trả lời có liên quan đến câu hỏi không.
Chỉ trả về một số từ 0.0 đến 1.0 (1.0 = rất liên quan, 0.0 = không liên quan).
Câu hỏi: {question}
Câu trả lời: {answer}
Score:"""
return judge(prompt)
def eval_context_recall(question: str, ground_truth: str, contexts: list[str]) -> float:
context_text = "\n".join(contexts)
prompt = f"""Đánh giá xem ngữ cảnh có chứa đủ thông tin để trả lời câu hỏi theo đáp án chuẩn không.
Chỉ trả về một số từ 0.0 đến 1.0 (1.0 = đủ thông tin, 0.0 = thiếu hoàn toàn).
Câu hỏi: {question}
Đáp án chuẩn: {ground_truth}
Ngữ cảnh: {context_text}
Score:"""
return judge(prompt)
def evaluate(golden_set_path: str = "data/golden_set.json", sample: int = 20):
with open(golden_set_path, encoding="utf-8") as f:
golden = json.load(f)
# Lấy sample câu hỏi có ground_truth thực (bỏ out_of_scope)
items = [q for q in golden if q.get("type") != "out_of_scope"][:sample]
results = []
print(f"Đánh giá {len(items)} câu hỏi...\n")
for i, item in enumerate(items, 1):
question = item["question"]
ground_truth = item["answer"]
print(f"[{i}/{len(items)}] {question[:60]}...")
try:
result = rag_answer(question, top_k=5)
ans = result["answer"]
context_texts = result["contexts"]
faith = eval_faithfulness(question, ans, context_texts)
relevancy = eval_answer_relevancy(question, ans)
recall = eval_context_recall(question, ground_truth, context_texts)
results.append({
"question": question,
"ground_truth": ground_truth,
"answer": ans,
"faithfulness": faith,
"answer_relevancy": relevancy,
"context_recall": recall,
})
print(f" Faithfulness: {faith:.2f} | Relevancy: {relevancy:.2f} | Recall: {recall:.2f}")
time.sleep(2) # tránh rate limit
except Exception as e:
print(f" Lỗi: {e}")
time.sleep(5)
if results:
avg_faith = sum(r["faithfulness"] for r in results) / len(results)
avg_rel = sum(r["answer_relevancy"] for r in results) / len(results)
avg_recall = sum(r["context_recall"] for r in results) / len(results)
print(f"\n{'='*50}")
print(f"KẾT QUẢ ĐÁNH GIÁ ({len(results)} câu hỏi)")
print(f"{'='*50}")
print(f"Faithfulness: {avg_faith:.3f}")
print(f"Answer Relevancy: {avg_rel:.3f}")
print(f"Context Recall: {avg_recall:.3f}")
print(f"Average: {(avg_faith + avg_rel + avg_recall) / 3:.3f}")
with open("eval_results.json", "w", encoding="utf-8") as f:
json.dump({
"summary": {
"faithfulness": avg_faith,
"answer_relevancy": avg_rel,
"context_recall": avg_recall,
},
"details": results,
}, f, ensure_ascii=False, indent=2)
print("\nĐã lưu chi tiết vào eval_results.json")
if __name__ == "__main__":
import sys
golden_path = sys.argv[1] if len(sys.argv) > 1 else "data/golden_set.json"
sample = int(sys.argv[2]) if len(sys.argv) > 2 else 20
evaluate(golden_set_path=golden_path, sample=sample)