How to Create a Personalized RAG Chatbot from Scratch
- Published on
- • 10 mins read•--- views
Tại sao lại là RAG?
- Giới thiệu khái niệm Retrieval-Augmented Generation (RAG).
- Vấn đề của LLMs khi không có thông tin thời gian thực hoặc dữ liệu cụ thể.
- Giải pháp RAG: kết hợp khả năng sinh văn bản của LLM với hệ thống truy xuất dữ liệu từ nguồn bên ngoài.
- Ứng dụng thực tế của RAG: chatbot chăm sóc khách hàng, tư vấn kiến thức chuyên ngành, hệ thống nội bộ doanh nghiệp,...
- Trong project này, mục tiêu là tạo một chatbot có khả năng trả lời các câu hỏi xoay quanh một chủ đề cá nhân (ví dụ: blog của bạn, sản phẩm cá nhân, kiến thức chuyên sâu,...)
Tổng quan dự án
- Mô tả luồng hoạt động tổng thể của chatbot RAG:
- Nhận câu hỏi từ người dùng
- Dùng embedding để tìm các đoạn văn bản liên quan trong tập dữ liệu cá nhân
- Đưa kết quả truy xuất + câu hỏi vào LLM để tạo câu trả lời
- Giới thiệu công nghệ sử dụng:
- Python
- LangChain (quản lý pipeline RAG)
- Hugging Face Transformers (mô hình ngôn ngữ + embeddings)
- FAISS hoặc ChromaDB (vector store)
- Streamlit (tạo giao diện web đơn giản)
Các bước xây dựng dự án
Bước 1: Cài đặt môi trường và thư viện cần thiết
Đối với Google Colab: Nếu bạn sử dụng Google Colab, chỉ cần chạy các lệnh sau để cài đặt các thư viện cần thiết:
!pip install -q transformers==4.52.4 !pip install -q bitsandbytes==0.46.0 !pip install -q accelerate==1.7.0 !pip install -q langchain==0.3.25 !pip install -q langchainhub==0.1.21 !pip install -q langchain-chroma==0.2.4 !pip install -q langchain_experimental==0.3.4 !pip install -q langchain-community==0.3.24 !pip install -q langchain_huggingface==0.2.0 !pip install -q python-dotenv==1.1.0 !pip install -q pypdf
Đối với môi trường local: Nếu bạn chạy trên máy local, cần thiết lập môi trường Conda trước. Dưới đây là một file
environment.yaml
mẫu để tạo môi trường:name: myenv channels: - pytorch - nvidia - conda-forge - defaults dependencies: - python=3.12 - numpy - pytorch=2.2.2 - pytorch-cuda=11.8
Để tạo môi trường từ file YAML này, lưu nội dung vào file
environment.yaml
, sau đó chạy lệnh:conda env create -f environment.yaml
Sau khi môi trường được tạo, kích hoạt môi trường bằng lệnh:
conda activate myenv
Tiếp theo, cài đặt các thư viện cần thiết bằng lệnh:
pip install -r requirements.txt
Nội dung file
requirements.txt
nên bao gồm:transformers==4.52.4 bitsandbytes==0.46.0 accelerate==1.7.0 langchain==0.3.25 langchainhub==0.1.21 langchain-chroma==0.2.4 langchain_experimental==0.3.4 langchain-community==0.3.24 langchain_huggingface==0.2.0 python-dotenv==1.1.0 pypdf
Sau khi cài đặt xong môi trường thì xin chúc mừng bạn đã vượt qua cửa ải đầu tiên.

Bước 2: Xây dựng hệ thống lưu trữ các vector embedding
Import các thư viện cần thiết: Đầu tiên, bạn cần import các thư viện để xây dựng hệ thống:
import torch from transformers import BitsAndBytesConfig from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline from langchain_huggingface import HuggingFaceEmbeddings from langchain_huggingface.llms import HuggingFacePipeline from langchain.memory import ConversationBufferMemory from langchain_community.chat_message_histories import ChatMessageHistory from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain.chains import ConversationalRetrievalChain from langchain_experimental.text_splitter import SemanticChunker from langchain_chroma import Chroma from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser from langchain import hub
Đọc file PDF: Bạn hãy chuẩn bị và upload một file PDF tầm 20 trang trở lên để test khi xây dựng hệ thống. Sau đó, sử dụng class
PyPDFLoader
để đọc file PDF này lên như sau:Loader = PyPDFLoader FILE_PATH = "ten_file_cua_ban.pdf" loader = Loader(FILE_PATH) documents = loader.load()
Embedding model: Là mô hình giúp vector hóa các văn bản nhằn thực hiện việc tính toán và lưu trữ, dưới đây là cách sử dụng HuggingFaceEmbeddings với mô hình bkai-foundation-models/vietnamese-bi-encoder được train để hiểu ngữ nghĩa với tiếng Việt.
EMBEDDING_MODEL_NAME = "bkai-foundation-models/vietnamese-bi-encoder" embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL_NAME)
- Chunking: Tách văn bản dài thành các đoạn ngắn hơn thay vì sử dụng các phương pháp truyền thống chỉ tách các văn bản theo độ dài thì mình sẽ sử dụng kỹ thuật Semantic Chunking để tách nhưng sẽ dựa vào mức độ ngữ nghĩa để tránh làm vỡ mạch văn và chia nhỏ các đoạn có cùng một chủ đề. Dưới đây là cách sử dụng SemanticChunker để triển khai:
semantic_splitter = SemanticChunker( embeddings=embeddings, buffer_size=1, breakpoint_threshold_type="percentile", breakpoint_threshold_amount=95, min_chunk_size=500, add_start_index=True )
Giải thích các tham số:
embeddings
: Đây là mô hình embedding được sử dụng để tính toán ngữ nghĩa của văn bản. Mô hình này giúp xác định mức độ liên quan về mặt nội dung giữa các câu hoặc đoạn văn, từ đó hỗ trợ việc tách đoạn một cách chính xác hơn.buffer_size
: Tham số này xác định số lượng câu hoặc đoạn văn được giữ trong bộ đệm để phân tích ngữ nghĩa. Với giá trị1
có nghĩa là 1 nhóm gồm 1 câu.breakpoint_threshold_type
: Đây là loại ngưỡng được sử dụng để xác định điểm cắt giữa các đoạn. Giá trị"percentile"
cho biết ngưỡng được tính dựa trên tỉ lệ phần trăm của sự khác biệt ngữ nghĩa giữa các câu hoặc đoạn.breakpoint_threshold_amount
: Với giá trị95
, ngưỡng cắt được đặt ở phân vị thứ 95. Điều này có nghĩa là chỉ khi sự khác biệt về ngữ nghĩa giữa hai câu hoặc đoạn vượt quá 95% các giá trị khác biệt khác thì điểm cắt mới được tạo.min_chunk_size
: Đây là kích thước tối thiểu của một đoạn văn sau khi cắt, được đặt là500
ký tự. Tham số này đảm bảo rằng các đoạn không bị chia quá nhỏ, giúp duy trì ý nghĩa và mạch văn.add_start_index
: Khi được đặt làTrue
, tham số này thêm chỉ số bắt đầu vào metadata của mỗi đoạn. Điều này hữu ích để theo dõi vị trí của đoạn trong văn bản gốc, đặc biệt khi cần tham chiếu ngược lại.- Thực hiện chia tài liệu:
documents = semantic_splitter.split_documents(documents)
Vector database: Nhằm lưu trữ các chunk ở dưới dạng các vector phục vụ trong quá trình truy vấn. Dùng
Chroma
, embedding model và input là documents để khởi tạo vector database.# khởi tạo vector database vec_db = Chroma.from_documents(documents=documents, embedding=embeddings) # giúp thực hiện truy vấn retriever = vec_db.as_retriever()
- Cách thức truy vấn:
result = retriever.invoke('Anya-chan là ai?') # Trả về 1 list các tài liệu có liên quan đến prompt
Bước 3: Kết nối với mô hình LLM
- Sử dụng model: trong dụ án của mình mình dùng model openchat-3.5-0106 làm LLM chính cho chatbot.
- Khởi tạo model:
# config giúp load model tốt hơn
nf4_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
# model và tokenizer
MODEL_NAME = "openchat/openchat-3.5-0106"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME,
quantization_config=nf4_config,
low_cpu_mem_usage=True
)
- Tạo 1 pipeline để sử dụng:
pipeline = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
max_new_tokens=256,
device_map="auto",
pad_token_id=tokenizer.eos_token_id,
)
llm = HungingFacePipeline(pipeline=pipeline)
- Kết hợp lại với LangChain:
# Kéo prompt từ hub
prompt = hub.pull("rlm/rag-prompt")
# Hàm định dạng tài liệu để sử dụng trong chuỗi
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# Xây dựng chuỗi RAG
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# Cách chạy chuỗi RAG để trả lời câu hỏi
result = rag_chain.invoke("Anya-chan là ai?")
print(result)
Giải thích đoạn mã:
prompt = hub.pull("rlm/rag-prompt")
: Kéo một prompt được định nghĩa sẵn từ LangChain Hub cho các ứng dụng RAG.format_docs
: Định dạng các tài liệu được truy xuất thành một chuỗi văn bản duy nhất.rag_chain
: Kết hợp các thành phần để truy xuất tài liệu, định dạng chúng, sử dụng prompt, gửi đến LLM và chuyển đổi đầu ra thành chuỗi văn bản.rag_chain.invoke()
: Chạy chuỗi với một câu hỏi cụ thể và trả về câu trả lời từ LLM dựa trên ngữ cảnh được truy xuất.
- 🎊Chúc mừng! Bạn vừa đạt được một bước tiến mới🎉 (Mở khóa kỹ năng mới - Tập trung liên tục)
Bước 4: Xây dựng giao diện với Streamlit
- Streamlit là gì?: Đây là thư viện Python giúp tạo giao diện web tương tác nhanh chóng. Với chatbot RAG, Streamlit sẽ hỗ trợ xây dựng giao diện để upload file PDF, hiển thị lịch sử chat và nhập câu hỏi.
- Cấu trúc cơ bản: Giao diện gồm sidebar để cài đặt và upload file, cùng khu vực chính hiển thị cuộc trò chuyện.
- Khởi tạo trạng thái: Sử dụng
st.session_state
để lưu trữ thông tin như lịch sử chat và trạng thái mô hình:if 'rag_chain' not in st.session_state: st.session_state.rag_chain = None if 'chat_history' not in st.session_state: st.session_state.chat_history = [] if 'pdf_processed' not in st.session_state: st.session_state.pdf_processed = False
- Tải mô hình hiệu quả: Dùng
@st.cache_resource
để tránh tải lại mô hình mỗi lần ứng dụng rerun:@st.cache_resource def load_embeddings(): return HuggingFaceEmbeddings( "bkai-foundation-models/vietnamese-bi-encoder" ) @st.cache_resource def load_llm(): bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4" ) model = AutoModelForCausalLM.from_pretrained( "openchat/openchat-3.5-0106", quantization_config=bnb_config ) tokenizer = AutoTokenizer.from_pretrained( "openchat/openchat-3.5-0106" ) return HuggingFacePipeline( pipeline=pipeline( "text-generation", model=model, tokenizer=tokenizer, max_new_tokens=512 ) )
- Xử lý PDF: Hàm
process_pdf
để đọc file, chia nhỏ và tạo chuỗi RAG:def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) def process_pdf(uploaded): loader = PyPDFLoader(uploaded) docs = loader.load() semantic_splitter = SemanticChunker( embeddings=st.session_state.embeddings ) docs = semantic_splitter.split_documents(docs) vector_db = Chroma.from_documents( docs, embedding=st.session_state.embeddings ) retriever = vector_db.as_retriever() rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | hub.pull("rlm/rag-prompt") | st.session_state.llm | StrOutputParser() ) return rag_chain, len(docs)
- Giao diện chính: Thiết lập bố cục với sidebar và khu vực chat:
def main(): st.set_page_config( page_title="PDF RAG Chatbot", layout="wide" ) st.title("PDF RAG Assistant") with st.sidebar: st.title("⚙️ Cài đặt") if st.button("🔄 Xử lý PDF"): st.session_state.rag_chain, num_chunks = process_pdf(uploaded) st.session_state.pdf_processed = True st.subheader("📋 Hướng dẫn") st.markdown("1. Upload PDF\n2. Đặt câu hỏi\n3. Nhận trả lời") user_input = st.chat_input("Nhập câu hỏi...") if user_input and st.session_state.pdf_processed: with st.chat_message("assistant"): output = st.session_state.rag_chain.invoke(user_input) st.write(output)
- Tóm tắt: Giao diện Streamlit giúp người dùng dễ dàng tương tác với chatbot, upload tài liệu và nhận câu trả lời dựa trên nội dung PDF.
Summary
- Tóm tắt lại quá trình:
- Thu thập dữ liệu → tạo embedding → truy xuất văn bản → sinh câu trả lời → xây dựng giao diện với Streamlit.
- Những công nghệ đã học được qua bài:
- LangChain để quản lý pipeline RAG.
- Vector DB (Chroma) để lưu trữ và truy xuất dữ liệu.
- Transformers để sử dụng mô hình ngôn ngữ và embeddings.
- Streamlit để tạo giao diện web tương tác.
- Summary về ứng dụng Streamlit:
- Ứng dụng Streamlit cung cấp một giao diện thân thiện để tương tác với chatbot RAG, cho phép người dùng upload file PDF, đặt câu hỏi và nhận câu trả lời dựa trên nội dung tài liệu. Ứng dụng tích hợp các tính năng như quản lý lịch sử chat, xử lý tài liệu theo thời gian thực và phản hồi từ AI.
🎉 Chúc mừng bạn đã đọc xong bài viết!
