Chunking Strategies
Khái niệm cơ bản
Tưởng tượng bạn đang cắt một chiếc pizza khổng lồ…
- Miếng quá to: Một người không ăn hết, phí phạm.
- Miếng quá nhỏ: Mất công gắp, thiếu thỏa mãn.
- Miếng vừa phải: Perfect!
Chunking documents cho AI cũng vậy:
- Chunk quá dài: Vector bị “loãng” ý nghĩa, khó match chính xác.
- Chunk quá ngắn: Thiếu context, AI hiểu sai.
- Chunk hợp lý: Trọn vẹn một ý, dễ tìm kiếm.
Tại sao phải Chunking?
- Context Window giới hạn: Không thể ném cả cuốn sách 500 trang vào prompt.
- Chi phí: Tokens = Tiền. Chunk nhỏ = Ít tokens = Rẻ hơn.
- Precision: Chunk nhỏ hơn = Vector đại diện chính xác hơn cho nội dung.
4 Chiến lược Chunking
1. Fixed-size Chunking (Cắt cố định)
def fixed_size_chunk(text: str, chunk_size: int = 500, overlap: int = 50):
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start = end - overlap # Overlap để giữ mạch văn
return chunks| Ưu | Nhược |
|---|---|
| Nhanh, đơn giản | Có thể cắt giữa câu |
| Predictable size | Không quan tâm cấu trúc văn bản |
Use case: Logs, data đơn giản.
2. Recursive Character Chunking
Cắt thông minh theo hierarchy: \n\n → \n → . →
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ".", " "]
)
chunks = splitter.split_text(long_document)| Ưu | Nhược |
|---|---|
| Giữ paragraphs nguyên vẹn | Chunk size có thể không đều |
| Ngắt tại điểm tự nhiên | Vẫn có thể cắt giữa ý |
Use case: Tài liệu, sách, articles. Recommended mặc định!
3. Semantic Chunking
Dùng Embeddings để detect sự thay đổi chủ đề:
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
chunker = SemanticChunker(embeddings)
# Tự động phát hiện khi nào đổi chủ đề
chunks = chunker.split_text(document)Cách hoạt động:
- Embed từng câu
- Tính cosine similarity giữa câu liên tiếp
- Nếu similarity drops mạnh → Ngắt chunk mới
| Ưu | Nhược |
|---|---|
| Chunks trọn vẹn ý nghĩa | Tốn compute (phải embed) |
| Tối ưu cho retrieval | Chậm hơn |
Use case: Research papers, legal docs.
4. Agentic Chunking
Dùng LLM để đọc và quyết định:
def agentic_chunk(text: str) -> list[str]:
prompt = f"""
Đọc văn bản sau và chia thành các chunks logic.
Mỗi chunk phải:
- Chứa trọn vẹn một ý/concept
- 100-500 từ
TEXT:
{text}
Trả về dạng JSON: ["chunk1", "chunk2", ...]
"""
return llm.generate(prompt)| Ưu | Nhược |
|---|---|
| Chunks chất lượng cao nhất | Rất chậm |
| Hiểu context | Đắt (gọi LLM mỗi doc) |
Use case: High-value documents, contract analysis.
Comparison Chart
| Strategy | Speed | Quality | Cost | Best For |
|---|---|---|---|---|
| Fixed | ⚡⚡⚡ | ⭐ | 💵 | Logs, simple data |
| Recursive | ⚡⚡ | ⭐⭐⭐ | 💵 | General docs (default!) |
| Semantic | ⚡ | ⭐⭐⭐⭐ | 💵💵 | Research, legal |
| Agentic | 🐢 | ⭐⭐⭐⭐⭐ | 💵💵💵 | High-value docs |
Best Practices
1. Chunk Size Guidelines
| Content Type | Recommended Size | Overlap |
|---|---|---|
| Short Q&A | 200-500 tokens | 20-50 |
| Articles/Docs | 500-1000 tokens | 100-200 |
| Books/Long | 1000-2000 tokens | 200-400 |
2. Bao gồm Metadata
chunks = []
for i, chunk in enumerate(raw_chunks):
chunks.append({
"content": chunk,
"metadata": {
"source": "company_handbook.pdf",
"page": i // 2, # Ước tính page
"chunk_index": i,
"total_chunks": len(raw_chunks)
}
})3. Test với Real Queries
test_queries = [
"Chính sách nghỉ phép như thế nào?",
"Làm sao để xin làm việc từ xa?",
"Quy định về OT?"
]
for query in test_queries:
results = vector_db.search(query, top_k=3)
print(f"Query: {query}")
print(f"Top result: {results[0]['content'][:100]}...")
# Kiểm tra xem chunk có chứa answer khôngBài tập thực hành: So sánh Chunking Strategies
Mục tiêu
So sánh kết quả retrieval giữa Fixed và Recursive chunking.
Code
from langchain.text_splitter import CharacterTextSplitter, RecursiveCharacterTextSplitter
import chromadb
# Sample document
doc = """
[Dán một bài viết dài 2000+ từ ở đây]
"""
# Strategy 1: Fixed
fixed_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
fixed_chunks = fixed_splitter.split_text(doc)
# Strategy 2: Recursive
recursive_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
recursive_chunks = recursive_splitter.split_text(doc)
print(f"Fixed: {len(fixed_chunks)} chunks")
print(f"Recursive: {len(recursive_chunks)} chunks")
# So sánh chunk đầu tiên
print("\n--- Fixed chunk 1 ---")
print(fixed_chunks[0])
print("\n--- Recursive chunk 1 ---")
print(recursive_chunks[0])Câu hỏi phân tích
- Chunk nào bị cắt giữa câu?
- Strategy nào giữ paragraph nguyên vẹn hơn?
- Với query cụ thể, strategy nào return kết quả tốt hơn?
Tóm tắt
| Khái niệm | Ý nghĩa |
|---|---|
| Chunk | Đoạn văn bản nhỏ để embed |
| Overlap | Phần gối đầu giữa chunks |
| Recursive | Mặc định, cắt theo hierarchy |
| Semantic | Cắt dựa trên thay đổi ý nghĩa |
Bài tiếp theo: Hybrid Search - Kết hợp keyword + semantic.
Last updated on