بسیاری از تیم‌های مهندسی در مواجهه با RAG (تولید متن تقویت‌شده با بازیابی) به بن‌بست مشابهی برخورد می‌کنند. آن‌ها از دستورالعمل‌های آموزشی پیروی می‌کنند: تکه‌کردن (chunking) اسناد به قطعات ثابت ۵۱۲ یا ۱۰۲۴ توکن، ارسال آن‌ها از طریق یک مدل embedding واحد، و فراخوانی یک پایگاه داده برداری با یک جستجوی ساده top-k. در یک ارائه (slide deck)، این روش عالی به نظر می‌رسد، اما در محیط عملیاتی (production) از هم می‌پاشد.

تکه‌های ثابت به محتوا اهمیتی نمی‌دهند. آن‌ها با خوشحالی یک قرارداد حقوقی را از وسط جمله تقسیم می‌کنند و بندهای مربوط به مسئولیت را در دو بخش بی‌ربط رها می‌کنند. آن‌ها کل توضیحات یک API endpoint را در یک تکه حجیم می‌ریزند که آن‌قدر بزرگ است که پارامتر خاصی که کاربر پرسیده در میان نویزها غرق می‌شود. و وقتی بازیابی (retrieval) کند باشد، هر میلی‌ثانیه تأخیر (latency) مستقیماً بر تجربه کاربری اثر می‌گذارد. ما این را به سختی یاد گرفتیم. سپس لایه بازیابی خود را تخریب کردیم و دوباره ساختیم. نرخ Recall ما در ۱۰ (recall at ten) از ۷۸ درصد به ۹۵ درصد رسید. تأخیر افزایش نیافت، بلکه به شدت کاهش یافت.

مشکل RAG کپی-پیستی

پشته (stack) استاندارد RAG به نوعی تنظیمات پیش‌فرض تبدیل شده است. تکه‌های کوچک، یک مدل embedding، جستجوی برداری، تمام. این رویکرد در دموها دوام می‌آورد چون دموها از سوالات تمیز و اسناد مرتب استفاده می‌کنند. اما داده‌های محیط عملیاتی هرگز مرتب نیستند.

اسناد حقوقی ساختار سلسله‌مراتبی دارند. بخش‌ها شامل زیربخش‌ها هستند و زیربخش‌ها شامل بندها. اگر با یک شمارنده توکن خام آن‌ها را برش دهید، دقیقاً همان روابطی را که مدل برای استدلال به آن‌ها نیاز دارد، از بین می‌برید. مستندات API نیز ساختار دارند، اما متفاوت است. امضای تابع (function signature)، پارامترهای آن، مقدار بازگشتی و یک مثال از کاربرد، یک واحد منطقی را تشکیل می‌دهند. اگر آن‌ها را در یک پنجره توکن ثابت محدود کنید، یا مثال را ناقص می‌گذارید یا تکه را با توابع بی‌ربط پر می‌کنید. تیکت‌های پشتیبانی نامنظم، گفتگومحور و پر از تغییرات ناگهانی موضوع هستند. ویکی‌ها گسترده و دارای ارجاعات متقابل هستند. یک استراتژی تکه‌کردن نمی‌تواند به همه این‌ها خدمت کند، با این حال تیم‌ها معمولاً دقیقاً همین کار را انجام می‌دهند. ما از تظاهر به اینکه چنین چیزی ممکن است، دست کشیدیم.

تکه‌کردن استراتژیک: تطبیق روش با محتوا

ما به سمت تکه‌کردن آگاه از محتوا (content-aware chunking) حرکت کردیم. برای اسناد حقوقی، از تکه‌کردن بازگشتی (recursive chunking) استفاده می‌کنیم که به سلسله‌مراتب سند احترام می‌گذارد. این کار بندها را دست‌نخورده نگه می‌دارد و روابط والد-فرزندی بین بخش‌ها را حفظ می‌کند. برای مستندات API، ما تکه‌کردن آگاه از تابع (function-aware chunking) را ساختیم که با هر تابع یا endpoint به عنوان یک مرز برخورد می‌کند. اگر توضیحات یک پارامتر طولانی شود، تکه به جای محدودیت توکن، حول آن تابع گسترش می‌یابد. برای تیکت‌های پشتیبانی، از تکه‌کردن معنایی (semantic chunking) استفاده می‌کنیم که مرزهای طبیعی موضوع را تشخیص می‌دهد. وقتی مشتری ناگهان از شکایت در مورد صورت‌حساب به یک باگ فنی تغییر موضوع می‌دهد، برش در همان نقطه رخ می‌دهد. برای ویکی‌ها و پایگاه‌های دانش بدون ساختار، از تکه‌کردن عامل‌محور (agentic chunking) استفاده می‌کنیم که در آن یک LLM سبک، متن را ارزیابی کرده و تصمیم می‌گیرد مرز معنادار کجا باید باشد. راه‌اندازی این روش نسبت به برش بر اساس کاراکتر کندتر است، اما تفاوت بین بازیابی‌ای که کار می‌کند و بازیابی‌ای که فقط حدس می‌زند، در همین است.

بازیابی ترکیبی: چرا جستجوی برداری به تنهایی کافی نیست

جستجوی برداری معنا را درک می‌کند، اما ممکن است تطبیق‌های دقیق را از دست بدهد. اگر کاربر یک کد خطا مانند ERR_CONNECTION_RESET_0x5F3 را وارد کند، شباهت معنایی ممکن است رتبه آن را پایین‌تر از پاراگراف‌هایی قرار دهد که صرفاً در مورد خطاهای شبکه به طور کلی بحث می‌کنند. از سوی دیگر، BM25 رشته‌های دقیق را پیدا می‌کند اما ارتباط مفهومی را از دست می‌دهد. شما به هر دو نیاز دارید.

ما جستجوی برداری و BM25 را به صورت موازی اجرا می‌کنیم. سپس نتایج را با استفاده از ادغام رتبه متقابل (Reciprocal Rank Fusion یا RRF) ترکیب می‌کنیم که امتیازات دو فضای جستجوی متفاوت را بدون مجبور کردن آن‌ها به یک مقیاس واحد، نرمال‌سازی می‌کند. پس از ادغام، کاندیداهای برتر را از طریق یک بازرتبه‌کننده cross-encoder ارسال می‌کنیم. این کار مقدار کمی تأخیر اضافه می‌کند، اما افزایش دقت بسیار قابل توجه است. بازرتبه‌کننده، پرس‌وجو (query) و هر کاندیدا را با هم می‌خواند و امتیاز مرتبطی را اختصاص می‌دهد که بسیار دقیق‌تر از شباهت کسینوسی (cosine similarity) در embedding اولیه است. در عمل، این ترکیب کدهای خطای دقیقی را که جستجوی برداری خالص از دست می‌دهد، پیدا می‌کند و در عین حال مراحل عیب‌یابی مرتبط از نظر مفهومی را که جستجوی کلمات کلیدی نادیده می‌گیرد، نمایش می‌دهد.

گسترش پرس‌وجو: اصلاح ورودی کاربر قبل از رسیدن به ایندکس

کاربران پرس‌وجوهای جستجوی بی‌نقصی نمی‌نویسند. آن‌ها سوالات چندمرحله‌ای (multi-hop) می‌پرسند، مانند «چرا آخرین استقرار (deploy) من شکست خورد و چگونه می‌توانم آن را به حالت قبل برگردانم؟» که مستلزم یافتن دو بدنه دانش جداگانه و متصل کردن آن‌هاست. یا سوالات مبهمی می‌پرسند که با ایندکس مطابقت ضعیفی دارند.

ما پرس‌وجوها را پیش از جستجو تغییر می‌دهیم. یک سوال چندمرحله‌ای به زیرسوال‌ها تجزیه می‌شود. یک قصد مبهم به چندین پرس‌وجوی جستجوی مشخص گسترش می‌یابد. ما دریافتیم که گسترش یک پرس‌وجوی کاربر به پنج پرس‌وجوی جستجوی متمایز می‌تواند نرخ بازیابی (recall) را از هفتاد و هشت درصد به نود و شش درصد افزایش دهد. موضوع این نیست که LLM را با پرامپت‌های سخت‌تر به چالش بکشیم؛ بلکه موضوع این است که به سیستم بازیابی فرصت‌های بیشتری برای یافتن بافتار (context) صحیح بدهیم. هر پرس‌وجوی تولیدشده، زاویه دید یا اصطلاح متفاوتی را پوشش می‌دهد و نتایج ادغام‌شده، تصویری کامل را ترسیم می‌کنند.

بهینه‌سازی بیزی: حدس زدن را متوقف کنید

زمانی که چندین استراتژی تکه‌بندی (chunking)، بازیابی ترکیبی (hybrid retrieval) و گسترش پرس‌وجو (query expansion) را در اختیار داشتید، با مشکل جدیدی روبرو می‌شوید. پارامترهای تنظیم بسیار زیادی وجود دارد. اندازه تکه‌ها (chunk size)، درصد هم‌پوشانی (overlap percentage)، وزن برداری در مقابل وزن BM25، آستانه‌های بازرتبه‌بندی (reranking thresholds) و مقادیر top-k همگی به روش‌های غیرخطی با هم در تعامل هستند. تنظیم دستی به یک بازی حدس و گمان تبدیل می‌شود.

ما حدس زدن را متوقف کردیم. ما با...