PBThuong96 commited on
Commit
dc011dc
·
verified ·
1 Parent(s): 313ed3c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +139 -0
app.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from typing import TypedDict, Annotated, Sequence
4
+ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
5
+ from langchain_huggingface import HuggingFaceEndpoint, HuggingFaceEmbeddings
6
+ from langchain_community.vectorstores import Qdrant
7
+ from qdrant_client import QdrantClient
8
+ from langgraph.graph import StateGraph, START, END
9
+ from langgraph.graph.message import add_messages
10
+ from langchain_core.prompts import PromptTemplate
11
+
12
+ # ==========================================
13
+ # 1. CẤU HÌNH API VÀ MÔ HÌNH (Lấy từ HF Secrets)
14
+ # ==========================================
15
+ HF_TOKEN = os.getenv("HF_TOKEN")
16
+ QDRANT_URL = os.getenv("QDRANT_URL", "MOCK_URL")
17
+ QDRANT_API_KEY = os.getenv("QDRANT_API_KEY", "MOCK_KEY")
18
+
19
+ # Khởi tạo Mô hình Sinh văn bản (LLM) - Dùng Qwen 2.5 7B Instruct (rất tốt cho tiếng Việt & Y tế)
20
+ # Lưu ý: Cần cấp quyền truy cập mô hình trên Hugging Face nếu mô hình bị khóa.
21
+ llm = HuggingFaceEndpoint(
22
+ repo_id="Qwen/Qwen2.5-7B-Instruct",
23
+ task="text-generation",
24
+ max_new_tokens=512,
25
+ temperature=0.1, # Nhiệt độ thấp để đảm bảo tính chính xác y khoa
26
+ huggingfacehub_api_token=HF_TOKEN,
27
+ )
28
+
29
+ # Khởi tạo Mô hình Nhúng (Embeddings) cho Y sinh / Đa ngôn ngữ
30
+ embeddings = HuggingFaceEmbeddings(
31
+ model_name="BAAI/bge-m3" # Có thể thay bằng "pritamdeka/S-PubMedBert-MS-MARCO" nếu chỉ dùng tiếng Anh
32
+ )
33
+
34
+ # Khởi tạo kết nối Vector DB (Qdrant Cloud)
35
+ # Trong môi trường thực tế, bạn cần URL và API Key thật. Ở đây có cơ chế try-except để demo không bị lỗi.
36
+ try:
37
+ client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
38
+ vector_store = Qdrant(
39
+ client=client,
40
+ collection_name="deepmed_documents",
41
+ embeddings=embeddings
42
+ )
43
+ retriever = vector_store.as_retriever(search_kwargs={"k": 3})
44
+ except Exception as e:
45
+ print("Cảnh báo: Chưa kết nối được Qdrant thực tế. Sẽ dùng Mock Retriever cho mục đích Demo.")
46
+ retriever = None
47
+
48
+ # ==========================================
49
+ # 2. XÂY DỰNG LUỒNG LANGGRAPH CHO DEEPMED-AI
50
+ # ==========================================
51
+
52
+ # Định nghĩa trạng thái (State) của Agent
53
+ class AgentState(TypedDict):
54
+ messages: Annotated[Sequence[BaseMessage], add_messages]
55
+ context: str
56
+
57
+ # Node 1: Truy xuất tài liệu (Retrieval)
58
+ def retrieve_node(state: AgentState):
59
+ messages = state["messages"]
60
+ last_message = messages[-1].content
61
+
62
+ if retriever:
63
+ docs = retriever.invoke(last_message)
64
+ context = "\n\n".join([doc.page_content for doc in docs])
65
+ else:
66
+ # Mock data nếu chưa có DB thật
67
+ context = "THÔNG TIN MÔ PHỎNG: Paracetamol liều dùng cho người lớn là 500mg - 1000mg mỗi 4-6 giờ, không quá 4000mg/ngày. Chống chỉ định với người suy gan nặng."
68
+
69
+ return {"context": context}
70
+
71
+ # Node 2: Sinh câu trả lời với Guardrails Y tế (Generation)
72
+ def generate_node(state: AgentState):
73
+ messages = state["messages"]
74
+ question = messages[-1].content
75
+ context = state["context"]
76
+
77
+ # RÀO CHẮN Y TẾ (Strict Guardrails)
78
+ medical_prompt = PromptTemplate.from_template(
79
+ """Bạn là DeepMed-AI, một chuyên gia y tế và dược lý học lâm sàng.
80
+ Nhiệm vụ của bạn là trả lời câu hỏi của người dùng dựa TRÊN CƠ SỞ NGỮ CẢNH ĐƯỢC CUNG CẤP DƯỚI ĐÂY.
81
+
82
+ NGỮ CẢNH Y KHOA:
83
+ {context}
84
+
85
+ LUẬT LỆ BẮT BUỘC (GUARDRAILS):
86
+ 1. Chỉ sử dụng thông tin từ NGỮ CẢNH Y KHOA để trả lời.
87
+ 2. NẾU NGỮ CẢNH KHÔNG CHỨA THÔNG TIN ĐỂ TRẢ LỜI, BẠN TUYỆT ĐỐI KHÔNG ĐƯỢC TỰ BỊA ĐẶT (KHÔNG HALLUCINATION). Hãy trả lời chính xác câu này: "Tôi không tìm thấy thông tin này trong cơ sở dữ liệu phác đồ và dược thư của DeepMed. Vui lòng tham khảo ý kiến bác sĩ chuyên khoa."
88
+ 3. Câu trả lời phải súc tích, chuyên nghiệp và có cảnh báo y tế ở cuối.
89
+
90
+ CÂU HỎI CỦA NGƯỜI DÙNG: {question}
91
+ CÂU TRẢ LỜI CỦA DEEPMED-AI:"""
92
+ )
93
+
94
+ chain = medical_prompt | llm
95
+ response = chain.invoke({"context": context, "question": question})
96
+
97
+ return {"messages": [AIMessage(content=response)]}
98
+
99
+ # Lắp ráp Đồ thị LangGraph
100
+ workflow = StateGraph(AgentState)
101
+ workflow.add_node("retrieve", retrieve_node)
102
+ workflow.add_node("generate", generate_node)
103
+
104
+ workflow.add_edge(START, "retrieve")
105
+ workflow.add_edge("retrieve", "generate")
106
+ workflow.add_edge("generate", END)
107
+
108
+ app_logic = workflow.compile()
109
+
110
+ # ==========================================
111
+ # 3. GIAO DIỆN NGƯỜI DÙNG VỚI GRADIO
112
+ # ==========================================
113
+
114
+ def chat_interface(message, history):
115
+ # Chuyển đổi lịch sử Gradio sang định dạng LangChain Messages
116
+ lc_messages = []
117
+ for human, ai in history:
118
+ lc_messages.append(HumanMessage(content=human))
119
+ lc_messages.append(AIMessage(content=ai))
120
+ lc_messages.append(HumanMessage(content=message))
121
+
122
+ # Chạy luồng LangGraph
123
+ result = app_logic.invoke({"messages": lc_messages, "context": ""})
124
+
125
+ # Lấy tin nhắn cuối cùng (AIMessage) để hiển thị
126
+ return result["messages"][-1].content
127
+
128
+ # Giao diện web
129
+ demo = gr.ChatInterface(
130
+ fn=chat_interface,
131
+ title="⚕️ DeepMed-AI Agentic RAG",
132
+ description="""Trợ lý Y tế & Dược lý học ứng dụng kiến trúc Agentic RAG.
133
+ *Lưu ý: Hệ thống chỉ cung cấp thông tin tham khảo dựa trên tài liệu được nạp vào. Luôn tuân thủ chỉ định của bác sĩ.*""",
134
+ examples=["Liều dùng tối đa của Paracetamol là bao nhiêu?", "Triệu chứng của bệnh sốt xuất huyết là gì?"],
135
+ theme="soft"
136
+ )
137
+
138
+ if __name__ == "__main__":
139
+ demo.launch()