Một chatbot thông minh sử dụng RAG (Retrieval-Augmented Generation) để trả lời câu hỏi chỉ dựa trên nội dung PDF được upload, không bịa thông tin, không dùng kiến thức bên ngoài.
Xây dựng một chatbot thông minh có thể:
- ✅ Upload bất kỳ file PDF (giáo trình, báo cáo tài chính, sổ tay sản phẩm, tài liệu kỹ thuật...)
- ✅ Đọc hiểu nội dung từ PDF bằng PyMuPDF (fitz)
- ✅ Trả lời câu hỏi chỉ dựa trên tài liệu đó, không bịa thông tin
- ✅ Không dùng kiến thức bên ngoài → RAG thực sự, đảm bảo tính chính xác
- ✅ Giao diện web thân thiện với Streamlit
- ✅ Hỗ trợ nhiều LLM models qua OpenRouter API
┌─────────────┐
│ PDF File │
└──────┬──────┘
│
▼
┌─────────────────┐ ┌──────────────────┐
│ PDF Loader │─────▶│ Text Splitter │
│ (PyMuPDF/fitz) │ │ (Recursive) │
└─────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ Embeddings │
│ (OpenRouter API)│
│ Qwen3-Embedding │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Vector Store │
│ (Chroma DB) │
└────────┬─────────┘
│
▼
┌──────────────┐ ┌──────────────────┐
│ User Query │─────────▶│ Retriever │
└──────────────┘ │ (Similarity) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ LLM Chain │
│ (OpenRouter) │
│ + RAG Prompt │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Answer │
└──────────────────┘
- PDF Processing: PyMuPDF (fitz) để trích xuất text từ PDF
- Text Chunking: RecursiveCharacterTextSplitter với overlap để giữ context
- Embedding: OpenRouter API với Qwen3-Embedding-0.6B model
- Vector Store: Chroma DB để lưu trữ và tìm kiếm vectors
- Retrieval: Semantic search để tìm context liên quan
- Generation: LLM qua OpenRouter (hỗ trợ nhiều models: Qwen, GPT-4, Claude...)
| Công nghệ | Phiên bản | Mục đích |
|---|---|---|
| Python | 3.11+ | Ngôn ngữ lập trình chính |
| Streamlit | Latest | Web UI framework |
| LangChain | 0.1+ | RAG framework và orchestration |
| Chroma | Latest | Vector database |
| PyMuPDF (fitz) | Latest | PDF text extraction |
| OpenRouter API | v1 | Unified API cho embeddings và LLMs |
-
OpenRouter:
- Unified API cho nhiều LLM providers (OpenAI, Anthropic, Qwen, Meta...)
- Hỗ trợ cả embedding và chat models
- Dễ dàng switch models mà không cần đổi code
- Cost-effective với nhiều free models
-
Chroma:
- Lightweight, embedded vector database
- Không cần server riêng, persist local
- Tích hợp tốt với LangChain
- Performance tốt cho small-medium datasets
-
PyMuPDF (fitz):
- Fast và accurate PDF text extraction
- Hỗ trợ tốt các loại PDF (text-based, scanned với OCR)
- Python-native, dễ tích hợp
-
Streamlit:
- Rapid prototyping và deployment
- Built-in components cho file upload, chat interface
- Không cần frontend knowledge
- Upload PDF: Drag & drop hoặc click để chọn file
- Chat Interface: Giao diện chat giống ChatGPT
- Model Selection: Chọn LLM model trong sidebar
- Status Display: Hiển thị trạng thái PDF và API key
- Chat History: Lưu lịch sử chat trong session
- Custom Embeddings: Custom class để tích hợp OpenRouter embeddings
- Error Handling: Comprehensive error handling với thông báo rõ ràng
- Vector Store Persistence: Lưu vector store để tái sử dụng
- Configurable: Dễ dàng đổi models, chunk size, search parameters
- Type Safety: Type hints đầy đủ cho code maintainability
- Unit tests cho tất cả modules
- Test fixtures và utilities
- Skip tests khi không có API key
- Coverage reporting
- Python 3.11 hoặc cao hơn
- pip package manager
- OpenRouter API key (Lấy tại đây)
git clone <repository-url>
cd ai_chatbot_pdf# Windows
python -m venv venv
venv\Scripts\activate
# Linux/Mac
python3 -m venv venv
source venv/bin/activatepip install -r requirements.txtTạo file .env trong thư mục gốc project:
OPENROUTER_API_KEY=your_api_key_hereLưu ý:
- Lấy API key tại OpenRouter Dashboard
- Bật "Allow Free Models" và "Allow Paid Models" trong API key settings
- Chọn providers phù hợp (Qwen cho embedding model)
streamlit run app.pyỨng dụng sẽ mở tại http://localhost:8501
- Click vào sidebar → "Upload PDF"
- Chọn file PDF từ máy tính
- Click "📥 Tải PDF lên"
- Đợi hệ thống xử lý (extract text → chunking → embedding → vector store)
- Nhập câu hỏi vào ô chat ở cuối trang
- Nhấn Enter hoặc click gửi
- Hệ thống sẽ:
- Tìm context liên quan trong PDF
- Gửi context + question đến LLM
- Trả về câu trả lời dựa trên PDF
- Vào sidebar → "⚙️ Cấu hình"
- Nhập tên model mới vào ô "Model LLM"
- Các model khả dụng:
qwen/qwen-2.5-72b-instruct(Qwen - mạnh)qwen/qwen-2.5-32b-instruct(Qwen - nhỏ hơn)openai/gpt-4o(OpenAI)anthropic/claude-3.5-sonnet(Anthropic)- Xem thêm tại OpenRouter Models
- "Tóm tắt nội dung chính của tài liệu này"
- "Giải thích khái niệm X trong PDF"
- "Liệt kê các bước thực hiện Y"
- "So sánh A và B trong tài liệu"
ai_chatbot_pdf/
│
├── app.py # Streamlit app chính
├── requirements.txt # Python dependencies
├── README.md # Tài liệu này
├── .env # Environment variables (tạo mới)
│
├── modules/ # Core modules
│ ├── config.py # Configuration và environment variables
│ ├── pdf_loader.py # PDF loading và text splitting
│ ├── embedding.py # Embedding với OpenRouter API
│ ├── retriever.py # Vector store retrieval
│ └── llm_response.py # LLM response generation
│
├── prompts/ # Prompt templates
│ └── rag_prompt_universal.txt # RAG prompt template
│
├── data/ # Data storage
│ ├── uploaded_docs/ # PDF files đã upload
│ └── vector_store/ # Chroma vector database
│
└── tests/ # Unit tests
├── conftest.py # Test fixtures
├── test_config.py # Tests cho config
├── test_pdf_loader.py # Tests cho PDF loader
├── test_embedding.py # Tests cho embedding
├── test_retriever.py # Tests cho retriever
└── test_llm_response.py # Tests cho LLM response
Chức năng: Trích xuất text từ PDF và chia thành chunks
Key Functions:
load_pdf(file_path): Trích xuất text từ PDF bằng PyMuPDFsplit_text(text, chunk_size, chunk_overlap): Chia text thành chunks với overlapload_and_split_pdf(file_path): Wrapper function kết hợp cả 2
Kỹ thuật:
- Sử dụng
RecursiveCharacterTextSplitterđể giữ context - Chunk overlap để không mất thông tin ở ranh giới
- Xử lý lỗi khi PDF không đọc được
Chức năng: Tạo embeddings cho text chunks
Key Classes/Functions:
OpenRouterEmbeddings: Custom embeddings class cho OpenRouter APIcreate_chroma_vectorstore(): Tạo và lưu vector storeget_chroma_vectorstore(): Load vector store đã có
Kỹ thuật:
- Custom
Embeddingsclass implement LangChain interface - Sử dụng OpenAI client với OpenRouter base URL
- Error handling chi tiết với troubleshooting guide
- Auto-recovery khi dimension mismatch (xóa collection cũ)
Chức năng: Load retriever từ vector store
Key Functions:
load_retriever(): Tạo retriever với configurable search parameters
Kỹ thuật:
- Sử dụng
as_retriever()vớisearch_kwargs - Default
k=4documents được retrieve - Tự động tạo thư mục nếu chưa tồn tại
Chức năng: Tạo câu trả lời từ LLM dựa trên retrieved context
Key Functions:
get_llm_response(): Main function tạo responseformat_documents(): Format documents thành string cho prompt
Kỹ thuật:
- LangChain chain:
retriever → format → prompt → model → parser - Custom RAG prompt để đảm bảo chỉ trả lời dựa trên context
- Comprehensive error handling cho API errors
- Validate retriever trước khi gọi LLM
Chức năng: Centralized configuration
Key Features:
- Load environment variables từ
.env - Path resolution cho cross-platform compatibility
- Debug mode để kiểm tra API key loading
# Tất cả tests
pytest
# Với coverage report
pytest --cov=modules --cov-report=html
# Một test file cụ thể
pytest tests/test_embedding.py
# Một test cụ thể
pytest tests/test_config.py::TestConfig::test_data_dir_exists- Unit tests cho mỗi module
- Fixtures trong
conftest.pyđể tái sử dụng - Skip tests tự động nếu không có API key
- Temporary directories để tránh ảnh hưởng dữ liệu thực
Xem chi tiết tại tests/README.md
Giải pháp:
- Kiểm tra file
.envcó trong thư mục gốc project - Format đúng:
OPENROUTER_API_KEY=your_key_here(không có dấu cách quanh=) - Restart Streamlit app sau khi sửa
.env
Giải pháp:
- Vào OpenRouter API Keys
- Chọn API key → Edit
- Bật "Allow Free Models" và "Allow Paid Models"
- Chọn providers phù hợp:
- Qwen cho
.... - OpenAI cho
....
- Qwen cho
- Save changes và thử lại
Nguyên nhân có thể:
- Câu hỏi không khớp với nội dung PDF
- PDF chưa được xử lý đúng (thử upload lại)
- Vector store bị lỗi (xóa
data/vector_store/và upload lại)
Giải pháp:
- Thử câu hỏi khác, cụ thể hơn
- Upload lại PDF
- Xóa vector store và tạo lại
Nguyên nhân:
- Model không được phép truy cập
- API key chưa có quyền cho model đó
- Model không khả dụng
Giải pháp:
- Kiểm tra API key settings (xem trên)
- Thử model khác (ví dụ:
...) - Kiểm tra model có khả dụng tại OpenRouter Models
Nguyên nhân:
- PDF bị mã hóa/password protected
- PDF là scanned images (cần OCR)
- PDF corrupted
Giải pháp:
- Unlock PDF nếu có password
- Sử dụng PDF có text layer (không phải scanned)
- Thử PDF khác
- Chunk size: 500-1000 ký tự tùy loại tài liệu
- Tài liệu kỹ thuật: 500-700
- Tài liệu văn bản: 800-1000
- Overlap: 10-20% của chunk size để giữ context
- Embedding:
... - LLM:
- Free tier:
... - Paid:
...
- Free tier:
- k (number of documents): 3-5 documents thường đủ
- Tăng k nếu câu hỏi cần nhiều context
- Giảm k nếu muốn response nhanh hơn
- Prompt trong
prompts/rag_prompt_universal.txtđược thiết kế để:- Chỉ trả lời dựa trên context
- Không bịa thông tin
- Trả lời tự nhiên, không nhắc đến "theo PDF"
MIT License - Xem LICENSE file để biết chi tiết.
- LangChain - RAG framework
- OpenRouter - Unified LLM API
- Chroma - Vector database
- Streamlit - Web framework
- PyMuPDF - PDF processing
Nếu có câu hỏi hoặc góp ý, vui lòng tạo issue hoặc pull request.
Made with ❤️ using Python, LangChain, OpenRouter, and Streamlit