PBThuong96 commited on
Commit
6bd6ff8
·
verified ·
1 Parent(s): c482be6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -248
app.py CHANGED
@@ -1,308 +1,135 @@
1
  import os
2
  import sys
3
  import logging
4
- import traceback
5
- import pandas as pd
6
- import docx2txt
7
  import chromadb
8
- from pathlib import Path
9
 
10
- # --- HACK FIX CHO CHROMA DB (Nếu chạy trên môi trường cũ) ---
11
  try:
12
  __import__("pysqlite3")
13
  sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
14
  except ImportError:
15
  pass
16
 
17
- import gradio as gr
18
- from chromadb.config import Settings
19
  from langchain_google_genai import ChatGoogleGenerativeAI
20
  from langchain_chroma import Chroma
21
- from langchain_community.document_loaders import PyPDFLoader
22
- from langchain_text_splitters import RecursiveCharacterTextSplitter
23
  from langchain_community.retrievers import BM25Retriever
24
  from langchain.retrievers.ensemble import EnsembleRetriever
25
- from langchain.chains import create_retrieval_chain, create_history_aware_retriever
26
  from langchain.chains.combine_documents import create_stuff_documents_chain
27
- from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
28
- from langchain_core.messages import HumanMessage, AIMessage
29
- from langchain_core.documents import Document
30
- from langchain_huggingface import HuggingFaceEmbeddings
31
  from langchain.retrievers import ContextualCompressionRetriever
32
  from langchain.retrievers.document_compressors import CrossEncoderReranker
33
  from langchain_community.cross_encoders import HuggingFaceCrossEncoder
 
34
 
35
- # --- CẤU HÌNH ---
36
- # Hãy chắc chắn bạn đã set biến môi trường GOOGLE_API_KEY hoặc điền trực tiếp vào đây (không khuyến khích share key)
37
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
38
- DATA_PATH = "medical_data"
39
- DB_PATH = "chroma_db"
40
- MAX_HISTORY_TURNS = 4
41
-
42
- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
43
 
44
- # --- 1. PHÂN LOẠI DỮ LIỆU (THEO ẢNH CỦA BẠN) ---
45
- def classify_by_folder_path(file_path_str: str) -> str:
46
- path_lower = file_path_str.lower().replace("\\", "/")
47
-
48
- if "thông tin thuốc nội bộ" in path_lower:
49
- return "drug_info"
50
- elif "phác đồ tại ttytkv thanh ba" in path_lower:
51
- return "local_regimen"
52
- elif "phác đồ bộ y tế" in path_lower:
53
- return "moh_regimen"
54
- elif "các hiệp hội" in path_lower:
55
- return "association"
56
- elif "general_knowledge" in path_lower:
57
- return "general_knowledge"
58
- else:
59
- return "general_knowledge"
60
 
61
  def get_category_vn_name(cat_code):
62
- mapping = {
63
  "drug_info": "💊 Thuốc Nội Bộ",
64
  "local_regimen": "🏥 Phác Đồ Thanh Ba",
65
  "moh_regimen": "🏛️ Bộ Y Tế",
66
- "association": "🌐 Hiệp Hội",
67
- "general_knowledge": "📚 Kiến thức chung"
68
- }
69
- return mapping.get(cat_code, "Khác")
70
-
71
- # --- XỬ LÝ FILE ---
72
- def process_excel_file(file_path: str, filename: str, category: str) -> list[Document]:
73
- docs = []
74
- try:
75
- df = pd.read_csv(file_path) if file_path.endswith(".csv") else pd.read_excel(file_path)
76
- df.dropna(how='all', inplace=True)
77
- df.fillna("Không có thông tin", inplace=True)
78
- cat_vn = get_category_vn_name(category)
79
-
80
- for idx, row in df.iterrows():
81
- content_parts = []
82
- for col_name, val in row.items():
83
- clean_val = str(val).strip()
84
- if clean_val and clean_val.lower() != "nan":
85
- content_parts.append(f"{col_name}: {clean_val}")
86
-
87
- if content_parts:
88
- page_content = f"[{cat_vn}] Nguồn: {filename}\n" + "\n".join(content_parts)
89
- metadata = {"source": filename, "row": idx+1, "type": "excel", "category": category}
90
- docs.append(Document(page_content=page_content, metadata=metadata))
91
- except Exception as e:
92
- logging.error(f"Lỗi Excel {filename}: {e}")
93
- return docs
94
-
95
- def load_documents_from_folder(root_folder: str) -> list[Document]:
96
- logging.info(f"--- Quét dữ liệu từ: {root_folder} ---")
97
- documents = []
98
- if not os.path.exists(root_folder):
99
- os.makedirs(root_folder, exist_ok=True)
100
- return []
101
-
102
- for root, _, files in os.walk(root_folder):
103
- for filename in files:
104
- file_path = os.path.join(root, filename)
105
-
106
- # --- Phân loại ---
107
- category = classify_by_folder_path(file_path)
108
- cat_vn = get_category_vn_name(category)
109
- logging.info(f"Load: {filename} -> Nhóm: {cat_vn}")
110
-
111
- try:
112
- new_docs = []
113
- if filename.lower().endswith(".pdf"):
114
- loader = PyPDFLoader(file_path)
115
- new_docs = loader.load()
116
- elif filename.lower().endswith(".docx"):
117
- text = docx2txt.process(file_path)
118
- if text.strip():
119
- new_docs = [Document(page_content=text)]
120
- elif filename.lower().endswith((".xlsx", ".xls", ".csv")):
121
- new_docs = process_excel_file(file_path, filename, category)
122
- elif filename.lower().endswith((".txt", ".md")):
123
- with open(file_path, "r", encoding="utf-8") as f:
124
- new_docs = [Document(page_content=f.read())]
125
-
126
- # Gắn metadata và Label vào text
127
- for doc in new_docs:
128
- doc.metadata["source"] = filename
129
- doc.metadata["category"] = category
130
- if not doc.page_content.startswith("["):
131
- doc.page_content = f"[{cat_vn}] Nguồn {filename}:\n{doc.page_content}"
132
-
133
- documents.extend(new_docs)
134
- except Exception as e:
135
- logging.error(f"Lỗi đọc {filename}: {e}")
136
- return documents
137
 
138
- # --- RETRIEVER CONFIG (Bộ lọc thông minh) ---
139
  def get_retrievers():
140
- logging.info("--- Tải Embedding Model ---")
141
- embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
142
- chroma_settings = Settings(anonymized_telemetry=False)
143
-
144
- # Logic nạp lại dữ liệu (Re-index nếu chưa có DB)
145
- if not os.path.exists(DB_PATH) or not os.listdir(DB_PATH):
146
- raw_docs = load_documents_from_folder(DATA_PATH)
147
- if not raw_docs: return None, None
148
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
149
- splits = text_splitter.split_documents(raw_docs)
150
- vectorstore = Chroma.from_documents(documents=splits, embedding=embedding_model, persist_directory=DB_PATH, client_settings=chroma_settings)
151
- else:
152
- logging.info("--- Sử dụng DB đã lưu ---")
153
- vectorstore = Chroma(persist_directory=DB_PATH, embedding_function=embedding_model, client_settings=chroma_settings)
154
- # Load lại raw để build BM25 (In-memory)
155
- raw_docs = load_documents_from_folder(DATA_PATH)
156
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
157
- splits = text_splitter.split_documents(raw_docs)
158
-
159
- # 1. TRA CỨU NHANH (Chỉ lấy 'drug_info')
160
- vector_retriever_fast = vectorstore.as_retriever(
161
- search_kwargs={"k": 5, "filter": {"category": "drug_info"}}
162
- )
163
- # BM25 cho thuốc
164
- drug_splits = [d for d in splits if d.metadata.get("category") == "drug_info"]
165
- if drug_splits:
166
- bm25_fast = BM25Retriever.from_documents(drug_splits)
167
- bm25_fast.k = 5
168
- fast_retriever = EnsembleRetriever(retrievers=[bm25_fast, vector_retriever_fast], weights=[0.4, 0.6])
169
- else:
170
- fast_retriever = vector_retriever_fast
171
 
172
- # 2. TRA CỨU CHUYÊN SÂU (Bỏ general_knowledge, ưu tiên các nhóm chuyên môn)
173
- target_categories = ["local_regimen", "moh_regimen", "association", "drug_info"]
174
 
175
- vector_retriever_deep = vectorstore.as_retriever(
176
- search_kwargs={"k": 25, "filter": {"category": {"$in": target_categories}}}
177
- )
178
- deep_splits = [d for d in splits if d.metadata.get("category") in target_categories]
179
- if deep_splits:
180
- bm25_deep = BM25Retriever.from_documents(deep_splits)
181
- bm25_deep.k = 25
182
- ensemble_deep = EnsembleRetriever(retrievers=[bm25_deep, vector_retriever_deep], weights=[0.5, 0.5])
183
- else:
184
- ensemble_deep = vector_retriever_deep
185
-
186
- # Reranker BGE-M3 (Bước lọc cuối cùng cực quan trọng)
 
 
 
 
 
 
 
 
 
 
187
  reranker = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3")
188
- compressor = CrossEncoderReranker(model=reranker, top_n=10) # Lấy top 10 đoạn văn chuẩn nhất
189
- deep_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=ensemble_deep)
190
 
191
  return fast_retriever, deep_retriever
192
 
193
- # --- BOT SETUP ---
194
  class DeepMedBot:
195
  def __init__(self):
196
  self.ready = False
197
- if not GOOGLE_API_KEY:
198
- logging.error("❌ Thiếu API Key! Vui lòng kiểm tra biến môi trường.")
199
- return
200
  try:
201
  self.fast_retriever, self.deep_retriever = get_retrievers()
202
- self.llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0.2, google_api_key=GOOGLE_API_KEY)
203
  self._build_chains()
204
  self.ready = True
205
  except Exception as e:
206
- logging.error(f"Lỗi khởi động: {e}")
207
 
208
  def _build_chains(self):
209
- # --- 1. PROMPT TRA CỨU NHANH (Tối ưu hiển thị Bảng thuốc) ---
210
- fast_system = (
211
- "Bạn là Dược Lâm sàng phụ trách kho dược nội bộ.\n"
212
- "Nhiệm vụ: Tra cứu thông tin thuốc từ dữ liệu [💊 Thuốc Nội Bộ] trả lời người dùng.\n\n"
213
- "YÊU CẦU ĐỊNH DẠNG (BẮT BUỘC):\n"
214
- "1. **Luôn trình bày kết quả dưới dạng Bảng (Markdown Table)** với các cột sau (nếu thông tin):\n"
215
- " | Tên thuốc | Hoạt chất | Hàm lượng | Đơn vị tính | Ghi chú/Tồn kho |\n"
216
- " | --- | --- | --- | --- | --- |\n"
217
- "2. Nếu tìm thấy nhiều thuốc tương tự, hãy liệt kê hết vào bảng.\n"
218
- "3. Sau bảng, có thể bổ sung ngắn gọn về chỉ định hoặc liều dùng nếu dữ liệu có đề cập.\n"
219
- "4. Nếu không tìm thấy thuốc nào khớp, trả lời: '❌ Không tìm thấy thuốc này trong danh mục nội bộ'.\n\n"
220
  "Context:\n{context}"
221
  )
222
- fast_prompt = ChatPromptTemplate.from_messages([("system", fast_system), ("human", "{input}")])
223
- self.fast_chain = create_retrieval_chain(self.fast_retriever, create_stuff_documents_chain(self.llm, fast_prompt))
224
 
225
- # --- 2. PROMPT CHUYÊN SÂU (Logic Thanh Ba + Bảng đối chiếu) ---
226
- deep_system = (
227
- "Bạn là Bác sĩ Trưởng khoa, hỗ trợ ra quyết định lâm sàng dựa trên bằng chứng.\n"
228
- "Nhiệm vụ: Đề xuất phác đồ điều trị đối chiếu thuốcsẵn.\n\n"
229
- "QUY TRÌNH DUY & TRẢ LỜI:\n"
230
- "Bước 1: Xác định Phác đồ (Theo thứ tự ưu tiên):\n"
231
- " - Ưu tiên 1: [🏥 Phác Đồ Thanh Ba]. (Nếu có, phải tuân thủ tuyệt đối).\n"
232
- " - Ưu tiên 2: [🏛️ Bộ Y Tế] hoặc [🌐 Hiệp Hội] (Chỉ dùng khi Thanh Ba không quy định).\n\n"
233
- "Bước 2: Đối chiếu Kho Dược (Quan trọng):\n"
234
- " - Kiểm tra xem các thuốc trong phác đồ có trong [💊 Thuốc Nội Bộ] hay không.\n\n"
235
- "YÊU CẦU ĐỊNH DẠNG ĐẦU RA:\n"
236
- "1. **Tóm tắt Chẩn đoán/Nguyên tắc:** (Ngắn gọn, gạch đầu dòng).\n"
237
- "2. **Phác đồ Điều trị:** (Nêu rõ nguồn áp dụng là Thanh Ba hay BYT).\n"
238
- "3. **Bảng Kê Đơn & Đối Chiếu Thuốc (BẮT BUỘC CÓ BẢNG):**\n"
239
- " | Tên thuốc (Theo phác đồ) | Liều dùng | Trạng thái Kho Dược | Gợi ý thay thế (Nếu thiếu) |\n"
240
- " | --- | --- | --- | --- |\n"
241
- " | Ví dụ: Paracetamol | 500mg | ✅ Có sẵn | - |\n"
242
- " | Ví dụ: Thuốc lạ X | ... | ❌ Không có | Dùng thuốc Y trong kho |\n\n"
243
- "Lưu ý: Chỉ đưa ra thông tin có trong Context, không tự bịa đặt thuốc."
244
  "Context:\n{context}"
245
  )
246
- deep_prompt = ChatPromptTemplate.from_messages([("system", deep_system), ("human", "{input}")])
247
- self.deep_chain = create_retrieval_chain(self.deep_retriever, create_stuff_documents_chain(self.llm, deep_prompt))
248
 
249
  def chat(self, msg, history, mode):
250
- if not self.ready: return "⚠️ Hệ thống đang khởi động hoặc chưa dữ liệu..."
251
-
252
  chain = self.deep_chain if mode == "Chuyên sâu" else self.fast_chain
253
- response = chain.invoke({"input": msg})
254
 
255
- answer = response['answer']
256
- # Tạo trích dẫn nguồn
257
- if 'context' in response and response['context']:
258
- refs = []
259
- seen = set()
260
- for doc in response['context']:
261
- cat_vn = get_category_vn_name(doc.metadata.get('category'))
262
- src = doc.metadata.get('source')
263
- ref_item = f"[{cat_vn}] {src}"
264
- if ref_item not in seen:
265
- refs.append(f"- {ref_item}")
266
- seen.add(ref_item)
267
- if refs:
268
- answer += "\n\n---\n📚 **Nguồn tham khảo:**\n" + "\n".join(refs)
269
- return answer
270
 
271
- # --- GRADIO UI ---
272
  bot = DeepMedBot()
273
 
274
  def respond(message, history, mode):
275
  return bot.chat(message, history, mode)
276
 
277
- css = """.gradio-container {min_height: 600px}"""
278
-
279
- demo = gr.ChatInterface(
280
  fn=respond,
281
- additional_inputs=[
282
- gr.Radio(
283
- ["Tra cứu nhanh (Chỉ thuốc)", "Chuyên sâu"],
284
- value="Tra cứu nhanh (Chỉ thuốc)",
285
- label="Chế độ tra cứu"
286
- )
287
- ],
288
- title="Hệ thống Hỗ trợ Lâm sàng (TTYT Thanh Ba)",
289
- description="Tra cứu thuốc nội bộ và Phác đồ điều trị (Ưu tiên dữ liệu Thanh Ba).",
290
- css=css
291
- )
292
-
293
- if __name__ == "__main__":
294
- # Tạo folder mẫu để tránh lỗi nếu thư mục chưa tồn tại (Dựa trên tên trong ảnh)
295
- folders = [
296
- "thông tin thuốc nội bộ",
297
- "phác đồ tại ttytkv thanh ba",
298
- "phác đồ bộ y tế",
299
- "các hiệp hội",
300
- "general_knowledge"
301
- ]
302
- for f in folders:
303
- path = os.path.join(DATA_PATH, f)
304
- if not os.path.exists(path):
305
- os.makedirs(path)
306
-
307
- # Lưu ý: Xóa folder 'chroma_db' cũ trước khi chạy lại lần đầu tiên sau khi sửa code!
308
- demo.launch()
 
1
  import os
2
  import sys
3
  import logging
 
 
 
4
  import chromadb
5
+ import gradio as gr
6
 
7
+ # Fix lỗi SQLite trên Hugging Face
8
  try:
9
  __import__("pysqlite3")
10
  sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
11
  except ImportError:
12
  pass
13
 
 
 
14
  from langchain_google_genai import ChatGoogleGenerativeAI
15
  from langchain_chroma import Chroma
16
+ from langchain_huggingface import HuggingFaceEmbeddings
 
17
  from langchain_community.retrievers import BM25Retriever
18
  from langchain.retrievers.ensemble import EnsembleRetriever
19
+ from langchain.chains import create_retrieval_chain
20
  from langchain.chains.combine_documents import create_stuff_documents_chain
21
+ from langchain_core.prompts import ChatPromptTemplate
 
 
 
22
  from langchain.retrievers import ContextualCompressionRetriever
23
  from langchain.retrievers.document_compressors import CrossEncoderReranker
24
  from langchain_community.cross_encoders import HuggingFaceCrossEncoder
25
+ from langchain_core.documents import Document
26
 
27
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
28
+ DB_PATH = "chroma_db"
 
 
 
 
 
 
29
 
30
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  def get_category_vn_name(cat_code):
33
+ return {
34
  "drug_info": "💊 Thuốc Nội Bộ",
35
  "local_regimen": "🏥 Phác Đồ Thanh Ba",
36
  "moh_regimen": "🏛️ Bộ Y Tế",
37
+ "association": "🌐 Hiệp Hội"
38
+ }.get(cat_code, "Khác")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
 
40
  def get_retrievers():
41
+ if not os.path.exists(DB_PATH):
42
+ raise FileNotFoundError("❌ Chưa upload folder 'chroma_db'!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ embedding = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
45
+ vectorstore = Chroma(persist_directory=DB_PATH, embedding_function=embedding)
46
 
47
+ # Tái tạo BM25 từ VectorStore (Trick để không phải upload raw data)
48
+ all_data = vectorstore.get()
49
+ splits = [Document(page_content=txt, metadata=m) for txt, m in zip(all_data['documents'], all_data['metadatas'])]
50
+
51
+ # 1. FAST (Chỉ thuốc)
52
+ vec_fast = vectorstore.as_retriever(search_kwargs={"k": 5, "filter": {"category": "drug_info"}})
53
+ drug_docs = [d for d in splits if d.metadata.get("category") == "drug_info"]
54
+ bm25_fast = BM25Retriever.from_documents(drug_docs) if drug_docs else None
55
+ bm25_fast.k = 5 if bm25_fast else 5
56
+
57
+ fast_retriever = EnsembleRetriever(retrievers=[bm25_fast, vec_fast], weights=[0.4, 0.6]) if bm25_fast else vec_fast
58
+
59
+ # 2. DEEP (Ưu tiên Thanh Ba)
60
+ cats = ["local_regimen", "moh_regimen", "association", "drug_info"]
61
+ vec_deep = vectorstore.as_retriever(search_kwargs={"k": 25, "filter": {"category": {"$in": cats}}})
62
+ deep_docs = [d for d in splits if d.metadata.get("category") in cats]
63
+ bm25_deep = BM25Retriever.from_documents(deep_docs) if deep_docs else None
64
+ bm25_deep.k = 25 if bm25_deep else 25
65
+
66
+ ensemble = EnsembleRetriever(retrievers=[bm25_deep, vec_deep], weights=[0.5, 0.5]) if bm25_deep else vec_deep
67
+
68
+ # Rerank
69
  reranker = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3")
70
+ compressor = CrossEncoderReranker(model=reranker, top_n=10)
71
+ deep_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=ensemble)
72
 
73
  return fast_retriever, deep_retriever
74
 
 
75
  class DeepMedBot:
76
  def __init__(self):
77
  self.ready = False
 
 
 
78
  try:
79
  self.fast_retriever, self.deep_retriever = get_retrievers()
80
+ self.llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0.2, google_api_key=GOOGLE_API_KEY)
81
  self._build_chains()
82
  self.ready = True
83
  except Exception as e:
84
+ logging.error(f"Lỗi: {e}")
85
 
86
  def _build_chains(self):
87
+ # Prompt Bảng cho Thuốc
88
+ fast_sys = (
89
+ "Bạn là Dược sĩ. Tra cứu [💊 Thuốc Nội Bộ] trả lời bằng **Bảng Markdown**:\n"
90
+ "| Tên thuốc | Hoạt chất | Hàm lượng | ĐVT | Ghi chú |\n"
91
+ "| --- | --- | --- | --- | --- |\n"
92
+ "Nếu không thấy, báo: '❌ Khôngtrong kho'."
 
 
 
 
 
93
  "Context:\n{context}"
94
  )
95
+ fast_chain = create_stuff_documents_chain(self.llm, ChatPromptTemplate.from_messages([("system", fast_sys), ("human", "{input}")]))
96
+ self.fast_chain = create_retrieval_chain(self.fast_retriever, fast_chain)
97
 
98
+ # Prompt Phác đồ ưu tiên Thanh Ba
99
+ deep_sys = (
100
+ "Bạn là Bác sĩ Trưởng khoa.\n"
101
+ "1. **Tìm phác đồ:** Ưu tiên tuyệt đối [🏥 Phác Đồ Thanh Ba]. Nếu không mới dùng [Bộ Y Tế].\n"
102
+ "2. **Đối chiếu thuốc:** Kiểm tra thuốc trong phác đồ có trong [💊 Thuốc Nội Bộ] không.\n"
103
+ "3. **Định dạng trả lời:**\n"
104
+ " - Chẩn đoán/Nguyên tắc.\n"
105
+ " - Phác đồ (Ghi nguồn).\n"
106
+ " - **Bảng đơn:**\n"
107
+ " | Tên thuốc | Liều dùng | trong kho? | Thay thế |\n"
108
+ " | --- | --- | --- | --- |\n"
 
 
 
 
 
 
 
 
109
  "Context:\n{context}"
110
  )
111
+ deep_chain = create_stuff_documents_chain(self.llm, ChatPromptTemplate.from_messages([("system", deep_sys), ("human", "{input}")]))
112
+ self.deep_chain = create_retrieval_chain(self.deep_retriever, deep_chain)
113
 
114
  def chat(self, msg, history, mode):
115
+ if not self.ready: return "⚠️ Đang khởi động... Vui lòng đợi 1 phút."
 
116
  chain = self.deep_chain if mode == "Chuyên sâu" else self.fast_chain
117
+ res = chain.invoke({"input": msg})
118
 
119
+ ans = res['answer']
120
+ if 'context' in res and res['context']:
121
+ refs = list(set([f"- [{get_category_vn_name(d.metadata.get('category'))}] {d.metadata.get('source')}" for d in res['context']]))
122
+ ans += "\n\n---\n📚 **Nguồn:**\n" + "\n".join(refs)
123
+ return ans
 
 
 
 
 
 
 
 
 
 
124
 
 
125
  bot = DeepMedBot()
126
 
127
  def respond(message, history, mode):
128
  return bot.chat(message, history, mode)
129
 
130
+ gr.ChatInterface(
 
 
131
  fn=respond,
132
+ additional_inputs=[gr.Radio(["Tra cứu nhanh (Chỉ thuốc)", "Chuyên sâu"], value="Tra cứu nhanh (Chỉ thuốc)", label="Chế độ")],
133
+ title="TTYT Thanh Ba - Hỗ trợ Lâm sàng",
134
+ css=".gradio-container {min_height: 600px}"
135
+ ).launch()