فاز 19 · درس 2

پروژه نهایی 2: پیاده‌سازی RAG روی کدبیس و جستجوی معنایی چندریپازیتوری

نوع
پروژه‌ی پایانی
زمان
حدود 10 دقیقه
زبان‌ها
Python (ingestion), TypeScript (API + UI)
پیش‌نیاز
فاز 5 (مبانی پردازش زبان طبیعی)، فاز 7 (ترنسفورمرها)، فاز 11 (مهندسی مدل‌های زبانی بزرگ)، فاز 13 (ابزارها)، فاز 17 (زیرساخت)
یک نقشه‌ی انتزاعی سه‌بعدی و شبکه‌ای از فایل‌ها و توابع کد متصل‌به‌هم که پرتویی از نور روی بخش‌های مرتبط متمرکز شده است
فهرست مطالب
  1. مسئله
  2. مبانی مفهومی
  3. معماری
  4. استک فنی
  5. پیاده‌سازی گام‌به‌گام
  6. استفاده از سیستم
  7. خروجی پروژه
  8. تمرین‌ها
  9. اصطلاحات کلیدی
  10. برای مطالعه‌ی بیشتر

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

در این پروژه نهایی، یک سامانه‌ی کامل RAG روی کدبیس پیاده می‌کنید که کد را با ابزار Tree-sitter در مرز توابع و کلاس‌ها قطعه‌بندی می‌کند، با جستجوی ترکیبی متراکم و پراکنده (BM25) داده‌ها را واکشی می‌نماید و با گراف نمادها و بازرتبه‌بندی کراس‌ان‌کودر، پاسخی مستند به شماره خطوط تحویل می‌دهد. همچنین این سیستم با هر تغییر در Git، فرایند ایندکس مجدد را به‌شکل افزایشی و سریع مدیریت می‌کند.

برای اجرای بخش‌های مختلف این پروژه، به ابزارهایی مانند Qdrant و مدل‌های زبانی خارجی یا محلی نیاز است. توجه داشته باشید که دسترسی به سرویس‌های ارائه‌دهنده‌ی API خارجی (مانند OpenAI یا ارائه‌دهندگان ابری) و پرداخت مستقیم ارزی برای آن‌ها از داخل ایران با چالش‌هایی همراه است؛ با این حال، می‌توانید در بخش‌های مختلف از مدل‌های متن‌باز خودمیزبان مانند nomic-embed-code روی زیرساخت محلی یا گیت‌وی‌های ارائه‌دهنده‌ی API سازگار استفاده کنید.

در سال 2026، هر سازمان مهندسی جدی یک سیستم جستجوی کد داخلی اجرا می‌کند که معنا را می‌فهمد، نه صرفاً رشته‌های متنی را. سیستم Amp در Sourcegraph، پاسخ‌های کدبیس در Cursor، گراف سازمانی Augment، ابزار repomap در Aider و MCP داخلی Pinterest همگی ساختار یکسانی دارند: ریپازیتوری‌های متعددی را می‌خوانند، با tree-sitter آن‌ها را پارس می‌کنند، قطعه‌های متنی را در سطح تابع و کلاس امبد می‌کنند، جستجوی ترکیبی (hybrid search) انجام می‌دهند، بازرتبه‌بندی می‌کنند و در نهایت با ارجاع دقیق به کد پاسخ می‌دهند. در این پروژه نهایی باید سیستمی بسازید که از پسِ 2 میلیون خط کد در 10 ریپازیتوری بربیاید و با هر بار git push، فرایند نمایه‌سازی مجدد افزایشی (incremental re-indexing) را بدون مشکل تاب بیاورد.

فازهای تمرین‌شده: P5 · P7 · P11 · P13 · P17
زمان: 30 ساعت

مسئله

تا سال 2026، هر ایجنت (Agent) برنامه‌نویسی پیشرو همراه با یک لایه‌ی بازیابی از کدبیس عرضه می‌شود؛ چرا که پنجره‌ی بافتار (Context Window) به‌تنهایی مسائل بین چند ریپازیتوری را حل نمی‌کند. پنجره‌ی بافتار 1 میلیون توکنی Claude کمک‌کننده است، اما نیاز به بازیابی رتبه‌بندی‌شده را از بین نمی‌برد. جستجوی ساده‌ی کسینوسی روی قطعه‌های خام متنی، نتایج را در مواجهه با کدهای تولیدشده توسط مدل، کدهای تکراری در مونوریپوها و کدهای کم‌استفاده در دنباله‌ی طولانی نمادها خراب می‌کند. راهکار محیط عملیاتی (پروداکشن)، جستجوی ترکیبی (امبدینگ متراکم + BM25) روی قطعه‌های متنی آگاه به AST به‌همراه یک بازرتبه‌بند (re-ranker) است که گرافی از ارجاعات نمادها پشتیبان آن باشد.

شما این فرایند را با ایندکس کردن یک ناوگان واقعی از کدها — نه یک ریپازیتوری آموزشی و تستی — و سنجش معیارهای MRR@10، وفاداری (Faithfulness) به ارجاعات و تازگی افزایشی یاد می‌گیرید. حالت‌های شکست (Failure modes) ماهیت زیرساختی دارند: یک مونوریپو با 100 هزار فایل، یک push که نیمی از فایل‌ها را دست‌کاری می‌کند و پرسشی که برای رسیدن به پاسخ درست باید از دلِ چهار ریپازیتوری مختلف عبور کند.

مبانی مفهومی

یک پایپ‌لاین (Pipeline) ورودیِ آگاه به AST هر فایل را با tree-sitter پارس می‌کند، گره‌های تابع و کلاس را بیرون می‌کشد و قطعه‌بندی متن (Chunking) را به‌جای پنجره‌های توکنی ثابت، در مرز گره‌ها انجام می‌دهد. هر قطعه سه بازنمایی دریافت می‌کند: یک امبدینگ متراکم (Dense embedding) با Voyage-code-3 یا nomic-embed-code، اصطلاحات پراکنده‌ی BM25 و یک خلاصه‌ی کوتاه به زبان طبیعی. این خلاصه، مودالیتی سومی برای بازیابی اضافه می‌کند؛ برای نمونه، کاربر می‌پرسد «X چطور مجوز می‌گیرد؟» و خلاصه واژه‌ی «authz» را ذکر می‌کند، حتی اگر داخل کد فقط check_permission آمده باشد.

بازیابی به‌صورت ترکیبی است. هر پرسش هم‌زمان جستجوی متراکم و BM25 را فعال می‌کند، k نتیجه‌ی برتر را ادغام کرده و اجتماع آن‌ها را به یک بازرتبه‌بند کراس‌ان‌کودر (Cross-encoder مانند Cohere rerank-3 یا bge-reranker-v2-gemma-2b) می‌سپارد. فهرست بازرتبه‌بندی‌شده به یک مدل سنتزکننده‌ی با بافتار طولانی (Claude Sonnet 4.7 با کش کردن پرامپت یا نسخه‌ی خودمیزبان Llama 3.3 70B) فرستاده می‌شود و سیستم به آن دستور می‌دهد به ازای هر ادعا، به نام فایل و بازه‌ی خطوط ارجاع دهد. پاسخ‌های بدون ارجاع توسط یک پس‌فیلتر رد می‌شوند.

تازگی داده‌ها در سطح زیرساخت، چالش اصلی است. دستور Git push یک diff می‌گیرد: کدام فایل‌ها و کدام نمادها تغییر کرده‌اند؟ فقط برای قطعه‌های متنی متأثر، دوباره امبدینگ تولید می‌شود. یال‌های بین‌فایلی نمادها که دچار تغییر شده‌اند (ایمپورت‌ها، فراخوانی متدها) دوباره محاسبه می‌شوند. بدین ترتیب ایندکس بدون پردازش دوباره‌ی 2 میلیون خط کد در هر commit، پایدار و به‌روز می‌ماند.

معماری

git push --> webhook --> ingest worker (LlamaIndex Workflow)
                           |
                           v
             tree-sitter parse + AST chunk
                           |
            +--------------+----------------+
            v              v                v
          dense        BM25 index       summary (LLM)
        (Voyage / bge)  (Tantivy)        (Haiku 4.5)
            |              |                |
            +------> Qdrant / pgvector <----+
                            |
                            v
                      symbol graph (Neo4j / kuzu)
                            |
  query --> LangGraph agent (retrieve -> rerank -> synth)
                            |
                            v
                 Claude Sonnet 4.7 1M context
                            |
                            v
                 answer + file:line citations

استک فنی

  • پارس کردن: tree-sitter با گرامر 17 زبان برنامه‌نویسی (Python، TS، Rust، Go، Java، C++ و غیره)
  • امبدینگ‌های متراکم: Voyage-code-3 (سرویس ابری) یا nomic-embed-code-v1.5 (خودمیزبان)، با bge-code-v1 به‌عنوان طرح پشتیبان (Fallback)
  • ایندکس پراکنده: Tantivy (زبان Rust) به‌همراه BM25F با وزن‌دهی فیلدی روی نام نماد در برابر بدنه‌ی آن
  • پایگاه داده‌ی برداری: Qdrant 1.12 با جستجوی ترکیبی، یا ترکیب pgvector + pgvectorscale برای تیم‌های با کمتر از 50 میلیون بردار
  • مدل خلاصه‌سازی قطعه‌ها: Claude Haiku 4.5 یا Gemini 2.5 Flash با کش کردن پرامپت
  • بازرتبه‌بند: Cohere rerank-3 یا نسخه‌ی خودمیزبان bge-reranker-v2-gemma-2b
  • ارکستراسیون (Orchestration): جریان‌های کاری LlamaIndex Workflows برای دریافت داده، LangGraph برای ایجنت پرس‌وجو
  • سنتزکننده: Claude Sonnet 4.7 (با بافتار 1 میلیون توکن) به‌همراه کش کردن پرامپت
  • گراف نمادها: Neo4j (مدیریت‌شده) یا kuzu (درون‌برنامه‌ای) برای یال‌های ایمپورت و فراخوانی
  • رصدپذیری: اسپن‌های (Spans) ابزار Langfuse برای هر مرحله‌ی بازیابی و سنتز
بازیابی ترکیبی (HYBRID RETRIEVAL)متراکم + BM25 - بازرتبه‌بندی
HYBRID RETRIEVALThe query fires both searches at once: dense nearest-neighbor over embeddings and sparse BM25 over terms. Their union feeds the cross-encoder, which reorders the list, and the chunk that BM25 alone would have buried rises to the top. The synthesizer must cite file and line from what survives.queryمتراکم (HNSW)BM25 (کلمات)اجتماعtop-kبازرتبه‌بندی کراس‌ان‌کودرقطعه a 0.31قطعه b 0.58قطعه c 0.87پاسخ به خط:فایل از برترین قطعه ارجاع می‌دهد
پرس‌وجو هر دو جستجو را هم‌زمان اجرا می‌کند: نزدیک‌ترین همسایه‌ی متراکم روی امبدینگ‌ها و BM25 پراکنده روی اصطلاحات. اجتماع آن‌ها به کراس‌ان‌کودر داده می‌شود که فهرست را دوباره مرتب می‌کند، و قطعه‌ای که BM25 به تنهایی دفن می‌کرد به صدر می‌آید. ترکیب‌کننده باید به فایل و خط از موارد باقی‌مانده ارجاع دهد.

پیاده‌سازی گام‌به‌گام

  1. پیمایشگر ورودی (Ingestion walker): تاریخچه‌ی git را در هر push hook پیمایش کنید. فایل‌های تغییریافته را گردآوری کنید. برای هر فایل، آن را با tree-sitter پارس کرده و گره‌های کلاس و تابع را همراه با اسپن کامل سورس‌کد استخراج کنید. رکوردهای قطعه‌ها را با فرمت {repo, path, start_line, end_line, symbol, body} بسازید.

  2. خلاصه‌ساز قطعه‌ها: قطعه‌ها را برای فراخوانی Haiku 4.5 با قابلیت کش کردن پرامپت روی متن اولیه‌ی سیستم (system preamble) دسته‌بندی کنید. پرامپت: «این تابع را در یک جمله خلاصه کنید و قرارداد عمومی (public contract) و اثرات جانبی آن را نام ببرید.» خلاصه را کنار قطعه ذخیره کنید.

  3. استخر امبدینگ: دو صف موازی بسازید: متراکم (Voyage-code-3 با دسته‌های 128 تایی) و خلاصه (همان مدل، ولی روی رشته‌ی متنی خلاصه). بردارها را با داده‌های پیوست {repo, path, start_line, end_line, symbol, kind} در Qdrant بنویسید.

  4. ایندکس BM25: یک ایندکس Tantivy با وزن‌دهی فیلدی راه‌اندازی کنید: وزن نام نماد 4، وزن بدنه‌ی نماد 1 و وزن خلاصه 2. این کار پاسخ به پرسش‌های «پیدا کردن تابع با نام X» را در کنار «پیدا کردن تابعی که کار X را انجام می‌دهد» ممکن می‌کند.

  5. گراف نمادها: برای هر قطعه، یال‌ها را ثبت کنید: ایمپورت‌ها (این فایل از نماد Y در ریپازیتوری Z استفاده می‌کند)، فراخوانی‌ها (این تابع متد M از کلاس C را صدا می‌زند)، وراثت. داده‌ها را در kuzu ذخیره کنید. از این گراف هنگام پرس‌وجو برای گسترش دامنه‌ی بازیابی در سراسر ریپازیتوری‌ها استفاده می‌شود.

  6. ایجنت پرس‌وجو: یک گراف LangGraph با سه گره بسازید. گره retrieve جستجوی متراکم و BM25 را موازی اجرا کرده و بر اساس (repo, path, symbol) موارد تکراری را حذف می‌کند. گره rerank کراس‌ان‌کودر را روی 50 مورد برتر اجرا کرده و 10 تای اول را نگه می‌دارد. گره synth مدل Claude Sonnet 4.7 را با قطعه‌های بازرتبه‌بندی‌شده در بافتار فراخوانی می‌کند، پرامپت سیستم را کش می‌کند و ارجاع با فرمت file:line را اجباری می‌سازد.

  7. اعمال سخت‌گیرانه‌ی ارجاع‌دهی: خروجی مدل را اعتبارسنجی کنید؛ هر ادعایی که فاقد نشانگر ارجاع (repo/path:start-end) باشد برای پرسش دوباره علامت‌گذاری شده یا حذف می‌شود. فقط پاسخی را که مستند به ارجاع است به کاربر تحویل دهید.

  8. ایندکس مجدد افزایشی: در هر وب‌هوک، diff را در سطح نماد محاسبه کنید. فقط قطعه‌هایی را دوباره امبد کنید که متن آن‌ها تغییر کرده است. یال‌های نمادها را برای قطعه‌هایی که ایمپورت‌هایشان عوض شده دوباره محاسبه کنید. معیار سنجش: ایندکس مجدد یک push شامل 50 فایل در کمتر از 60 ثانیه برای کدی با مجموع 2 میلیون خط.

  9. ارزیابی: 100 پرسش بین‌ریپازیتوری را همراه با ارجاعات دقیق طلایی (file:line) برچسب‌گذاری کنید. معیارهای MRR@10، nDCG@10، وفاداری به ارجاعات (نسبت ادعاهای دارای ارجاع قابل بررسی) و تاخیر در چارک‌های p50 و p99 را اندازه بگیرید.

استفاده از سیستم

$ code-rag ask "how is S3 multipart abort wired into our retry budget?"
[retrieve]  12 chunks dense + 7 chunks bm25, 16 unique after dedup
[rerank]    top-5 kept (cohere rerank-3)
[synth]     claude-sonnet-4.7, cache hit rate 68%, 2.1s
answer:
  Multipart aborts are triggered by `AbortMultipartOnFail` in
  services/uploader/retry.go:122-148, which decrements the per-bucket
  retry budget defined in config/budgets.yaml:34-51 ...
  citations: [services/uploader/retry.go:122-148, config/budgets.yaml:34-51,
              libs/s3client/multipart.ts:44-61]

خروجی پروژه

مهارت قابل‌تحویل در قالب فایل outputs/skill-codebase-rag.md. این سیستم با دریافت مجموعه‌ای از ریپازیتوری‌ها، پایپ‌لاین ورودی، ایندکس ترکیبی و ایجنت پرس‌وجو را برپا می‌کند و برای هر پرسش بین‌ریپازیتوری، پاسخی مستند به ارجاع برمی‌گرداند. شیوه‌نامه‌ی نمره‌دهی (Rubric):

وزن معیار شیوه‌ی سنجش
25 کیفیت بازیابی MRR@10 و nDCG@10 روی یک مجموعه‌ی ارزیابی مستقل 100 سوالی
20 وفاداری به ارجاعات نسبت ادعاهای داخل پاسخ که ارجاع معتبر و قابل بررسی به file:line دارند
20 تاخیر و مقیاس‌پذیری تاخیر p95 پرس‌وجو تحت بار 10k پرسش در ثانیه (QPS) روی کل داده‌های ایندکس‌شده
20 صحت ایندکس‌گذاری افزایشی زمان سپری‌شده از git push تا لحظه‌ی قابل‌جستجو شدن برای یک commit شامل 50 فایل
15 رابط کاربری و ساختار پاسخ قابلیت کلیک روی ارجاعات، پیش‌نمایش تکه‌کدها، امکان پرسش‌های ادامه‌دار
100

تمرین‌ها

  1. مدل Voyage-code-3 را با نسخه‌ی خودمیزبان nomic-embed-code جایگزین کنید. اختلاف MRR@10 را اندازه بگیرید. گزارش دهید که آیا فعال کردن بازرتبه‌بندی، این اختلاف را جبران می‌کند یا خیر.

  2. حدود 20% کد تولیدشده توسط هوش مصنوعی (boilerplate تولیدی LLM) به مجموعه‌ی کدها اضافه کرده و دوباره ارزیابی کنید. خراب شدن نتایج بازیابی را مشاهده کنید. یک فلگ «generated» به متادیتای قطعه‌ها بیفزایید و وزن این نتایج را کاهش دهید.

  3. کارایی جستجوی ترکیبی Qdrant را در مقایسه با pgvector + pgvectorscale روی ابعاد دیتای خود بنچمارک کنید. تاخیر p99 را در اندازه‌ی دسته‌ی 1 گزارش دهید.

  4. یک بررسی رانش (drift) مبتنی بر نمونه‌گیری اضافه کنید: هر هفته ارزیابی 100 سوالی را تکرار کنید. در صورت افت MRR@10 به بیش از 5%، هشدار ارسال کنید.

  5. سیستم را برای تفکیک نمادهای چندزبانه توسعه دهید: تابع پایتونی که یک سرویس Go را روی gRPC صدا می‌زند. از گراف نمادها برای اتصال آن‌ها استفاده کنید.

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

اصطلاح در گفت‌وگوها معنای دقیق فنی
قطعه‌بندی متن آگاه به AST (AST-aware chunking) «تفکیک در سطح تابع» برش دادن کد در مرز گره‌های tree-sitter به‌جای پنجره‌های توکنی ثابت
جستجوی ترکیبی (Hybrid search) «متراکم + پراکنده» اجرای موازی BM25 و جستجوی برداری، ادغام k نتیجه‌ی برتر و بازرتبه‌بندی نهایی
بازرتبه‌بندی با کراس‌ان‌کودر (Cross-encoder rerank) «رتبه‌بندی مرحله‌ی دوم» مدلی که هر جفت (پرسش، کاندیدا) را هم‌زمان امتیازدهی می‌کند و دقیق‌تر از شباهت کسینوسی است
کش کردن پرامپت (Prompt caching) «پرامپت سیستم کش‌شده» قابلیت مدل‌های 2026 شرکت‌های Claude و OpenAI که هزینه‌ی توکن‌های پیشوند تکراری را تا 90% کاهش می‌دهد
گراف نمادها (Symbol graph) «گراف کد» ثبت یال‌ها برای روابط ایمپورت، فراخوانی و وراثت میان فایل‌ها و ریپازیتوری‌های مختلف
وفاداری به ارجاعات (Citation faithfulness) «نرخ پاسخ‌های مستند» نسبتی از ادعاها که کاربر می‌تواند با کلیک روی ارجاع و خواندن اسپنِ ارجاع‌شده درستی آن‌ها را بررسی کند
ایندکس مجدد افزایشی (Incremental re-index) «مدت‌زمان از push تا جستجو» زمان واقعی سپری‌شده از لحظه‌ی git push تا زمانی که نمادهای تغییریافته قابل جستجو شوند

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

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسچرا جستجوی کسینوسی ساده روی قطعات خام برای بازیابی کد بین مخازن (cross-repo) ناکافی است؟
  2. پیش از درسقطعه‌بندی متن مبتنی بر AST در پایپ‌لاین بارگذاری داده به چه معناست؟
  3. حین درسهر قطعه در این پایپ‌لاین کدام سه ساختار قابل بازیابی را دریافت می‌کند؟
  4. حین درسنقش بازرتبه‌بندی‌کننده‌ی کراس‌ان‌کودر پس از مرحله‌ی بازیابی ترکیبی چیست؟
  5. حین درسچرا ترکیب‌کننده پاسخ‌های فاقد ارجاع‌های (repo/path:start-end) را رد می‌کند؟
  6. پس از درسنمایه‌سازی مجدد افزایشی (incremental) برای کمتر از 60 ثانیه ماندن در یک push با 50 فایل به چه چیزی نیاز دارد؟
  7. پس از درسکدام معیار اندازه‌گیری می‌کند که آیا ادعاهای بازیابی‌شده در سورس کد قابل راستی‌آزمایی هستند یا خیر؟

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

چرا در RAG کدبیس از قطعه‌بندی آگاه به AST به‌جای پنجره‌های توکنی ثابت استفاده می‌شود؟

برش کد در مرز توابع و کلاس‌ها با Tree-sitter مانع از شکسته‌شدن ناگهانی منطق برنامه در میانه‌ی یک تابع یا بلوک می‌شود و معنای ساختار نحوی کد را برای امبدینگ حفظ می‌کند.

جستجوی ترکیبی (Hybrid Search) در این پروژه چه نیازی را برطرف می‌کند؟

جستجوی ترکیبی با ادغام جستجوی متراکم برداری و ایندکس پراکنده‌ی BM25F، امکان پاسخ‌گویی دقیق به پرسش‌های مفهومی (مانند نحوه احراز هویت) و جستجوی دقیق نام متغیرها و توابع را هم‌زمان فراهم می‌سازد.

چگونه می‌توان ایندکس کد را با هر Git push بدون پردازش مجدد کل پروژه به‌روز نگه داشت؟

با استفاده از diff در سطح نمادها؛ سیستم فقط فایل‌ها و توابعی را که تغییر کرده‌اند دوباره امبد کرده و یال‌های ایمپورت و فراخوانی متأثر را در گراف نمادها به‌روزرسانی می‌کند.

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

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