Spaces:
Build error
Build error
Create app.py
Browse files
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()
|