بیشتر آموزش‌های RAG دقیقاً همان‌جایی تمام می‌شوند که مرحله تولید (production) آغاز می‌گردد. شما اسناد خود را به قطعات ۵۱۲ توکنی تقسیم می‌کنید، آن‌ها را از یک مدل امبدینگ (embedding model) واحد عبور می‌دهید و با یک جستجوی ساده top-k، یک پایگاه داده برداری را فراخوانی می‌کنید. در یک نسخه دموی اولیه، این روش متقاعدکننده به نظر می‌رسد. از بات درباره سیاست مرخصی شرکت سوال کنید و او یک پاراگراف منسجم برمی‌گرداند. همه سر تکان می‌دهند. اما متأسفانه، دموها دروغ می‌گویند.

محیط عملیاتی، تمام میان‌برها را برملا می‌کند. قطعه‌بندی‌های ثابت، قراردادهای حقوقی را از وسط بندهای غرامت می‌برند. مستندات API به نویزهای هم‌پوشان تبدیل می‌شوند که سیگنال مورد نیاز شما را غرق می‌کنند. تأخیر (latency) آن‌قدر بالا می‌رود که کاربران پیش از رسیدن پاسخ، پرس‌وجو را رها می‌کنند. ما به این دیوار برخوردیم و مجبور شدیم همه چیز را بازسازی کنیم. لایه بازیابی ما از حالت «جستجوی معنایی و امید به نتیجه» به یک خط لوله (pipeline) دقیق و ابزارگذاری‌شده تبدیل شد. نتیجه، رسیدن به ۹۵٪ فراخوانی (recall) و کاهش ۴۰ درصدی تأخیر بود. در اینجا آنچه واقعاً کارآمد بود را می‌آوریم.

استراتژی قطعه‌بندی (Chunking) را با نوع سند مطابقت دهید

استاندارد ۵۱۲ توکنی همچنان پابرجاست، اما نه به این دلیل که درست است، بلکه به این دلیل که انجام دادنش آسان است. اسناد مختلف، معنا را به اشکال متفاوتی منتقل می‌کنند و استراتژی قطعه‌بندی شما باید منعکس‌کننده این واقعیت باشد.

برای قراردادهای حقوقی، از قطعه‌بندی بازگشتی (recursive chunking) استفاده کنید که مرزهای ساختاری را رعایت کند. زبان حقوقی دارای ساختار تو در تو است. یک بند به بخش بالایی خود وابسته است و برش دادن ثابت در میان یک جمله، منطق یک تعهد را از بین می‌برد. قطعه‌بندی بازگشتی تلاش می‌کند ابتدا بر اساس جداکننده‌های طبیعی — ابتدا پاراگراف‌ها و سپس جملات — تقسیم‌بندی را انجام دهد و تنها پس از آن محدودیت توکن را اعمال کند. این کار باعث می‌شود بندهای غرامت یا مسئولیت، دست‌نخورده باقی بمانند.

برای مستندات API، از قطعه‌بندی آگاه از تابع (function-aware chunking) استفاده کنید. توسعه‌دهندگان به دنبال پاراگراف‌های تصادفی نمی‌گردند؛ آن‌ها به دنبال نقاط اتصال (endpoints)، پارامترها و امضاهای خطا هستند. یک قطعه باید شامل امضای کامل تابع، توضیحات آن و طرحواره (schema) بازگشتی به عنوان یک واحد منطقی باشد. اگر آن بلوک را از وسط نصف کنید، سیستم بازیابی تنها نیمی از بافتار (context) را برمی‌گرداند و مدل تولیدکننده، بقیه آن را با توهم (hallucination) می‌سازد.

برای تیکت‌های پشتیبانی، بر قطعه‌بندی معنایی (semantic chunking) که از چرخش‌های گفتگو پیروی می‌کند، تکیه کنید. رشته‌های پشتیبانی خطی و تکراری هستند. مشتری مشکل را تکرار می‌کند، کارشناس درخواست لاگ می‌کند و مشتری آن‌ها را پیوست می‌کند. هر چرخش، یک واحد معنایی مستقل است. قطعه‌بندی بر اساس چرخش‌ها باعث می‌شود مشخص شود چه کسی، چه چیزی را و در چه زمانی گفته است؛ موضوعی که وقتی کاربر می‌پرسد «کارشناس روز سه‌شنبه چه پیشنهادی داد؟»، بسیار حیاتی است.

برای ویکی‌های داخلی، قطعه‌بندی عامل‌محور (agentic chunking) را امتحان کنید. یک بخش از متن را به یک LLM بدهید و از آن بخواهید تصمیم بگیرد که کجا یک موضوع تمام و موضوع دیگر شروع می‌شود. این کار هزینه بیشتری در زمان ورود داده (ingest) دارد، اما ویکی‌ها معمولاً نامنظم هستند. صفحات حاوی به‌روزرسانی‌های بی‌ربط از تیم‌های مختلف هستند و مرزهای تعریف‌شده توسط انسان به‌ندرت کمک‌کننده است. اجازه دادن به یک مدل برای ترسیم مرزها بر اساس تغییر موضوع، نویز را به شدت کاهش می‌دهد.

اجرای چندین استراتژی در یک خط لوله واحد، مستلزم برچسب‌گذاری اسناد بر اساس نوع آن‌ها در زمان ورود داده است. این نظم کوچک در ساختار داده (schema)، بلافاصله سود خود را نشان می‌دهد.

روش‌های جستجو را ترکیب کنید، نه اینکه فقط یکی را انتخاب کنید

جستجوی برداری (Vector search) قصد کاربر را درک می‌کند، اما معمولاً در تطبیق‌های دقیق (exact matches) شکست می‌خورد. اگر کد خطای ERR_CONNECTION_REFUSED یا یک SKU خاص را بخواهید، امبدینگ‌های متراکم (dense embeddings) اغلب نتایجی را برمی‌گردانند که از نظر مفهومی مشابه اما از نظر واقعیت اشتباه هستند. BM25، روش کلاسیک بازیابی کلمات کلیدی (sparse retrieval)، رشته‌های دقیق را به زیبایی مدیریت می‌کند اما ظرافت‌های معنایی را از دست می‌دهد. شما به هر دو نیاز دارید.

از بازیابی ترکیبی (hybrid retrieval) استفاده کنید. جستجوی برداری و BM25 را به صورت موازی اجرا کنید. سپس آن‌ها را با استفاده از تلفیق رتبه متقابل (Reciprocal Rank Fusion یا RRF) ترکیب کنید. RRF به اسنادی پاداش می‌دهد که هر دو روش بر مرتبط بودن آن‌ها توافق دارند، در حالی که همچنان کاندیداهای قوی از هر دو رویکرد را نمایش می‌دهد. محاسبات آن ساده و نتیجه پایدار است: هیچ روش بازیابی واحدی بر رتبه‌بندی نهایی تسلط نخواهد داشت.

پس از ترکیب (fusion)، یک بازرتبه‌بند کراس-انکودر (cross-encoder reranker) اضافه کنید. مرحله اول — یعنی جستجوی برداری به اضافه جستجوی کلمات کلیدی — سریع و گسترده است. سپس کراس-انکودر هر جفتِ «پرس‌وجو-سند» را با توجه کامل (full attention) امتیازدهی می‌کند، به این معنی که واقعاً کاندیدا را در مقابل سوال اصلی می‌خواند. بله، این کار باعث افزایش تأخیر می‌شود؛ در مورد ما، تقریباً بین پنجاه تا صد میلی‌ثانیه. اما افزایش دقت آن‌قدر چشمگیر است که این معامله کاملاً منطقی است. اگر دقت فراخوانی (recall) برایتان مهم است، نمی‌توانید از این مرحله چشم‌پوشی کنید.

پیش از اصلاح ایندکس، پرس‌وجو (Query) را اصلاح کنید

کاربران پرس‌وجوها را برای موتور جستجوی شما نمی‌نویسند؛ آن‌ها را برای انسان‌ها می‌نویسند. «کار نمی‌کند» یک پرس‌وجوی رایج در پشتیبانی است. یک توصیف مبهم از یک ویژگی، یک جستجوی رایج در ویکی‌های داخلی است. اگر با همان ورودی خام در ایندکس جستجو کنید، پاسخ‌های بی‌ارزش دریافت خواهید کرد.

پیش از آنکه پرس‌وجو به بازیاب (retriever) برسد، آن را تغییر شکل دهید.

از query expansion برای تولید نسخه‌های متعددی از سؤال کاربر استفاده کنید. اگر کسی تایپ کند «server down»، سیستم شما باید عبارت‌های «service unavailable»، «502 error» و «connection timeout» را نیز جستجو کند. پوشش دادن این گونه‌های مختلف از قصد کاربر، میزان recall ما را از ۷۸٪ به ۹۶٪ رساند. این کار تنها یک مرحله است و در مقایسه با دستاوردی که دارد، تقریباً هیچ هزینه‌ای ندارد.

برای سؤالات پیچیده از query decomposition استفاده کنید. وقتی کاربر چیزی شبیه به این می‌پرسد: «چگونه از legacy billing API به نسخه جدید مهاجرت کنم و چه تغییرات ساختاری (breaking changes) بر حساب‌های سازمانی تأثیر می‌گذارد؟»، آن را به زیر-سؤال‌ها تقسیم کنید. یک زیر-سؤال بر مراحل مهاجرت تمرکز دارد و دیگری بر تغییرات ساختاری مختص سازمان‌ها. هر کدام به بخش متفاوتی از index دسترسی پیدا می‌کنند. مدل زبانی پایین‌دستی، پاسخ نهایی را از تکه‌های (chunks) به‌خوبی بازیابی‌شده ترکیب می‌کند، به جای اینکه در یک context window نویزدار حدس بزند.

از حدس زدن هایپرپارامترها دست بردارید

زمانی که استراتژی‌های تکه‌بندی (chunking)، بازیابی ترکیبی (hybrid retrieval) و تغییر شکل پرس‌وجو (query transformation) را داشتید، با یک مسئله ترکیبیاتی روبرو می‌شوید. اندازه تکه (chunk size)، هم‌پوشانی (overlap)، وزن‌های ادغام (fusion weights)، عمق reranker و تعداد توسعه همگی با هم در تعامل هستند. تغییر دادن یکی از آن‌ها به تنهایی، باعث از کار افتادن دیگری می‌شود. انجام grid search در این فضا، هدر دادن وقت و انرژی است.

در عوض از بهینه‌سازی بیزی (Bayesian optimization) استفاده کنید. با این کار مانند یک فرآیند تنظیم (tuning) مدل یادگیری ماشین رفتار کنید. هدف خود را به‌وضوح تعریف کنید: بیشینه‌سازی recall در حالی که latency (تأخیر) زیر یک سقف مشخص باقی بماند. یک golden dataset بسازید — چند صد سؤال معرف که دقیقاً می‌دانید کدام تکه‌ها (chunks) باید بازیابی شوند. سپس اجازه دهید جستجوی بیزی فضای پیکربندی را به‌طور کارآمد کاوش کند. این روش یک مدل احتمالی از آنچه کارآمد است می‌سازد و در مرحله بعد، نویدبخش‌ترین مناطق را آزمایش می‌کند.

هر پیکربندی کاندید باید پیش از رسیدن به محیط staging، از golden dataset عبور کند. اگر اندازه تکه جدید باعث کاهش recall شود یا یک reranker سنگین‌تر شما را از بودجه latency عبور دهد، فرآیند بهینه‌سازی به‌طور خودکار آن را شناسایی می‌کند. این کار باعث می‌شود بحث‌های نظری از میان برود. دیگر بحث نمی‌کنید که آیا ۲۵۶ یا ۵۱۲ توکن «بهتر» است، بلکه شروع به خواندن نتایج می‌کنید.

نتیجه

تغییرات در خط لوله (pipeline) دقیقاً همان‌طور که امیدوار بودیم، اثر هم‌افزایی داشتند.

  • Recall@10 از ۷۸٪ به ۹۵٪ افزایش یافت.
  • P95 latency از ۸۵۰ میلی‌ثانیه به ۳۲۰ میلی‌ثانیه کاهش یافت.
  • نرخ توهم (Hallucination rate) از ۱۲٪ به ۳٪ رسید.
  • هزینه هر پرس‌وجو ۳۸٪ کاهش یافت، که عمدتاً به این دلیل بود که بازیابی بهتر به ما اجازه داد از یک مدل تولید (generation model) کوچک‌تر و توکن‌های prompt کمتری استفاده کنیم.

کاهش latency برخی از افراد تیم را شگفت‌زده کرد. اضافه کردن rerankerها و query expansion به نظر می‌رسد که باید سرعت را کاهش دهد. اما چون کیفیت بازیابی بهبود یافت، مدل تولید به prompting کمتر، حدس‌زدن کمتر و تلاش مجدد (retry) کمتری نیاز داشت. بازیابی خوب، تمام مراحل پایین‌دستی را ارزان‌تر می‌کند.

با بازیابی مانند زیرساخت رفتار کنید

بازیابی (Retrieval) یک نوت‌بوک نیست که یک بار آن را اجرا کنید و فراموش کنید. بازیابی یک زیرساخت است و باید مانند کد مدیریت شود. استراتژی‌های تکه‌بندی خود را نسخه‌بندی (version) کنید. وقتی تیم حقوقی یک قالب قرارداد جدید منتشر می‌کند، جداکننده بازگشتی (recursive splitter) خود را پیش از رسیدن به مرحله تولید (production) آزمایش کنید. golden dataset خود را به عنوان اسناد زنده نگه دارید، نه یک فایل CSV ایستا از فصل گذشته. ارزیابی‌های خود را در CI خودکارسازی کنید تا هر pull request که یک مدل embedding یا یک وزن fusion را تغییر می‌دهد، پیش از آنکه انسانی آن را بررسی کند، کامنتی حاوی اعداد recall و latency دریافت کند.

کاربران شما هرگز نخواهند پرسید که از کدام مدل embedding استفاده می‌کنید. آن‌ها اهمیتی به روش تکه‌بندی (chunking heuristic) یا معماری reranker شما نخواهند داد. آن‌ها اهمیت می‌دهند که آیا پاسخ درست است، آیا سریع می‌رسد و آیا می‌توانند به آن اعتماد کنند. خط لوله‌ای بسازید که این اعتماد را جلب کند، آن را صادقانه اندازه‌گیری کنید و از برخورد با بازیابی به عنوان یک موضوع فرعی خودداری کنید.

Source: Optimizing RAG At Scale
Join the discussion: GyaanSetu AI Community