فاز 11 · درس 6

معماری RAG چیست؟ راهنمای کامل ساخت پایپ‌لاین بازیابی و تولید

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
یک نقشه‌ی مسیر روشن که اسناد متنی را با خطوط نوری به بردارهای عددی، یک پایگاه داده قفسه‌ای و در نهایت به یک هسته‌ی پردازشی متصل می‌کند
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. الگوی RAG
  5. چرا RAG از فاین‌تیونینگ بهتر است؟
  6. مدل‌های امبدینگ
  7. شباهت برداری
  8. استراتژی‌های قطعه‌بندی متن
  9. پایگاه‌های داده‌ی برداری
  10. پایپ‌لاین کامل
  11. اعداد و ارقام در دنیای واقعی
  12. پیاده‌سازی از صفر
  13. گام 1: قطعه‌بندی اسناد
  14. گام 2: ساخت امبدینگ با TF-IDF
  15. گام 3: جستجوی شباهت کسینوسی
  16. گام 4: ساخت پرامپت
  17. گام 5: پایپ‌لاین کامل RAG
  18. گام 6: تولید پاسخ (شبیه‌سازی‌شده)
  19. استفاده از ابزارهای واقعی
  20. خروجی این درس
  21. تمرین‌ها
  22. اصطلاحات کلیدی
  23. برای مطالعه‌ی بیشتر

مدل‌های زبانی بزرگ هر اندازه هم که قدرتمند باشند، دانشی فراتر از تاریخ آموزش خود ندارند و هیچ درکی از مستندات شرکتی، کدهای خصوصی یا داده‌های داخلی شما نخواهند داشت. حل این مشکل با بازآموزی یا فاین‌تیونینگ (Fine-tuning) اغلب گران، زمان‌بر و غیرمنعطف است. معماری «تولید مبتنی بر بازیابی» یا RAG دقیقاً این گره را باز می‌کند: اتصال پویای پایگاه دانش به ورودی مدل برای رسیدن به پاسخ‌هایی دقیق، به‌روز و مستند.

اگر در ایران پروژه‌های مبتنی بر هوش مصنوعی توسعه می‌دهید، استفاده از سرویس‌های تجاری خارجی با محدودیت‌هایی مثل عدم امکان پرداخت ارزی با کارت‌های بانکی داخلی و مسدود بودن برخی APIها همراه است. با این حال، زیبایی معماری RAG در استقلال منطق آن از یک ارائه‌دهنده‌ی خاص نهفته است؛ شما می‌توانید کل پایپ‌لاین را با پایگاه‌داده‌های محلی مثل Chroma و مدل‌های متن‌باز سبک روی سیستم شخصی خود پیاده کنید، یا در صورت تمایل از گیت‌وی‌های ارائه‌دهنده‌ی API سازگار با ساختار استاندارد بهره ببرید.

در این درس گام‌به‌گام یاد می‌گیرید که چطور اسناد را قطعه‌بندی کنید، با مدل‌های امبدینگ بردار آن‌ها را بسازید، شباهت معنایی را بسنجید و پرامپتی غنی‌شده برای تولید پاسخ نهایی توسط مدل زبانی فراهم کنید.

مدل زبانی بزرگ شما (LLM) همه‌چیز را تا زمان اتمام دوره‌ی آموزشش می‌داند؛ اما هیچ شناختی از مستندات شرکتی، پایگاه‌کد یا یادداشت‌های جلسه‌ی هفته‌ی قبل شما ندارد. راهکار این مسئله، «تولید مبتنی بر بازیابی» یا RAG (مخفف Retrieval-Augmented Generation) است: اسناد مرتبط بازیابی و درون پرامپت جاسازی می‌شوند. این الگو بیشترین کاربرد عملیاتی را در سیستم‌های هوش مصنوعی دنیای واقعی دارد. اگر از این دوره فقط یک چیز می‌سازید، آن پایپ‌لاین RAG باشد.

مطالب مرتبط: فاز 5 · بخش 23 (استراتژی‌های قطعه‌بندی متن برای RAG) برای بررسی شش الگوریتم قطعه‌بندی متن و زمان برتری هرکدام. فاز 5 · بخش 22 (بررسی عمیق مدل‌های امبدینگ) برای انتخاب مدل امبدینگ مناسب. فاز 11 · بخش 07 (RAG پیشرفته) برای جستجوی ترکیبی (hybrid search)، بازرتبه‌بندی (reranking) و بازنویسی پرس‌وجو (query transformation).

اهداف یادگیری

  • ساخت یک پایپ‌لاین RAG کامل: بارگذاری اسناد، قطعه‌بندی متن، امبدینگ، ذخیره‌سازی برداری، بازیابی و تولید پاسخ
  • پیاده‌سازی جستجوی معنایی (semantic search) با استفاده از یک پایگاه داده‌ی برداری (ChromaDB، FAISS یا Pinecone) همراه با ایندکس‌گذاری اصولی
  • توضیح چرایی برتری RAG نسبت به فاین‌تیونینگ (fine-tuning) در کاربردهای مبتنی بر دانش مستند (هزینه، تازگی داده‌ها، قابلیت ارجاع)
  • ارزیابی کیفیت RAG بر اساس معیارهای بازیابی (دقت، بازخوانی) و معیارهای تولید (وفاداری، ارتباط موضوعی)

صورت مسئله

فرض کنید یک چت‌بات برای شرکت خود می‌سازید. مشتری می‌پرسد: «سیاست بازگشت وجه در پلن‌های سازمانی چطور است؟» مدل پاسخی کلی درباره‌ی سیاست‌های متداول بازگشت وجه در شرکت‌های SaaS می‌دهد. درحالی‌که سیاست واقعی شما در یک صفحه‌ی ویکی داخلی 200 صفحه‌ای پنهان است: مشتریان سازمانی مهلتی 60 روزه با بازگشت وجه متناسب با مدت مصرف دارند. مدل هرگز این سند را ندیده است و طبیعتاً چیزی را که رویش آموزش ندیده، نمی‌تواند بداند.

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

RAG راهکار دیگر است: مدل را دست‌نخورده نگه دارید. وقتی سؤالی مطرح شد، مخزن اسناد خود را برای یافتن بندهای مرتبط جستجو کنید، این بندها را قبل از سؤال داخل پرامپت بگذارید و اجازه دهید مدل با تکیه بر این بندها به‌عنوان زمینه (context)، پاسخ دهد. مخزن اسناد را می‌توان ظرف چند دقیقه به‌روزرسانی کرد. دقیقاً می‌بینید کدام اسناد بازیابی شده‌اند. خود مدل هم هیچ‌وقت تغییر نمی‌کند. دلیل اینکه RAG الگوی غالب در محیط‌های عملیاتی است دقیقاً همین است: ارزان‌تر است، اطلاعاتش تازه‌تر می‌ماند، قابلیت ارزیابی و ردگیری بیشتری دارد و با هر مدلی کار می‌کند.

مفاهیم پایه

الگوی RAG

کل این الگو در چهار مرحله خلاصه می‌شود:

تولید پاسخافزودنبازیابیپرسش کاربربازیابیافزودن به پرامپتتولید پاسخپاسختعبیه‌سازی پرسشجستجو در پایگاه داده برداریبازگرداندن k تکه برترقالب‌بندی تکه‌ها در پرامپتترکیب با پرسش کاربرتولید پاسخ توسط LLMپاسخ مستند به اسناد بازیابی‌شده

پرس‌وجو -> بازیابی -> غنی‌سازی پرامپت -> تولید پاسخ. تمام سیستم‌های RAG از این چارچوب پیروی می‌کنند. تفاوت سیستم‌های عملیاتی در جزئیات اجرای هر مرحله است: چگونه قطعه‌بندی می‌کنید، امبدینگ را چطور می‌سازید، چگونه جستجو می‌کنید و پرامپت را به چه شکلی می‌چینید.

چرا RAG از فاین‌تیونینگ بهتر است؟

دغدغه فاین‌تیونینگ RAG
هزینه 1,000 تا بیش از 100,000 دلار برای هر بار آموزش 0.01 تا 0.10 دلار به ازای هر پرسش (امبدینگ + LLM)
تازگی داده‌ها منسوخ تا زمان آموزش مجدد به‌روزرسانی چنددقیقه‌ای با ایندکس مجدد اسناد
قابلیت ردگیری (Auditability) منبع پاسخ قابل ردیابی نیست قطعه‌های دقیق بازیابی‌شده قابل نمایش هستند
توهم (Hallucination) همچنان آزادانه توهم می‌زند مبتنی بر اسناد بازیابی‌شده و مستند است
حریم خصوصی داده‌ها داده‌های آموزشی با وزن‌های مدل ترکیب می‌شوند اسناد درون پایگاه داده‌ی برداری خودتان باقی می‌مانند

فاین‌تیونینگ وزن‌های مدل را برای همیشه دگرگون می‌کند؛ اما RAG زمینه‌ی متنی مدل را به‌طور موقت تغییر می‌دهد. برای بیشتر کاربردها، دقیقاً به همین زمینه‌ی متنی موقت نیاز دارید.

تنها حالتی که فاین‌تیونینگ پیروز می‌شود زمانی است که بخواهید مدل سبک، لحن نگارش یا الگوی استدلال خاصی پیدا کند که فقط با پرامپت‌نویسی حاصل نمی‌شود. برای بازیابی دانش مبتنی بر واقعیت، RAG همیشه برگ برنده را دارد.

مدل‌های امبدینگ

مدل امبدینگ متن را به یک بردار متراکم (dense vector) تبدیل می‌کند. متن‌های مشابه، در این فضای چندبعدی بردارهایی نزدیک به هم می‌سازند. برای نمونه، «چطور رمزم را بازیابی کنم؟» و «نیاز به تغییر گذرواژه دارم» با اینکه کلمات مشترک کمی دارند، بردارهای تقریباً یکسانی تولید می‌کنند؛ اما «گربه روی موکت نشست» بردار کاملاً متفاوتی می‌سازد.

مدل‌های امبدینگ متداول (چینش سال 2026 — برای تحلیل کامل به فاز 5 · بخش 22 مراجعه کنید):

مدل ابعاد ارائه‌دهنده توضیحات
text-embedding-3-small 1536 (ماتریوشکا) OpenAI بهترین نسبت قیمت به کارایی برای بیشتر کاربردها
text-embedding-3-large 3072 (ماتریوشکا) OpenAI دقت بالاتر، با قابلیت برش ابعاد به 256/512/1024
Gemini Embedding 2 3072 (ماتریوشکا) Google بالاترین امتیاز بازیابی در MTEB؛ پنجره‌ی متنی 8K
voyage-4 1024/2048 (ماتریوشکا) Voyage AI دارای نسخه‌های تخصصی حوزه‌ای (کدنویسی، مالی، حقوقی)
Cohere embed-v4 1024 (ماتریوشکا) Cohere قدرتمند در چندزبانگی، پنجره‌ی متنی 128K
BGE-M3 1024 (متراکم + خلوت + ColBERT) BAAI (وزن‌های باز) ارائه‌ی سه نما در قالب یک مدل
Qwen3-Embedding 4096 (ماتریوشکا) Alibaba (وزن‌های باز) بالاترین امتیاز بازیابی در بین مدل‌های وزن‌های باز
all-MiniLM-L6-v2 384 وزن‌های باز (Sentence Transformers) خط مبنا برای ساخت نمونه‌ی اولیه

در این درس، ما امبدینگ ساده‌ی خودمان را با استفاده از الگوریتم TF-IDF پیاده می‌کنیم. نه به این دلیل که در سیستم‌های واقعی از TF-IDF استفاده می‌شود، بلکه چون مفهوم را کاملاً ملموس می‌کند: متنی وارد می‌شود، برداری خارج می‌گردد و متن‌های مشابه بردار‌های مشابهی به دست می‌دهند.

شباهت برداری

با داشتن دو بردار، شباهت آن‌ها را چطور اندازه می‌گیرید؟ سه انتخاب دارید:

شباهت کسینوسی (Cosine similarity): کسینوس زاویه‌ی بین دو بردار. بازه‌ی آن از 1- (کاملاً متضاد) تا 1 (کاملاً یکسان) است. اندازه‌ی بردار (magnitude) را نادیده می‌گیرد و فقط به جهت آن اهمیت می‌دهد. این معیار گزینه‌ی پیش‌فرض RAG است.

cosine_sim(a,b)=dot(a,b)∥a∥⋅∥b∥\mathop{\mathrm{cosine\_sim}}(a, b) = \frac{\mathop{\mathrm{dot}}(a, b)}{\lVert a \rVert \cdot \lVert b \rVert}

ضرب داخلی (Dot product): ضرب داخلی خام. بردارهای بزرگ‌تر امتیاز بالاتری می‌گیرند. وقتی اندازه‌ی بردار حاوی اطلاعات باشد (مثلاً طولانی‌تر بودن سند نشانه‌ی ارتباط بیشتر باشد)، به کار می‌آید.

dot(a,b)=∑iaibi\mathop{\mathrm{dot}}(a, b) = \sum_i a_i b_i

فاصله‌ی اقلیدسی یا L2: فاصله‌ی خط مستقیم در فضای برداری. فاصله‌ی کمتر یعنی شباهت بیشتر. این معیار به اختلاف در اندازه‌ی بردارها حساس است.

L2(a,b)=∑i(ai−bi)2\mathop{\mathrm{L2}}(a, b) = \sqrt{\sum_i (a_i - b_i)^2}

شباهت کسینوسی همان استاندارد رایج است؛ چون با نرمال‌سازی بر اساس اندازه، با اسناد در طول‌های مختلف بسیار منطقی رفتار می‌کند. وقتی کسی می‌گوید «جستجوی برداری»، تقریباً همیشه منظورش شباهت کسینوسی است.

استراتژی‌های قطعه‌بندی متن

اسناد طولانی‌تر از آن هستند که بتوان آن‌ها را در قالب یک تک‌بردار امبد کرد. یک فایل PDF پنجاه‌صفحه‌ای شاید امبدینگ بسیار نامرغوبی بسازد، چون ده‌ها موضوع مختلف را پوشش می‌دهد. در عوض، اسناد را به قطعه‌های کوچک‌تر خرد و هر قطعه را جداگانه امبد می‌کنید.

قطعه‌بندی با اندازه‌ی ثابت: متن را سر هر N توکن خرد می‌کنید؛ ساده و پیش‌بینی‌پذیر. یک قطعه‌ی 512 توکنی با 50 توکن هم‌پوشانی (overlap) به این معنی است که قطعه‌ی 1 شامل توکن‌های 0 تا 511 است، قطعه‌ی 2 توکن‌های 462 تا 973 را در بر می‌گیرد و الی آخر. وجود هم‌پوشانی تضمین می‌کند جمله‌ای را ناخواسته در مرزی نامناسب قطع نکنید.

قطعه‌بندی معنایی (Semantic chunking): برش‌ها در مرزهای طبیعی زبان انجام می‌شوند؛ مثل پاراگراف‌ها، بخش‌ها یا هدرهای Markdown. هر قطعه یک واحد معنایی یکپارچه است. پیاده‌سازی این روش پیچیده‌تر است، اما بازیابی بهتری ارائه می‌دهد.

قطعه‌بندی بازگشتی (Recursive chunking): ابتدا تلاش می‌کند متن را بر اساس بزرگ‌ترین مرز (عنوان بخش‌ها) تقسیم کند. اگر یک بخش همچنان بیش از حد بزرگ بود، سراغ مرز پاراگراف‌ها می‌رود و اگر پاراگراف هم بزرگ بود، مرز جملات را بررسی می‌کند. این منطق شیوه‌ی کار RecursiveCharacterTextSplitter در LangChain است و در عمل نتایج درخشانی دارد.

اندازه‌ی قطعه اهمیتی فراتر از حد تصور عموم دارد:

  • قطعات بسیار کوچک (64 تا 128 توکن): قطعه فاقد زمینه‌ی متنی کافی است. جمله‌ای مثل «در فصل گذشته 15٪ رشد کرد» بدون دانستن اینکه به چه چیزی ارجاع می‌دهد، معنایی ندارد.
  • قطعات بسیار بزرگ (بیش از 2048 توکن): هر قطعه چندین موضوع را در بر می‌گیرد و ارتباط موضوعی تضعیف می‌شود. وقتی داده‌های درآمد را جستجو می‌کنید، قطعه‌ای دریافت می‌کنید که 10٪ آن به درآمد و 90٪ بقیه‌اش به تعداد پرسنل مربوط است.
  • نقطه‌ی طلایی (256 تا 512 توکن): محتوای مستقل و متمرکز، درعین‌حال دارای زمینه‌ی کافی برای معنادار بودن.

بیشتر سیستم‌های عملیاتی RAG از قطعات 256 تا 512 توکنی با 50 توکن هم‌پوشانی استفاده می‌کنند؛ مستندات راهنمای RAG در Anthropic نیز همین بازه را پیشنهاد می‌دهد.

پایگاه‌های داده‌ی برداری

وقتی امبدینگ‌ها آماده شدند، برای ذخیره و جستجوی آن‌ها به محیطی نیاز دارید. گزینه‌های پیش رو:

پایگاه داده نوع مناسب برای
FAISS کتابخانه (درون‌پروسسی) نمونه‌سازی، مجموعه‌داده‌های کوچک تا متوسط
Chroma دیتابیس سبک توسعه‌ی محلی، استقرارهای کوچک
Pinecone سرویس مدیریت‌شده استقرار عملیاتی بدون دغدغه‌ی مدیریت زیرساخت
Weaviate دیتابیس متن‌باز استقرار عملیاتی روی سرورهای اختصاصی
pgvector افزونه‌ی Postgres سامانه‌هایی که هم‌اکنون از Postgres استفاده می‌کنند
Qdrant دیتابیس متن‌باز استقرار اختصاصی با عملکرد بسیار بالا

ما در این درس یک مخزن برداری سبک درون حافظه می‌سازیم. این مخزن بردارها را در قالب یک لیست ذخیره می‌کند و جستجوی شباهت کسینوسی را با روش همه‌جانبه و مقایسه‌ی تک‌تک موارد (brute-force) انجام می‌دهد؛ یعنی معادل FAISS با ایندکس Flat. این رویکرد تا حدود 100,000 بردار خوب کار می‌کند و بعد از آن کند می‌شود. در سیستم‌های عملیاتی از الگوریتم‌های نزدیک‌ترین همسایه‌ی تقریبی (ANN) مانند HNSW استفاده می‌کنند تا میلیون‌ها بردار را در چند میلی‌ثانیه جستجو کنند.

پایپ‌لاین کامل

پرس‌وجو (آنلاین)نمایه‌سازی (آفلاین)فضای برداری یکساناسنادتکه‌تکه‌سازیتعبیه‌سازی هر تکهذخیره بردارها + متنپرسش کاربرتعبیه‌سازی پرسشجستجوی برداری (k برتر)ساخت پرامپت با تکه‌هاتولید پاسخ توسط LLM

مرحله‌ی ایندکس‌گذاری فقط یک بار به ازای هر سند اجرا می‌شود (یا زمانی که اسناد به‌روزرسانی شوند). در مقابل، مرحله‌ی پرس‌وجو به ازای هر درخواست کاربر اجرا می‌شود. در سیستم‌های عملیاتی، ممکن است ایندکس‌گذاری میلیون‌ها سند ساعت‌ها طول بکشد؛ اما مرحله‌ی پرس‌وجو باید حتماً در کسری از ثانیه پاسخ دهد.

اعداد و ارقام در دنیای واقعی

بیشتر سیستم‌های عملیاتی RAG با این پارامترها کار می‌کنند:

  • بازیابی k = 5 تا 10 قطعه برای هر پرس‌وجو
  • اندازه‌ی هر قطعه = 256 تا 512 توکن به همراه 50 توکن هم‌پوشانی
  • بودجه‌ی زمینه (Context budget): 2,500 تا 5,000 توکن محتوای بازیابی‌شده برای هر پرس‌وجو
  • مجموع حجم پرامپت: حدود 8,000 تا 16,000 توکن (پرامپت سیستم + قطعات بازیابی‌شده + تاریخچه‌ی مکالمه + پرس‌وجوی کاربر)
  • ابعاد امبدینگ: 384 تا 3072 با توجه به مدل انتخابی
  • نرخ پردازش ایندکس‌گذاری: 100 تا 1,000 سند در ثانیه هنگام استفاده از APIهای امبدینگ
  • تاخیر در پاسخ‌دهی به پرس‌وجو: 50 تا 200 میلی‌ثانیه برای بازیابی، 500 تا 3000 میلی‌ثانیه برای تولید متن توسط مدل
قطعه‌بندی متن در RAGاندازه قطعه و k را بکشید
قطعه‌های کوچک بخش مرتبط را دقیقاً مشخص می‌کنند اما سیاق متن (context) را تکه‌تکه کرده و تعداد بردارهای ایندکس را چند برابر می‌سازند. قطعه‌های بزرگ سیاق را یکپارچه نگه می‌دارند اما تطابق‌ها را کم‌رنگ کرده و تعداد توکن‌های ارسالی به پرامپت را به شدت افزایش می‌دهند. همپوشانی مرزها را ملایم‌تر می‌کند، اما به قیمت ایجاد قطعات بیشتر.

پیاده‌سازی از صفر

گام 1: قطعه‌بندی اسناد

def chunk_text(text, chunk_size=200, overlap=50):
    words = text.split()
    chunks = []
    start = 0
    while start < len(words):
        end = start + chunk_size
        chunk = " ".join(words[start:end])
        chunks.append(chunk)
        start += chunk_size - overlap
    return chunks

گام 2: ساخت امبدینگ با TF-IDF

یک تابع ساده برای امبدینگ می‌نویسیم. هرچند TF-IDF (معیار بسامد ترم در معکوس بسامد سند) یک امبدینگ عصبی نیست، اما متن را طوری به بردار تبدیل می‌کند که اهمیت هر واژه حفظ شود. کلماتی که در یک سند مدام تکرار شوند TF بالاتری می‌گیرند و کلماتی که در سراسر پیکره‌ی متنی کمیاب باشند IDF بیشتری به دست می‌آورند. حاصل‌ضرب این دو، برداری تولید می‌کند که واژگان تمایزبخش و مهم در آن مقادیر بالاتری دارند.

import math
from collections import Counter

def build_vocabulary(documents):
    vocab = set()
    for doc in documents:
        vocab.update(doc.lower().split())
    return sorted(vocab)

def compute_tf(text, vocab):
    words = text.lower().split()
    count = Counter(words)
    total = len(words)
    return [count.get(word, 0) / total for word in vocab]

def compute_idf(documents, vocab):
    n = len(documents)
    idf = []
    for word in vocab:
        doc_count = sum(1 for doc in documents if word in doc.lower().split())
        idf.append(math.log((n + 1) / (doc_count + 1)) + 1)
    return idf

def tfidf_embed(text, vocab, idf):
    tf = compute_tf(text, vocab)
    return [t * i for t, i in zip(tf, idf)]
def cosine_similarity(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return dot / (norm_a * norm_b)

def search(query_embedding, stored_embeddings, top_k=5):
    scores = []
    for i, emb in enumerate(stored_embeddings):
        sim = cosine_similarity(query_embedding, emb)
        scores.append((i, sim))
    scores.sort(key=lambda x: x[1], reverse=True)
    return scores[:top_k]

گام 4: ساخت پرامپت

بخش «غنی‌سازی» در معماری RAG دقیقاً در همین نقطه رخ می‌دهد. قطعه‌های بازیابی‌شده را بردارید، آن‌ها را در قالب یک پرامپت مرتب کنید و از مدل بخواهید پاسخ را با تکیه بر این زمینه‌ی متنی بنویسد.

def build_rag_prompt(query, retrieved_chunks):
    context = "\n\n---\n\n".join(
        f"[Source {i+1}]\n{chunk}"
        for i, chunk in enumerate(retrieved_chunks)
    )
    return f"""Answer the question based ONLY on the following context.
If the context doesn't contain enough information, say "I don't have enough information to answer that."

Context:
{context}

Question: {query}

Answer:"""

گام 5: پایپ‌لاین کامل RAG

class RAGPipeline:
    def __init__(self):
        self.chunks = []
        self.embeddings = []
        self.vocab = []
        self.idf = []

    def index(self, documents):
        all_chunks = []
        for doc in documents:
            all_chunks.extend(chunk_text(doc))
        self.chunks = all_chunks
        self.vocab = build_vocabulary(all_chunks)
        self.idf = compute_idf(all_chunks, self.vocab)
        self.embeddings = [
            tfidf_embed(chunk, self.vocab, self.idf)
            for chunk in all_chunks
        ]

    def query(self, question, top_k=5):
        query_emb = tfidf_embed(question, self.vocab, self.idf)
        results = search(query_emb, self.embeddings, top_k)
        retrieved = [(self.chunks[i], score) for i, score in results]
        prompt = build_rag_prompt(
            question, [chunk for chunk, _ in retrieved]
        )
        return prompt, retrieved

گام 6: تولید پاسخ (شبیه‌سازی‌شده)

در محیط عملیاتی، در این مرحله API مدل زبانی را فراخوانی می‌کنید. برای این درس، کار تولید را با استخراج مرتبط‌ترین جمله از متن بازیابی‌شده شبیه‌سازی می‌کنیم.

def simple_generate(prompt, retrieved_chunks):
    query_words = set(prompt.lower().split("question:")[-1].split())
    best_sentence = ""
    best_score = 0
    for chunk in retrieved_chunks:
        for sentence in chunk.split("."):
            sentence = sentence.strip()
            if not sentence:
                continue
            words = set(sentence.lower().split())
            overlap = len(query_words & words)
            if overlap > best_score:
                best_score = overlap
                best_sentence = sentence
    return best_sentence if best_sentence else "I don't have enough information."

استفاده از ابزارهای واقعی

با بهره‌گیری از یک مدل امبدینگ واقعی و LLM، چارچوب کد به سختی تغییر می‌کند:

from openai import OpenAI

client = OpenAI()

def embed(text):
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

def generate(prompt):
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    return response.choices[0].message.content

یا با استفاده از Anthropic:

import anthropic

client = anthropic.Anthropic()

def generate(prompt):
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.content[0].text

پایپ‌لاین کاملاً مشابه است. فقط تابع امبدینگ و تابع تولید پاسخ را عوض می‌کنید. منطق بازیابی، قطعه‌بندی متن و ساخت پرامپت مستقل از نوع مدل ثابت می‌ماند.

برای ذخیره‌سازی داده‌های برداری در مقیاس بزرگ، جستجوی همه‌جانبه‌ی ساده را با یک پایگاه داده‌ی برداری استاندارد جایگزین کنید:

import chromadb

client = chromadb.Client()
collection = client.create_collection("my_docs")

collection.add(
    documents=chunks,
    ids=[f"chunk_{i}" for i in range(len(chunks))]
)

results = collection.query(
    query_texts=["What is the refund policy?"],
    n_results=5
)

ابزار Chroma ساخت امبدینگ را در لایه‌ی درونی خود مدیریت می‌کند (به‌طور پیش‌فرض از all-MiniLM-L6-v2 بهره می‌برد) و بردارها را روی یک دیتابیس محلی ذخیره می‌سازد. همان الگو، فقط زیرساخت متفاوت.

خروجی این درس

این درس این خروجی‌ها را می‌سازد:

  • outputs/prompt-rag-architect.md -- پرامپتی جهت طراحی سیستم‌های RAG برای کاربردهای مشخص
  • outputs/skill-rag-pipeline.md -- مهارتی که ساخت و اشکال‌زدایی پایپ‌لاین‌های RAG را به ایجنت‌ها یاد می‌دهد

تمرین‌ها

  1. امبدینگ TF-IDF را با یک رویکرد ساده‌ی کیسه‌ی کلمات (bag-of-words) جایگزین کنید (باینری: اگر کلمه بود 1 و اگر نبود 0). کیفیت بازیابی را روی اسناد نمونه مقایسه کنید. روش TF-IDF به دلیل وزن دادن بیشتر به واژگان کمیاب باید نتیجه‌ی بهتری بدهد.

  2. با اندازه‌های مختلف قطعه‌ها آزمایش کنید: مقادیر 50، 100، 200 و 500 کلمه را روی همان مجموعه‌ی اسناد امتحان کنید. برای هر اندازه، 5 پرس‌وجوی یکسان را اجرا کنید و ببینید در چند مورد، قطعه‌ی مرتبط جزو 3 نتیجه‌ی برتر بازگردانده می‌شود. نقطه‌ی بهینه‌ای را بیابید که کیفیت بازیابی در آن به اوج می‌رسد.

  3. به هر قطعه‌ی متنی متادیتا اضافه کنید (نام سند مبدا، جایگاه قطعه در سند). قالب پرامپت (Prompt template) را طوری تغییر دهید که ارجاع به منبع را در بر بگیرد تا LLM منابع خود را ذکر کند.

  4. یک ارزیابی ساده پیاده‌سازی کنید: با داشتن 10 جفت پرسش و پاسخ، هر پرسش را به پایپ‌لاین RAG بفرستید و بسنجید چند درصد از قطعه‌های بازیابی‌شده حاوی پاسخ هستند. این معیار همان بازخوانی بازیابی در k (یا recall at k) است.

  5. یک پایپ‌لاین RAG با درک مکالمه بسازید: تاریخچه‌ای از 3 تبادل آخر را نگه دارید و آن‌ها را در کنار قطعه‌های بازیابی‌شده به پرامپت اضافه کنید. پایپ‌لاین را با سؤالات پیگیرانه مثل «طرح سازمانی چطور؟» بعد از پرسیدن درباره‌ی قیمت‌ها بیازمایید.

اصطلاحات کلیدی

اصطلاح معنایی که در بازار رواج دارد مفهوم فنی و دقیق
RAG «هوش مصنوعی که اسناد شما را می‌خواند» بازیابی اسناد مرتبط، قرار دادن آن‌ها درون پرامپت و تولید پاسخ مستند به آن اسناد
امبدینگ (Embedding) «تبدیل کلمات به اعداد» بازنمایی متراکم متن در قالب یک بردار؛ طوری که مفاهیم نزدیک، بردارهای نزدیکی تولید کنند
پایگاه داده‌ی برداری «موتور جستجو برای هوش مصنوعی» مخزن داده‌ای بهینه‌سازی‌شده برای ذخیره‌ی بردارها و پیدا کردن نزدیک‌ترین همسایه‌ها بر اساس شباهت
قطعه‌بندی متن (Chunking) «تکه کردن اسناد» خرد کردن متون به بخش‌های کوچک‌تر (معمولاً 256 تا 512 توکن) برای امبدینگ و بازیابی مستقل هر بخش
شباهت کسینوسی «میزان شباهت دو بردار» کسینوس زاویه‌ی بین دو بردار؛ مقدار 1 یعنی جهت کاملاً یکسان، 0 یعنی عمود و 1- یعنی کاملاً متضاد
بازیابی Top-k «گرفتن k نتیجه‌ی برتر» دریافت k قطعه از پایگاه داده‌ی برداری که بالاترین شباهت را با پرس‌وجو دارند
پنجره‌ی زمینه (Context window) «حجم متنی که مدل می‌تواند ببیند» حداکثر توکن‌هایی که مدل می‌تواند در یک درخواست پردازش کند؛ قطعات بازیابی‌شده باید داخل این ظرفیت جا شوند
تولید ارتقایافته (Augmented generation) «پاسخ دادن با توجه به متن دریافتی» تولید پاسخ با تکیه بر اسناد بازیابی‌شده به‌عنوان زمینه، به جای اتکای صرف به دانش حاصل از آموزش
TF-IDF «امتیازدهی به اهمیت کلمات» حاصل‌ضرب بسامد ترم در معکوس بسامد سند؛ کلمات را بر اساس میزان تمایزبخشی آن‌ها در سراسر یک پیکره وزن‌دهی می‌کند
ایندکس‌گذاری (Indexing) «آماده کردن اسناد برای جستجو» فرآیند آفلاین قطعه‌بندی متن، امبدینگ و ذخیره‌سازی اسناد به شکلی که در زمان ارسال پرس‌وجو قابل جستجو باشند

برای مطالعه‌ی بیشتر

  • مقاله‌ی Lewis et al با عنوان «Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks» (منتشرشده در 2020) -- مقاله‌ی اصلی تیم فیس‌بوک (FAIR) که الگوی بازیابی-سپس-تولید را صورتبندی کرد.
  • مستندات راهنمای RAG در Anthropic به نشانی (docs.anthropic.com) -- ارائه‌دهنده‌ی نکات کاربردی درباره‌ی اندازه‌ی قطعات، ساخت پرامپت و فرآیندهای ارزیابی.
  • مرکز آموزش Pinecone، مقاله‌ی «?What is RAG» -- ارائه‌ی توضیحاتی تصویری و شفاف از پایپ‌لاین RAG و نکات پیاده‌سازی آن در ابعاد عملیاتی.
  • مقاله‌ی Sentence-BERT از Reimers و Gurevych (منتشرشده در 2019) -- مقاله‌ی مرجع مدل‌های امبدینگ سری all-MiniLM که شیوه‌ی آموزش بای‌ان‌کودرها (Bi-encoder) را برای یافتن شباهت‌های معنایی نشان داد.
  • مقاله‌ی Karpukhin et al با عنوان «Dense Passage Retrieval for Open-Domain Question Answering» (کنفرانس EMNLP 2020) -- مقاله‌ی DPR که ثابت کرد بازیابی متراکم مبتنی بر بای‌ان‌کودر نسبت به الگوریتم BM25 در سامانه‌های پرسش و پاسخ با دامنه‌ی باز برتری دارد و الگوی بازیاب‌های مدرن RAG را پایه‌گذاری کرد.
  • مفاهیم سطح بالای LlamaIndex -- مفاهیم اصلی برای ساخت پایپ‌لاین‌های RAG: بارگذارهای داده، تجزیه‌کننده‌های نود، ایندکس‌ها، موتورهای بازیابی و سنتزکننده‌های پاسخ.
  • آموزش RAG در LangChain -- هماهنگ‌کننده‌ای با سبک متفاوت؛ نگاه زنجیره‌ای از اجزای اجرایی (Runnable) به همان الگوی بازیابی-سپس-تولید.

سؤالات متداول

چه تفاوتی میان RAG و فاین‌تیونینگ وجود دارد؟

فاین‌تیونینگ وزن‌های مدل را به‌صورت دائمی و پرهزینه برای یادگیری لحن یا سبک تغییر می‌دهد، اما RAG بدون دستکاری وزن‌ها، اسناد تازه را لحظه‌ای بازیابی کرده و درون پرامپت قرار می‌دهد که بسیار ارزان‌تر و قابل‌ردگیری‌تر است.

اندازه‌ی بهینه برای قطعه‌بندی متن (Chunk Size) در RAG چقدر است؟

در بیشتر سیستم‌های عملیاتی، نقطه‌ی طلایی بین 256 تا 512 توکن همراه با حدود 50 توکن هم‌پوشانی (Overlap) است تا متن هم دارای زمینه‌ی کافی باشد و هم تمرکز معنایی خود را از دست ندهد.

معیار پیش‌فرض برای سنجش شباهت متن‌ها در RAG چیست؟

شباهت کسینوسی (Cosine similarity) استاندارد اصلی است؛ زیرا بدون در نظر گرفتن اندازه‌ی بردار و طول سند، تنها زاویه و جهت معنایی بردارها را ارزیابی می‌کند.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.