Text Chunking
Chunking - Chia nhỏ tài liệu
Bạn có Bộ luật Lao động 200 trang. Embedding model chỉ xử lý tối đa 512 token/lần. Nhét cả 200 trang vào 1 lần được không?
Hãy tưởng tượng bạn đang biên tập một cuốn sách dày 500 trang để đưa lên một hệ thống tra cứu thông minh. Nếu bạn giao cả cuốn sách cho máy tìm kiếm, nó sẽ "ngộp" — không biết nên nhớ phần nào. Ngược lại, nếu bạn xé từng dòng ra làm thẻ nhớ, mỗi thẻ lại quá ngắn để người đọc hiểu nội dung.
Chunking là nghệ thuật chia văn bản dài thành các đoạn vừa vặn: đủ nhỏ để máy xử lý được, đủ lớn để vẫn mang trọn một ý hoàn chỉnh. Giống như cắt bánh mì — cắt quá dày thì khó ăn, cắt quá mỏng thì vụn và mất mùi vị.
Trong bối cảnh RAG (Retrieval-Augmented Generation), chunking là bước tiền xử lý quyết định chất lượng toàn hệ thống. Chunking kém → embedding kém → search trả ngữ cảnh sai → LLM đưa câu trả lời sai. Ngược lại, chunking khéo léo giúp mọi bước sau chạy trơn tru.
Ba chiến lược phổ biến nhất là Fixed-size (cắt đều), Semantic (cắt theo chủ đề), và Recursive (cắt theo ranh giới tự nhiên). Phần tương tác dưới đây cho bạn điều chỉnh chunk size, overlap và xem ngay điểm truy xuất thay đổi.
Hình minh họa
Tài liệu mẫu là trích đoạn giả lập Bộ luật Lao động (~1483 ký tự). Chọn chiến lược, kéo chunk size và overlap để quan sát số chunk và điểm truy xuất thay đổi trực tiếp.
Fixed-size Chunking
Chia văn bản thành các đoạn có độ dài cố định (theo ký tự hoặc token). Đơn giản, nhanh, nhưng có thể cắt giữa câu hoặc giữa ý.
% truy vấn mẫu tìm thấy ít nhất 1 chunk khớp
Chunk quá dài làm nhiễu, quá ngắn mất ngữ cảnh
256-800 ký tự thường là sweet spot cho RAG
Điều 128 Bộ luật Lao động 2019 quy định rõ trách nhiệm của người sử dụng lao động khi ký hợp đồng thử việc với người lao động. Thời gian thử việc không được quá 180 ngày đối với công việc của người quản lý doanh nghiệp, và không quá 60 ngày đối với công việc y…
hất bằng 85% mức lương chính thức của công việc đó theo thỏa thuận giữa hai bên. Khi hết thời gian thử việc, nếu người lao động đạt yêu cầu thì người sử dụng lao động phải giao kết hợp đồng lao động chính thức trong vòng 03 ngày làm việc. Nếu không đạt yêu cầu…
phải bồi thường. Trong quá trình thực hiện hợp đồng, người lao động có quyền đơn phương chấm dứt hợp đồng lao động với thời hạn báo trước theo quy định của pháp luật. Cụ thể, với hợp đồng không xác định thời hạn, thời hạn báo trước là ít nhất 45 ngày; với hợp …
o động không được sa thải hoặc đơn phương chấm dứt hợp đồng đối với lao động nữ vì lý do kết hôn, mang thai hoặc nuôi con dưới 12 tháng tuổi. Trường hợp vi phạm, doanh nghiệp phải bồi thường toàn bộ tiền lương trong những ngày người lao động không được làm việ…
định về an toàn lao động, bảo hiểm xã hội bắt buộc và quyền thương lượng tập thể trong toàn bộ quá trình quan hệ lao động.
Mẹo: thử kéo chunk size thật nhỏ (dưới 150) — bạn sẽ thấy điểm Context richness tụt xuống. Ngược lại, chunk quá lớn (1200+) làm Precision giảm vì chunk chứa quá nhiều nhiễu. Sweet spot thường rơi vào khoảng 400–800 ký tự.
Ưu điểm
- • Cực kỳ đơn giản — chỉ cần 1 vòng lặp
- • Tốc độ xử lý nhanh, dễ song song hóa
- • Kích thước chunk đồng đều → dễ quản lý bộ nhớ
Hạn chế
- • Cắt cơ học, không quan tâm ranh giới câu/đoạn
- • Thông tin ở rìa chunk thường bị mất ngữ cảnh
- • Không phù hợp với tài liệu có cấu trúc rõ ràng
Thử nghiệm có chủ đích: ở tab Fixed-size, hạ chunk size xuống 100 và đặt overlap = 0. Nhìn Coverage vẫn cao nhưng Context richness tụt hẳn — đó là dấu hiệu chunk đã quá ngắn để embedding mang nghĩa. Tăng size lên 1500 thì Precision giảm vì chunk chứa nhiều thông tin không liên quan cùng lúc.
Chunking giống chia cuốn sách thành bookmarks: mỗi bookmark đánh dấu 1 đoạn ý hoàn chỉnh. Chunk quá nhỏ = bookmark mỗi dòng (thiếu ngữ cảnh). Chunk quá lớn = bookmark mỗi chương (khó tìm chính xác). 256-512 token là sweet spot cho hầu hết bài toán RAG — đủ dài để nắm một ý, đủ ngắn để embedding tập trung.
Chunk_1 kết thúc: '...theo Điều 128'. Chunk_2 bắt đầu: 'Bộ luật Lao động, người sử dụng lao động phải...'. Vấn đề gì xảy ra?
Bạn có một tài liệu pháp lý dài, truy vấn của người dùng thường là những câu hỏi ngắn gọn (ví dụ 'thời gian thử việc tối đa?'). Để cân bằng precision cho search và context đủ cho LLM, bạn nên dùng chiến lược nào?
Giải thích
Chunking (chia nhỏ) là bước tiền xử lý trong pipeline RAG, chia văn bản dài thành các đoạn nhỏ (chunk) phù hợp với giới hạn context của embedding model. Mỗi chunk sau đó được nhúng thành vector và lưu trong vector database.
Nhìn thoáng qua thì chunking đơn giản — chỉ là cắt chuỗi. Nhưng chất lượng chunking quyết định chất lượng toàn hệ thống RAG: chunk cắt kém → embedding thiếu ngữ cảnh → search trả chunk không liên quan → LLM hallucinate hoặc trả lời thiếu chính xác. Nghiên cứu thực nghiệm cho thấy chỉ cần thay đổi chunk_size từ 256 → 512, recall@10 có thể dao động 15-25% trên cùng một dataset.
1. Fixed-size: Chia theo số token cố định (VD: 512). Đơn giản nhưng có thể cắt giữa ý.
2. Sentence-based: Chia theo ranh giới câu. Giữ ý hoàn chỉnh nhưng chunk không đều.
3. Recursive: Thử chia theo paragraph, nếu quá dài thì chia theo sentence, rồi theo word. LangChain mặc định.
4. Overlap: Chunk kề nhau chia sẻ 10-20% nội dung. Không mất thông tin ranh giới.
5. Semantic: Embed từng câu, cắt khi cosine similarity giảm mạnh (chủ đề thay đổi). Thông minh nhất.
Kích thước chunk tối ưu bị ràng buộc cứng bởi embedding model:
all-MiniLM-L6-v2: max 256 token. text-embedding-3-small: max 8192 token. bge-m3: max 8192 token. Thực tế, 256-1024 token/chunk thường cho kết quả tốt nhất cho RAG.
Overlap giữa các chunk giúp bảo toàn ngữ cảnh ở ranh giới. Nếu một câu quan trọng nằm vắt ngang 2 chunk, không có overlap thì nó bị chia đôi — cả 2 chunk đều mất ý. Với overlap O ký tự, câu đó xuất hiện trong ít nhất một chunk đầy đủ (miễn là câu ngắn hơn O). Công thức số chunk:
Trong đó L là độ dài tổng, S là chunk size, O là overlap. Overlap càng lớn, số chunk càng nhiều → chi phí lưu trữ và embedding tăng theo.
Fixed-size chunking cắt cơ học tại vị trí ký tự thứ N, không quan tâm đó là giữa từ, giữa câu hay giữa bảng. Hậu quả: "Điều 128 Bộ luật Lao..." | "...động, người sử dụng lao động phải..." — chunk_1 treo lửng, chunk_2 thiếu chủ ngữ. Giải pháp: dùng Recursive splitter hoặc đặt overlap ≥ 10% chunk size.
Chunk < 100 token thường thiếu ngữ cảnh — embedding model không nắm được "đoạn này nói về cái gì". Chunk > 1500 token bị dilution: tín hiệu của một ý quan trọng bị pha loãng bởi nhiều ý khác cùng nằm trong vector. Luôn đo retrieval quality thực tế trên query set của bạn, không chỉ dựa vào cảm tính.
- Tách từ: Tiếng Việt cần tách từ đúng (underthesea, vncorenlp) trước khi chunk theo token, nếu không số token ước lượng sẽ lệch 20-40%.
- Metadata: Lưu kèm metadata (tên tài liệu, số trang, ngày ban hành, điều khoản) cho mỗi chunk để filter và trích nguồn.
- Giữ heading: Prepend tiêu đề mục/tiểu mục vào chunk giúp embedding nắm chủ đề tốt hơn.
- Test A/B: Không tin phép thử của người khác — A/B test chunk_size (256 vs 512 vs 1024) trên bài toán cụ thể của bạn.
Fixed-size: tài liệu đồng nhất, cần tốc độ (logs, transcript, tweet feed). Recursive: tài liệu có cấu trúc rõ ràng (Markdown, HTML, pháp lý, sách kỹ thuật). Semantic: tài liệu dài, nhiều chủ đề đan xen (báo cáo nghiên cứu, blog post dài, podcast transcript). Chi phí embedding phụ trợ đáng kể — chỉ dùng khi chất lượng là ưu tiên tuyệt đối.
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Đọc Bộ luật Lao động
with open("bo_luat_lao_dong_2025.txt", encoding="utf-8") as f:
text = f.read() # ~200 trang
# Recursive Text Splitting (mặc định LangChain)
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # ~500 ký tự/chunk
chunk_overlap=50, # 50 ký tự chồng lấp (~10%)
separators=[ # Thứ tự ưu tiên, từ lớn đến nhỏ
"\n\n", # Paragraph break
"\n", # Line break
". ", # Sentence
", ", # Clause
" ", # Word
"", # Fallback: cắt theo ký tự
],
length_function=len, # Có thể thay bằng tiktoken encoder
is_separator_regex=False,
)
chunks = splitter.split_text(text)
print(f"Tổng {len(chunks)} chunks từ {len(text)} ký tự")
# Ví dụ: ~400 chunks, mỗi chunk ~500 ký tự
# Variant dùng token thay cho ký tự
from langchain.text_splitter import TokenTextSplitter
token_splitter = TokenTextSplitter(
chunk_size=256,
chunk_overlap=32,
encoding_name="cl100k_base", # tiktoken encoding của GPT-4
)
token_chunks = token_splitter.split_text(text)
# Đảm bảo chunk luôn ≤ 256 token thật, không lệch do tách từfrom langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
# Semantic chunking dựa trên embedding của từng câu
semantic_splitter = SemanticChunker(
OpenAIEmbeddings(model="text-embedding-3-small"),
breakpoint_threshold_type="percentile",
# Cắt khi similarity giảm xuống dưới percentile 95 của khoảng cách
breakpoint_threshold_amount=95,
# Có thể đổi sang "standard_deviation" hoặc "interquartile"
)
semantic_chunks = semantic_splitter.split_text(text)
# Kiểm tra độ dài chunk không đồng đều — đó là hành vi mong đợi
lengths = [len(c) for c in semantic_chunks]
print(f"min={min(lengths)}, max={max(lengths)}, avg={sum(lengths)//len(lengths)}")
# Biến thể parent-child: index chunk nhỏ, trả về parent lớn
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_community.vectorstores import Chroma
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1600)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=256)
retriever = ParentDocumentRetriever(
vectorstore=Chroma(embedding_function=OpenAIEmbeddings()),
docstore=InMemoryStore(),
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
retriever.add_documents(docs)
# Search dùng child chunk (precision cao), trả về parent chunk (context đủ)Ứng dụng thực tế
- Trợ lý pháp lý:chunking Bộ luật theo điều/khoản rồi embed, cho phép luật sư hỏi nhanh "quy định hiện hành về thử việc?" và hệ thống trả về đúng điều khoản kèm số hiệu.
- Wiki nội bộ: chia Wiki công ty thành chunk, nhân viên mới tra cứu chính sách ngay trên Slack bot mà không cần lục từng trang Confluence.
- Y tế: chunking hồ sơ bệnh án + guidelines để hỗ trợ bác sĩ rà soát nhanh (luôn đi kèm audit trail và trích dẫn nguồn để tránh rủi ro pháp lý).
- Nghiên cứu học thuật: chunking paper PDF, cho phép Semantic Scholar trả lời câu hỏi cụ thể thay vì chỉ list paper — chunk theo section (Abstract, Methods, Results).
- Ngân hàng:chunking hợp đồng tín dụng + T&C; khi khách hỏi điều khoản, chatbot trích đúng đoạn và số điều, kèm confidence score.
- E-commerce:chunking mô tả sản phẩm + review để xây dựng semantic search cho website — khách tìm "áo khoác chống nước đi phượt" sẽ trúng đúng sản phẩm.
- Giáo dục:chunking giáo trình, bài giảng để xây tutor AI — học sinh hỏi "định lý Pythagoras áp dụng thế nào?", bot trích đúng ví dụ trong sách giáo khoa.
Các pitfall hay gặp
- Đặt chunk_size bằng đơn vị ký tự nhưng embedding model tính theo token → chunk thực có thể vượt max_seq_length mà không hay.
- Quên metadata (nguồn, section, page) → không trích được nguồn khi trả lời, giảm tin cậy.
- Overlap quá lớn (> 40%) làm vector database phình to và chunk trùng lặp khi retrieve.
- Chunking 1 lần rồi giữ nguyên cả đời; thực tế cần re-chunk khi đổi embedding model hoặc thêm loại tài liệu mới.
- Bỏ qua bảng, công thức, code block — các đoạn này cần chiến lược riêng (giữ nguyên khối, không cắt).
- Chunking chia tài liệu dài thành đoạn nhỏ vừa giới hạn embedding model (thường 256-1024 token).
- Ba chiến lược chính: Fixed-size (đơn giản), Recursive (mặc định LangChain, tôn trọng cấu trúc), Semantic (cắt theo chủ đề).
- Overlap 10-20% chunk_size đảm bảo thông tin ở ranh giới không bị mất.
- Parent-child chunking: index chunk nhỏ cho precision, retrieve parent lớn cho context — kết hợp cả hai ưu điểm.
- Đo bằng số thật (recall, precision trên eval set) — không chọn chunk_size theo cảm tính.
- Luôn lưu kèm metadata (nguồn, trang, section) để trả lời có trích nguồn và audit được.
Kiểm tra hiểu biết
Chunk quá nhỏ (50 token) có vấn đề gì?