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

“…I”

Browse files
VectorDB/.lock DELETED
@@ -1 +0,0 @@
1
- tmp lock file
 
 
VectorDB/collection/chung/storage.sqlite DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c1d8a04276ee97871976635bc7d339d32dfd5d4df556206c6f64f1a8bc4e189c
3
- size 12288
 
 
 
 
VectorDB/collection/doanh_nghiep/storage.sqlite DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c1d8a04276ee97871976635bc7d339d32dfd5d4df556206c6f64f1a8bc4e189c
3
- size 12288
 
 
 
 
VectorDB/collection/ky_thuat/storage.sqlite DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c1d8a04276ee97871976635bc7d339d32dfd5d4df556206c6f64f1a8bc4e189c
3
- size 12288
 
 
 
 
VectorDB/collection/rag_input/storage.sqlite DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:ebc272d4c79930cce8098f4a96b3f45ae10c375e0015eeaa08e6973ac7151cfb
3
- size 212992
 
 
 
 
VectorDB/meta.json DELETED
@@ -1 +0,0 @@
1
- {"collections": {"ky_thuat": {"vectors": {"dense": {"size": 384, "distance": "Cosine", "hnsw_config": null, "quantization_config": null, "on_disk": null, "datatype": null, "multivector_config": null}}, "shard_number": null, "sharding_method": null, "replication_factor": null, "write_consistency_factor": null, "on_disk_payload": null, "hnsw_config": null, "wal_config": null, "optimizers_config": null, "quantization_config": null, "sparse_vectors": {"sparse": {"index": null, "modifier": null}}, "strict_mode_config": null, "metadata": null}, "doanh_nghiep": {"vectors": {"dense": {"size": 384, "distance": "Cosine", "hnsw_config": null, "quantization_config": null, "on_disk": null, "datatype": null, "multivector_config": null}}, "shard_number": null, "sharding_method": null, "replication_factor": null, "write_consistency_factor": null, "on_disk_payload": null, "hnsw_config": null, "wal_config": null, "optimizers_config": null, "quantization_config": null, "sparse_vectors": {"sparse": {"index": null, "modifier": null}}, "strict_mode_config": null, "metadata": null}, "chung": {"vectors": {"dense": {"size": 384, "distance": "Cosine", "hnsw_config": null, "quantization_config": null, "on_disk": null, "datatype": null, "multivector_config": null}}, "shard_number": null, "sharding_method": null, "replication_factor": null, "write_consistency_factor": null, "on_disk_payload": null, "hnsw_config": null, "wal_config": null, "optimizers_config": null, "quantization_config": null, "sparse_vectors": {"sparse": {"index": null, "modifier": null}}, "strict_mode_config": null, "metadata": null}, "rag_input": {"vectors": {"dense": {"size": 384, "distance": "Cosine", "hnsw_config": null, "quantization_config": null, "on_disk": null, "datatype": null, "multivector_config": null}}, "shard_number": null, "sharding_method": null, "replication_factor": null, "write_consistency_factor": null, "on_disk_payload": null, "hnsw_config": null, "wal_config": null, "optimizers_config": null, "quantization_config": null, "sparse_vectors": {"sparse": {"index": null, "modifier": null}}, "strict_mode_config": null, "metadata": null}}, "aliases": {}}
 
 
app/core/config.py CHANGED
@@ -12,7 +12,7 @@ QDRANT_PATH = os.path.join(BASE_DIR, "VectorDB")
12
  COLLECTION_NAME = "rag_input"
13
  CATEGORIES = ["ky_thuat", "doanh_nghiep", "chung"]
14
 
15
- EMBED_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
16
  LLM_MODEL_NAME = "gemini-2.5-flash"
17
  RERANK_MODEL_NAME = "cross-encoder/ms-marco-MiniLM-L-6-v2"
18
 
 
12
  COLLECTION_NAME = "rag_input"
13
  CATEGORIES = ["ky_thuat", "doanh_nghiep", "chung"]
14
 
15
+ EMBED_MODEL_NAME = "bkai-foundation-models/vietnamese-bi-encoder"
16
  LLM_MODEL_NAME = "gemini-2.5-flash"
17
  RERANK_MODEL_NAME = "cross-encoder/ms-marco-MiniLM-L-6-v2"
18
 
app/service/ingest.py CHANGED
@@ -1,53 +1,77 @@
1
  import os
2
- from llama_index.core import SimpleDirectoryReader
3
- from langchain_core.documents import Document
 
 
 
 
 
4
  from langchain_experimental.text_splitter import SemanticChunker
5
  from langchain_qdrant import QdrantVectorStore
6
  from qdrant_client import QdrantClient
7
  from qdrant_client.http import models
8
  from app.core.config import DATA_PATH, QDRANT_PATH, COLLECTION_NAME, get_embeddings, CATEGORIES
9
 
10
- def load_document():
11
- # 1. Load documents using LlamaIndex
12
- llama_docs = SimpleDirectoryReader(
13
- input_dir=DATA_PATH,
14
- recursive=True
15
- ).load_data()
 
 
16
 
17
- # 2. Convert to LangChain documents
18
  langchain_docs = []
19
- for doc in llama_docs:
20
- lc_doc = Document(page_content=doc.text, metadata=doc.metadata)
21
- langchain_docs.append(lc_doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  if not langchain_docs:
24
  return None
25
 
26
- # 3. Chunking
27
  embeddings = get_embeddings()
28
- from langchain_text_splitters import RecursiveCharacterTextSplitter
29
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
30
- chunks = text_splitter.split_documents(langchain_docs)
 
 
31
 
32
- # 4. Vector Store setup
33
  client = QdrantClient(path=QDRANT_PATH)
34
-
35
- # Initialize collections for all categories + default
36
  all_collections = CATEGORIES + [COLLECTION_NAME]
 
37
 
38
  for coll in all_collections:
39
  if not client.collection_exists(coll):
40
  client.create_collection(
41
  collection_name=coll,
42
  vectors_config={
43
- "dense": models.VectorParams(size=len(embeddings.embed_query("test")), distance=models.Distance.COSINE)
44
  },
45
  sparse_vectors_config={
46
  "sparse": models.SparseVectorParams()
47
  }
48
  )
49
 
50
- # 5. Ingest into default collection
51
  vector_store = QdrantVectorStore(
52
  client=client,
53
  collection_name=COLLECTION_NAME,
@@ -58,5 +82,4 @@ def load_document():
58
  return vector_store
59
 
60
  if __name__ == "__main__":
61
- os.makedirs(DATA_PATH, exist_ok=True)
62
  load_document()
 
1
  import os
2
+ from langchain_community.document_loaders import (
3
+ TextLoader,
4
+ PyPDFLoader,
5
+ Docx2txtLoader,
6
+ CSVLoader,
7
+ UnstructuredMarkdownLoader
8
+ )
9
  from langchain_experimental.text_splitter import SemanticChunker
10
  from langchain_qdrant import QdrantVectorStore
11
  from qdrant_client import QdrantClient
12
  from qdrant_client.http import models
13
  from app.core.config import DATA_PATH, QDRANT_PATH, COLLECTION_NAME, get_embeddings, CATEGORIES
14
 
15
+ LOADER_MAPPING = {
16
+ ".pdf": PyPDFLoader,
17
+ ".docx": Docx2txtLoader,
18
+ ".doc": Docx2txtLoader,
19
+ ".txt": TextLoader,
20
+ ".csv": CSVLoader,
21
+ ".md": UnstructuredMarkdownLoader,
22
+ }
23
 
24
+ def load_document():
25
  langchain_docs = []
26
+
27
+ if not os.path.exists(DATA_PATH):
28
+ os.makedirs(DATA_PATH)
29
+ return None
30
+
31
+ for root, dirs, files in os.walk(DATA_PATH):
32
+ for file in files:
33
+ ext = os.path.splitext(file)[1].lower()
34
+ if ext in LOADER_MAPPING:
35
+ file_path = os.path.join(root, file)
36
+ try:
37
+ loader_cls = LOADER_MAPPING[ext]
38
+ loader = loader_cls(file_path)
39
+ langchain_docs.extend(loader.load())
40
+ except Exception as e:
41
+ print(f"[Error")
42
+ else:
43
+ if not file.startswith('.'):
44
+ print(f"Ignore format : {file}")
45
 
46
  if not langchain_docs:
47
  return None
48
 
49
+ # 2. Chunking
50
  embeddings = get_embeddings()
51
+ semantic_chunker = SemanticChunker(
52
+ embeddings,
53
+ breakpoint_threshold_amount=0.8
54
+ )
55
+ chunks = semantic_chunker.split_documents(langchain_docs)
56
 
57
+ # 3. Vector Store setup
58
  client = QdrantClient(path=QDRANT_PATH)
 
 
59
  all_collections = CATEGORIES + [COLLECTION_NAME]
60
+ embed_dim = len(embeddings.embed_query("test"))
61
 
62
  for coll in all_collections:
63
  if not client.collection_exists(coll):
64
  client.create_collection(
65
  collection_name=coll,
66
  vectors_config={
67
+ "dense": models.VectorParams(size=embed_dim, distance=models.Distance.COSINE)
68
  },
69
  sparse_vectors_config={
70
  "sparse": models.SparseVectorParams()
71
  }
72
  )
73
 
74
+ # 4. Ingest into default collection
75
  vector_store = QdrantVectorStore(
76
  client=client,
77
  collection_name=COLLECTION_NAME,
 
82
  return vector_store
83
 
84
  if __name__ == "__main__":
 
85
  load_document()
requirements.txt CHANGED
@@ -9,6 +9,8 @@ llama-index
9
  qdrant-client
10
  sentence-transformers
11
  python-dotenv
 
 
12
  pydantic
13
  fastapi
14
  uvicorn
 
9
  qdrant-client
10
  sentence-transformers
11
  python-dotenv
12
+ unstructured[all-docs]
13
+ numpy<2.0.0
14
  pydantic
15
  fastapi
16
  uvicorn
run.sh CHANGED
@@ -17,9 +17,10 @@ fi
17
  echo -e "Chọn một tùy chọn:"
18
  echo -e "${GREEN}1)${NC} Ingest Data (Nạp dữ liệu từ thư mục /data vào VectorDB)"
19
  echo -e "${GREEN}2)${NC} Run Web API (Bắt đầu Web Chatbot trên http://localhost:8000)"
20
- echo -e "${GREEN}3)${NC} Thoát"
 
21
 
22
- read -p "Nhập lựa chọn của bạn [1-3]: " choice
23
 
24
  case $choice in
25
  1)
@@ -39,6 +40,10 @@ case $choice in
39
  PYTHONPATH=. python3 api.py
40
  ;;
41
  3)
 
 
 
 
42
  echo -e "Tạm biệt!"
43
  exit 0
44
  ;;
 
17
  echo -e "Chọn một tùy chọn:"
18
  echo -e "${GREEN}1)${NC} Ingest Data (Nạp dữ liệu từ thư mục /data vào VectorDB)"
19
  echo -e "${GREEN}2)${NC} Run Web API (Bắt đầu Web Chatbot trên http://localhost:8000)"
20
+ echo -e "${GREEN}3)${NC} Clear VectorDB (Xóa dữ liệu cũ để đổi mô hình)"
21
+ echo -e "${GREEN}4)${NC} Thoát"
22
 
23
+ read -p "Nhập lựa chọn của bạn [1-4]: " choice
24
 
25
  case $choice in
26
  1)
 
40
  PYTHONPATH=. python3 api.py
41
  ;;
42
  3)
43
+ echo -e "${RED}[!] Cảnh báo: Tất cả dữ liệu vector sẽ bị xóa.${NC}"
44
+ PYTHONPATH=. python3 scripts/clear_db.py
45
+ ;;
46
+ 4)
47
  echo -e "Tạm biệt!"
48
  exit 0
49
  ;;
scripts/clear_db.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import shutil
2
+ import os
3
+ from app.core.config import QDRANT_PATH, COLLECTION_NAME, CATEGORIES
4
+ from qdrant_client import QdrantClient
5
+
6
+ def clear_vector_db():
7
+ print(f"Checking Qdrant database at: {QDRANT_PATH}")
8
+
9
+ # Method 1: Delete via Client (Cleaner if Qdrant is running or using local path)
10
+ try:
11
+ client = QdrantClient(path=QDRANT_PATH)
12
+ all_collections = CATEGORIES + [COLLECTION_NAME]
13
+
14
+ for coll in all_collections:
15
+ if client.collection_exists(coll):
16
+ print(f"Deleting collection: {coll}")
17
+ client.delete_collection(coll)
18
+ else:
19
+ print(f"Collection {coll} does not exist.")
20
+ except Exception as e:
21
+ print(f"Error deleting collections via client: {e}")
22
+
23
+ # Method 2: Force delete the storage directory if client method is not enough
24
+ if os.path.exists(QDRANT_PATH):
25
+ print(f"Removing storage directory: {QDRANT_PATH}")
26
+ try:
27
+ shutil.rmtree(QDRANT_PATH)
28
+ print("Successfully removed VectorDB directory.")
29
+ except Exception as e:
30
+ print(f"Error removing directory: {e}")
31
+ else:
32
+ print("VectorDB directory already cleared.")
33
+
34
+ if __name__ == "__main__":
35
+ confirm = input("This will delete all existing vector data. Are you sure? (y/n): ")
36
+ if confirm.lower() == 'y':
37
+ clear_vector_db()
38
+ else:
39
+ print("Operation cancelled.")
static/index.html CHANGED
@@ -15,7 +15,7 @@
15
  <img src="https://api.dicebear.com/7.x/bottts/svg?seed=ragbot&backgroundColor=1e1e2f" alt="Bot Avatar" class="avatar">
16
  </div>
17
  <div class="header-text">
18
- <h1>Enterprise AI Assistant</h1>
19
  <span class="status"><span class="dot"></span> Trực tuyến</span>
20
  </div>
21
  </div>
@@ -30,7 +30,7 @@
30
  <div class="message bot">
31
  <img src="https://api.dicebear.com/7.x/bottts/svg?seed=ragbot&backgroundColor=1e1e2f" alt="Bot" class="msg-avatar">
32
  <div class="msg-content">
33
- <p>Xin chào! Tôi là AI Assistant được tích hợp hệ thống RAG (Retrieval-Augmented Generation). Tôi có thể giúp gì cho bạn hôm nay?</p>
34
  </div>
35
  </div>
36
  </main>
 
15
  <img src="https://api.dicebear.com/7.x/bottts/svg?seed=ragbot&backgroundColor=1e1e2f" alt="Bot Avatar" class="avatar">
16
  </div>
17
  <div class="header-text">
18
+ <h1>AI Assistant</h1>
19
  <span class="status"><span class="dot"></span> Trực tuyến</span>
20
  </div>
21
  </div>
 
30
  <div class="message bot">
31
  <img src="https://api.dicebear.com/7.x/bottts/svg?seed=ragbot&backgroundColor=1e1e2f" alt="Bot" class="msg-avatar">
32
  <div class="msg-content">
33
+ <p>Xin chào! Tôi là AI Assistant được tích hợp hệ thống RAG (Retrieval-Augmented Generation). Tôi có thể giúp gì cho bạn?</p>
34
  </div>
35
  </div>
36
  </main>
static/style.css CHANGED
@@ -1,13 +1,13 @@
1
  :root {
2
- --bg-color: #0f172a;
3
- --container-bg: #1e293b;
4
- --text-main: #f8fafc;
5
  --text-muted: #94a3b8;
6
- --accent: #3b82f6;
7
  --accent-hover: #60a5fa;
8
- --bot-msg-bg: #334155;
9
- --user-msg-bg: #2563eb;
10
- --border-color: #334155;
11
  }
12
 
13
  * {
@@ -44,7 +44,7 @@ body {
44
 
45
  .chat-header {
46
  padding: 20px;
47
- background: rgba(30, 41, 59, 0.8);
48
  backdrop-filter: blur(10px);
49
  border-bottom: 1px solid var(--border-color);
50
  display: flex;
@@ -67,7 +67,7 @@ body {
67
  content: '';
68
  position: absolute;
69
  top: -2px; left: -2px; right: -2px; bottom: -2px;
70
- background: linear-gradient(45deg, #3b82f6, #8b5cf6);
71
  border-radius: 50%;
72
  z-index: -1;
73
  animation: pulse 2s infinite;
@@ -185,7 +185,7 @@ body {
185
  .message.user .msg-content {
186
  background: var(--user-msg-bg);
187
  border-top-right-radius: 4px;
188
- box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
189
  }
190
 
191
  .typing-indicator {
@@ -216,14 +216,14 @@ body {
216
 
217
  .chat-input-area {
218
  padding: 20px;
219
- background: rgba(30, 41, 59, 0.95);
220
  border-top: 1px solid var(--border-color);
221
  }
222
 
223
  #chatForm {
224
  display: flex;
225
  gap: 15px;
226
- background: #0f172a;
227
  padding: 8px 8px 8px 20px;
228
  border-radius: 30px;
229
  border: 1px solid var(--border-color);
@@ -232,7 +232,7 @@ body {
232
 
233
  #chatForm:focus-within {
234
  border-color: var(--accent);
235
- box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
236
  }
237
 
238
  #userInput {
@@ -249,7 +249,7 @@ body {
249
  }
250
 
251
  #sendBtn {
252
- background: linear-gradient(45deg, var(--accent), #8b5cf6);
253
  border: none;
254
  width: 44px;
255
  height: 44px;
@@ -264,7 +264,7 @@ body {
264
 
265
  #sendBtn:hover {
266
  transform: scale(1.05);
267
- box-shadow: 0 0 15px rgba(139, 92, 246, 0.5);
268
  }
269
 
270
  #sendBtn:active {
 
1
  :root {
2
+ --bg-color: #abdef2;
3
+ --container-bg: #ffffff;
4
+ --text-main: #373e45;
5
  --text-muted: #94a3b8;
6
+ --accent: #f9f9f9;
7
  --accent-hover: #60a5fa;
8
+ --bot-msg-bg: #ffffff;
9
+ --user-msg-bg: #ffffff;
10
+ --border-color: #010101;
11
  }
12
 
13
  * {
 
44
 
45
  .chat-header {
46
  padding: 20px;
47
+ background: rgba(255, 255, 255, 0.8);
48
  backdrop-filter: blur(10px);
49
  border-bottom: 1px solid var(--border-color);
50
  display: flex;
 
67
  content: '';
68
  position: absolute;
69
  top: -2px; left: -2px; right: -2px; bottom: -2px;
70
+ background: linear-gradient(45deg, #3b82f6, #5ce9f6);
71
  border-radius: 50%;
72
  z-index: -1;
73
  animation: pulse 2s infinite;
 
185
  .message.user .msg-content {
186
  background: var(--user-msg-bg);
187
  border-top-right-radius: 4px;
188
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
189
  }
190
 
191
  .typing-indicator {
 
216
 
217
  .chat-input-area {
218
  padding: 20px;
219
+ background: rgba(255, 255, 255, 0.95);
220
  border-top: 1px solid var(--border-color);
221
  }
222
 
223
  #chatForm {
224
  display: flex;
225
  gap: 15px;
226
+ background: #ffffff;
227
  padding: 8px 8px 8px 20px;
228
  border-radius: 30px;
229
  border: 1px solid var(--border-color);
 
232
 
233
  #chatForm:focus-within {
234
  border-color: var(--accent);
235
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);
236
  }
237
 
238
  #userInput {
 
249
  }
250
 
251
  #sendBtn {
252
+ background: linear-gradient(45deg, var(--accent), #5baed2);
253
  border: none;
254
  width: 44px;
255
  height: 44px;
 
264
 
265
  #sendBtn:hover {
266
  transform: scale(1.05);
267
+ box-shadow: 0 0 15px rgba(109, 73, 192, 0.5);
268
  }
269
 
270
  #sendBtn:active {