PBThuong commited on
Commit
bc1b4c2
·
verified ·
1 Parent(s): ae05c68

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -59
app.py CHANGED
@@ -1,77 +1,193 @@
1
- import chainlit as cl
2
- from app import bot # Import bot đã khởi tạo sẵn từ file app.py
 
 
 
 
 
3
 
4
- # --- CẤU HÌNH ---
5
- # Tùy chỉnh avatar cho bot và user (nếu muốn)
6
- # Bạn thể đặt file 'bot.png' và 'user.png' vào thư mục public/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  @cl.on_chat_start
9
  async def start():
10
- """Hàm chạy khi bắt đầu đoạn chat mới"""
11
-
12
- # 1. Gửi tin nhắn chào mừng
13
- welcome_msg = """👋 **Xin chào! Tôi là DeepMed AI.**
14
 
15
- Tôi trợ lý ảo hỗ trợ tra cứu thông tin thuốc và phác đồ điều trị.
 
16
 
17
- 💡 **Ví dụ câu hỏi:**
18
- - *Liều dùng Paracetamol cho trẻ em?*
19
- - *Chống chỉ định của thuốc Aspirin?*
20
- - *Phác đồ điều trị viêm phổi?*
21
- """
22
- await cl.Message(content=welcome_msg).send()
23
-
24
- # 2. Tạo các nút chọn chế độ (Actions)
25
  actions = [
26
- cl.Action(name="set_mode", value="Tốc độ", label="⚡ Tốc độ (Nhanh)", collapsed=False),
27
- cl.Action(name="set_mode", value="Chuyên sâu", label="🧠 Chuyên sâu (Chậm & Kỹ)", collapsed=False)
28
  ]
29
- await cl.Message(content="👇 **Vui lòng chọn chế độ hoạt động:**", actions=actions).send()
30
 
31
- # Mặc định chế độ "Tốc độ"
32
- cl.user_session.set("mode", "Tốc độ")
33
-
34
- @cl.action_callback("set_mode")
35
- async def on_action(action):
36
- """Xử khi người dùng bấm nút chọn chế độ"""
37
- mode = action.value
38
- cl.user_session.set("mode", mode)
39
- await cl.Message(content=f"✅ Đã chuyển sang chế độ: **{mode}**").send()
40
- # Có thể xóa nút sau khi chọn để gọn giao diện (tùy chọn)
41
- # await action.remove()
42
 
43
  @cl.on_message
44
  async def main(message: cl.Message):
45
- """Hàm xử lý tin nhắn chính"""
 
 
 
 
 
 
 
 
 
46
 
47
- # 1. Lấy chế độ từ session (nếu chưa chọn thì mặc định Tốc độ)
48
- mode = cl.user_session.get("mode", "Tốc độ")
49
 
50
- # 2. Tạo tin nhắn rỗng để chuẩn bị stream dữ liệu trả về
51
  msg = cl.Message(content="")
52
  await msg.send()
53
 
54
- # 3. Lấy lịch sử chat (nếu cần thiết cho context)
55
- # Chainlit tự quản lý history UI, nhưng bot của chúng ta cần list history dạng [(user, bot), ...]
56
- # Ở đây để đơn giản ta truyền list rỗng hoặc bạn có thể tự build history từ cl.user_session
57
- chat_history = []
58
 
59
- # 4. Gọi Bot (DeepMedBot)
60
- # Lưu ý: bot.chat_stream của chúng ta trả về Full text tích lũy (Accumulated Text)
61
- # Chainlit cần Stream Token (từng từ một). Chúng ta cần xử lý logic này.
62
-
63
- stream_iterator = bot.chat_stream(message.content, chat_history, mode)
64
-
65
- previous_text = ""
66
-
67
- # Duyệt qua từng chunk dữ liệu bot trả về
68
- for full_response in stream_iterator:
69
- # Tính toán phần văn bản mới (delta) để tạo hiệu ứng gõ máy
70
- # hàm cũ trả về toàn bộ văn bản mỗi lần yield
71
- if len(full_response) > len(previous_text):
72
- token = full_response[len(previous_text):]
73
- await msg.stream_token(token)
74
- previous_text = full_response
75
-
76
- # Cập nhật lần cuối để đảm bảo hiển thị đúng định dạng Markdown/Bảng
77
- await msg.update()
 
 
 
 
 
 
1
+ import os
2
+ try:
3
+ __import__("pysqlite3")
4
+ import sys
5
+ sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
6
+ except ImportError:
7
+ pass
8
 
9
+ import logging
10
+ import traceback
11
+ import pandas as pd
12
+ import docx2txt
13
+ import chromadb
14
+ from chromadb.config import Settings
15
+ import chainlit as cl # Thư viện giao diện mới
16
+
17
+ from langchain_google_genai import ChatGoogleGenerativeAI
18
+ from langchain_chroma import Chroma
19
+ from langchain_community.document_loaders import PyPDFLoader
20
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
21
+ from langchain_community.retrievers import BM25Retriever
22
+ from langchain.retrievers.ensemble import EnsembleRetriever
23
+ from langchain.chains import create_retrieval_chain, create_history_aware_retriever
24
+ from langchain.chains.combine_documents import create_stuff_documents_chain
25
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
26
+ from langchain_core.messages import HumanMessage, AIMessage
27
+ from langchain_core.documents import Document
28
+ from langchain_huggingface import HuggingFaceEmbeddings
29
+ from langchain.retrievers import ContextualCompressionRetriever
30
+ from langchain.retrievers.document_compressors import CrossEncoderReranker
31
+ from langchain_community.cross_encoders import HuggingFaceCrossEncoder
32
+
33
+ # === CẤU HÌNH ===
34
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
35
+ DATA_PATH = "medical_data"
36
+ DB_PATH = "chroma_db"
37
+ MAX_HISTORY_TURNS = 4
38
+
39
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
40
+
41
+ # ... (GIỮ NGUYÊN CÁC HÀM: process_excel_file, load_documents_from_folder, get_retrievers) ...
42
+ # Để tiết kiệm chỗ hiển thị, tôi không paste lại đoạn code xử lý file ở đây
43
+ # Bạn hãy copy-paste lại các hàm process_excel_file, load_documents_from_folder, get_retrievers Y HỆT NHƯ CŨ vào đây.
44
+
45
+ # === LOGIC BOT (Sửa nhẹ để tách biệt khởi tạo) ===
46
+ class DeepMedBot:
47
+ def __init__(self):
48
+ self.fast_chain = None
49
+ self.deep_chain = None
50
+ self.ready = False
51
+
52
+ if not GOOGLE_API_KEY:
53
+ return
54
+
55
+ try:
56
+ self.fast_retriever, self.deep_retriever = get_retrievers()
57
+
58
+ self.llm = ChatGoogleGenerativeAI(
59
+ model="gemini-2.5-flash",
60
+ temperature=0.2,
61
+ google_api_key=GOOGLE_API_KEY,
62
+ convert_system_message_to_human=True
63
+ )
64
+
65
+ if self.fast_retriever and self.deep_retriever:
66
+ self._build_chains()
67
+ self.ready = True
68
+ else:
69
+ self.ready = True
70
+
71
+ except Exception as e:
72
+ logging.error(f"Lỗi khởi tạo bot: {e}")
73
+
74
+ def _build_chains(self):
75
+ context_system_prompt = (
76
+ "Dựa trên lịch sử chat và câu hỏi mới nhất, hãy viết lại câu hỏi "
77
+ "thành một câu hoàn chỉnh để tìm kiếm thông tin. "
78
+ "CHỈ TRẢ VỀ CÂU HỎI ĐÃ VIẾT LẠI, KHÔNG TRẢ LỜI."
79
+ )
80
+ context_prompt = ChatPromptTemplate.from_messages([
81
+ ("system", context_system_prompt),
82
+ MessagesPlaceholder("chat_history"),
83
+ ("human", "{input}"),
84
+ ])
85
+
86
+ qa_system_prompt = (
87
+ "Bạn là 'DeepMed-AI' - Trợ lý Dược lâm sàng chuyên nghiệp.\n"
88
+ "Nhiệm vụ: Tư vấn điều trị CHỈ DỰA TRÊN Dữ liệu nội bộ (Context).\n"
89
+ "QUY TẮC: Trung thực tuyệt đối, Kiểm tra thuốc có trong kho, Trích dẫn nguồn.\n"
90
+ "Context:\n{context}"
91
+ )
92
+ qa_prompt = ChatPromptTemplate.from_messages([
93
+ ("system", qa_system_prompt),
94
+ MessagesPlaceholder("chat_history"),
95
+ ("human", "{input}"),
96
+ ])
97
+
98
+ question_answer_chain = create_stuff_documents_chain(self.llm, qa_prompt)
99
+
100
+ history_aware_fast = create_history_aware_retriever(self.llm, self.fast_retriever, context_prompt)
101
+ self.fast_chain = create_retrieval_chain(history_aware_fast, question_answer_chain)
102
+
103
+ history_aware_deep = create_history_aware_retriever(self.llm, self.deep_retriever, context_prompt)
104
+ self.deep_chain = create_retrieval_chain(history_aware_deep, question_answer_chain)
105
+
106
+ def _build_references_text(self, docs) -> str:
107
+ lines = []
108
+ seen = set()
109
+ for doc in docs:
110
+ src = doc.metadata.get("source", "Tài liệu")
111
+ row_info = f"(Dòng {doc.metadata['row']})" if "row" in doc.metadata else ""
112
+ ref_str = f"- {src} {row_info}"
113
+ if ref_str not in seen:
114
+ lines.append(ref_str)
115
+ seen.add(ref_str)
116
+ return "\n".join(lines)
117
+
118
+ # === PHẦN GIAO DIỆN CHAINLIT ===
119
 
120
  @cl.on_chat_start
121
  async def start():
122
+ # Khởi tạo bot khi phiên chat bắt đầu
123
+ msg = cl.Message(content="Đang khởi động hệ thống DeepMed AI...")
124
+ await msg.send()
 
125
 
126
+ bot = DeepMedBot()
127
+ cl.user_session.set("bot", bot) # Lưu bot vào session của người dùng
128
 
129
+ # Gửi tin nhắn chào mừng và các nút chọn chế độ
 
 
 
 
 
 
 
130
  actions = [
131
+ cl.Action(name="fast_mode", value="fast", label="⚡ Tốc độ (Nhanh)", description="Trả lời nhanh"),
132
+ cl.Action(name="deep_mode", value="deep", label="🧠 Chuyên sâu (Kỹ)", description="Phân tích kỹ dữ liệu")
133
  ]
134
+ cl.user_session.set("mode", "fast") # Mặc định fast
135
 
136
+ await msg.update(content="👋 Xin chào! Tôi là **DeepMed AI**. \n\nTôi có thể hỗ trợ tra cứu thuốc, phác đồ điều trị và thông tin dược lâm sàng. \n\n*Mặc định đang ở chế độ: Tốc độ.*", actions=actions)
137
+
138
+ @cl.action_callback("fast_mode")
139
+ async def on_fast_mode(action):
140
+ cl.user_session.set("mode", "fast")
141
+ await cl.Message(content=" Đã chuyển sang chế độ: **Tốc độ (Nhanh)**").send()
142
+
143
+ @cl.action_callback("deep_mode")
144
+ async def on_deep_mode(action):
145
+ cl.user_session.set("mode", "deep")
146
+ await cl.Message(content="✅ Đã chuyển sang chế độ: **Chuyên sâu (Chậm & Chính xác)**").send()
147
 
148
  @cl.on_message
149
  async def main(message: cl.Message):
150
+ bot = cl.user_session.get("bot")
151
+ mode_setting = cl.user_session.get("mode")
152
+
153
+ if not bot or not bot.ready:
154
+ await cl.Message(content="⚠️ Hệ thống chưa sẵn sàng hoặc gặp lỗi khởi tạo.").send()
155
+ return
156
+
157
+ # Lấy lịch sử chat từ Chainlit
158
+ # Chainlit tự quản lý history, nhưng để tương thích code cũ, ta có thể lấy memory
159
+ # Ở đây ta dùng bộ nhớ session đơn giản hoặc để LangChain tự lo
160
 
161
+ # Xác định chain cần dùng
162
+ active_chain = bot.deep_chain if mode_setting == "deep" else bot.fast_chain
163
 
 
164
  msg = cl.Message(content="")
165
  await msg.send()
166
 
167
+ # Lấy lịch sử chat (đơn giản hóa cho demo)
168
+ history = []
 
 
169
 
170
+ full_response = ""
171
+ retrieved_docs = []
172
+
173
+ # Gọi Chain Async
174
+ try:
175
+ async for chunk in active_chain.astream({"input": message.content, "chat_history": history}):
176
+ if "answer" in chunk:
177
+ token = chunk["answer"]
178
+ full_response += token
179
+ await msg.stream_token(token)
180
+ elif "context" in chunk:
181
+ retrieved_docs = chunk["context"]
182
+
183
+ # Hiển thị nguồn tham khảo đẹp mắt hơn
184
+ if retrieved_docs:
185
+ refs = bot._build_references_text(retrieved_docs)
186
+ if refs:
187
+ ref_block = f"\n\n---\n📚 **Nguồn tham khảo:**\n{refs}"
188
+ await msg.stream_token(ref_block)
189
+
190
+ await msg.update()
191
+
192
+ except Exception as e:
193
+ await cl.Message(content=f"⚠️ Lỗi: {str(e)}").send()