fix log and download file
Browse files- app.py +19 -1
- rag_core/business.py +1 -1
- rag_core/chunker.py +77 -60
- rag_core/llm.py +2 -1
app.py
CHANGED
|
@@ -3,6 +3,9 @@ from rag_core.business import answer_query, rescan_index
|
|
| 3 |
from ui import app_ui
|
| 4 |
import gradio as gr
|
| 5 |
import logging
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
logging.info("🚀 Khởi động ứng dụng FastAPI...")
|
| 8 |
|
|
@@ -20,7 +23,22 @@ async def rescan_api():
|
|
| 20 |
logging.info("♻️ API /rescan được gọi")
|
| 21 |
return rescan_index()
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
# Mount Gradio UI vào FastAPI tại root
|
| 24 |
app = gr.mount_gradio_app(app, app_ui, path="")
|
| 25 |
|
| 26 |
-
logging.info("✅ Gradio UI đã mount vào root /")
|
|
|
|
| 3 |
from ui import app_ui
|
| 4 |
import gradio as gr
|
| 5 |
import logging
|
| 6 |
+
from fastapi.responses import JSONResponse
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
|
| 10 |
logging.info("🚀 Khởi động ứng dụng FastAPI...")
|
| 11 |
|
|
|
|
| 23 |
logging.info("♻️ API /rescan được gọi")
|
| 24 |
return rescan_index()
|
| 25 |
|
| 26 |
+
@app.get("/get_structure_file")
|
| 27 |
+
def get_structure_file():
|
| 28 |
+
path = "faiss_index/chunk_structure.json"
|
| 29 |
+
if os.path.exists(path):
|
| 30 |
+
try:
|
| 31 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 32 |
+
data = json.load(f)
|
| 33 |
+
return JSONResponse(content=data)
|
| 34 |
+
except Exception as e:
|
| 35 |
+
logging.error(f"❌ Lỗi đọc file JSON: {e}")
|
| 36 |
+
return {"error": f"Lỗi đọc file: {str(e)}"}
|
| 37 |
+
else:
|
| 38 |
+
logging.warning("⚠️ File chunk_structure.json không tồn tại.")
|
| 39 |
+
return {"error": "File không tồn tại."}
|
| 40 |
+
|
| 41 |
# Mount Gradio UI vào FastAPI tại root
|
| 42 |
app = gr.mount_gradio_app(app, app_ui, path="")
|
| 43 |
|
| 44 |
+
logging.info("✅ Gradio UI đã mount vào root /")
|
rag_core/business.py
CHANGED
|
@@ -66,7 +66,7 @@ def answer_query(query: str) -> str:
|
|
| 66 |
|
| 67 |
prompt = (
|
| 68 |
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên các đoạn luật sau. "
|
| 69 |
-
"Chỉ sử dụng thông tin có trong các đoạn, không tự đoán
|
| 70 |
)
|
| 71 |
prompt += "\n\n".join(docs)
|
| 72 |
prompt += f"\n\nCâu hỏi: {query}\nTrả lời:"
|
|
|
|
| 66 |
|
| 67 |
prompt = (
|
| 68 |
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên các đoạn luật sau. "
|
| 69 |
+
"Chỉ sử dụng thông tin có trong các đoạn, không tự đoán.\n"
|
| 70 |
)
|
| 71 |
prompt += "\n\n".join(docs)
|
| 72 |
prompt += f"\n\nCâu hỏi: {query}\nTrả lời:"
|
rag_core/chunker.py
CHANGED
|
@@ -1,74 +1,91 @@
|
|
| 1 |
import re
|
| 2 |
-
import os
|
| 3 |
import json
|
|
|
|
| 4 |
import logging
|
| 5 |
-
from typing import List
|
| 6 |
-
from rag_core.utils import log_timed
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
| 28 |
"clauses": []
|
| 29 |
}
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
| 43 |
else:
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
# Flatten để tạo chunks
|
| 50 |
chunks = []
|
| 51 |
-
for
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
continue
|
| 56 |
-
for
|
| 57 |
-
|
| 58 |
-
chunks.append(clause["title"])
|
| 59 |
-
else:
|
| 60 |
-
for point in clause.get("points"):
|
| 61 |
-
chunks.append(point)
|
| 62 |
-
|
| 63 |
-
# Log cấu trúc nested ra file JSON
|
| 64 |
-
json_path_local = "faiss_index/chunk_structure.json"
|
| 65 |
-
os.makedirs(os.path.dirname(json_path_local), exist_ok=True)
|
| 66 |
-
with open(json_path_local, "w", encoding="utf-8") as f:
|
| 67 |
-
json.dump(chapters, f, indent=2, ensure_ascii=False)
|
| 68 |
-
logging.info(f"✅ Đã ghi cấu trúc nested vào {json_path_local}")
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
return chunks
|
|
|
|
| 1 |
import re
|
|
|
|
| 2 |
import json
|
| 3 |
+
import os
|
| 4 |
import logging
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
| 7 |
+
|
| 8 |
+
SECTION_RE = re.compile(r"^\s*(Điều\s+\d+[A-Z]?)\.?\s*(.*)")
|
| 9 |
+
CLAUSE_RE = re.compile(r"^\s*(\d+)\.?\s+(.*)")
|
| 10 |
+
POINT_RE = re.compile(r"^\s*([a-zA-Z])\)\s+(.*)")
|
| 11 |
+
|
| 12 |
+
PUBLIC_CHUNK_JSON_PATH = "faiss_index/chunk_structure.json"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def chunk_legal_text(text):
|
| 16 |
+
logging.info("📑 Bắt đầu chunk văn bản luật...")
|
| 17 |
+
articles = []
|
| 18 |
+
current_article = None
|
| 19 |
+
current_clause = None
|
| 20 |
|
| 21 |
+
for line in text.splitlines():
|
| 22 |
+
line = line.strip()
|
| 23 |
+
if not line:
|
| 24 |
+
continue
|
| 25 |
+
|
| 26 |
+
sec_match = SECTION_RE.match(line)
|
| 27 |
+
clause_match = CLAUSE_RE.match(line)
|
| 28 |
+
point_match = POINT_RE.match(line)
|
| 29 |
+
|
| 30 |
+
if sec_match:
|
| 31 |
+
if current_article:
|
| 32 |
+
articles.append(current_article)
|
| 33 |
+
current_article = {
|
| 34 |
+
"article": sec_match.group(1),
|
| 35 |
+
"title": sec_match.group(2),
|
| 36 |
"clauses": []
|
| 37 |
}
|
| 38 |
+
current_clause = None
|
| 39 |
+
elif clause_match and current_article:
|
| 40 |
+
current_clause = {
|
| 41 |
+
"clause": clause_match.group(1),
|
| 42 |
+
"text": clause_match.group(2),
|
| 43 |
+
"points": []
|
| 44 |
+
}
|
| 45 |
+
current_article["clauses"].append(current_clause)
|
| 46 |
+
elif point_match and current_clause:
|
| 47 |
+
current_clause["points"].append({
|
| 48 |
+
"point": point_match.group(1),
|
| 49 |
+
"text": point_match.group(2)
|
| 50 |
+
})
|
| 51 |
+
elif current_clause:
|
| 52 |
+
if current_clause["points"]:
|
| 53 |
+
current_clause["points"][-1]["text"] += " " + line
|
| 54 |
else:
|
| 55 |
+
current_clause["text"] += " " + line
|
| 56 |
+
elif current_article:
|
| 57 |
+
if current_article["clauses"]:
|
| 58 |
+
current_article["clauses"][-1]["text"] += " " + line
|
| 59 |
+
|
| 60 |
+
if current_article:
|
| 61 |
+
articles.append(current_article)
|
| 62 |
+
|
| 63 |
+
logging.info(f"🔎 Đã phân tích được {len(articles)} điều luật")
|
| 64 |
|
|
|
|
| 65 |
chunks = []
|
| 66 |
+
for article in articles:
|
| 67 |
+
article_header = f"{article['article']}. {article['title']}"
|
| 68 |
+
if not article.get("clauses"):
|
| 69 |
+
chunks.append(article_header)
|
| 70 |
+
continue
|
| 71 |
+
for clause in article.get("clauses", []):
|
| 72 |
+
clause_header = f"{article['article']}.{clause['clause']}: {clause['text']}"
|
| 73 |
+
if not clause.get("points"):
|
| 74 |
+
chunks.append(f"{article_header}\n{clause_header}")
|
| 75 |
continue
|
| 76 |
+
for point in clause.get("points", []):
|
| 77 |
+
chunks.append(f"{article_header}\n{clause_header}\n{point['point']}) {point['text']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
try:
|
| 80 |
+
os.makedirs(os.path.dirname(PUBLIC_CHUNK_JSON_PATH), exist_ok=True)
|
| 81 |
+
with open(PUBLIC_CHUNK_JSON_PATH, "w", encoding="utf-8") as f:
|
| 82 |
+
json.dump(articles, f, ensure_ascii=False, indent=2)
|
| 83 |
+
logging.info(f"✅ Đã ghi cấu trúc nested vào {PUBLIC_CHUNK_JSON_PATH}")
|
| 84 |
+
if os.path.exists(PUBLIC_CHUNK_JSON_PATH):
|
| 85 |
+
logging.info("📁 File chunk_structure.json đã được tạo thành công và có thể truy cập công khai.")
|
| 86 |
+
else:
|
| 87 |
+
logging.warning("⚠️ File chunk_structure.json không tồn tại sau khi ghi.")
|
| 88 |
+
except Exception as e:
|
| 89 |
+
logging.error(f"❌ Lỗi khi ghi file JSON: {e}")
|
| 90 |
|
| 91 |
return chunks
|
rag_core/llm.py
CHANGED
|
@@ -2,7 +2,7 @@ import requests
|
|
| 2 |
import logging
|
| 3 |
import time
|
| 4 |
|
| 5 |
-
LLM_ENDPOINT = "https://vietcat-gemma34b.hf.space/
|
| 6 |
|
| 7 |
def generate_answer(prompt: str) -> str:
|
| 8 |
max_retries = 3
|
|
@@ -10,6 +10,7 @@ def generate_answer(prompt: str) -> str:
|
|
| 10 |
|
| 11 |
for attempt in range(1, max_retries + 1):
|
| 12 |
try:
|
|
|
|
| 13 |
logging.info(f"📡 Gửi request đến LLM (lần {attempt}, timeout={timeout}s)...")
|
| 14 |
response = requests.post(
|
| 15 |
LLM_ENDPOINT,
|
|
|
|
| 2 |
import logging
|
| 3 |
import time
|
| 4 |
|
| 5 |
+
LLM_ENDPOINT = "https://vietcat-gemma34b.hf.space/purechat"
|
| 6 |
|
| 7 |
def generate_answer(prompt: str) -> str:
|
| 8 |
max_retries = 3
|
|
|
|
| 10 |
|
| 11 |
for attempt in range(1, max_retries + 1):
|
| 12 |
try:
|
| 13 |
+
logging.info(f"📡 Gửi request đến LLM tại {LLM_ENDPOINT}")
|
| 14 |
logging.info(f"📡 Gửi request đến LLM (lần {attempt}, timeout={timeout}s)...")
|
| 15 |
response = requests.post(
|
| 16 |
LLM_ENDPOINT,
|