استراتيجيات التقطيع المتقدمة
تصميم القطع الأمثل
3 دقيقة للقراءة
حجم القطعة، التداخل، والتنسيق يؤثرون بشكل كبير على جودة الاسترجاع. إليك كيفية تحسين هذه المعلمات لحالة استخدامك.
إرشادات حجم القطعة
| الحجم (رموز) | حالة الاستخدام | المقايضات |
|---|---|---|
| 128-256 | أسئلة وأجوبة، إجابات دقيقة | دقة عالية، قد يفتقد السياق |
| 256-512 | RAG عام (موصى به) | دقة/سياق متوازن |
| 512-1024 | طويل، مواضيع معقدة | سياق أكثر، دقة أقل |
| 1024+ | سياق المستند الكامل | خطر الضوضاء، مكلف |
إيجاد الحجم الأمثل
اختبر أحجام مختلفة تجريبياً:
def evaluate_chunk_size(documents, queries, ground_truth, sizes=[256, 512, 768, 1024]):
"""تقييم جودة الاسترجاع عبر أحجام القطع."""
results = {}
for size in sizes:
# إنشاء القطع
splitter = RecursiveCharacterTextSplitter(
chunk_size=size,
chunk_overlap=int(size * 0.1)
)
chunks = splitter.split_documents(documents)
# الفهرسة
vectorstore = Chroma.from_documents(chunks, embeddings)
# التقييم
recall = calculate_recall(vectorstore, queries, ground_truth)
precision = calculate_precision(vectorstore, queries, ground_truth)
results[size] = {
"recall": recall,
"precision": precision,
"f1": 2 * (recall * precision) / (recall + precision),
"num_chunks": len(chunks)
}
return results
# مثال الإخراج:
# 256: {"recall": 0.82, "precision": 0.71, "f1": 0.76}
# 512: {"recall": 0.78, "precision": 0.79, "f1": 0.78} <- أفضل F1
# 768: {"recall": 0.74, "precision": 0.82, "f1": 0.78}
# 1024: {"recall": 0.68, "precision": 0.85, "f1": 0.75}
استراتيجية التداخل
التداخل يمنع فقدان المعلومات عند حدود القطع:
# قاعدة عامة: 10-20% تداخل
chunk_size = 512
chunk_overlap = 50 # ~10%
# للمحتوى التقني مع مراجع متقاطعة
chunk_overlap = 100 # ~20%
# تمثيل بصري:
# القطعة 1: [===================]
# القطعة 2: [===================]
# القطعة 3: [===================]
# ^تداخل^
متى تزيد التداخل:
- التوثيق التقني مع مراجع متقاطعة
- النص القانوني مع عبارات تابعة
- الكود مع عبارات متعددة الأسطر
الحفاظ على التنسيق
الحفاظ على هيكل المستند في القطع:
class FormatPreservingChunker:
"""الحفاظ على تنسيق المستند في القطع."""
def chunk(self, document: str) -> list[dict]:
chunks = []
# اكتشاف تنسيق المستند
if self._is_markdown(document):
chunks = self._chunk_markdown(document)
elif self._is_code(document):
chunks = self._chunk_code(document)
else:
chunks = self._chunk_plain(document)
return chunks
def _chunk_markdown(self, document: str) -> list[dict]:
"""حافظ على عناوين markdown مع محتواها."""
sections = re.split(r'(^#{1,3}\s.+$)', document, flags=re.MULTILINE)
chunks = []
current_header = ""
for section in sections:
if re.match(r'^#{1,3}\s', section):
current_header = section.strip()
elif section.strip():
chunks.append({
"content": f"{current_header}\n\n{section.strip()}",
"header": current_header,
"type": "markdown"
})
return chunks
def _chunk_code(self, document: str) -> list[dict]:
"""قسم الكود حسب الدوال/الفئات."""
chunks = []
# نمط دالة Python
functions = re.findall(
r'((?:def|class)\s+\w+.*?(?=\n(?:def|class)|\Z))',
document,
re.DOTALL
)
for func in functions:
chunks.append({
"content": func.strip(),
"type": "code",
"language": "python"
})
return chunks
استراتيجيات خاصة بالمحتوى
الجداول
حافظ على الجداول سليمة:
def preserve_tables(document: str) -> str:
"""علّم الجداول لمنع التقسيم."""
# ابحث عن جداول markdown
table_pattern = r'(\|.+\|(?:\n\|.+\|)+)'
tables = re.findall(table_pattern, document)
# استبدل بعناصر نائبة
for i, table in enumerate(tables):
document = document.replace(table, f"[[TABLE_{i}]]")
return document, tables
def restore_tables(chunks: list[str], tables: list[str]) -> list[str]:
"""استعد الجداول في القطع."""
restored = []
for chunk in chunks:
for i, table in enumerate(tables):
chunk = chunk.replace(f"[[TABLE_{i}]]", table)
restored.append(chunk)
return restored
كتل الكود
لا تقسم كتل الكود أبداً:
def chunk_with_code_preservation(document: str, chunk_size: int) -> list[str]:
"""قطّع مع الحفاظ على كتل الكود."""
# استخرج كتل الكود
code_pattern = r'```[\s\S]*?```'
code_blocks = re.findall(code_pattern, document)
# استبدل بعناصر نائبة
for i, block in enumerate(code_blocks):
document = document.replace(block, f"[[CODE_{i}]]")
# قسم النص
splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size)
chunks = splitter.split_text(document)
# استعد كتل الكود
restored = []
for chunk in chunks:
for i, block in enumerate(code_blocks):
chunk = chunk.replace(f"[[CODE_{i}]]", block)
restored.append(chunk)
return restored
قائمة فحص الجودة
| الفحص | الهدف | الاختبار |
|---|---|---|
| الاكتمال | لا جمل مبتورة | تحقق من نهايات القطع |
| السياق | القطع مكتفية ذاتياً | هل يمكنك الفهم بدون النص المحيط؟ |
| الصلة | محتوى مركّز | موضوع واحد لكل قطعة |
| التنسيق | الهيكل محفوظ | الجداول، الكود، القوائم سليمة |
أداة التشخيص
def diagnose_chunks(chunks: list[str]) -> dict:
"""تحليل جودة القطع."""
diagnostics = {
"total_chunks": len(chunks),
"avg_length": sum(len(c) for c in chunks) / len(chunks),
"issues": []
}
for i, chunk in enumerate(chunks):
# تحقق من الجمل المبتورة
if chunk[-1] not in '.!?"\'':
diagnostics["issues"].append(f"القطعة {i}: قد تكون مبتورة")
# تحقق من المراجع اليتيمة
if re.search(r'\b(it|this|these|that)\b', chunk[:50]):
diagnostics["issues"].append(f"القطعة {i}: تبدأ بمرجع ضمير")
# تحقق من سلامة كتلة الكود
if chunk.count('```') % 2 != 0:
diagnostics["issues"].append(f"القطعة {i}: كتلة كود مكسورة")
# تحقق من تباين الطول
if len(chunk) < diagnostics["avg_length"] * 0.3:
diagnostics["issues"].append(f"القطعة {i}: قصيرة جداً ({len(chunk)} حرف)")
return diagnostics
الافتراضيات الموصى بها
# RAG للأغراض العامة
DEFAULT_CONFIG = {
"chunk_size": 512,
"chunk_overlap": 50,
"separators": ["\n\n", "\n", ". ", " ", ""],
"preserve_code_blocks": True,
"preserve_tables": True
}
# أسئلة وأجوبة / FAQ
QA_CONFIG = {
"chunk_size": 256,
"chunk_overlap": 25,
"separators": ["\n\n", "\n", "? ", ". ", " "],
"keep_qa_pairs": True
}
# التوثيق التقني
TECHNICAL_CONFIG = {
"chunk_size": 768,
"chunk_overlap": 100,
"separators": ["\n## ", "\n### ", "\n\n", "\n", ". "],
"preserve_code_blocks": True,
"include_headers": True
}
مبدأ رئيسي: تصميم القطع الأمثل يعتمد على استعلاماتك، وليس مستنداتك. صمم القطع لتطابق كيف سيبحث المستخدمون، وليس كيف يُنظم المحتوى.
في الوحدة التالية، سنستكشف البحث الهجين وإعادة الترتيب لتعظيم جودة الاسترجاع. :::