Chunking là khâu quyết định chất lượng RAG nhiều nhất nhưng lại hay bị làm ẩu. Nếu chunk quá to, retrieval trả về đoạn dài loãng thông tin; nếu quá nhỏ, mất ngữ cảnh, câu trả lời rời rạc. Bài này dạy cắt tài liệu để mỗi chunk là một "đơn vị ý nghĩa" trọn vẹn.
Nguyên tắc vàng
Một chunk tốt = đủ ngữ cảnh để tự trả lời một câu hỏi, nhưng không lẫn nhiều chủ đề. Mục tiêu: khi retrieval kéo chunk này ra, LLM đọc là đủ hiểu.
Các chiến lược chunking
- Fixed-size (theo số token): cắt mỗi 300–500 token. Đơn giản, nhanh, nhưng cắt giữa câu, giữa ý.
- Theo cấu trúc (structure-aware): cắt theo tiêu đề, đoạn, mục — giữ nguyên ranh giới ngữ nghĩa. Tốt nhất cho tài liệu có heading.
- Recursive: thử cắt theo đoạn trước, nếu vẫn dài thì cắt theo câu, cuối cùng mới cắt cứng.
- Semantic chunking: dùng embedding phát hiện chỗ chủ đề đổi để cắt. Chất lượng cao, tốn tính toán.
Overlap — chồng lấn giữa các chunk
Để tránh mất ngữ cảnh ở ranh giới, cho các chunk chồng lấn 10–20%. Ví dụ chunk 500 token, overlap 75 token: câu bị cắt ở cuối chunk A vẫn xuất hiện đầu chunk B.
Ví dụ cụ thể
Tài liệu quy chế có đoạn:
> "4.2 Quy đổi phép năm. Nhân viên có phép năm chưa dùng được quy đổi tối đa 5 ngày thành tiền, tính theo lương cơ bản. Phần vượt quá 5 ngày sẽ bị hủy vào 31/12."
- Chunk xấu (fixed-size cắt cứng): dừng ở "quy đổi tối đa 5 ngày thành" → mất phần "tính theo lương cơ bản", agent trả lời thiếu.
- Chunk tốt (structure-aware): giữ trọn mục 4.2 làm một chunk → agent trả lời đầy đủ.
Các bước triển khai
- Trích xuất text sạch từ PDF/HTML, giữ lại tiêu đề mục.
- Cắt recursive theo separator ưu tiên:
\n\n(đoạn) →\n→.→ space. - Gắn tiêu đề mục vào đầu mỗi chunk (contextual header) để chunk tự mang ngữ cảnh:
def chunk_with_header(section_title, body, size=500, overlap=75):
words = body.split()
chunks, i = [], 0
while i < len(words):
piece = " ".join(words[i:i+size])
chunks.append(f"[{section_title}] {piece}")
i += size - overlap
return chunks
- Đo độ dài mỗi chunk bằng đúng tokenizer của embedding model, không đếm từ thô.
- Kiểm tra thủ công 10 chunk ngẫu nhiên: mỗi cái có tự đứng vững không?
Chọn kích thước theo loại tài liệu
| Loại tài liệu | Kích thước gợi ý |
|---|---|
| FAQ, Q&A ngắn | 100–200 token, mỗi Q&A một chunk |
| Tài liệu chính sách | 300–500 token theo mục |
| Bài kỹ thuật dài | 500–800 token + overlap |
| Code / API docs | Theo hàm/endpoint |
Checklist chunking
- [ ] Đo kích thước bằng tokenizer thật, không bằng ký tự.
- [ ] Có overlap 10–20% ở ranh giới.
- [ ] Mỗi chunk mang theo tiêu đề/ngữ cảnh nguồn.
- [ ] Không cắt giữa bảng, giữa đoạn code, giữa câu quan trọng.
- [ ] Đã đọc tay 10 chunk mẫu và thấy chúng "tự đứng được".
Sai lầm thường gặp
- Chunk quá to (2000+ token): retrieval trả về đúng nhưng LLM phải lọc trong biển chữ, dễ bỏ sót.
- Chunk quá nhỏ, không overlap: mất ngữ cảnh, đại từ "nó", "khoản này" mất tham chiếu.
- Cắt cứng theo ký tự giữa từ tiếng Việt có dấu → hỏng token.
- Không gắn header: chunk "tối đa 5 ngày" không nói rõ 5 ngày của cái gì → retrieval kém.
- Một size cho mọi loại tài liệu: FAQ và hợp đồng cần chiến lược khác nhau.