1. Cơn ác mộng xử lý tài liệu khi build pipeline RAG
Tháng trước, team mình nhận một đề bài quen thuộc: dựng chatbot hỏi đáp nội bộ cho khối Vận hành và Tài chính. Kho dữ liệu khách hàng giao cho team là một thư mục Google Drive nặng gần 15 GB, chứa hơn 400 file đủ mọi định dạng:
- Báo cáo tài chính lưu bằng file
.xlsxvới 15-20 sheet và bảng biểu chằng chịt. - Quy trình vận hành chuẩn viết trên Word
.docxchứa bảng tra cứu và danh sách gạch đầu dòng lồng nhau 4-5 cấp. - Slide đào tạo nhân sự định dạng PowerPoint
.pptx. - Tài liệu chính sách xuất dưới dạng
.pdfhỗn hợp (cả text gốc lẫn scan).
Ban đầu, cả team chọn giải pháp “cây nhà lá vườn”. Anh em ghép nối các thư viện quen mặt như pypdf, python-docx, openpyxl để bóc raw text rồi đẩy thẳng vào text splitter. Kết quả thử nghiệm khiến tất cả thất vọng.
Khi hỏi thử: “Hạn mức công tác phí khách sạn của Tech Lead tại Hà Nội là bao nhiêu?”, quy chế gốc ghi rõ 1.200.000 VNĐ/đêm. Thế nhưng bot lại trả lời 800.000 VNĐ, trích nhầm sang hạn mức của nhân viên thử việc.
Mở vector database kiểm tra context được retrieve, vấn đề lộ ra ngay. Toàn bộ bảng phụ cấp trong Excel bị bóc thành một chuỗi text dính liền, mất sạch header cột và hàng. Bộ chia chunk cắt đôi hàng dữ liệu ngay giữa mức tiền và chức vụ. Mất ngữ cảnh, mô hình bắt đầu bịa thông tin.
2. Vì sao text thô làm sụp đổ độ chính xác của RAG?
Nhiều bạn mới làm RAG thường nghĩ chỉ cần trích xuất được chữ ra khỏi file là đủ. Thực tế, mô hình ngôn ngữ lớn (LLM) cần cấu trúc ngữ nghĩa để hiểu đúng mối liên kết giữa các mẩu thông tin.
Text thô phá vỡ pipeline RAG ở 3 điểm:
- Mất cấu trúc phân cấp: Tiêu đề mục (H1, H2) và bullet points giúp LLM định vị phạm vi hiệu lực của quy định. Khi biến thành một khối text phẳng, LLM không thể phân biệt điều khoản chung với quy định ngoại lệ.
- Bảng dữ liệu bị san phẳng: Một ô số liệu chỉ có ý nghĩa khi gắn với tên cột và tên hàng. Chuyển đổi cẩu thả sẽ biến ma trận dữ liệu hai chiều thành danh sách con số vô nghĩa.
- Chunking bị đứt gãy: Các bộ cắt văn bản như
RecursiveCharacterTextSplitterhoạt động tốt nhất khi bám theo cú pháp Markdown (như#,##, hàng bảng|---|). Mất các mốc này, văn bản bị chém ngẫu nhiên theo số ký tự. Hậu quả là thông tin quan trọng bị xé đôi giữa hai chunk.
3. Hai ngõ cụt thường gặp khi tự xử lý tài liệu
Trước khi tìm ra MarkItDown, team mình đã thử qua hai hướng phổ biến:
Hướng 1: Tự viết custom parser bằng Python
Tự viết script riêng: dùng pdfplumber cho PDF, python-docx cho Word và pandas cho Excel. Cách này ban đầu tưởng nhanh, nhưng mã nguồn nhanh chóng phình lên hơn 1.200 dòng. Hàng loạt lỗi biên (edge cases) liên tục xuất hiện: ô bị merge trong Excel, bảng lồng bảng trong Word hay chữ lệch dòng trong PDF. Chi phí bảo trì biến thành gánh nặng lớn cho dự án.
Hướng 2: Gọi API parsing thương mại (Unstructured, LlamaParse)
Chất lượng bóc tách của các giải pháp SaaS này rất tốt. Dù vậy, team vướng hai rào cản lớn: chi phí và bảo mật. Với kho tài liệu vài chục nghìn trang, hóa đơn API mỗi tháng đội lên đáng kể. Nghiêm trọng hơn, chính sách bảo mật của khách hàng cấm tuyệt đối việc đẩy dữ liệu tài chính nội bộ ra server bên ngoài.
4. Chuẩn hóa dữ liệu với Microsoft MarkItDown
Vào cuối năm 2024, Microsoft giới thiệu MarkItDown dưới dạng mã nguồn mở. Thư viện Python này chuyên xử lý một bài toán cụ thể: gom các định dạng phổ biến (PDF, DOCX, XLSX, PPTX, HTML, CSV, JSON) về một định dạng chuẩn duy nhất là Markdown.
Tại sao lại là Markdown? Thứ nhất, định dạng này giữ trọn vẹn heading và cấu trúc bảng dạng | Col1 | Col2 |. Thứ hai, Markdown cực kỳ tiết kiệm token so với HTML hay JSON, đồng thời thân thiện tối đa với bộ nhận diện ngữ cảnh của LLM.
Cài đặt thư viện
Bạn có thể cài đặt bản tiêu chuẩn hoặc bản mở rộng tùy nhu cầu:
# Bản cơ bản cho file văn phòng thông dụng
pip install markitdown
# Bản đầy đủ nếu cần thêm OCR cho ảnh và trích xuất audio
pip install "markitdown[all]"
Chuyển đổi tài liệu cơ bản
Cú pháp khởi tạo rất gọn. Bạn chỉ cần gọi class MarkItDown và thực thi hàm convert():
from markitdown import MarkItDown
md = MarkItDown()
# Bóc tách file quy chế Word (.docx)
result_docx = md.convert("quy-che-cong-tac-phi.docx")
print(result_docx.text_content[:300])
# Chuyển bảng tính Excel (.xlsx) sang bảng Markdown
result_xlsx = md.convert("bang-gia-dich-vu.xlsx")
print(result_xlsx.text_content)
Đầu ra của bảng tính Excel được chuyển thành dạng bảng Markdown chuẩn, giữ nguyên tiêu đề cột và giá trị từng dòng:
| Ma_DV | Ten_Dich_Vu | Gia_Goi_Thang | Ghi_Chu |
| --- | --- | --- | --- |
| SRV01 | Cloud VPS Basic | 150000 | Ram 2GB, 1 Core |
| SRV02 | Cloud VPS Pro | 350000 | Ram 4GB, 2 Core |
Xây dựng Pipeline xử lý hàng loạt file
Trong thực tế, bạn sẽ cần duyệt qua cả thư mục chứa hàng trăm file tài liệu, chuyển đổi đồng loạt sang .md rồi mới nạp vào Vector Database:
from pathlib import Path
from markitdown import MarkItDown
def batch_convert_to_markdown(source_dir: str, target_dir: str):
md = MarkItDown()
valid_extensions = {".pdf", ".docx", ".xlsx", ".pptx", ".csv", ".html"}
src = Path(source_dir)
dst = Path(target_dir)
dst.mkdir(parents=True, exist_ok=True)
for file_path in src.iterdir():
if file_path.suffix.lower() not in valid_extensions:
continue
try:
print(f"[+] Đang xử lý: {file_path.name}")
parsed = md.convert(str(file_path))
output_file = dst / f"{file_path.stem}.md"
output_file.write_text(parsed.text_content, encoding="utf-8")
print(f"[OK] Đã lưu: {output_file.name}")
except Exception as err:
print(f"[!] Lỗi khi xử lý {file_path.name}: {err}")
if __name__ == "__main__":
batch_convert_to_markdown("./raw_docs", "./cleaned_markdown")
Tích hợp Vision Model mô tả sơ đồ và hình ảnh
Một điểm sáng của MarkItDown là khả năng gắn kèm client OpenAI hoặc Azure OpenAI. Khi xử lý slide PPTX chứa sơ đồ hạ tầng, thư viện sẽ tự động gọi mô hình vision để tóm tắt nội dung ảnh thành lời văn và chèn trực tiếp vào file Markdown:
import os
from openai import OpenAI
from markitdown import MarkItDown
# Cấu hình LLM Client
ai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
# Khởi tạo MarkItDown kèm model GPT-4o để đọc ảnh
md_vision = MarkItDown(llm_client=ai_client, llm_model="gpt-4o")
# Chuyển đổi slide PowerPoint chứa diagram kỹ thuật
result = md_vision.convert("so-do-ha-tang.pptx")
output_path = Path("so-do-ha-tang.md")
output_path.write_text(result.text_content, encoding="utf-8")
Kinh nghiệm thực chiến trên môi trường Production
- Cắt nhỏ file Excel quá lớn: Với file bảng tính trên 2.000 dòng, bảng Markdown sinh ra sẽ làm tràn context window (vượt ngưỡng 8k-16k token). Bạn nên lọc theo sheet hoặc chia nhỏ dữ liệu trước khi convert.
- Đừng phó mặc cho parser khi gặp PDF scan: MarkItDown bóc text PDF native rất sạch. Tuy nhiên, với tài liệu scan mờ hoặc hóa đơn chụp nghiêng, bạn vẫn cần pipeline OCR chuyên dụng (như PaddleOCR hoặc Tesseract) để xử lý ảnh trước.
- Bổ sung YAML Frontmatter: Trước khi lưu file
.md, hãy chèn thêm metadata vào đầu file (đường dẫn gốc, phòng ban ban hành, ngày cập nhật). Khi truy vấn, retriever có thể lọc qua metadata filter trước khi vector search, giúp tăng tốc độ và độ chuẩn xác rõ rệt.
Sau khi đổi toàn bộ khâu chuẩn bị dữ liệu sang MarkItDown, tỷ lệ trả lời đúng số liệu chi tiết trong dự án của team mình tăng từ 68% lên hơn 92%. Một công cụ nhỏ, chạy gọn trên máy nội bộ, nhưng giải quyết triệt để bài toán rác dữ liệu trước khi đưa vào mô hình.
