أصبحت أنظمة التوليد المعزز بالاسترجاع (RAG) المعيار الصناعي لتأريض نماذج اللغة الكبيرة (LLMs) في بيانات محددة وملك خاص. ومع ذلك، غالباً ما يُحدد جودة نظام RAG ليس النموذج نفسه، بل المعالجة المسبقة للبيانات المدخلة. الخطوة الأكثر حرجاً في هذا المسار هي التقطيع. إذا كانت القطع صغيرة جداً، تفقد السياق؛ وإذا كانت كبيرة جداً، تخفف من الصلة الدلالية. اختيار استراتيجية التقطيع الصحيحة هو الفرق بين مساعد مفيد وروبوت يهذي.
لماذا يهم التقطيع
تمتلك نماذج اللغة الكبيرة (LLMs) نافذة سياق محدودة. تخزن قواعد البيانات المتجهية (Vector databases) التضمينات (embeddings)، والتي تمثل المعنى الدلالي للنص. لضمان أن البحث المتجهي يمكنه العثور على المعلومات الأكثر صلة، يجب علينا تقسيم المستندات إلى قطع قابلة للإدارة. الهدف هو إنشاء قطع تكون مستقلة بذاتها ومع ذلك متسقة دلالياً. المستند المقطوع بشكل سيء قد يقسم جملة إلى نصفين أو يفصل الترويسة عن نصها، مما يؤدي إلى استرجاعات غير ذات صلة.
1. التقطيع بالحجم الثابت: الخط الأساسي
أبسط نهج هو التقطيع بالحجم الثابت. يقسم هذا النص إلى قطع بعدد محدد من الأحرف أو الرموز (tokens)، غالباً مع بعض التداخل. إنه سريع وسهل التنفيذ لكنه يتجاهل السلامة الهيكلية للنص.
import textsplitter
def fixed_size_chunk(text, chunk_size=500, overlap=50):
chunks = []
for i in range(0, len(text), chunk_size - overlap):
chunks.append(text[i : i + chunk_size])
return chunks
# Example:
# text = "This is a very long document..."
# chunks = fixed_size_chunk(text)
2. التقسيم التكراري للأحرف: النهج الهيكلي
تستخدم معظم الأنظمة الإنتاجية التقسيم التكراري للأحرف. تحاول هذه الطريقة تقسيم النص أولاً على الحدود الطبيعية (مثل الفقرات أو الجمل) قبل اللجوء إلى الأحرف. تحترم هذه الطريقة التسلسل الهرمي للنص: فقرة → جملة → كلمة → حرف. يحافظ هذا على المعنى بشكل أفضل من طرق الحجم الثابت.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
separators=["\n\n", "\n", " ", ""]
)
# This splitter will try to keep paragraphs together,
# then sentences, then words, and finally characters.
docs = splitter.split_documents([Document(page_content=raw_text)])
3. التقطيع الدلالي: الطريقة المتقدمة
يستخدم التقطيع الدلالي التضمينات (embeddings) للكشف عن الأماكن التي يتغير فيها معنى النص بشكل كبير. يحلل التشابه الجيبية (cosine similarity) بين الجمل المتجاورة. عندما ينخفض التشابه دون عتبة معينة، يفترض أن موضوعاً جديداً قد بدأ ويخلق فاصل تقطيع.
على الرغم من كونه مكلفاً حاسوبياً، غالباً ما تعطي هذه الطريقة أعلى دقة في الاسترجاع لأنها تجمع المفاهيم ذات الصلة معاً، بغض النظر عن هيكل المستند.
# Pseudocode for semantic chunking
def semantic_chunk(text, threshold=0.8):
sentences = split_into_sentences(text)
embeddings = get_embeddings(sentences)
chunks = []
current_chunk = [sentences[0]]
for i in range(1, len(sentences)):
sim = cosine_similarity(embeddings[i-1], embeddings[i])
if sim < threshold:
chunks.append(" ".join(current_chunk))
current_chunk = [sentences[i]]
else:
current_chunk.append(sentences[i])
chunks.append(" ".join(current_chunk))
return chunks
أفضل الممارسات للتنفيذ
- التداخل هو المفتاح: قم دائماً بتضمين بعض التداخل (10-20%) بين القطع لمنع فقدان السياق عند الحدود.
- احترم البيانات الوصفية: إذا كانت بيانات المصدر منظمة (مثل JSON أو Markdown)، قم بتحليلها أولاً وقم بالتقطيع حسب الوحدات المنطقية (مثلاً، لكل نقطة نهاية API أو لكل قسم).
- جرّب: لا يوجد حل "مناسب للجميع". اختبر أحجام واستراتيجيات تقطيع مختلفة باستخدام مجموعة بيانات الاستعلام الفعلية الخاصة بك لقياس دقة الاسترجاع.
الخلاصة
التقطيع ليس مجرد خطوة معالجة مسبقة؛ بل هو مكون أساسي في بنية RAG الخاصة بك. ابدأ بالتقسيم التكراري للأحرف للحصول على خط أساسي قوي، وانتقل إلى التقطيع الدلالي إذا وجدت أن استرجاعاتك تفقد سياقاً دقيقاً. من خلال محاذاة استراتيجية التقطيع الخاصة بك مع هيكل بياناتك ودلالات استعلامات المستخدمين، ستعزز بشكل كبير أداء وموثوقية تطبيقات الذكاء الاصطناعي الخاصة بك.