🏗️ Kiến trúc tổng quan
Chúng ta sẽ xây dựng một ứng dụng web nhỏ với các thành phần:
-
Giao diện người dùng (UI): Dùng Streamlit, một thư viện Python giúp tạo giao diện web rất nhanh và trực quan .
-
Xử lý tài liệu: Dùng LangChain (framework phổ biến cho ứng dụng LLM) để đọc PDF (
PyPDFLoader), chia nhỏ văn bản (RecursiveCharacterTextSplitter) . -
Lưu trữ và tìm kiếm: Dùng FAISS (Facebook AI Similarity Search) để tạo một “bộ nhớ vector”. Nó sẽ lưu các đoạn văn bản dưới dạng vector và tìm kiếm nhanh các đoạn liên quan đến câu hỏi của bạn .
-
Mô hình ngôn ngữ: Dùng Ollama để chạy model (ví dụ:
deepseek-r1,qwen2.5,llama3, …) và tạo câu trả lời .
Sơ đồ hoạt động sẽ như sau:
-
Bạn upload file PDF lên giao diện Streamlit.
-
LangChain trích xuất văn bản và chia thành các đoạn nhỏ (chunks).
-
Các đoạn này được chuyển thành vector (mã hóa) và lưu vào FAISS.
-
Bạn nhập câu hỏi.
-
Hệ thống dùng câu hỏi để tìm kiếm trong FAISS, lấy ra 3-5 đoạn văn bản liên quan nhất.
-
Câu hỏi và các đoạn văn bản này được gửi đến model qua Ollama.
-
Model tổng hợp thông tin và trả lời bạn, kèm theo nguồn trích dẫn .
📝 Hướng dẫn chi tiết các bước thực hiện
Bước 1: Chuẩn bị môi trường trên máy chủ Ollama (192.168.3.6)
Truy cập vào máy chủ Ollama của bạn qua SSH và thực hiện các lệnh sau.
1. Cài đặt Python và các thư viện cần thiết:
# Cập nhật package list sudo apt update # Cài đặt pip nếu chưa có sudo apt install python3-pip python3-venv -y # Tạo một thư mục cho dự án mkdir ~/pdf-chatbot && cd ~/pdf-chatbot # Tạo môi trường ảo Python (khuyến nghị để tránh xung đột thư viện) python3 -m venv venv source venv/bin/activate
2. Cài đặt các thư viện Python:
pip install streamlit langchain langchain-community pypdf faiss-cpu ollama sentence-transformers
-
streamlit: Tạo giao diện web. -
langchain,langchain-community: Framework chính để xây dựng pipeline RAG. -
pypdf: Đọc file PDF. -
faiss-cpu: Thư viện vector database (dùng CPU). -
ollama: Thư viện Python để kết nối tới Ollama đang chạy trên máy . -
sentence-transformers: Dùng để tạo embedding vector cho văn bản (thay thế chonomic-embed-textmà LangChain hay dùng, nhưng dùng thư viện này sẽ linh hoạt hơn). Chúng ta sẽ dùng modelall-MiniLM-L6-v2.
3. Đảm bảo Ollama đang chạy và có model:
Kiểm tra xem Ollama đã sẵn sàng chưa. Bạn cần chọn một model để dùng. Với cấu hình 8GB RAM, các model nhỏ như deepseek-r1:1.5b hoặc qwen2.5:latest là lựa chọn tốt.
# Kiểm tra xem service Ollama có chạy không sudo systemctl status ollama # Kiểm tra danh sách model đã có ollama list # Nếu chưa có model, hãy pull một model phù hợp (ví dụ: deepseek-r1:1.5b) ollama pull deepseek-r1:1.5b
Bước 2: Xây dựng ứng dụng Chat với PDF (Tạo file app.py)
Tạo một file tên là app.py trong thư mục ~/pdf-chatbot và copy toàn bộ nội dung sau vào. Các dòng code đã được chú thích rõ ràng để bạn dễ theo dõi .
# Import các thư viện cần thiết import streamlit as st from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS from langchain_community.llms import Ollama from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate import tempfile import os # --- Cấu hình trang Streamlit --- st.set_page_config(page_title="📄 Chat với tài liệu (PDF)", page_icon="📄") st.title("📄 Trò chuyện với tài liệu PDF của bạn") st.markdown("Upload file PDF và bắt đầu đặt câu hỏi về nội dung bên trong.") # --- Hàm xử lý tài liệu (được lưu vào cache của Streamlit để chạy lại nhanh) --- @st.cache_resource def process_document(file_path): """ Đọc file PDF, chia nhỏ văn bản, tạo embeddings và trả về vector store. """ with st.spinner("🔄 Đang xử lý tài liệu..."): # 1. Load PDF loader = PyPDFLoader(file_path) documents = loader.load() # 2. Chia nhỏ văn bản (chunking) text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, # Kích thước mỗi đoạn (khoảng 1000 ký tự) chunk_overlap=200, # Chồng lấn 200 ký tự để giữ ngữ cảnh length_function=len, separators=["\n\n", "\n", " ", ""] ) chunks = text_splitter.split_documents(documents) # 3. Tạo embeddings (vector hóa văn bản) embedding_model = HuggingFaceEmbeddings( model_name="all-MiniLM-L6-v2" # Model embedding nhẹ, chạy tốt trên CPU ) # 4. Tạo FAISS vector store vectorstore = FAISS.from_documents(chunks, embedding_model) st.success(f"✅ Đã xử lý xong {len(documents)} trang, chia thành {len(chunks)} đoạn.") return vectorstore # --- Khởi tạo model LLM từ Ollama --- @st.cache_resource def get_llm(): """ Kết nối tới Ollama và trả về model. """ # Thay 'deepseek-r1:1.5b' bằng model bạn đã pull return Ollama(model="deepseek-r1:1.5b", temperature=0.1) # --- Giao diện chính --- # 1. Upload file PDF uploaded_file = st.file_uploader("Chọn file PDF", type="pdf") if uploaded_file is not None: # Lưu file tạm thời with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file: tmp_file.write(uploaded_file.getvalue()) tmp_path = tmp_file.name # Xử lý tài liệu để lấy vector store vectorstore = process_document(tmp_path) llm = get_llm() # Tạo retrieval chain retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # Lấy 3 đoạn liên quan nhất # Tạo prompt để hướng dẫn model chỉ trả lời dựa trên tài liệu prompt_template = """ Hãy trả lời câu hỏi dựa CHỈ trên thông tin được cung cấp trong ngữ cảnh dưới đây. Nếu câu trả lời không có trong ngữ cảnh, hãy nói "Tôi không tìm thấy thông tin này trong tài liệu." Đừng thêm thông tin từ kiến thức của bạn. Ngữ cảnh: {context} Câu hỏi: {question} Trả lời: """ PROMPT = PromptTemplate( template=prompt_template, input_variables=["context", "question"] ) # Xây dựng chain QA qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=retriever, chain_type_kwargs={"prompt": PROMPT}, return_source_documents=True # Trả về nguồn để biết đoạn nào được dùng ) # Giao diện chat st.markdown("---") st.subheader("💬 Đặt câu hỏi") # Khởi tạo lịch sử chat trong session state if "messages" not in st.session_state: st.session_state.messages = [] # Hiển thị lịch sử chat for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # Input cho câu hỏi mới if prompt := st.chat_input("Nhập câu hỏi của bạn..."): # Thêm câu hỏi vào lịch sử và hiển thị st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) # Gửi câu hỏi đến QA chain và nhận kết quả with st.chat_message("assistant"): with st.spinner("Đang suy nghĩ..."): result = qa_chain.invoke({"query": prompt}) answer = result['result'] # source_docs = result['source_documents'] # Bạn có thể dùng để hiển thị nguồn st.markdown(answer) # Ví dụ: Hiển thị nguồn tham khảo (nếu muốn) # with st.expander("Xem nguồn tham khảo"): # for i, doc in enumerate(source_docs): # st.write(f"**Đoạn {i+1}:**") # st.write(doc.page_content) # st.write("---") # Thêm câu trả lời vào lịch sử st.session_state.messages.append({"role": "assistant", "content": answer}) # Xóa file tạm sau khi xử lý os.unlink(tmp_path) else: st.info("👆 Vui lòng upload một file PDF để bắt đầu.")
Giải thích một số điểm quan trọng trong code:
-
@st.cache_resource: Giúp lưu lại kết quả xử lý tài liệu và model. Nếu bạn upload lại cùng một file, hệ thống sẽ dùng lại kết quả cũ thay vì xử lý lại từ đầu, rất nhanh. -
RecursiveCharacterTextSplitter: Chia văn bản một cách thông minh, cố gắng giữ nguyên các đoạn văn, câu . -
HuggingFaceEmbeddings: Tạo ra các vector số học để máy tính có thể “hiểu” và so sánh nội dung của các đoạn văn bản . -
RetrievalQA: Một pipeline có sẵn của LangChain, kết hợp việc tìm kiếm (retriever) và sinh câu trả lời (llm). -
prompt_template: Phần này rất quan trọng để “ra lệnh” cho model chỉ được dùng thông tin trong tài liệu bạn cung cấp, tránh bịa đặt thông tin .
Bước 3: Chạy ứng dụng
Trong terminal, khi bạn đang ở trong môi trường ảo venv và thư mục ~/pdf-chatbot, hãy chạy lệnh:
streamlit run app.py
Bạn sẽ thấy output tương tự:
You can now view your Streamlit app in your browser. Local URL: http://localhost:8501 Network URL: http://192.168.3.6:8501
Giờ bạn có thể mở trình duyệt trên bất kỳ máy nào trong mạng LAN (ví dụ máy Windows của bạn) và truy cập vào địa chỉ http://192.168.3.6:8501 để bắt đầu trò chuyện với tài liệu PDF của mình.