Dus Tran commited on
Commit
ebeb2ae
·
1 Parent(s): feb5410

Add and refactor evaluation scripts

Browse files
evaluation/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # RAG Chatbot Evaluation Package
evaluation/evaluate.py ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ RAGAS Evaluation Suite cho RAG Chatbot.
3
+ Các metrics được đánh giá:
4
+ 1. Faithfulness - Câu trả lời có trung thực với context không?
5
+ 2. Answer Relevancy - Câu trả lời có liên quan đến câu hỏi không?
6
+ 3. Context Precision - Context retrieve được có chính xác không?
7
+ 4. Context Recall - Context có đủ để trả lời câu hỏi không?
8
+ 5. Answer Correctness - Câu trả lời có đúng so với ground truth không?
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import json
14
+ import time
15
+ import logging
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ import pandas as pd
19
+ from datasets import Dataset
20
+
21
+
22
+
23
+ from ragas import evaluate
24
+ from ragas.metrics import (
25
+ Faithfulness,
26
+ AnswerRelevancy,
27
+ ContextPrecision,
28
+ ContextRecall,
29
+ AnswerCorrectness,
30
+ )
31
+ from langchain_groq import ChatGroq
32
+ from langchain_huggingface import HuggingFaceEmbeddings
33
+ from ragas.llms import LangchainLLMWrapper
34
+ from ragas.embeddings import LangchainEmbeddingsWrapper
35
+ from ragas.run_config import RunConfig
36
+ from evaluation.rag_pipeline import run_rag_pipeline
37
+
38
+ # ──────────────────────────────────────────────────────────────
39
+ # Logging
40
+ # ──────────────────────────────────────────────────────────────
41
+ logging.basicConfig(
42
+ level=logging.INFO,
43
+ format="%(asctime)s [%(levelname)s] %(message)s",
44
+ handlers=[logging.StreamHandler()],
45
+ )
46
+ logger = logging.getLogger(__name__)
47
+
48
+ # ──────────────────────────────────────────────────────────────
49
+ # Cấu hình LLM & Embedding cho RAGAS
50
+ # ──────────────────────────────────────────────────────────────
51
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
52
+ RAGAS_EMBED_MODEL = "bkai-foundation-models/vietnamese-bi-encoder"
53
+
54
+ def get_ragas_llm():
55
+ """Khởi tạo LLM cho RAGAS 0.4 dùng Groq (Llama 3.1 70B) và LangchainLLMWrapper."""
56
+ if not GROQ_API_KEY:
57
+ raise ValueError("Chưa cấu hình GROQ_API_KEY trong file .env")
58
+ groq_llm = ChatGroq(
59
+ model="llama-3.3-70b-versatile",
60
+ groq_api_key=GROQ_API_KEY,
61
+ temperature=0,
62
+ max_retries=10
63
+ )
64
+ return LangchainLLMWrapper(groq_llm)
65
+
66
+ def get_ragas_embeddings():
67
+ """Khởi tạo Embedding cho RAGAS 0.4 dùng HuggingFace và LangchainEmbeddingsWrapper."""
68
+ hf_embeddings = HuggingFaceEmbeddings(model_name=RAGAS_EMBED_MODEL)
69
+ return LangchainEmbeddingsWrapper(hf_embeddings)
70
+
71
+ # ──────────────────────────────────────────────────────────────
72
+ # Thu thập dữ liệu từ RAG pipeline
73
+ # ──────────────────────────────────────────────────────────────
74
+ def collect_rag_outputs(dataset_path: str, limit: int = None, delay_seconds: float = 5.0) -> list[dict]:
75
+ """
76
+ Chạy RAG pipeline trên từng câu hỏi trong dataset và thu thập kết quả.
77
+ """
78
+ with open(dataset_path, "r", encoding="utf-8") as f:
79
+ samples = json.load(f)
80
+
81
+ print("⏳ LLM (GPT-4o-mini) sẽ tính điểm từng metric — có thể mất 2-3 phút (chạy sequential để tránh Rate Limit)...")
82
+ if limit is not None and limit > 0:
83
+ logger.info(f"Giới hạn bộ test còn {limit} mẫu để tránh Rate Limit API.")
84
+ samples = samples[:limit]
85
+
86
+ results = []
87
+ total = len(samples)
88
+
89
+ for idx, sample in enumerate(samples, 1):
90
+ question = sample["question"]
91
+ ground_truth = sample["ground_truth"]
92
+
93
+ logger.info(f"[{idx}/{total}] Đang chạy RAG cho: '{question[:50]}...'")
94
+
95
+ try:
96
+ output = run_rag_pipeline(
97
+ question=question,
98
+ session_id=f"eval_{idx}",
99
+ )
100
+
101
+ # Format context và đảm bảo là list
102
+ contexts = output.get("contexts", [])
103
+ if isinstance(contexts, str):
104
+ contexts = [contexts]
105
+
106
+ results.append(
107
+ {
108
+ "question": question,
109
+ "answer": output["answer"],
110
+ "contexts": contexts,
111
+ "ground_truth": ground_truth,
112
+ }
113
+ )
114
+ logger.info(f" ✓ Answer: {output['answer'][:70]}...")
115
+ except Exception as e:
116
+ logger.error(f" ✗ Lỗi khi xử lý câu hỏi #{idx}: {e}")
117
+ results.append(
118
+ {
119
+ "question": question,
120
+ "answer": f"ERROR: {str(e)}",
121
+ "contexts": ["Không tìm thấy ngữ cảnh vì xảy ra lỗi."],
122
+ "ground_truth": ground_truth,
123
+ }
124
+ )
125
+
126
+ if idx < total:
127
+ time.sleep(delay_seconds)
128
+
129
+ return results
130
+
131
+ # ──────────────────────────────────────────────────────────────
132
+ # Vẽ biểu đồ kết quả
133
+ # ──────────────────────────────────────────────────────────────
134
+ def generate_plots(df_results: pd.DataFrame, output_dir: str):
135
+ """
136
+ Vẽ các biểu đồ trực quan hóa kết quả và lưu dưới dạng ảnh PNG.
137
+ """
138
+ try:
139
+ import matplotlib.pyplot as plt
140
+ import numpy as np
141
+ except ImportError:
142
+ logger.warning("Không thể import matplotlib hoặc numpy, bỏ qua bước vẽ biểu đồ.")
143
+ return
144
+
145
+ METRICS = [
146
+ "faithfulness",
147
+ "answer_relevancy",
148
+ "context_precision",
149
+ "context_recall",
150
+ "answer_correctness",
151
+ ]
152
+ METRIC_LABELS = {
153
+ "faithfulness": "Faithfulness\n(Trung thực)",
154
+ "answer_relevancy": "Answer Relevancy\n(Liên quan)",
155
+ "context_precision": "Context Precision\n(Chính xác)",
156
+ "context_recall": "Context Recall\n(Đầy đủ)",
157
+ "answer_correctness": "Answer Correctness\n(Đúng đắn)",
158
+ }
159
+
160
+ available_metrics = [m for m in METRICS if m in df_results.columns]
161
+ avg_scores = df_results[available_metrics].mean()
162
+ plot_scores = np.nan_to_num(avg_scores.values, nan=0.0)
163
+
164
+ # 1. Bar Chart & Radar Chart (Side by Side)
165
+ fig = plt.figure(figsize=(15, 6))
166
+ fig.suptitle("KẾT QUẢ ĐÁNH GIÁ CHẤT LƯỢNG CHATBOT (RAGAS)", fontsize=15, fontweight="bold", y=0.98)
167
+
168
+ # Bar chart
169
+ ax1 = fig.add_subplot(1, 2, 1)
170
+ colors = [
171
+ "#27ae60" if (not np.isnan(s) and s >= 0.7) else ("#f39c12" if (not np.isnan(s) and s >= 0.5) else "#d35400")
172
+ for s in avg_scores.values
173
+ ]
174
+ bars = ax1.bar(
175
+ [METRIC_LABELS.get(m, m) for m in available_metrics],
176
+ plot_scores,
177
+ color=colors, width=0.5, edgecolor="white", linewidth=1.2
178
+ )
179
+ ax1.set_ylim(0, 1.15)
180
+ ax1.set_ylabel("Điểm số (Score)", fontsize=11)
181
+ ax1.set_title("Điểm Trung Bình Từng Metric", fontsize=12, fontweight="bold")
182
+ ax1.axhline(y=0.7, color="#27ae60", linestyle="--", alpha=0.6, label="Tốt (>=0.7)")
183
+ ax1.axhline(y=0.5, color="#f39c12", linestyle="--", alpha=0.6, label="Tạm ổn (>=0.5)")
184
+ ax1.legend(loc="upper right", fontsize=9)
185
+ ax1.set_xticks(range(len(available_metrics)))
186
+ ax1.set_xticklabels([METRIC_LABELS.get(m, m) for m in available_metrics], fontsize=9)
187
+ for bar, score in zip(bars, avg_scores.values):
188
+ height = bar.get_height()
189
+ label_val = f"{score:.3f}" if not np.isnan(score) else "NaN"
190
+ ax1.text(bar.get_x() + bar.get_width() / 2.0, height + 0.02,
191
+ label_val, ha="center", va="bottom", fontweight="bold", fontsize=10)
192
+ ax1.set_facecolor("#fcfcfc")
193
+ ax1.grid(axis="y", alpha=0.2)
194
+
195
+ # Radar chart
196
+ ax2 = fig.add_subplot(1, 2, 2, projection="polar")
197
+ N = len(available_metrics)
198
+ angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist()
199
+ values = plot_scores.tolist()
200
+
201
+ # Close the polygon
202
+ angles_c = angles + angles[:1]
203
+ values_c = values + values[:1]
204
+
205
+ ax2.plot(angles_c, values_c, "o-", linewidth=2, color="#2980b9")
206
+ ax2.fill(angles_c, values_c, alpha=0.2, color="#2980b9")
207
+ ax2.set_xticks(angles)
208
+ ax2.set_xticklabels([m.replace("_", "\n").title() for m in available_metrics], fontsize=9)
209
+ ax2.set_ylim(0, 1.0)
210
+ ax2.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0])
211
+ ax2.set_yticklabels(["0.2", "0.4", "0.6", "0.8", "1.0"], fontsize=8)
212
+ ax2.set_title("Biểu Đồ Radar Đa Chiều", pad=20, fontsize=12, fontweight="bold")
213
+ ax2.grid(True, alpha=0.3)
214
+
215
+ plt.tight_layout()
216
+ chart_path = os.path.join(output_dir, "ragas_metrics_chart.png")
217
+ plt.savefig(chart_path, dpi=150, bbox_inches="tight")
218
+ plt.close()
219
+ logger.info(f"Đã cập nhật biểu đồ: {chart_path}")
220
+
221
+ # 2. Heatmap per Sample
222
+ heatmap_data = df_results[available_metrics].values.astype(float)
223
+ question_col = "user_input" if "user_input" in df_results.columns else "question"
224
+ q_labels = [f"Q{i+1}: {str(q)[:30]}..." for i, q in enumerate(df_results.get(question_col, ["Unknown"] * len(df_results)))]
225
+
226
+ fig, ax = plt.subplots(figsize=(10, max(4, len(df_results) * 0.5)))
227
+ im = ax.imshow(heatmap_data, cmap="RdYlGn", aspect="auto", vmin=0, vmax=1)
228
+ plt.colorbar(im, ax=ax, fraction=0.04, pad=0.04)
229
+
230
+ ax.set_xticks(range(len(available_metrics)))
231
+ ax.set_xticklabels(available_metrics, rotation=20, ha="right", fontsize=9)
232
+ ax.set_yticks(range(len(df_results)))
233
+ ax.set_yticklabels(q_labels, fontsize=9)
234
+ ax.set_title("Bản Đồ Nhiệt (Heatmap) Điểm Số Từng Mẫu", fontsize=13, fontweight="bold", pad=12)
235
+
236
+ for i in range(len(df_results)):
237
+ for j in range(len(available_metrics)):
238
+ val = heatmap_data[i, j]
239
+ if not np.isnan(val):
240
+ ax.text(j, i, f"{val:.2f}", ha="center", va="center",
241
+ fontsize=8.5, fontweight="bold",
242
+ color="white" if val < 0.4 else "black")
243
+
244
+ plt.tight_layout()
245
+ heatmap_path = os.path.join(output_dir, "ragas_heatmap.png")
246
+ plt.savefig(heatmap_path, dpi=150, bbox_inches="tight")
247
+ plt.close()
248
+ logger.info(f"✓ Đã cập nhật Heatmap: {heatmap_path}")
249
+
250
+ # ──────────────────────────────────────────────────────────────
251
+ # Chạy RAGAS Evaluation
252
+ # ──────────────────────────────────────────────────────────────
253
+ def run_ragas_evaluation(
254
+ rag_results: list[dict],
255
+ output_dir: str = None,
256
+ ) -> tuple[pd.DataFrame, dict]:
257
+ """
258
+ Chạy RAGAS evaluation trên tập kết quả từ RAG pipeline.
259
+ """
260
+ if output_dir is None:
261
+ output_dir = os.path.join(os.path.dirname(__file__), "results")
262
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
263
+
264
+ # Chuyển sang Hugging Face Dataset format
265
+ dataset_dict = {
266
+ "question": [r["question"] for r in rag_results],
267
+ "answer": [r["answer"] for r in rag_results],
268
+ "contexts": [r["contexts"] for r in rag_results],
269
+ "ground_truth": [r["ground_truth"] for r in rag_results],
270
+ }
271
+ hf_dataset = Dataset.from_dict(dataset_dict)
272
+
273
+ logger.info("Đang khởi tạo LLM và Embeddings cho RAGAS...")
274
+ ragas_llm = get_ragas_llm()
275
+ ragas_embeddings = get_ragas_embeddings()
276
+
277
+ # Khởi tạo các metric RAGAS
278
+ metrics = [
279
+ Faithfulness(llm=ragas_llm),
280
+ AnswerRelevancy(llm=ragas_llm, embeddings=ragas_embeddings),
281
+ ContextPrecision(llm=ragas_llm),
282
+ ContextRecall(llm=ragas_llm),
283
+ AnswerCorrectness(llm=ragas_llm, embeddings=ragas_embeddings),
284
+ ]
285
+
286
+ logger.info(f"Bắt đầu chấm điểm {len(rag_results)} mẫu với {len(metrics)} metrics...")
287
+ logger.info(f"Metrics: {[m.name for m in metrics]}")
288
+
289
+ run_config = RunConfig(max_workers=1, timeout=300)
290
+ result = evaluate(
291
+ dataset=hf_dataset,
292
+ metrics=metrics,
293
+ run_config=run_config,
294
+ raise_exceptions=False,
295
+ )
296
+
297
+ df = result.to_pandas()
298
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
299
+ df["timestamp"] = timestamp
300
+
301
+ # Tính điểm trung bình
302
+ metric_cols = [m.name for m in metrics]
303
+ avg_scores = df[metric_cols].mean()
304
+
305
+ logger.info("\n" + "=" * 60)
306
+ logger.info("KẾT QUẢ ĐÁNH GIÁ RAGAS TRUNG BÌNH")
307
+ logger.info("=" * 60)
308
+ for col, score in avg_scores.items():
309
+ if pd.isna(score):
310
+ bar = "░" * 20
311
+ logger.info(f" {col:<25} {bar} NaN (Failed/API Quota Exceeded)")
312
+ else:
313
+ bar = "█" * int(score * 20) + "░" * (20 - int(score * 20))
314
+ logger.info(f" {col:<25} {bar} {score:.4f}")
315
+ logger.info("=" * 60)
316
+
317
+ # Lưu kết quả theo dạng timestamp
318
+ csv_path = os.path.join(output_dir, f"ragas_results_{timestamp}.csv")
319
+ json_path = os.path.join(output_dir, f"ragas_results_{timestamp}.json")
320
+ summary_path = os.path.join(output_dir, f"ragas_summary_{timestamp}.json")
321
+
322
+ df.to_csv(csv_path, index=False, encoding="utf-8-sig")
323
+ df.to_json(json_path, orient="records", force_ascii=False, indent=2)
324
+
325
+ summary = {
326
+ "evaluation_timestamp": timestamp,
327
+ "total_samples": len(rag_results),
328
+ "metrics_used": [m.name for m in metrics],
329
+ "average_scores": avg_scores.to_dict(),
330
+ "overall_score": avg_scores.mean(),
331
+ }
332
+ with open(summary_path, "w", encoding="utf-8") as f:
333
+ json.dump(summary, f, ensure_ascii=False, indent=2)
334
+
335
+ # Lưu thêm phiên bản tĩnh "latest" để dashboard dễ truy cập
336
+ latest_json_path = os.path.join(output_dir, "ragas_results_latest.json")
337
+ latest_summary_path = os.path.join(output_dir, "ragas_summary_latest.json")
338
+
339
+ df.to_json(latest_json_path, orient="records", force_ascii=False, indent=2)
340
+ with open(latest_summary_path, "w", encoding="utf-8") as f:
341
+ json.dump(summary, f, ensure_ascii=False, indent=2)
342
+
343
+ logger.info("Đã lưu các kết quả dạng timestamp và dạng 'latest'")
344
+
345
+ # Vẽ biểu đồ tĩnh
346
+ generate_plots(df, output_dir)
347
+
348
+ return df, summary
349
+
350
+
evaluation/optimization_dataset.json ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": 1,
4
+ "source_file": "Optimization_Chap04_2_new.pdf",
5
+ "question": "Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là gì và có chức năng như thế nào?",
6
+ "ground_truth": "Siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Đây chính là siêu phẳng tách tập lồi X và điểm xk không thuộc X."
7
+ },
8
+ {
9
+ "id": 2,
10
+ "source_file": "Optimization_Chap04_2_new.pdf",
11
+ "question": "Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là gì và giới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là bao nhiêu?",
12
+ "ground_truth": "Khi giải hệ phương trình ∇xψ(x, α) = 0, ta nhận được điểm dừng x*(α) = (x*1(α), x*2(α))T với x*1(α) = (√1 + 2α + 3α - 1)/2 và x*2(α) = 1 + (√1 + 2α)/2. Khi tham số α > 0 và giảm dần về 0, giới hạn của các thành phần của điểm dừng là limα→0+ x*1(α) = 0 và limα→0+ x*2(α) = 1."
13
+ },
14
+ {
15
+ "id": 3,
16
+ "source_file": "Optimization_Chap02_4.pdf",
17
+ "question": "Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất là gì khi một phương pháp được áp dụng và khi không được áp dụng?",
18
+ "ground_truth": "Theo Định lý về độ lệch bù, đối với các phương pháp sản xuất, nếu phương pháp thứ j được áp dụng (t∗j > 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj. Ngược lại, nếu phương pháp thứ j không được áp dụng (t∗j = 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, điều này có nghĩa là sản xuất theo phương pháp này sẽ bị lỗ."
19
+ },
20
+ {
21
+ "id": 4,
22
+ "source_file": "Optimization_Chap02_4.pdf",
23
+ "question": "Theo đoạn văn, mục đích của bài toán đối ngẫu là gì và nó liên quan đến việc xác định yếu tố nào để đạt được mục tiêu đó?",
24
+ "ground_truth": "Mục đích của bài toán đối ngẫu là xác định giá trị zi cho mỗi đơn vị sản phẩm loại i sao cho tổng giá trị toàn bộ sản phẩm theo yêu cầu của xã hội là lớn nhất."
25
+ },
26
+ {
27
+ "id": 5,
28
+ "source_file": "Optimization_Chap02_1.pdf",
29
+ "question": "Theo đoạn văn, nghiệm tối ưu (phương án tối ưu) được định nghĩa như thế nào?",
30
+ "ground_truth": "Nghiệm tối ưu (phương án tối ưu) là một nghiệm chấp nhận được x∗ ∈ D thỏa mãn f (x∗) = ⟨c, x∗⟩ ≤ f (x) = ⟨c, x⟩, với mọi x ∈ D."
31
+ },
32
+ {
33
+ "id": 6,
34
+ "source_file": "Optimization_Chap02_1.pdf",
35
+ "question": "Trong bài toán tối ưu hóa, làm thế nào để chuyển đổi một ràng buộc đẳng thức thành các ràng buộc bất đẳng thức?",
36
+ "ground_truth": "Để chuyển đổi một ràng buộc đẳng thức có dạng nXj=1 aijxj = bi, ta có thể thay thế nó bằng hai ràng buộc bất đẳng thức: nXj=1 aijxj ≥ bi và −nXj=1 aijxj ≥ −bi."
37
+ },
38
+ {
39
+ "id": 7,
40
+ "source_file": "Optimization_Chap02_3.pdf",
41
+ "question": "Khi giải bài toán (LP) bằng phương pháp đơn hình, để chuyển từ bảng đơn hình cuối cùng của Pha 1 sang bảng đơn hình xuất phát của Pha 2, cần thực hiện những sửa đổi nào?",
42
+ "ground_truth": "Bảng đơn hình đầu tiên của Pha 2 là bảng đơn hình cuối cùng ở Pha 1 nhưng với một số sửa đổi như sau: Xóa tất cả các cột tương ứng với các biến giả; Thay cột CB bởi hệ số mục tiêu cơ sở tương ứng của bài toán gốc; Thay các hệ số mục tiêu của bài toán phụ ở dòng 1 bằng hệ số mục tiêu của bài toán gốc; Nếu trong cơ sở tương ứng với phương án tối ưu của bài toán phụ (LP*) không có vector giả thì cơ sở ứng với phương án này cũng chính là cơ sở tương ứng với x0. Để có bảng đơn hình xuất phát cho bài toán ban đầu, ta chỉ cần tính giá trị hàm mục tiêu tại x0 và tính lại các ước lượng theo công thức f(x0) = Σj∈J(x0) cjx0j và Δk = Σj∈J(x0) zjkcj − ck."
43
+ },
44
+ {
45
+ "id": 8,
46
+ "source_file": "Optimization_Chap02_3.pdf",
47
+ "question": "Dựa vào Bảng 3 trong Pha 2 của quá trình giải bài toán gốc, hãy xác định giá trị hàm mục tiêu hiện tại và các biến cơ sở cùng với giá trị của chúng.",
48
+ "ground_truth": "Từ Bảng 3, giá trị hàm mục tiêu hiện tại là -82/5. Các biến cơ sở và giá trị của chúng là: A3 = 12, A4 = 0, và A2 = 7."
49
+ },
50
+ {
51
+ "id": 9,
52
+ "source_file": "Optimization_Chap03_2.pdf",
53
+ "question": "Thuật toán Newton thuần túy có những ưu điểm nổi bật nào khi áp dụng cho hàm toàn phương và khi điểm xuất phát đủ gần điểm dừng?",
54
+ "ground_truth": "Thuật toán Newton thuần túy có hai ưu điểm nổi bật: Thứ nhất, nếu f là hàm toàn phương f(x) = 1/2 xTAx − bTx + c với A đối xứng xác định dương, thuật toán sẽ cho nghiệm chỉ sau một vòng lặp. Thứ hai, nếu xuất phát đủ gần điểm dừng, thuật toán hội tụ rất nhanh (bậc hai)."
55
+ },
56
+ {
57
+ "id": 10,
58
+ "source_file": "Optimization_Chap03_2.pdf",
59
+ "question": "Dựa vào Bước lặp k = 3, hãy cho biết điều kiện dừng thuật toán được thỏa mãn là gì và nghiệm tìm được sau khi dừng thuật toán là bao nhiêu?",
60
+ "ground_truth": "Tại Bước lặp k = 3, thuật toán dừng lại vì gradient của hàm mục tiêu tại x4, tức là ∇f(x4), bằng vectơ không (0, 0, 0). Nghiệm tìm được sau khi dừng thuật toán là x* = (-1, -0.2, -0.04)."
61
+ },
62
+ {
63
+ "id": 11,
64
+ "source_file": "Optimization_Chap01.pdf",
65
+ "question": "Trong Quy hoạch phi tuyến, Quy hoạch lồi được định nghĩa như thế nào?",
66
+ "ground_truth": "Quy hoạch lồi là bài toán cực tiểu một hàm mục tiêu f(x) là hàm lồi trên tập chấp nhận được D ⊆ R^n là tập lồi."
67
+ },
68
+ {
69
+ "id": 12,
70
+ "source_file": "Optimization_Chap01.pdf",
71
+ "question": "Bài toán được mô tả trong đoạn văn bản yêu cầu xác định những vị trí nào?",
72
+ "ground_truth": "Bài toán yêu cầu xác định vị trí để xây dựng một trạm biến áp tổng để cung cấp điện cho 4 khu chung cư và vị trí (xi, yi) ∈ Ki để đặt trạm biến áp phân phối của mỗi khu chung cư Ki, i = 1, ."
73
+ },
74
+ {
75
+ "id": 13,
76
+ "source_file": "Optimization_Chap02_2.pdf",
77
+ "question": "Theo thuật toán đơn hình, điều kiện để phương án cực biên x0 được coi là nghiệm tối ưu là gì?",
78
+ "ground_truth": "Theo Bước 3 của thuật toán đơn hình, nếu tất cả các ước lượng biến ∆k ≤ 0 với mọi k không thuộc tập chỉ số cơ sở J(x0), thì x0 là nghiệm tối ưu và thuật toán sẽ dừng lại."
79
+ },
80
+ {
81
+ "id": 14,
82
+ "source_file": "Optimization_Chap02_2.pdf",
83
+ "question": "Dựa vào bảng đơn hình cuối cùng được cung cấp, hãy cho biết điều kiện để đạt được nghiệm tối ưu, nghiệm tối ưu là gì và giá trị tối ưu tương ứng?",
84
+ "ground_truth": "Dựa vào bảng đơn hình cuối cùng, điều kiện để đạt được nghiệm tối ưu là khi tất cả ∆k ≤ 0. Nghiệm tối ưu duy nhất là x∗ = (4/3, 0, 4/3, 2/3, 0, 0)^T và giá trị tối ưu fmin = 0."
85
+ },
86
+ {
87
+ "id": 15,
88
+ "source_file": "Optimization_Chap03_1.pdf",
89
+ "question": "Theo Định lý hội tụ, các điều kiện nào cần được thỏa mãn để mọi điểm tụ của dãy {xk} sinh bởi Thuật toán 3.1 đều có gradient của hàm f bằng 0?",
90
+ "ground_truth": "Theo Định lý hội tụ, các điều kiện cần được thỏa mãn là: x0 ∈ Rn, hàm f khả vi liên tục trên Rn và có tập mức dưới {x ∈ Rn | f (x) ≤ f (x0)} bị chặn. Khi đó, mọi điểm tụ x∗ của dãy {xk} sinh bởi Thuật toán 3.1 đều thỏa mãn ∇f (x∗) = 0."
91
+ },
92
+ {
93
+ "id": 16,
94
+ "source_file": "Optimization_Chap03_1.pdf",
95
+ "question": "Bài toán quy hoạch phi tuyến không ràng buộc (P) được định nghĩa như thế nào và hàm mục tiêu f trong bài toán này có những tính chất gì theo đoạn văn?",
96
+ "ground_truth": "Bài toán quy hoạch phi tuyến không ràng buộc (P) được định nghĩa là min f(x) v.đ.k. x ∈ Rn. Hàm mục tiêu f trong bài toán này là hàm phi tuyến và được xác định, khả vi trên Rn."
97
+ },
98
+ {
99
+ "id": 17,
100
+ "source_file": "Optimization_Chap04_1.pdf",
101
+ "question": "Chương 4 của môn Cơ sở Tối ưu hóa, với tiêu đề 'Quy hoạch phi tuyến có ràng buộc', bao gồm những nội dung chính nào?",
102
+ "ground_truth": "Chương 4: Quy hoạch phi tuyến có ràng buộc bao gồm các nội dung chính sau: Điều kiện tối ưu, Định lý Karush–Kuhn–Tucker (KKT), Phương pháp nhân tử Lagrange, Phương pháp tuyến tính hóa, Phương pháp hướng có thể, Phương pháp Frank-Wolfe và Phương pháp hàm phạt."
103
+ },
104
+ {
105
+ "id": 18,
106
+ "source_file": "Optimization_Chap04_1.pdf",
107
+ "question": "Theo Định lý về điều kiện tối ưu cơ bản, điều kiện cần để x* là cực tiểu địa phương của bài toán min{f(x) : x ∈ X} là gì, và điều kiện để x* là cực tiểu địa phương chặt là gì?",
108
+ "ground_truth": "Theo Định lý v��� điều kiện tối ưu cơ bản: (i) Nếu x* ∈ X là cực tiểu địa phương của bài toán min{f(x) : x ∈ X} thì ⟨∇f(x*), v⟩ ≥ 0 ∀v ∈ T(X, x*). (ii) Nếu x* ∈ X thỏa mãn ⟨∇f(x*), v⟩ > 0 ∀v ∈ T(X, x*) \\ {0}, thì x* là cực tiểu địa phương chặt."
109
+ }
110
+ ]
evaluation/rag_pipeline.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from app.service.memory import contextualize_user_query, add_message_to_history
3
+ from app.service.router import logical_router
4
+ from app.service.retriever import hybrid_retrieval, apply_post_retrieval_rerank
5
+ from app.service.generator import generate_answer
6
+ from app.core.config import get_llm
7
+
8
+
9
+ def run_rag_pipeline(question: str, session_id: str = "eval_session") -> dict:
10
+ """
11
+ Chạy toàn bộ pipeline RAG và trả về answer + contexts.
12
+
13
+ Args:
14
+ question: Câu hỏi của người dùng.
15
+ session_id: ID phiên để quản lý memory.
16
+
17
+ Returns:
18
+ dict với các key:
19
+ - question (str): Câu hỏi gốc
20
+ - answer (str): Câu trả lời từ RAG pipeline
21
+ - contexts (list[str]): Danh sách các đoạn context đã retrieve được
22
+ """
23
+ llm = get_llm()
24
+
25
+ # Bước 1: Contextualize query với lịch sử hội thoại
26
+ full_query = contextualize_user_query(question, session_id)
27
+
28
+ # Bước 2: Router phân loại độ phức tạp và nguồn dữ liệu
29
+ routing_res = logical_router(full_query)
30
+
31
+ # Bước 3: Retrieve tài liệu liên quan
32
+ raw_docs = hybrid_retrieval(routing_res)
33
+
34
+ # Bước 4: Rerank để lấy các context tốt nhất
35
+ final_context_str = apply_post_retrieval_rerank(full_query, raw_docs)
36
+
37
+ # Tách chuỗi context thành danh sách
38
+ contexts = [
39
+ c.strip()
40
+ for c in final_context_str.split("\n\n---\n\n")
41
+ if c.strip()
42
+ ]
43
+
44
+ # Bước 5: Sinh câu trả lời
45
+ answer = generate_answer(full_query, final_context_str, llm)
46
+
47
+ # Lưu vào memory
48
+ add_message_to_history(session_id, "user", question)
49
+ add_message_to_history(session_id, "ai", answer)
50
+
51
+ return {
52
+ "question": question,
53
+ "answer": answer,
54
+ "contexts": contexts if contexts else ["Không tìm thấy ngữ cảnh phù hợp."],
55
+ }
evaluation/results/ragas_metrics_chart.png ADDED

Git LFS Details

  • SHA256: 309f5b8ee0ae63e0def7fae1166a2507cd1ea352fa1b0de606670c79b5a710d3
  • Pointer size: 131 Bytes
  • Size of remote file: 165 kB
evaluation/results/ragas_results_20260523_212654.csv ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ user_input,retrieved_contexts,response,reference,faithfulness,answer_relevancy,context_precision,context_recall,answer_correctness,timestamp
2
+ "Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là gì và có chức năng như thế nào?","['6/46\nChú ý: Tại Bước (k3) của thuật toán trên:\ni) Ta luôn có ∇gik(xk) ̸= 0 vì nếu ngược lại thì dogik là hàm lồi\nnên ta có\ngik(x) ≥ gik(xk) + ⟨∇gik(xk), x − xk⟩ > 0 ∀x\nvà điều này có nghĩa là tập chấp nhận được X của bài toán\nđang xét bằng rỗng;\nii) Siêu phẳng\n{x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0}\nđể xây dựng đa diệnXk+1 được gọi là siêu phẳng cắt. Đây\nchính là siêu phẳng tách tập lồi X và điểm xk /∈ X.', 'Giải\nmin\n\x1a\n⟨∇f (x1), x⟩ = 2\n5x1 + 4\n5x2\n\x0c\x0c\x0c\x0c x ∈ D\n\x1b\n.', 'Phương án cực biên\nx0 = (b1, b2, .']","Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Siêu phẳng này có chức năng tách tập lồi X và điểm xk không thuộc X. Nó được sử dụng để xây dựng đa diện Xk+1.","Siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Đây chính là siêu phẳng tách tập lồi X và điểm xk không thuộc X.",1.0,,0.9999999999,1.0,0.6475692390187693,20260523_212654
3
+ "Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là gì và giới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là bao nhiêu?","['45/46\nGiải hệ phương trình\n∇xψ(x, α) = 0 ⇐ ⇒\n\uf8f1\n\uf8f4\uf8f4\uf8f2\n\uf8f4\uf8f4\uf8f3\n1 − α\n1 + x1 − x2\n2\n= 0\n−2 + 2αx2\n1 + x1 − x2\n2\n− α\nx2\n= 0\nta nhận được điểm dừngx∗(α) = (x∗\n1(α), x∗\n2(α))T với\nx∗\n1(α) =\n√\n1 + 2α + 3α − 1\n2 , x∗\n2(α) = 1 +\n√\n1 + 2α\n2 . Vì tham sốα > 0 và giảm dần về 0 nên\nlim\nα→0+\nx∗\n1(α) = 0, lim\nα→0+\nx∗\n2(α) = 1.', 'Kiểm tra gradient tại x1:\n∇f (x1) =\n\x123 · 12 − 3\n2 · 1 − 2\n\x13\n=\n\x120\n0\n\x13\n. Kết luận: Dừng thuật toán với điểm dừng\nx1 = (1, 1).', '25/37\nVì∇f (x1) = 0, điểm x1 = (1, 1)T là điểm dừng của hàm f . Kết luận: Dừng thuật toán tạix1 = (1, 1).']","Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là:
4
+
5
+ * x∗1(α) = √(1 + 2α + 3α²) / 2
6
+ * x∗2(α) = 1 + √(1 + 2α) / 2
7
+
8
+ Giới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là:
9
+
10
+ * lim α→0+ x∗1(α) = 0
11
+ * lim α→0+ x∗2(α) = 1
12
+
13
+ Điều này cho thấy rằng khi tham số α giảm dần về 0, điểm dừng x*(α) sẽ tiến về điểm (0, 1).","Khi giải hệ phương trình ∇xψ(x, α) = 0, ta nhận được điểm dừng x*(α) = (x*1(α), x*2(α))T với x*1(α) = (√1 + 2α + 3α - 1)/2 và x*2(α) = 1 + (√1 + 2α)/2. Khi tham số α > 0 và giảm dần về 0, giới hạn của các thành phần của điểm dừng là limα→0+ x*1(α) = 0 và limα→0+ x*2(α) = 1.",0.6666666666666666,,0.9999999999,1.0,0.5591398630901252,20260523_212654
14
+ "Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất là gì khi một phương pháp được áp dụng và khi không được áp dụng?","['20/29\nTheo Định lý về độ lệch bù, ta có những ý nghĩa kinh tế quan\ntrọng sau đây:\n/cgĐối với các phương pháp sản xuất:\n Nếu phương pháp thứ j được áp dụng (t∗\nj > 0):\nmP\ni=1\naijz∗\ni = cj\n Tổng giá trị các sản phẩm sản xuất theo phương phápj phải\nvừa đúng bằng chi phí cj. ○ Nếu phương pháp thứ j không được áp dụng (t∗\nj = 0):\nmP\ni=1\naijz∗\ni < cj Tổng giá trị các sản phẩm sản xuất theo\nphương pháp j thấp hơn chi phícj (sản xuất theo phương pháp\nnày sẽ bị lỗ).', '12/29\nĐịnh lý về độ lệch bù\nGiả sử x là phương án của (P) và y là phương án của (D). Khi đó, x, y là phương án tối ưu của cặp bài toán đối ngẫu\nkhi và chỉ khi\n(\n⟨Ax − b, y⟩ = 0 (1)\n⟨c − ATy, x⟩ = 0 (2)\nNhận xét:\ni) Nếu bài toán gốc có dạng chính tắc (Ax = b, x ≥ 0), điều kiện\n(1) luôn thỏa mãn.', 'Khi đó, điều kiện độ lệch bù chỉ còn là:\n⟨c − ATy, x⟩ = 0\nii) Điều kiện độ lệch bù cho phép tìm nghiệm tối ưu của bài toán\nnày khi biết nghiệm tối ưu của bài toán kia.']","Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất như sau:
15
+
16
+ * Khi phương pháp thứ j được áp dụng (t∗j > 0):
17
+ + Tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj.
18
+ * Khi phương pháp thứ j không được áp dụng (t∗j = 0):
19
+ + Tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, nghĩa là sản xuất theo phương pháp này sẽ bị lỗ.","Theo Định lý về độ lệch bù, đối với các phương pháp sản xuất, nếu phương pháp thứ j được áp dụng (t∗j > 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj. Ngược lại, nếu phương pháp thứ j không được áp dụng (t∗j = 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, điều này có nghĩa là sản xuất theo phương pháp này sẽ bị lỗ.",1.0,,0.9999999999,1.0,0.6709179689144527,20260523_212654
evaluation/results/ragas_results_20260523_212654.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "user_input":"Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là gì và có chức năng như thế nào?",
4
+ "retrieved_contexts":[
5
+ "6\/46\nChú ý: Tại Bước (k3) của thuật toán trên:\ni) Ta luôn có ∇gik(xk) ̸= 0 vì nếu ngược lại thì dogik là hàm lồi\nnên ta có\ngik(x) ≥ gik(xk) + ⟨∇gik(xk), x − xk⟩ > 0 ∀x\nvà điều này có nghĩa là tập chấp nhận được X của bài toán\nđang xét bằng rỗng;\nii) Siêu phẳng\n{x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0}\nđể xây dựng đa diệnXk+1 được gọi là siêu phẳng cắt. Đây\nchính là siêu phẳng tách tập lồi X và điểm xk \/∈ X.",
6
+ "Giải\nmin\n\u001a\n⟨∇f (x1), x⟩ = 2\n5x1 + 4\n5x2\n\f\f\f\f x ∈ D\n\u001b\n.",
7
+ "Phương án cực biên\nx0 = (b1, b2, ."
8
+ ],
9
+ "response":"Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Siêu phẳng này có chức năng tách tập lồi X và điểm xk không thuộc X. Nó được sử dụng để xây dựng đa diện Xk+1.",
10
+ "reference":"Siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Đây chính là siêu phẳng tách tập lồi X và điểm xk không thuộc X.",
11
+ "faithfulness":1.0,
12
+ "answer_relevancy":null,
13
+ "context_precision":0.9999999999,
14
+ "context_recall":1.0,
15
+ "answer_correctness":0.647569239,
16
+ "timestamp":"20260523_212654"
17
+ },
18
+ {
19
+ "user_input":"Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là gì và giới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là bao nhiêu?",
20
+ "retrieved_contexts":[
21
+ "45\/46\nGiải hệ phương trình\n∇xψ(x, α) = 0 ⇐ ⇒\n\n\n\n1 − α\n1 + x1 − x2\n2\n= 0\n−2 + 2αx2\n1 + x1 − x2\n2\n− α\nx2\n= 0\nta nhận được điểm dừngx∗(α) = (x∗\n1(α), x∗\n2(α))T với\nx∗\n1(α) =\n√\n1 + 2α + 3α − 1\n2 , x∗\n2(α) = 1 +\n√\n1 + 2α\n2 . Vì tham sốα > 0 và giảm dần về 0 nên\nlim\nα→0+\nx∗\n1(α) = 0, lim\nα→0+\nx∗\n2(α) = 1.",
22
+ "Kiểm tra gradient tại x1:\n∇f (x1) =\n\u00123 · 12 − 3\n2 · 1 − 2\n\u0013\n=\n\u00120\n0\n\u0013\n. Kết luận: Dừng thuật toán với điểm dừng\nx1 = (1, 1).",
23
+ "25\/37\nVì∇f (x1) = 0, điểm x1 = (1, 1)T là điểm dừng của hàm f . Kết luận: Dừng thuật toán tạix1 = (1, 1)."
24
+ ],
25
+ "response":"Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là:\n\n* x∗1(α) = √(1 + 2α + 3α²) \/ 2\n* x∗2(α) = 1 + √(1 + 2α) \/ 2\n\nGiới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là:\n\n* lim α→0+ x∗1(α) = 0\n* lim α→0+ x∗2(α) = 1\n\nĐiều này cho thấy rằng khi tham số α giảm dần về 0, điểm dừng x*(α) sẽ tiến về điểm (0, 1).",
26
+ "reference":"Khi giải hệ phương trình ∇xψ(x, α) = 0, ta nhận được điểm dừng x*(α) = (x*1(α), x*2(α))T với x*1(α) = (√1 + 2α + 3α - 1)\/2 và x*2(α) = 1 + (√1 + 2α)\/2. Khi tham số α > 0 và giảm dần về 0, giới hạn của các thành phần của điểm dừng là limα→0+ x*1(α) = 0 và limα→0+ x*2(α) = 1.",
27
+ "faithfulness":0.6666666667,
28
+ "answer_relevancy":null,
29
+ "context_precision":0.9999999999,
30
+ "context_recall":1.0,
31
+ "answer_correctness":0.5591398631,
32
+ "timestamp":"20260523_212654"
33
+ },
34
+ {
35
+ "user_input":"Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất là gì khi một phương pháp được áp dụng và khi không được áp dụng?",
36
+ "retrieved_contexts":[
37
+ "20\/29\nTheo Định lý về độ lệch bù, ta có những ý nghĩa kinh tế quan\ntrọng sau đây:\n\/cgĐối với các phương pháp sản xuất:\n Nếu phương pháp thứ j được áp dụng (t∗\nj > 0):\nmP\ni=1\naijz∗\ni = cj\n Tổng giá trị các sản phẩm sản xuất theo phương phápj phải\nvừa đúng bằng chi phí cj. ○ Nếu phương pháp thứ j không được áp dụng (t∗\nj = 0):\nmP\ni=1\naijz∗\ni < cj Tổng giá trị các sản phẩm sản xuất theo\nphương pháp j thấp hơn chi phícj (sản xuất theo phương pháp\nnày sẽ bị lỗ).",
38
+ "12\/29\nĐịnh lý về độ lệch bù\nGiả sử x là phương án của (P) và y là phương án của (D). Khi đó, x, y là phương án tối ưu của cặp bài toán đối ngẫu\nkhi và chỉ khi\n(\n⟨Ax − b, y⟩ = 0 (1)\n⟨c − ATy, x⟩ = 0 (2)\nNhận xét:\ni) Nếu bài toán gốc có dạng chính tắc (Ax = b, x ≥ 0), điều kiện\n(1) luôn thỏa mãn.",
39
+ "Khi đó, điều kiện độ lệch bù chỉ còn là:\n⟨c − ATy, x⟩ = 0\nii) Điều kiện độ lệch bù cho phép tìm nghiệm tối ưu của bài toán\nnày khi biết nghiệm tối ưu của bài toán kia."
40
+ ],
41
+ "response":"Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất như sau:\n\n* Khi phương pháp thứ j được áp dụng (t∗j > 0):\n + Tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj.\n* Khi phương pháp thứ j không được áp dụng (t∗j = 0):\n + Tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, nghĩa là sản xuất theo phương pháp này sẽ bị lỗ.",
42
+ "reference":"Theo Định lý về độ lệch bù, đối với các phương pháp sản xuất, nếu phương pháp thứ j được áp dụng (t∗j > 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj. Ngược lại, nếu phương pháp thứ j không được áp dụng (t∗j = 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, điều này có nghĩa là sản xuất theo phương pháp này sẽ bị lỗ.",
43
+ "faithfulness":1.0,
44
+ "answer_relevancy":null,
45
+ "context_precision":0.9999999999,
46
+ "context_recall":1.0,
47
+ "answer_correctness":0.6709179689,
48
+ "timestamp":"20260523_212654"
49
+ }
50
+ ]
evaluation/results/ragas_results_latest.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "user_input":"Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là gì và có chức năng như thế nào?",
4
+ "retrieved_contexts":[
5
+ "6\/46\nChú ý: Tại Bước (k3) của thuật toán trên:\ni) Ta luôn có ∇gik(xk) ̸= 0 vì nếu ngược lại thì dogik là hàm lồi\nnên ta có\ngik(x) ≥ gik(xk) + ⟨∇gik(xk), x − xk⟩ > 0 ∀x\nvà điều này có nghĩa là tập chấp nhận được X của bài toán\nđang xét bằng rỗng;\nii) Siêu phẳng\n{x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0}\nđể xây dựng đa diệnXk+1 được gọi là siêu phẳng cắt. Đây\nchính là siêu phẳng tách tập lồi X và điểm xk \/∈ X.",
6
+ "Giải\nmin\n\u001a\n⟨∇f (x1), x⟩ = 2\n5x1 + 4\n5x2\n\f\f\f\f x ∈ D\n\u001b\n.",
7
+ "Phương án cực biên\nx0 = (b1, b2, ."
8
+ ],
9
+ "response":"Trong Bước (k3) của thuật toán, siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Siêu phẳng này có chức năng tách tập lồi X và điểm xk không thuộc X. Nó được sử dụng để xây dựng đa diện Xk+1.",
10
+ "reference":"Siêu phẳng {x ∈ Rn | ⟨∇gik(xk), x − xk⟩ + gik(xk) = 0} được gọi là siêu phẳng cắt. Đây chính là siêu phẳng tách tập lồi X và điểm xk không thuộc X.",
11
+ "faithfulness":1.0,
12
+ "answer_relevancy":null,
13
+ "context_precision":0.9999999999,
14
+ "context_recall":1.0,
15
+ "answer_correctness":0.647569239,
16
+ "timestamp":"20260523_212654"
17
+ },
18
+ {
19
+ "user_input":"Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là gì và giới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là bao nhiêu?",
20
+ "retrieved_contexts":[
21
+ "45\/46\nGiải hệ phương trình\n∇xψ(x, α) = 0 ⇐ ⇒\n\n\n\n1 − α\n1 + x1 − x2\n2\n= 0\n−2 + 2αx2\n1 + x1 − x2\n2\n− α\nx2\n= 0\nta nhận được điểm dừngx∗(α) = (x∗\n1(α), x∗\n2(α))T với\nx∗\n1(α) =\n√\n1 + 2α + 3α − 1\n2 , x∗\n2(α) = 1 +\n√\n1 + 2α\n2 . Vì tham sốα > 0 và giảm dần về 0 nên\nlim\nα→0+\nx∗\n1(α) = 0, lim\nα→0+\nx∗\n2(α) = 1.",
22
+ "Kiểm tra gradient tại x1:\n∇f (x1) =\n\u00123 · 12 − 3\n2 · 1 − 2\n\u0013\n=\n\u00120\n0\n\u0013\n. Kết luận: Dừng thuật toán với điểm dừng\nx1 = (1, 1).",
23
+ "25\/37\nVì∇f (x1) = 0, điểm x1 = (1, 1)T là điểm dừng của hàm f . Kết luận: Dừng thuật toán tạix1 = (1, 1)."
24
+ ],
25
+ "response":"Khi giải hệ phương trình ∇xψ(x, α) = 0, điểm dừng x*(α) nhận được là:\n\n* x∗1(α) = √(1 + 2α + 3α²) \/ 2\n* x∗2(α) = 1 + √(1 + 2α) \/ 2\n\nGiới hạn của các thành phần của điểm dừng này khi tham số α tiến về 0+ là:\n\n* lim α→0+ x∗1(α) = 0\n* lim α→0+ x∗2(α) = 1\n\nĐiều này cho thấy rằng khi tham số α giảm dần về 0, điểm dừng x*(α) sẽ tiến về điểm (0, 1).",
26
+ "reference":"Khi giải hệ phương trình ∇xψ(x, α) = 0, ta nhận được điểm dừng x*(α) = (x*1(α), x*2(α))T với x*1(α) = (√1 + 2α + 3α - 1)\/2 và x*2(α) = 1 + (√1 + 2α)\/2. Khi tham số α > 0 và giảm dần về 0, giới hạn của các thành phần của điểm dừng là limα→0+ x*1(α) = 0 và limα→0+ x*2(α) = 1.",
27
+ "faithfulness":0.6666666667,
28
+ "answer_relevancy":null,
29
+ "context_precision":0.9999999999,
30
+ "context_recall":1.0,
31
+ "answer_correctness":0.5591398631,
32
+ "timestamp":"20260523_212654"
33
+ },
34
+ {
35
+ "user_input":"Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất là gì khi một phương pháp được áp dụng và khi không được áp dụng?",
36
+ "retrieved_contexts":[
37
+ "20\/29\nTheo Định lý về độ lệch bù, ta có những ý nghĩa kinh tế quan\ntrọng sau đây:\n\/cgĐối với các phương pháp sản xuất:\n Nếu phương pháp thứ j được áp dụng (t∗\nj > 0):\nmP\ni=1\naijz∗\ni = cj\n Tổng giá trị các sản phẩm sản xuất theo phương phápj phải\nvừa đúng bằng chi phí cj. ○ Nếu phương pháp thứ j không được áp dụng (t∗\nj = 0):\nmP\ni=1\naijz∗\ni < cj Tổng giá trị các sản phẩm sản xuất theo\nphương pháp j thấp hơn chi phícj (sản xuất theo phương pháp\nnày sẽ bị lỗ).",
38
+ "12\/29\nĐịnh lý về độ lệch bù\nGiả sử x là phương án của (P) và y là phương án của (D). Khi đó, x, y là phương án tối ưu của cặp bài toán đối ngẫu\nkhi và chỉ khi\n(\n⟨Ax − b, y⟩ = 0 (1)\n⟨c − ATy, x⟩ = 0 (2)\nNhận xét:\ni) Nếu bài toán gốc có dạng chính tắc (Ax = b, x ≥ 0), điều kiện\n(1) luôn thỏa mãn.",
39
+ "Khi đó, điều kiện độ lệch bù chỉ còn là:\n⟨c − ATy, x⟩ = 0\nii) Điều kiện độ lệch bù cho phép tìm nghiệm tối ưu của bài toán\nnày khi biết nghiệm tối ưu của bài toán kia."
40
+ ],
41
+ "response":"Theo Định lý về độ lệch bù, ý nghĩa kinh tế đối với các phương pháp sản xuất như sau:\n\n* Khi phương pháp thứ j được áp dụng (t∗j > 0):\n + Tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj.\n* Khi phương pháp thứ j không được áp dụng (t∗j = 0):\n + Tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, nghĩa là sản xuất theo phương pháp này sẽ bị lỗ.",
42
+ "reference":"Theo Định lý về độ lệch bù, đối với các phương pháp sản xuất, nếu phương pháp thứ j được áp dụng (t∗j > 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j phải vừa đúng bằng chi phí cj. Ngược lại, nếu phương pháp thứ j không được áp dụng (t∗j = 0), tổng giá trị các sản phẩm sản xuất theo phương pháp j thấp hơn chi phí cj, điều này có nghĩa là sản xuất theo phương pháp này sẽ bị lỗ.",
43
+ "faithfulness":1.0,
44
+ "answer_relevancy":null,
45
+ "context_precision":0.9999999999,
46
+ "context_recall":1.0,
47
+ "answer_correctness":0.6709179689,
48
+ "timestamp":"20260523_212654"
49
+ }
50
+ ]
evaluation/results/ragas_summary_20260523_212654.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "evaluation_timestamp": "20260523_212654",
3
+ "total_samples": 3,
4
+ "metrics_used": [
5
+ "faithfulness",
6
+ "answer_relevancy",
7
+ "context_precision",
8
+ "context_recall",
9
+ "answer_correctness"
10
+ ],
11
+ "average_scores": {
12
+ "faithfulness": 0.8888888888888888,
13
+ "answer_relevancy": NaN,
14
+ "context_precision": 0.9999999999,
15
+ "context_recall": 1.0,
16
+ "answer_correctness": 0.6258756903411157
17
+ },
18
+ "overall_score": 0.8786911447825011
19
+ }
evaluation/results/ragas_summary_latest.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "evaluation_timestamp": "20260523_212654",
3
+ "total_samples": 3,
4
+ "metrics_used": [
5
+ "faithfulness",
6
+ "answer_relevancy",
7
+ "context_precision",
8
+ "context_recall",
9
+ "answer_correctness"
10
+ ],
11
+ "average_scores": {
12
+ "faithfulness": 0.8888888888888888,
13
+ "answer_relevancy": NaN,
14
+ "context_precision": 0.9999999999,
15
+ "context_recall": 1.0,
16
+ "answer_correctness": 0.6258756903411157
17
+ },
18
+ "overall_score": 0.8786911447825011
19
+ }
evaluation/run_eval.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Script chạy RAGAS evaluation trực tiếp (không qua notebook).
3
+ Dùng khi notebook timeout hoặc có vấn đề với kernel.
4
+ """
5
+ import os, sys
6
+
7
+ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8
+ sys.path.insert(0, PROJECT_ROOT)
9
+ os.chdir(PROJECT_ROOT)
10
+
11
+ from dotenv import load_dotenv
12
+ load_dotenv(os.path.join(PROJECT_ROOT, ".env"))
13
+
14
+ # ── Bước 1: Test RAG pipeline 1 câu ──────────────────────────
15
+ print("=" * 60)
16
+ print("BƯỚC 1: Test RAG pipeline")
17
+ print("=" * 60)
18
+ from evaluation.rag_pipeline import run_rag_pipeline
19
+
20
+ test_q = "Trong Bước (k3) của thuật toán, siêu phẳng là gì?"
21
+ print(f"Test câu hỏi: {test_q[:60]}...")
22
+ result = run_rag_pipeline(test_q, session_id="test_eval")
23
+ print(f"Answer ({len(result['answer'])} ký tự): {result['answer'][:200]}...")
24
+ print(f"Contexts: {len(result['contexts'])}")
25
+ print()
26
+
27
+ # ── Bước 2: Thu thập RAG outputs ─────────────────────────────
28
+ print("=" * 60)
29
+ print("BƯỚC 2: Thu thập RAG outputs (3 câu, delay=4s)")
30
+ print("=" * 60)
31
+ from evaluation.evaluate import collect_rag_outputs
32
+
33
+ rag_results = collect_rag_outputs(
34
+ "evaluation/optimization_dataset.json",
35
+ delay_seconds=2.0
36
+ )
37
+
38
+ # ── Bước 3: RAGAS evaluation ──────────────────────────────────
39
+ print("=" * 60)
40
+ print("BƯỚC 3: RAGAS Evaluation (Groq Llama 3.1 70B)")
41
+ print("=" * 60)
42
+ from evaluation.evaluate import run_ragas_evaluation
43
+
44
+ df, summary = run_ragas_evaluation(rag_results, output_dir="evaluation/results")
45
+
46
+ print("\n" + "=" * 60)
47
+ print("KẾT QUẢ CUỐI CÙNG")
48
+ print("=" * 60)
49
+ for metric, score in summary["average_scores"].items():
50
+ import math
51
+ if score and not math.isnan(score):
52
+ bar = "#" * int(score * 20) + "-" * (20 - int(score * 20))
53
+ grade = "GOOD" if score >= 0.7 else ("OK" if score >= 0.5 else "POOR")
54
+ print(f" {metric:<25} [{bar}] {score:.4f} ({grade})")
55
+ else:
56
+ print(f" {metric:<25} [--------------------] NaN")
57
+ print("=" * 60)
58
+ print(f" Overall Score: {summary['overall_score']:.4f}")
59
+ print("=" * 60)
60
+ print(f"\nKết quả lưu tại: evaluation/results/")