Huyen My commited on
Commit
1539090
·
0 Parent(s):
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.jpg filter=lfs diff=lfs merge=lfs -text
3
+ *.svg filter=lfs diff=lfs merge=lfs -text
ChatBot/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from chatbot import ChatBot
ChatBot/__pycache__/app.cpython-311.pyc ADDED
Binary file (1.82 kB). View file
 
ChatBot/__pycache__/chatbot.cpython-311.pyc ADDED
Binary file (5.59 kB). View file
 
ChatBot/__pycache__/query_transformation.cpython-311.pyc ADDED
Binary file (4.68 kB). View file
 
ChatBot/__pycache__/retrieval.cpython-311.pyc ADDED
Binary file (17.6 kB). View file
 
ChatBot/__pycache__/route.cpython-311.pyc ADDED
Binary file (4.53 kB). View file
 
ChatBot/__pycache__/server.cpython-311.pyc ADDED
Binary file (1.16 kB). View file
 
ChatBot/app.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
3
+ from chatbot import ChatBot
4
+ import warnings
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ # Tắt các cảnh báo
7
+ warnings.filterwarnings("ignore")
8
+
9
+ # Khởi tạo ứng dụng FastAPI
10
+ app = FastAPI()
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["http://localhost:5173"], # Cho phép kết nối từ frontend
14
+ allow_credentials=True,
15
+ allow_methods=["*"],
16
+ allow_headers=["*"],
17
+ )
18
+
19
+
20
+ # Khởi tạo chatbot
21
+ chatbot = ChatBot()
22
+
23
+ # Định nghĩa schema cho dữ liệu đầu vào
24
+ class QueryRequest(BaseModel):
25
+ query: str
26
+
27
+ # Endpoint để khởi tạo lại chatbot
28
+ @app.post("/newchat/")
29
+ async def new_chat():
30
+ global chatbot
31
+ chatbot = ChatBot() # Khởi tạo lại chatbot mới
32
+ return {"message": "ChatBot đã được khởi tạo lại."}
33
+
34
+ # Endpoint để xử lý câu hỏi
35
+ @app.post("/process_query/")
36
+ async def process_query(request: QueryRequest):
37
+ global chatbot
38
+
39
+ raw_query = request.query # Lấy query từ người dùng
40
+ # Xử lý truy vấn bằng chatbot
41
+ response = chatbot.process_query(raw_query)
42
+ # Trả về câu trả lời từ chatbot
43
+ return {"response": response}
ChatBot/chatbot.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.chains import LLMChain
2
+ from langchain.prompts import PromptTemplate
3
+ from langchain.memory import ConversationBufferMemory
4
+ from langchain_google_genai import ChatGoogleGenerativeAI
5
+ from langchain_community.embeddings import HuggingFaceEmbeddings
6
+ from langchain_qdrant import FastEmbedSparse
7
+ from query_transformation import QueryTransform
8
+ from route import Router
9
+ from retrieval import Retriever
10
+ import os
11
+ from dotenv import load_dotenv
12
+
13
+ class ChatBot:
14
+ def __init__(self, embedding_model='bkai-foundation-models/vietnamese-bi-encoder', sparse_embedding="Qdrant/BM25"):
15
+ self.embedding_model = HuggingFaceEmbeddings(model_name=embedding_model)
16
+ self.sparse_embedding = FastEmbedSparse(model_name=sparse_embedding)
17
+
18
+ load_dotenv()
19
+ genai_api_key = os.getenv('GOOGLE_API_KEY')
20
+
21
+ print ("Khởi tạo Query Transform")
22
+ self.query_transform = QueryTransform()
23
+
24
+ print ("Khởi tạo Router")
25
+ self.router = Router()
26
+
27
+ print ("Khởi tạo Retriever")
28
+ self.retriever = Retriever(self.embedding_model, self.sparse_embedding)
29
+
30
+ print ("Khởi tạo LLM")
31
+ self.llm = ChatGoogleGenerativeAI(api_key=genai_api_key, model="gemini-1.5-flash")
32
+
33
+ # Sử dụng ConversationBufferMemory để lưu trữ lịch sử hội thoại
34
+ self.memory = ConversationBufferMemory(
35
+ memory_key="history",
36
+ input_key="query",
37
+ returnMessages=False, # Trả về chuỗi thay vì đối tượng
38
+ aiPrefix="AI:",
39
+ humanPrefix="User:",
40
+ k=2)
41
+
42
+ self.prompt_template = """
43
+ Bạn là một chatbot chuyên về Luật Hôn Nhân và Gia Đình Việt Nam với tên "ChatBot hỏi đáp về Luật Hôn Nhân và Gia Đình Việt Nam". Hãy trả lời từng câu hỏi đã được làm rõ (converted_query) dựa trên loại câu hỏi (question_type) và ngữ cảnh (context). Tuân thủ các quy tắc sau:
44
+
45
+ - Với `question_type = 1|4|6|8|9`:
46
+ 1. Dựa vào ngữ cảnh và câu hỏi đã được làm rõ (converted_query) để trả lời câu hỏi gốc (query). KHÔNG ĐƯỢC sử dụng các cụm từ tương tự như "Dựa vào thông tin được cung cấp", "Dựa vào ngữ cảnh".
47
+ 2. **Chỉ sử dụng thông tin trong ngữ cảnh.** Trả lời một cách **diễn giải chi tiết**, giải thích rõ lý do, cơ sở pháp lý (trong ngữ cảnh)
48
+
49
+ - Với `question_type = 2` (smalltalk): Trả lời thân thiện, phù hợp với ngữ cảnh.
50
+
51
+ - Với các loại khác: *Trả về ngữ cảnh y như nó được cung cấp, không thêm bớt bất kỳ thông tin nào khác.*
52
+
53
+ Mỗi câu hỏi đã được làm rõ (converted_query[i]) tương ứng với `context[i]` và `question_type[i]`. Trả lời lần lượt từng câu hỏi theo các quy tắc trên.
54
+
55
+ Dữ liệu cung cấp:
56
+ - Loại câu hỏi: {question_type}
57
+ - Ngữ cảnh: {context}
58
+ - Câu hỏi gốc: {query}
59
+ - Câu hỏi đã được làm rõ: {converted_query}
60
+
61
+ """
62
+
63
+ # Prompt template cho LLMChain
64
+ self.prompt = PromptTemplate(
65
+ input_variables=["question_type", "context", "query", "converted_query"],
66
+ template=self.prompt_template,
67
+ )
68
+
69
+ # LLMChain kết hợp LLM và Prompt
70
+ self.chain = LLMChain(llm=self.llm, prompt=self.prompt, memory=self.memory)
71
+
72
+ def process_query(self, raw_query):
73
+ print ('QUERY GỐC: ', raw_query, '\n---------------------------------------------------------')
74
+ # Bước 1: Biến đổi câu truy vấn dựa trên lịch sử
75
+ converted_queries = self.query_transform.transform(raw_query, self.memory.load_memory_variables({})["history"])
76
+ print ('QUERY ĐÃ TRANSFORM: ', converted_queries, '\n---------------------------------------------------------')
77
+ query_types = []
78
+ # Bước 2: Phân loại loại câu hỏi
79
+ for q in converted_queries:
80
+ query_types.append(self.router.route_query(q))
81
+ print ('LOẠI CỦA QUERY: ', query_types, '\n---------------------------------------------------------')
82
+ # Bước 3: Truy vấn thông tin ngữ cảnh
83
+ context = self.retriever.retrieve(converted_queries, query_types)
84
+ print ('CONTEXT: ', context, '\n---------------------------------------------------------')
85
+ # Bước 4: Tạo câu trả lời bằng LangChain
86
+ response = self.chain.run(question_type=query_types, context=context, query=raw_query, converted_query=converted_queries)
87
+
88
+ return response
ChatBot/data/chunking_save.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from sentence_transformers import SentenceTransformer
3
+ from qdrant_client import models
4
+ from langchain_qdrant import QdrantVectorStore, FastEmbedSparse, RetrievalMode
5
+ from langchain_huggingface import HuggingFaceEmbeddings
6
+ from langchain.schema import Document
7
+ from typing import List
8
+ import numpy as np
9
+ from sklearn.metrics.pairwise import cosine_similarity
10
+ import re
11
+
12
+ # Khởi tạo SentenceTransformer
13
+ model_name = 'bkai-foundation-models/vietnamese-bi-encoder'
14
+ model = SentenceTransformer(model_name)
15
+
16
+
17
+ # Load dữ liệu từ file data.json
18
+ with open('data.json', 'r', encoding='utf-8') as f:
19
+ data = json.load(f)
20
+
21
+ # Hàm chunking ngữ nghĩa và tạo Document
22
+ # class SemanticChunker:
23
+ # def __init__(self, model, breakpoint_percentile_threshold=80):
24
+ # self.model = model
25
+ # self.breakpoint_percentile_threshold = breakpoint_percentile_threshold
26
+
27
+ # def split_text(self, text: str) -> List[str]:
28
+ # modified_text = re.sub(r'(\d+)\.', r'\1)', text)
29
+ # # Tách câu
30
+ # sentences = re.split(r'(?<=[.!?])\s+', modified_text)
31
+
32
+ # # Tính embedding cho các câu
33
+ # embeddings = self.model.encode(sentences, convert_to_tensor=True).cpu().numpy()
34
+
35
+ # # Tính khoảng cách cosine giữa các câu liên tiếp
36
+ # distances = [
37
+ # 1 - cosine_similarity([embeddings[i]], [embeddings[i + 1]])[0][0]
38
+ # for i in range(len(embeddings) - 1)
39
+ # ]
40
+
41
+ # distances.append(0) # Thêm khoảng cách cho câu cuối (không có câu kế tiếp)
42
+
43
+ # # Xác định ngưỡng chia đoạn
44
+ # threshold = np.percentile(distances, self.breakpoint_percentile_threshold)
45
+ # breakpoints = [i for i, dist in enumerate(distances) if dist > threshold]
46
+
47
+ # # Tạo các đoạn văn bản
48
+ # chunks = []
49
+ # start = 0
50
+ # for bp in breakpoints:
51
+ # chunks.append(' '.join(sentences[start:bp + 1]))
52
+ # start = bp + 1
53
+
54
+ # if start < len(sentences):
55
+ # chunks.append(' '.join(sentences[start:]))
56
+
57
+ # return chunks
58
+ class SemanticChunker:
59
+ def __init__(self, model, buffer_size=1, breakpoint_percentile_threshold=80):
60
+ self.model = model
61
+ self.buffer_size = buffer_size
62
+ self.breakpoint_percentile_threshold = breakpoint_percentile_threshold
63
+
64
+ def split_text(self, text: str) -> List[str]:
65
+ modified_text = re.sub(r'(\d+)\.', r'\1)', text)
66
+ # Tách câu
67
+ # sentences = sent_tokenize(modified_text)
68
+ sentences = re.split(r'(?<=[.!?])\s+', modified_text)
69
+
70
+ combined_sentences = [
71
+ ' '.join(sentences[max(i - self.buffer_size, 0): i + self.buffer_size + 1])
72
+ for i in range(len(sentences))
73
+ ]
74
+
75
+ # Tính embedding cho các câu
76
+ embeddings = self.model.encode(combined_sentences, convert_to_tensor=True).cpu().numpy()
77
+
78
+ # Tính khoảng cách cosine
79
+ distances = [
80
+ 1 - cosine_similarity([embeddings[i]], [embeddings[i + 1]])[0][0]
81
+ for i in range(len(embeddings) - 1)
82
+ ]
83
+
84
+ distances.append(0) # Thêm khoảng cách cho câu cuối
85
+
86
+ # Xác định ngưỡng chia đoạn
87
+ threshold = np.percentile(distances, self.breakpoint_percentile_threshold)
88
+ breakpoints = [i for i, dist in enumerate(distances) if dist > threshold]
89
+
90
+ # Tạo các đoạn văn bản
91
+ chunks = []
92
+ start = 0
93
+ for bp in breakpoints:
94
+ chunks.append(' '.join(sentences[start:bp + 1]))
95
+ start = bp + 1
96
+
97
+ if start < len(sentences):
98
+ chunks.append(' '.join(sentences[start:]))
99
+
100
+ return chunks
101
+
102
+ # Khởi tạo SemanticChunker
103
+ chunker = SemanticChunker(model)
104
+
105
+ # Hàm xử lý dữ liệu và tạo Document
106
+ def chunk_and_embed(data):
107
+ documents = []
108
+
109
+ for chapter in data['chapters']:
110
+ print (f"Loading {chapter['chapter']} ...")
111
+ if "sections" in chapter:
112
+ for section in chapter['sections']:
113
+ for article in section['articles']:
114
+ chunks = chunker.split_text(article['content'])
115
+ for chunk in chunks:
116
+ documents.append(Document(
117
+ page_content=chunk,
118
+ metadata={
119
+ 'chapter': chapter['chapter'] + " " + chapter['title'],
120
+ 'section': section['section'] + " " + section['title'],
121
+ 'article': article['article'] + " " + article['title']
122
+ }
123
+ ))
124
+
125
+ else:
126
+ for article in chapter['articles']:
127
+ chunks = chunker.split_text(article['content'])
128
+ # modified_text = re.sub(r'(\d+)\.', r'\1)', article['content'])
129
+ # chunks = sent_tokenize(modified_text)
130
+
131
+ for chunk in chunks:
132
+ documents.append(Document(
133
+ page_content=chunk,
134
+ metadata={
135
+ 'chapter': chapter['chapter'] + " " + chapter['title'],
136
+ 'section': "",
137
+ 'article': article['article'] + " " + article['title']
138
+ }
139
+ ))
140
+ return documents
141
+
142
+ # Tạo các Document từ dữ liệu
143
+ documents = chunk_and_embed(data)
144
+
145
+ print (f'Number of chunks: {len(documents)}')
146
+
147
+ # Tạo embedding cho các Document bằng SentenceTransformer
148
+ embedding_model = HuggingFaceEmbeddings(model_name=model_name)
149
+ sparse_embeddings = FastEmbedSparse(model_name="Qdrant/BM25")
150
+
151
+ # Lưu dữ liệu vào Qdrant
152
+ collection_name = "LawData"
153
+ vectorstore = QdrantVectorStore.from_documents(
154
+ documents,
155
+ embedding_model,
156
+ sparse_embedding=sparse_embeddings,
157
+ retrieval_mode=RetrievalMode.HYBRID,
158
+ url='https://c1e67a53-62f6-4464-b5ed-086b3c298e23.europe-west3-0.gcp.cloud.qdrant.io',
159
+ api_key='-s29x9W2DpfpvmsR-1bA_CbpKrtp__xVJ2YKUmPgcf6n7MQ95o6fBQ',
160
+ collection_name=collection_name,
161
+ distance=models.Distance.COSINE
162
+ )
163
+
164
+ print("Dữ liệu đã được lưu vào Qdrant Cloud!")
ChatBot/data/data.docx ADDED
Binary file (85.2 kB). View file
 
ChatBot/data/data.json ADDED
The diff for this file is too large to render. See raw diff
 
ChatBot/data/data_preprocessing.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ from docx import Document
4
+
5
+ # Hàm trích xuất tham chiếu từ một đoạn văn bản
6
+ def extract_references(text):
7
+ pattern = r"(khoản \d+(?:, khoản \d+)* Điều \d+|Điều \d+)"
8
+ matches = re.findall(pattern, text)
9
+ refer = []
10
+ for match in matches:
11
+ if match.startswith("khoản"):
12
+ parts = match.split(" Điều ")
13
+ khoan_part = parts[0]
14
+ dieu_part = "Điều " + parts[1]
15
+ khoan_list = khoan_part.replace("khoản", "").strip().split(", ")
16
+ for khoan in khoan_list:
17
+ refer.append(f"khoản {khoan.strip()} {dieu_part}")
18
+ else:
19
+ refer.append(match)
20
+ return refer
21
+
22
+ # Hàm phân tích dữ liệu và tạo cấu trúc JSON
23
+ def parse_data_to_json(data):
24
+ chapters = []
25
+ current_chapter = None
26
+ current_section = None
27
+ current_article = None
28
+ lines = data.split("\n")
29
+ i = 0
30
+
31
+ while i < len(lines):
32
+ line = lines[i].strip()
33
+ # Xử lí chương
34
+ if line.startswith("CHƯƠNG"):
35
+ if current_chapter:
36
+ if current_section: # Thêm mục hiện tại vào chương trước khi chuyển sang chương mới
37
+ if current_article:
38
+ current_section["articles"].append(current_article)
39
+ current_article = None
40
+ current_chapter["sections"].append(current_section)
41
+ current_section = None
42
+ chapters.append(current_chapter)
43
+ current_chapter = {
44
+ "chapter": f"{line} {lines[i+1].strip()}", # Kết hợp số chương và tiêu đề
45
+ "sections": []
46
+ }
47
+ i += 2
48
+
49
+ # Xử lí Mục
50
+ elif line.startswith("Mục"):
51
+ if current_section:
52
+ if current_article:
53
+ current_section["articles"].append(current_article)
54
+ current_article = None
55
+ current_chapter["sections"].append(current_section)
56
+ current_section = {
57
+ "section": f"{line} {lines[i+1].strip()}", # Kết hợp số mục và tiêu đề
58
+ "articles": []
59
+ }
60
+ i += 2
61
+
62
+ # Xử lí Điều
63
+ elif line.startswith("Điều"):
64
+ if current_section is None:
65
+ current_section = {
66
+ "section": "", # Kết hợp số mục và tiêu đề
67
+ "articles": []
68
+ }
69
+ elif current_article:
70
+ current_section["articles"].append(current_article)
71
+
72
+ current_article = {
73
+ "article": line.split('.')[0].strip(),
74
+ "title": line.split('.')[1].strip(),
75
+ "clauses": []
76
+ }
77
+ i += 1
78
+ # Kiểm tra xem điều này có câu ngắn trước các khoản không (sử dụng re.match)
79
+ if i < len(lines) and lines[i].strip() and re.match(r"^\D", lines[i].strip()):
80
+ # Lưu câu ngắn để ghép vào khoản đầu tiên
81
+ text = lines[i].strip()
82
+ i += 1
83
+ else:
84
+ text = ""
85
+ flag = False # Kiểm tra có khoản không
86
+
87
+ # Xử lý các khoản
88
+ while i < len(lines) and lines[i].strip() and lines[i].strip()[0].isdigit():
89
+ flag = True
90
+ # Bắt đầu một khoản mới
91
+ clause_number = f"Khoản {lines[i].strip().split('.')[0]}"
92
+ clause_content = lines[i].strip()
93
+ i += 1
94
+ # Xử lý các điểm trong khoản
95
+ while i < len(lines) and lines[i].strip() and not re.match(r"^(CHƯƠNG\s+[IVXLCDM]+|Mục\s+\d+|Điều\s+\d+)", lines[i].strip()) and not lines[i].strip()[0].isdigit():
96
+ clause_content += " " + lines[i].strip()
97
+ i += 1
98
+ # Ghép câu ngắn vào khoản đầu tiên
99
+ if text:
100
+ clause_content = f"{text} {clause_content}"
101
+ text = "" # Đã sử dụng câu ngắn, không cần ghép vào khoản tiếp theo
102
+ refer = extract_references(clause_content)
103
+ current_article["clauses"].append({
104
+ "clause": clause_number, # Số khoản
105
+ "content": clause_content,
106
+ "refer": refer
107
+ })
108
+ if flag == False:
109
+ while i < len(lines) and lines[i].strip() and not re.match(r"^(CHƯƠNG\s+[IVXLCDM]+|Mục\s+\d+|Điều\s+\d+)", lines[i].strip()):
110
+ text += " " + lines[i].strip()
111
+ i += 1
112
+ refer = extract_references(text)
113
+ current_article["clauses"].append({
114
+ "clause": "",
115
+ "content": text,
116
+ "refer": refer
117
+ })
118
+ else:
119
+ i += 1
120
+
121
+ # Thêm các phần còn lại vào cấu trúc
122
+ if current_article:
123
+ current_section["articles"].append(current_article)
124
+ if current_section:
125
+ current_chapter["sections"].append(current_section)
126
+ if current_chapter:
127
+ chapters.append(current_chapter)
128
+
129
+ return {"chapters": chapters}
130
+
131
+ # Đọc file .docx
132
+ def read_docx(file_path):
133
+ doc = Document(file_path)
134
+ full_text = []
135
+ for para in doc.paragraphs:
136
+ full_text.append(para.text)
137
+ return "\n".join(full_text)
138
+
139
+ # Đường dẫn đến file .docx
140
+ file_path = "data.docx"
141
+
142
+ # Đọc dữ liệu từ file .docx
143
+ data = read_docx(file_path)
144
+
145
+ # Phân tích dữ liệu và tạo JSON
146
+ json_data = parse_data_to_json(data)
147
+
148
+ # Lưu dữ liệu vào file JSON
149
+ with open("new_data.json", "w", encoding="utf-8") as json_file:
150
+ json.dump(json_data, json_file, ensure_ascii=False, indent=4)
151
+
152
+ print("Dữ liệu đã được lưu vào file new_data.json")
ChatBot/data/new_data.json ADDED
The diff for this file is too large to render. See raw diff
 
ChatBot/data/preprocessing.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ from docx import Document
4
+
5
+ # Hàm trích xuất tham chiếu từ một đoạn văn bản
6
+ def extract_references(text):
7
+ # Biểu thức chính quy để bắt các chuỗi "khoản X, khoản Y, ... điều Z" hoặc "điều Z"
8
+ pattern = r"(khoản \d+(?:, khoản \d+)* điều \d+|điều \d+)"
9
+ matches = re.findall(pattern, text, re.IGNORECASE)
10
+ refer = []
11
+ for match in matches:
12
+ refer.append(match)
13
+ return refer
14
+
15
+ # Hàm phân tích dữ liệu và tạo cấu trúc JSON
16
+ def parse_data_to_json(data):
17
+ chapters = []
18
+ current_chapter = None
19
+ current_section = None
20
+ current_article = None
21
+ lines = data.split("\n")
22
+ i = 0
23
+
24
+ while i < len(lines):
25
+ line = lines[i].strip()
26
+ # Xử lí chương
27
+ if line.startswith("CHƯƠNG"):
28
+ if current_chapter:
29
+ if current_section: # Thêm mục hiện tại vào chương trước khi chuyển sang chương mới
30
+ current_chapter["sections"].append(current_section)
31
+ current_section = None
32
+ chapters.append(current_chapter)
33
+ current_chapter = {
34
+ "chapter": f"{line} {lines[i+1].strip()}", # Kết hợp số chương và tiêu đề
35
+ "sections": []
36
+ }
37
+ i += 2
38
+
39
+ # Xử lí Mục
40
+ elif line.startswith("Mục"):
41
+ if current_section:
42
+ if current_article:
43
+ current_section["articles"].append(current_article)
44
+ current_article = None
45
+ current_chapter["sections"].append(current_section)
46
+ current_section = {
47
+ "section": f"{line} {lines[i+1].strip()}", # Kết hợp số mục và tiêu đề
48
+ "articles": []
49
+ }
50
+ i += 2
51
+
52
+ # Xử lí Điều
53
+ elif line.startswith("Điều"):
54
+ if current_section is None:
55
+ current_section = {
56
+ "section": "", # Kết hợp số mục và tiêu đề
57
+ "articles": []
58
+ }
59
+ if current_article:
60
+ current_section["articles"].append(current_article)
61
+ current_article = {
62
+ "article": line,
63
+ "title": line.split('.')[1].strip(),
64
+ "clauses": []
65
+ }
66
+ # text = line.split('.')[1].strip()
67
+ i += 1
68
+ # Kiểm tra xem điều này có câu ngắn trước các khoản không (sử dụng re.match)
69
+ if i < len(lines) and lines[i].strip() and re.match(r"^\D", lines[i].strip()):
70
+ # Lưu câu ngắn để ghép vào khoản đầu tiên
71
+ text = lines[i].strip()
72
+ i += 1
73
+ else:
74
+ text = ""
75
+ flag = False
76
+
77
+ # Xử lý các khoản
78
+ while i < len(lines) and lines[i].strip() and lines[i].strip()[0].isdigit():
79
+ flag = True
80
+ # Bắt đầu một khoản mới
81
+ clause_number = f"Khoản {lines[i].strip().split('.')[0]}"
82
+ clause_content = lines[i].strip()
83
+ i += 1
84
+ # Xử lý các điểm trong khoản
85
+ while i < len(lines) and lines[i].strip() and re.match(r"^[a-zđ]\)", lines[i].strip()):
86
+ clause_content += " " + lines[i].strip()
87
+ i += 1
88
+ # Ghép câu ngắn vào khoản đầu tiên
89
+ if text:
90
+ clause_content = f"{text} {clause_content}"
91
+ text = "" # Đã sử dụng câu ngắn, không cần ghép vào khoản tiếp theo
92
+ refer = extract_references(clause_content)
93
+ current_article["clauses"].append({
94
+ "clause": clause_number, # Số khoản
95
+ "content": clause_content,
96
+ "refer": refer
97
+ })
98
+ if flag == False:
99
+ refer = extract_references(text)
100
+ current_article["clauses"].append({
101
+ "clause": "",
102
+ "content": text,
103
+ "refer": refer
104
+ })
105
+ else:
106
+ i += 1
107
+
108
+ # Thêm các phần còn lại vào cấu trúc
109
+ if current_article:
110
+ current_section["articles"].append(current_article)
111
+ if current_section:
112
+ current_chapter["sections"].append(current_section)
113
+ if current_chapter:
114
+ chapters.append(current_chapter)
115
+
116
+ return {"chapters": chapters}
117
+
118
+ # Đọc file .docx
119
+ def read_docx(file_path):
120
+ doc = Document(file_path)
121
+ full_text = []
122
+ for para in doc.paragraphs:
123
+ full_text.append(para.text)
124
+ return "\n".join(full_text)
125
+
126
+ # Đường dẫn đến file .docx
127
+ file_path = "data.docx"
128
+
129
+ # Đọc dữ liệu từ file .docx
130
+ data = read_docx(file_path)
131
+
132
+ # Phân tích d��� liệu và tạo JSON
133
+ json_data = parse_data_to_json(data)
134
+
135
+ # Lưu dữ liệu vào file JSON
136
+ with open("legal_data2.json", "w", encoding="utf-8") as json_file:
137
+ json.dump(json_data, json_file, ensure_ascii=False, indent=4)
138
+
139
+ print("Dữ liệu đã được lưu vào file legal_data2.json")
ChatBot/indexing.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from qdrant_client import models
3
+ from langchain_qdrant import QdrantVectorStore, FastEmbedSparse, RetrievalMode
4
+ from langchain_huggingface import HuggingFaceEmbeddings
5
+ from langchain.schema import Document
6
+ import os
7
+ from dotenv import load_dotenv
8
+
9
+ def save_to_vector_database(data):
10
+ documents = []
11
+
12
+ for chapter in data['chapters']:
13
+ print (f"Loading {chapter['chapter']} ...")
14
+ for section in chapter['sections']:
15
+ for article in section['articles']:
16
+ for clause in article['clauses']:
17
+ chunk = clause['content']
18
+ if clause['clause'] == '' or clause['clause'] == 'Khoản 1':
19
+ chunk = article['title'] + ". " + clause['content']
20
+ documents.append(Document(
21
+ page_content=chunk,
22
+ metadata={
23
+ 'chapter': chapter['chapter'],
24
+ 'section': section['section'],
25
+ 'article': article['article'],
26
+ 'art_title': article['title'],
27
+ 'clause': clause['clause'],
28
+ 'refer': clause['refer']
29
+ }
30
+ ))
31
+
32
+ return documents
33
+
34
+ with open('./data/new_data.json', 'r', encoding='utf-8') as f:
35
+ data = json.load(f)
36
+
37
+ # Tạo các Document từ dữ liệu
38
+ documents = save_to_vector_database(data)
39
+
40
+ # Tạo embedding cho các Document bằng SentenceTransformer
41
+ model_name = "dangvantuan/vietnamese-embedding"
42
+ embedding_model = HuggingFaceEmbeddings(model_name=model_name)
43
+ sparse_embeddings = FastEmbedSparse(model_name="Qdrant/BM25")
44
+
45
+ load_dotenv()
46
+ # Lưu dữ liệu vào Qdrant
47
+ vectorstore = QdrantVectorStore.from_documents(
48
+ documents,
49
+ embedding_model,
50
+ sparse_embedding=sparse_embeddings,
51
+ retrieval_mode=RetrievalMode.HYBRID,
52
+ url=os.getenv("QDRANT_URL"),
53
+ api_key=os.getenv("QDRANT_API_KEY"),
54
+ collection_name="LAWDATA",
55
+ distance=models.Distance.COSINE
56
+ )
57
+
58
+ print("Dữ liệu đã được lưu vào Qdrant Cloud!")
ChatBot/query_transformation.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_google_genai import ChatGoogleGenerativeAI
2
+ from langchain.prompts import ChatPromptTemplate
3
+ from langchain_core.output_parsers import StrOutputParser
4
+ import os
5
+ from dotenv import load_dotenv
6
+ class QueryTransform:
7
+ def __init__(self, model="gemini-1.5-flash", temperature=0.3):
8
+ load_dotenv()
9
+ genai_api_key = os.getenv('GOOGLE_API_KEY')
10
+
11
+ # Prompt
12
+ self.transform_prompt = ChatPromptTemplate.from_template("""
13
+ Bạn là một mô hình phân tích câu hỏi. Nhiệm vụ của bạn là xử lý câu hỏi (hoặc câu khẳng định) của người dùng theo từng bước sau:
14
+ 1. Nếu câu hỏi là ngôn ngữ khác tiếng Việt, trả về Kết quả: Xin lỗi, tôi không hiểu bạn đang nói gì, vui lòng sử dụng tiếng Việt.
15
+ 2. Sửa lỗi chính tả, gõ sai, viết tắt cho câu hỏi trong tiếng Việt. Ví dụ: Tôi li hôn đc k? -> Tôi ly hôn được không?, thũ tụt -> thủ tục
16
+ 3. NẾU CÂU HỎI CÓ NHIỀU Ý HỎI, tách chúng thành từng câu hỏi riêng biệt. Với mỗi câu hỏi thực hiện các bước tiếp theo.
17
+ 4. NẾU CÂU HỎI chứa SỐ ĐIỀU/SỐ CHƯƠNG cụ thể và trong lịch sử trò chuyện có chứa:
18
+ + "Vui lòng chọn Điều cụ thể.", thì ý định của câu hỏi là: Khoản <số Khoản trong lịch sử> Điều <SỐ/SỐ ĐIỀU trong câu hỏi>. Ngược lại ý định là: Điều <SỐ/SỐ ĐIỀU trong câu hỏi>
19
+ + "Vui lòng chọn Chương cụ thể", thì ý định của câu hỏi là: Mục <số Mục trong lịch sử> Chương <SỐ/SỐ CHƯƠNG trong câu hỏi>. Ngược lại ý định là: Chương <SỐ/SỐ CHƯƠNG trong câu hỏi>
20
+ 5. **NẾU CÂU HỎI KHÔNG RÕ RÀNG HOẶC CHƯA ĐẦY ĐỦ**, hãy sử dụng lịch sử trò chuyện được cung cấp **CHỈ KHI NÓ LIÊN QUAN TRỰC TIẾP ĐẾN CÂU HỎI** để làm rõ ý nghĩa và ngữ cảnh truy vấn của người dùng. **BỎ QUA THÔNG TIN KHÔNG LIÊN QUAN**.
21
+ 6. Suy ra nội dung chính của câu hỏi (hoặc câu khẳng định) thật đơn giản, rõ ràng và dễ hiểu (dùng các từ ngữ trong luật Hôn nhân và Gia đình nếu có thể), **BỎ QUA ĐẠI TỪ DANH XƯNG NẾU CÓ THỂ**. Ví dụ: Tôi là nữ 16 tuổi thì có lấy chồng được không? -> Nữ 16 tuổi có đủ điều kiện kết hôn không?, Người bị điên có lấy vợ được không? -> Người mất hành vi dân sự có được phép kết hôn không?
22
+
23
+ Chỉ cung cấp kết quả **theo định dạng sau** và không thêm bất kỳ văn bản, giải thích hoặc bình luận nào khác:
24
+ Kết quả: <nội dung chính của câu hỏi 1>|<nội dung chính của câu hỏi 2>...
25
+
26
+ Lịch sử: {history}
27
+ Câu hỏi gốc: {query}
28
+ """)
29
+
30
+ self.model = ChatGoogleGenerativeAI(api_key=genai_api_key, model=model, temperature=temperature)
31
+ self.parser = StrOutputParser()
32
+
33
+ def transform(self, raw_query, history):
34
+ prompt = self.transform_prompt.format_prompt(query=raw_query, history=history)
35
+ response = self.parser.parse(self.model.invoke(prompt).content)
36
+ lines = response.split("\n")
37
+ result = lines[0].replace("Kết quả:", "").strip()
38
+ result = result.split("|")
39
+ return result
ChatBot/requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ python-docx
2
+ transformers
3
+ sentence-transformers
4
+ qdrant-client
5
+ langchain
6
+ langchain-community
7
+ langchain-qdrant
8
+ fastembed
9
+ langchain-google-genai
10
+ semantic-router
11
+ neo4j
12
+ roman
ChatBot/retrieval.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from langchain_qdrant import RetrievalMode
4
+ from langchain_qdrant import QdrantVectorStore
5
+ from qdrant_client import models
6
+ from langchain_qdrant import QdrantVectorStore, RetrievalMode
7
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
8
+ import torch
9
+ from neo4j import GraphDatabase
10
+ import re
11
+ import roman
12
+
13
+ class Retriever:
14
+ def __init__(self, embedding_model, sparse_embedding, rerank_model="itdainb/PhoRanker"):
15
+ # Tải các biến môi trường từ file .env
16
+ load_dotenv()
17
+ # Qdrant
18
+ collection_name = os.getenv('QDRANT_COLLECTION_NAME')
19
+ url = os.getenv('QDRANT_URL')
20
+ api_key = os.getenv('QDRANT_API_KEY')
21
+
22
+ uri = os.getenv('NEO4J_URI')
23
+ auth = (os.getenv('NEO4J_USERNAME'), os.getenv('NEO4J_PASS'))
24
+
25
+ # Khởi tạo kho lưu trữ vector
26
+ self.vector_store = QdrantVectorStore.from_existing_collection(
27
+ embedding=embedding_model,
28
+ collection_name=collection_name,
29
+ url=url,
30
+ api_key=api_key,
31
+ sparse_embedding=sparse_embedding,
32
+ retrieval_mode=RetrievalMode.DENSE,
33
+ )
34
+
35
+ self.retriever = self.vector_store.as_retriever()
36
+ self.tokenizer = AutoTokenizer.from_pretrained(rerank_model)
37
+ self.rerank_model = AutoModelForSequenceClassification.from_pretrained(rerank_model)
38
+ self.driver = GraphDatabase.driver(uri, auth=auth)
39
+
40
+ self.methods = {
41
+ 0: self.unknown, # Ngôn ngữ khác
42
+ 1: self.marriage_and_family, # Tổng quát
43
+ 2: self.smalltalk, # Smalltalk
44
+ 3: self.unrelated, # Không liên quan
45
+ 4: self.khoan_with_dieu, # Khoản có Điều
46
+ 5: self.khoan_no_dieu, # Khoản không có
47
+ 6: self.muc_with_chuong, # Mục có Chương
48
+ 7: self.muc_no_chuong, # Mục không có Chương
49
+ 8: self.dieu_only, # Chỉ có Điều
50
+ 9: self.chuong_only, # Chỉ có Chương
51
+ }
52
+
53
+ def rerank(self, query, documents):
54
+ # Tokenize query và documents
55
+ inputs = self.tokenizer([query] * len(documents), documents, return_tensors="pt", padding=True, truncation=True)
56
+
57
+ # Tính điểm số
58
+ with torch.no_grad():
59
+ outputs = self.rerank_model(**inputs)
60
+ scores = outputs.logits.squeeze().tolist()
61
+
62
+ indexed_documents = list(zip(range(len(documents)), scores))
63
+ indexed_documents.sort(key=lambda x: x[1], reverse=True)
64
+ ranked_indices = [i for i, _ in indexed_documents]
65
+
66
+ return ranked_indices
67
+
68
+ def unknown(self, query):
69
+ return query
70
+
71
+ def marriage_and_family(self, query, top_k=10, top_n=5, rerank=False):
72
+
73
+ results = self.retriever.get_relevant_documents(query, k=top_k)
74
+
75
+ if rerank:
76
+ docs = [doc.page_content for doc in results]
77
+ docs = self.rerank(query, docs)
78
+ results = [results[i] for i in docs]
79
+
80
+ res = {}
81
+ refers = set()
82
+ for doc in results[:top_n]:
83
+ article = doc.metadata['article']
84
+ if article == "Điều 3":
85
+ if article not in res:
86
+ res[article] = {'title': doc.metadata['art_title'],
87
+ 'content': set([doc.page_content])}
88
+ else: res[article]['content'].add(doc.page_content)
89
+
90
+ elif article not in res:
91
+ res[article] = {'title': doc.metadata['art_title']}
92
+
93
+ filter_condition = models.Filter(must=[models.FieldCondition(
94
+ key="metadata.article", # Trường metadata cần lọc
95
+ match=models.MatchValue(value=article),
96
+ )])
97
+ temp = self.retriever.get_relevant_documents(query="", filter=filter_condition)
98
+ res[article]['content'] = set([d.page_content for d in temp])
99
+ for art in temp:
100
+ if art.metadata['refer']: refers.update(art.metadata['refer'])
101
+
102
+ for ref in refers:
103
+ match_ = re.match(r"(?:Khoản\s*(\d+)\s*)?điều\s*(\d+)", ref, re.IGNORECASE)
104
+ refer_clause = match_.group(1)
105
+ refer_article = f"Điều {match_.group(2)}"
106
+ if refer_article not in res:
107
+ res[refer_article] = {'title': None, 'content': set()}
108
+ filter_condition2 = models.Filter(must=[models.FieldCondition(
109
+ key="metadata.article",
110
+ match=models.MatchValue(
111
+ value=refer_article))] )
112
+ if refer_clause:
113
+ filter_condition2.must.append(models.FieldCondition(
114
+ key="metadata.clause",
115
+ match=models.MatchValue(value=f"Khoản {refer_clause}")))
116
+ temp2 = self.retriever.get_relevant_documents(query="", filter=filter_condition2)
117
+ res[refer_article]['content'].update([d.page_content for d in temp2])
118
+ res[refer_article]['title'] = temp2[0].metadata['art_title']
119
+
120
+ context = ""
121
+ for key, value in res.items():
122
+ context += f"{key}: {value['title']}\n"
123
+ for item in value["content"]:
124
+ context += f"{item}\n"
125
+ context += "\n"
126
+ return context
127
+
128
+ def smalltalk(self, query):
129
+ return query
130
+
131
+ def unrelated(self, query):
132
+ return "Xin lỗi, câu hỏi này không phải lĩnh vực của tôi."
133
+
134
+ def muc_no_chuong(self, query):
135
+ return "Vui lòng chọn Chương cụ thể."
136
+
137
+ def muc_with_chuong(self, query):
138
+ # Tìm mục và chương từ câu truy vấn
139
+ match = re.search(r"mục\s+(\d+).*chương\s+(\w+)", query, re.IGNORECASE)
140
+
141
+ section = int(match.group(1))
142
+ chapter = match.group(2).upper()
143
+
144
+ # Xử lý chương
145
+ if chapter.isdigit():
146
+ chapter_number = int(chapter)
147
+ chapter_roman = roman.toRoman(chapter_number)
148
+ else:
149
+ try:
150
+ chapter_number = roman.fromRoman(chapter)
151
+ chapter_roman = chapter
152
+ except roman.InvalidRomanNumeralError:
153
+ return f"'{chapter}' không phải là số hoặc số La Mã hợp lệ."
154
+
155
+ if chapter_number < 1 or chapter_number > 10:
156
+ return f"Luật Hôn nhân và Gia Đình Việt Nam không có Chương {chapter}."
157
+
158
+ # Truy vấn cơ sở dữ liệu
159
+ with self.driver.session() as session:
160
+ result = session.run(
161
+ """
162
+ MATCH (ch:Chapter)-[:HAS_SECTION]->(sec:Section)-[:HAS_ARTICLE]->(art:Article)
163
+ WHERE ch.name =~ $chapter_pattern AND sec.name =~ $section_pattern
164
+ RETURN ch.name AS chapter_name, sec.name AS section_name, art.name AS article_name
165
+ """,
166
+ chapter_pattern=f"^CHƯƠNG {chapter_roman}(\\s|$).*",
167
+ section_pattern=f"^Mục {section}(\\s|$).*"
168
+ )
169
+
170
+ # Xử lý kết quả
171
+ records = result.data()
172
+ if not records:
173
+ return f"Chương {chapter} không có mục {section}."
174
+
175
+ # Xây dựng nội dung trả về
176
+ context = f"{records[0]['chapter_name']} - {records[0]['section_name']}:"
177
+ for record in records:
178
+ context += f"\n - {record['article_name']}"
179
+
180
+ return context
181
+
182
+ def khoan_no_dieu(self, query):
183
+ return "Vui lòng chọn Điều cụ thể."
184
+
185
+ def khoan_with_dieu(self, query): # Khoản có Điều
186
+ match = re.search(r"khoản\s+(\d+)\s+điều\s+(\d+)", query, re.IGNORECASE)
187
+ clause, article = match.group(1), match.group(2)
188
+ if int(article) < 1 or int(article) > 133:
189
+ return f'Luật Hôn nhân và Gia Đình Việt Nam không có Điều {article}'
190
+
191
+ with self.driver.session() as session:
192
+ result = session.run(
193
+ """
194
+ MATCH (a:Article)-[:HAS_CLAUSE]->(c:Clause)
195
+ WHERE a.name =~ $article_number AND c.name = $clause_number
196
+ RETURN a.name AS article_name, c.content AS clause_content
197
+ """,
198
+ article_number=f"^Điều {article}(\\s|$).*",
199
+ clause_number=f"Khoản {clause}",
200
+ )
201
+ record = result.single()
202
+ if record:
203
+ context = f"{record['article_name']}:\n{record['clause_content']}"
204
+ return context
205
+ return f"Điều {article} không có Khoản {clause}."
206
+
207
+
208
+ def dieu_only(self, query):
209
+ # Tìm số điều từ query
210
+ match = re.search(r"điều\s+(\d+)", query, re.IGNORECASE)
211
+
212
+ article = match.group(1)
213
+ with self.driver.session() as session:
214
+ result = session.run(
215
+ """
216
+ MATCH (a:Article)-[:HAS_CLAUSE]->(c:Clause)
217
+ WHERE a.name =~ $article_number
218
+ RETURN a.name AS article_name, c.content AS clause_content
219
+ """,
220
+ article_number=f"^Điều {article}(\\s|$).*",
221
+ )
222
+
223
+ # Xử lý kết quả truy vấn
224
+ records = result.data()
225
+ if not records:
226
+ return f'Luật Hôn nhân và Gia Đình Việt Nam không có Điều {article}'
227
+
228
+ # Tạo nội dung trả về
229
+ context = f"{records[0]['article_name']}:"
230
+ for record in records:
231
+ context += f"\n{record['clause_content']}"
232
+ return context
233
+
234
+
235
+ def chuong_only(self, query):
236
+ # Tìm số chương từ câu truy vấn
237
+ match = re.search(r"chương\s+(\w+)", query, re.IGNORECASE)
238
+ chapter = match.group(1).upper()
239
+ if chapter.isdigit():
240
+ chapter_number = int(chapter)
241
+ chapter_roman = roman.toRoman(chapter_number)
242
+ else:
243
+ try:
244
+ chapter_number = roman.fromRoman(chapter)
245
+ chapter_roman = chapter
246
+ except roman.InvalidRomanNumeralError:
247
+ return f"'{chapter}' không phải là số hoặc số La Mã hợp lệ."
248
+
249
+ with self.driver.session() as session:
250
+ result = session.run(
251
+ """
252
+ MATCH (ch:Chapter)
253
+ WHERE ch.name =~ $chapter_pattern
254
+ OPTIONAL MATCH (ch)-[:HAS_SECTION]->(sec:Section)-[:HAS_ARTICLE]->(art:Article)
255
+ OPTIONAL MATCH (ch)-[:HAS_ARTICLE]->(art_direct:Article)
256
+ RETURN ch.name AS chapter_name,
257
+ sec.name AS section_name,
258
+ art.name AS article_name,
259
+ art_direct.name AS direct_article_name
260
+ """,
261
+ chapter_pattern=f"^CHƯƠNG {chapter_roman}(\\s|$).*"
262
+ )
263
+
264
+ # Xử lý kết quả
265
+ records = result.data()
266
+ if not records:
267
+ return f"Không tìm thấy dữ liệu cho Chương {chapter}."
268
+
269
+ # Xây dựng nội dung trả về
270
+ context = f"{records[0]['chapter_name']}:"
271
+ sections = {}
272
+ direct_articles = []
273
+
274
+ for record in records:
275
+ if record['section_name']:
276
+ section_name = record['section_name']
277
+ article_name = record['article_name']
278
+ if section_name not in sections:
279
+ sections[section_name] = []
280
+ if article_name:
281
+ sections[section_name].append(article_name)
282
+ if record['direct_article_name']:
283
+ direct_articles.append(record['direct_article_name'])
284
+
285
+ # Ghi các section và article của chúng
286
+ for section, articles in sections.items():
287
+ context += f"\n{section}:"
288
+ for article in articles:
289
+ context += f"\n - {article}"
290
+
291
+ # Ghi các article trực tiếp của chương
292
+ if direct_articles:
293
+ for article in direct_articles:
294
+ context += f"\n - {article}"
295
+
296
+ return context
297
+
298
+ def retrieve(self, queries, types):
299
+ context = ""
300
+ for q, t in zip(queries, types):
301
+ context += self.methods[t](q) + "\n----------------"
302
+
303
+ return context
ChatBot/route.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from semantic_router import Route
2
+ from semantic_router.encoders import HuggingFaceEncoder
3
+ from semantic_router.layer import RouteLayer
4
+ import re
5
+
6
+
7
+ class Router:
8
+ def __init__(self, model_name="dangvantuan/vietnamese-embedding"):
9
+ self.encoder = HuggingFaceEncoder(model_name=model_name)
10
+ self.routes = self.initialize_routes()
11
+ self.rl = RouteLayer(encoder=self.encoder, routes=self.routes)
12
+
13
+ def initialize_routes(self):
14
+ # Route cho câu hỏi liên quan đến hôn nhân và gia đình
15
+ marriage_and_family = Route(
16
+ name="1",
17
+ score_thresold=0.64,
18
+ utterances=[
19
+ "quy định về kết hôn như thế nào?",
20
+ "ly hôn có cần sự đồng ý của cả hai không?",
21
+ "quyền nuôi con sau khi ly hôn thuộc về ai?",
22
+ "phân chia tài sản khi ly hôn thế nào?",
23
+ "điều kiện để nhận con nuôi là gì?",
24
+ "kết hôn có cần đăng ký không?",
25
+ "ngoại tình có vi phạm pháp luật không?",
26
+ "người không nuôi con có quyền thăm con sau ly hôn không?",
27
+ "kết hôn với người nước ngoài cần những giấy tờ gì?",
28
+ "tảo hôn là gì?",
29
+ "ông bà có quyền nuôi cháu sau khi cha mẹ ly hôn không?",
30
+ ],
31
+ )
32
+
33
+ # Route cho smalltalk
34
+ smalltalk = Route(
35
+ name="2",
36
+ score_thresold=0.34,
37
+ utterances=[
38
+ "chào bạn!",
39
+ "hôm nay trời đẹp nhỉ?",
40
+ "bạn có thể giúp tôi không?",
41
+ "bạn tên là gì?",
42
+ "bạn làm việc ở đâu?",
43
+ "bạn khỏe không?",
44
+ "rất vui được gặp bạn!",
45
+ "bạn thông minh lắm đấy!",
46
+ "bạn có sở thích gì không?",
47
+ "cảm ơn bạn.",
48
+ "tạm biệt.",
49
+ "Xin chào",
50
+ "Bạn thú vị đó!",
51
+ "Có gì vui không, kể cho tôi nghe với?",
52
+ "Giúp tôi tí được không?",
53
+ "Có kế hoạch gì hay không bạn?",
54
+ "Thời tiết hôm nay thế nào?",
55
+ ],
56
+ )
57
+
58
+ # Route cho câu hỏi không liên quan
59
+ unrelated = Route(
60
+ name="3",
61
+ score_thresold=0.9,
62
+ utterances=[
63
+ "Những món ăn nào phổ biến nhất ở Ấn Độ?",
64
+ "Tại sao con người cần có mục tiêu?",
65
+ "Tự do cá nhân có quan trọng không?",
66
+ "Cách chăm sóc cây cảnh trong nhà?",
67
+ "AI có thể thay đổi cuộc sống của con người ra sao?",
68
+ "Blockchain là gì và nó hoạt động như thế nào?",
69
+ "Ai là cầu thủ ghi nhiều bàn thắng nhất mọi thời đại?",
70
+ "Tại sao Kim tự tháp được coi là kỳ quan?",
71
+ "Cuộc cách mạng công nghiệp diễn ra khi nào?",
72
+ "Ý nghĩa cuộc sống?",
73
+ "Nước là gì?",
74
+ "Con người có phải loài thông minh nhất?"
75
+ ],
76
+ )
77
+ return [marriage_and_family, smalltalk, unrelated]
78
+
79
+ def route_query(self, query):
80
+ """
81
+ Xác định loại câu hỏi dựa trên nội dung query.
82
+ """
83
+ query_ = query.strip().lower() # Chuẩn hóa query
84
+
85
+ if query_ == "không hiểu":
86
+ return 0
87
+
88
+ patterns = {
89
+ 4: r"\bkhoản\s+\d+\b.*\bđiều\s+\d+\b", # Có khoản và điều cụ thể
90
+ 5: r"\bkhoản\s+\d+\b(?!.*\bđiều\b)", # Chỉ có khoản cụ thể, không có điều
91
+ 6: r"\bmục\s+\d+\b.*\bchương\s+([IVXLCDMivxlcdm]+|\d+)\b", # Có mục và chương cụ thể (số La Mã hoặc số thường)
92
+ 7: r"\bmục\s+\d+\b(?!.*\bchương\b)", # Chỉ có mục cụ thể, không có chương
93
+ 8: r"\bđiều\s+\d+\b", # Chỉ có điều cụ thể
94
+ 9: r"\bchương\s+([IVXLCDMivxlcdm]+|\d+)\b" # Chỉ có chương cụ thể (số La Mã hoặc số thường)
95
+ }
96
+
97
+ # Kiểm tra từng mẫu regex
98
+ for query_type, pattern in patterns.items():
99
+ if re.search(pattern, query_, re.IGNORECASE):
100
+ return query_type
101
+
102
+ query_type = self.rl(query).name
103
+ return int(query_type) if query_type else 3
ChatBot/try.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
FrontEnd/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
FrontEnd/README.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # React + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
FrontEnd/eslint.config.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import react from 'eslint-plugin-react'
4
+ import reactHooks from 'eslint-plugin-react-hooks'
5
+ import reactRefresh from 'eslint-plugin-react-refresh'
6
+
7
+ export default [
8
+ { ignores: ['dist'] },
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ languageOptions: {
12
+ ecmaVersion: 2020,
13
+ globals: globals.browser,
14
+ parserOptions: {
15
+ ecmaVersion: 'latest',
16
+ ecmaFeatures: { jsx: true },
17
+ sourceType: 'module',
18
+ },
19
+ },
20
+ settings: { react: { version: '18.3' } },
21
+ plugins: {
22
+ react,
23
+ 'react-hooks': reactHooks,
24
+ 'react-refresh': reactRefresh,
25
+ },
26
+ rules: {
27
+ ...js.configs.recommended.rules,
28
+ ...react.configs.recommended.rules,
29
+ ...react.configs['jsx-runtime'].rules,
30
+ ...reactHooks.configs.recommended.rules,
31
+ 'react/jsx-no-target-blank': 'off',
32
+ 'react-refresh/only-export-components': [
33
+ 'warn',
34
+ { allowConstantExport: true },
35
+ ],
36
+ },
37
+ },
38
+ ]
FrontEnd/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + React</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
FrontEnd/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
FrontEnd/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^18.3.1",
14
+ "react-dom": "^18.3.1"
15
+ },
16
+ "devDependencies": {
17
+ "@eslint/js": "^9.17.0",
18
+ "@types/react": "^18.3.18",
19
+ "@types/react-dom": "^18.3.5",
20
+ "@vitejs/plugin-react": "^4.3.4",
21
+ "eslint": "^9.17.0",
22
+ "eslint-plugin-react": "^7.37.2",
23
+ "eslint-plugin-react-hooks": "^5.0.0",
24
+ "eslint-plugin-react-refresh": "^0.4.16",
25
+ "globals": "^15.14.0",
26
+ "vite": "^6.0.5"
27
+ }
28
+ }
FrontEnd/public/vite.svg ADDED

Git LFS Details

  • SHA256: 4a748afd443918bb16591c834c401dae33e87861ab5dbad0811c3a3b4a9214fb
  • Pointer size: 129 Bytes
  • Size of remote file: 1.5 kB
FrontEnd/src/ChatBot.css ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Reset CSS */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ /* Container chính của chatbot */
9
+ .chatbot-container {
10
+ width: 66.66%; /* Chiếm 2/3 màn hình */
11
+ max-width: 800px; /* Giới hạn chiều rộng tối đa */
12
+ height: 80vh; /* Chiếm 80% chiều cao màn hình */
13
+ border: 1px solid #ccc;
14
+ border-radius: 10px;
15
+ display: flex;
16
+ flex-direction: column;
17
+ overflow: hidden;
18
+ font-family: Arial, sans-serif;
19
+ background-color: #f9f9f9;
20
+ margin: 0 auto; /* Căn giữa màn hình */
21
+ position: absolute;
22
+ top: 50%;
23
+ left: 50%;
24
+ transform: translate(-50%, -50%);
25
+ }
26
+
27
+ /* Header của chatbot */
28
+ .chat-header {
29
+ display: flex;
30
+ justify-content: space-between;
31
+ align-items: center;
32
+ padding: 10px;
33
+ background-color: #007bff;
34
+ color: white;
35
+ }
36
+
37
+ .chat-header h2 {
38
+ font-size: 18px;
39
+ }
40
+
41
+ .new-chat-btn {
42
+ background-color: #0056b3;
43
+ color: white;
44
+ border: none;
45
+ padding: 5px 10px;
46
+ border-radius: 5px;
47
+ cursor: pointer;
48
+ }
49
+
50
+ .new-chat-btn:hover {
51
+ background-color: #004080;
52
+ }
53
+
54
+ /* Cửa sổ hiển thị tin nhắn */
55
+ .chat-window {
56
+ flex: 1;
57
+ padding: 10px;
58
+ overflow-y: auto;
59
+ background-color: #fff;
60
+ display: flex;
61
+ flex-direction: column; /* Đảm bảo các tin nhắn hiển thị theo chiều dọc */
62
+ }
63
+
64
+ /* Tin nhắn của người dùng và chatbot */
65
+ .message {
66
+ margin-bottom: 10px;
67
+ padding: 8px 12px;
68
+ border-radius: 10px;
69
+ max-width: 80%;
70
+ word-wrap: break-word; /* Đảm bảo chữ không tràn ra ngoài */
71
+ display: inline-block; /* Để ô tin nhắn co giãn theo nội dung */
72
+ }
73
+
74
+ .message.user {
75
+ background-color: #007bff;
76
+ color: white;
77
+ margin-left: auto;
78
+ }
79
+
80
+ .message.bot {
81
+ background-color: #e1e1e1;
82
+ color: #333;
83
+ margin-right: auto;
84
+ }
85
+
86
+ /* Thanh nhập tin nhắn */
87
+ .input-container {
88
+ display: flex;
89
+ padding: 10px;
90
+ background-color: #f1f1f1;
91
+ }
92
+
93
+ .input-container input {
94
+ flex: 1;
95
+ padding: 8px;
96
+ border: 1px solid #ccc;
97
+ border-radius: 5px;
98
+ margin-right: 10px;
99
+ }
100
+
101
+ .input-container button {
102
+ padding: 8px 12px;
103
+ background-color: #007bff;
104
+ color: white;
105
+ border: none;
106
+ border-radius: 5px;
107
+ cursor: pointer;
108
+ }
109
+
110
+ .input-container button:hover {
111
+ background-color: #0056b3;
112
+ }
FrontEnd/src/ChatBot.jsx ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react'; // Thêm useRef và useEffect
2
+ import './ChatBot.css';
3
+
4
+ const ChatBot = () => {
5
+ const [messages, setMessages] = useState([]);
6
+ const [userInput, setUserInput] = useState("");
7
+ const chatWindowRef = useRef(null); // Tham chiếu đến cửa sổ chat
8
+
9
+ // Tự động cuộn xuống tin nhắn mới nhất
10
+ useEffect(() => {
11
+ if (chatWindowRef.current) {
12
+ chatWindowRef.current.scrollTop = chatWindowRef.current.scrollHeight;
13
+ }
14
+ }, [messages]); // Kích hoạt mỗi khi messages thay đổi
15
+
16
+ const sendQueryToBackend = async (query) => {
17
+ try {
18
+ const response = await fetch('http://127.0.0.1:8000/process_query', {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ },
23
+ body: JSON.stringify({ query }),
24
+ });
25
+
26
+ if (!response.ok) {
27
+ throw new Error('Lỗi khi gửi yêu cầu');
28
+ }
29
+
30
+ const data = await response.json();
31
+ return data.response;
32
+ } catch (error) {
33
+ console.error('Lỗi:', error);
34
+ return "Đã xảy ra lỗi khi kết nối với ChatBot.";
35
+ }
36
+ };
37
+
38
+ const handleSend = async () => {
39
+ if (userInput.trim() === "") return;
40
+
41
+ // Thêm tin nhắn của người dùng vào danh sách tin nhắn
42
+ setMessages((prevMessages) => [
43
+ ...prevMessages,
44
+ { sender: "user", text: userInput },
45
+ ]);
46
+
47
+ // Xóa input của người dùng (sau khi đã nhận được phản hồi)
48
+ setUserInput("");
49
+
50
+ // Gửi yêu cầu đến backend và nhận phản hồi
51
+ const botResponse = await sendQueryToBackend(userInput);
52
+
53
+ // Thêm phản hồi từ chatbot vào danh sách tin nhắn
54
+ setMessages((prevMessages) => [
55
+ ...prevMessages,
56
+ { sender: "bot", text: botResponse },
57
+ ]);
58
+
59
+ };
60
+
61
+ const handleNewChat = () => {
62
+ setMessages([]); // Xóa tất cả tin nhắn
63
+ };
64
+
65
+ return (
66
+ <div className="chatbot-container">
67
+ <div className="chat-header">
68
+ <h2>ChatBot</h2>
69
+ <button className="new-chat-btn" onClick={handleNewChat}>
70
+ New Chat
71
+ </button>
72
+ </div>
73
+
74
+ {/* Thêm ref vào cửa sổ chat */}
75
+ <div className="chat-window" ref={chatWindowRef}>
76
+ {messages.map((message, index) => (
77
+ <div key={index} className={`message ${message.sender}`}>
78
+ <p>{message.text}</p>
79
+ </div>
80
+ ))}
81
+ </div>
82
+
83
+ <div className="input-container">
84
+ <input
85
+ type="text"
86
+ value={userInput}
87
+ onChange={(e) => setUserInput(e.target.value)}
88
+ placeholder="Gõ câu hỏi của bạn..."
89
+ onKeyPress={(e) => e.key === "Enter" && handleSend()}
90
+ />
91
+ <button onClick={handleSend}>Gửi</button>
92
+ </div>
93
+ </div>
94
+ );
95
+ };
96
+
97
+ export default ChatBot;
FrontEnd/src/assets/react.svg ADDED

Git LFS Details

  • SHA256: 35ef61ed53b323ae94a16a8ec659b3d0af3880698791133f23b084085ab1c2e5
  • Pointer size: 129 Bytes
  • Size of remote file: 4.13 kB
FrontEnd/src/index.css ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ margin: 0;
3
+ font-family: Arial, sans-serif;
4
+ display: flex;
5
+ justify-content: center;
6
+ align-items: center;
7
+ height: 100vh;
8
+ background-color: #f0f0f0;
9
+ }
10
+
11
+ #root {
12
+ width: 100%;
13
+ max-width: 1200px;
14
+ margin: 0 auto;
15
+ }
FrontEnd/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import './index.css';
4
+ import ChatBot from './ChatBot.jsx';
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <ChatBot />
9
+ </StrictMode>
10
+ );
FrontEnd/vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })
package-lock.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "name": "LawChatBot",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {}
6
+ }