اگر برنامهنویس هستید و از ابزارهای AI برای تحلیل پروژههای حجیم استفاده میکنید، احتمالاً متوجه شدهاید که مدلها گاهی سادهترین ارتباطات بین توابع را نمیبینند. این «نقطه کور» یک نقص فنی در نحوه بازیابی دادههاست، نه کمبود هوش در مدل زبانی.
طبق اعلام پژوهشگران در مجموعهای از مقالات منتشر شده در ۶ آگوست ۲۰۲۶، بازیابی متنی خالص از یافتن پیوندهای ساختاری — مثل رابطه بین یک فرآیند پرداخت و تابعی که مجموع سفارش را محاسبه میکند — به دلیل ماهیت دادهها ناتوان است. این نقطه کور معماری از طریق پنج آزمایش مختلف که در یک سری مقاله شرح داده شده، به اثبات رسید و منجر به طراحی یک سیستم بازیابی چندمسیره (Multi-path Retrieval) شد. یافته کلیدی این است که پیوندهای ساختاری روی لبههای یک گراف فراخوانی (Call Graph) قرار دارند و نه در متن خود تابع.
به طور خاص، مورد Q8 — که مربوط به پردازش پرداخت و ایجاد شارژ در Stripe بود — در طول پنج مقاله پژوهشی به یک چالش همیشگی تبدیل شد. حقیقت زمینی (Ground Truth) برای Q8 شامل تابع calculate_order_total بود. تمام ترفندهای بازیابی متنی شکست خوردند: متدهای پایه برداری، سه استراتژی مختلف تکهبندی (Chunking) و حتی کدگذاری called_by در بردارهای جاسازی شده (که تنها شباهت را به ۰.۵۱ رساند) نتوانستند آن را پیدا کنند. در نهایت، «سلاح نهایی» صنعت، یعنی جستوجوی ترکیبی BM25 + برداری، نه تنها شکست خورد، بلکه نرخ بازیابی کل (Recall) را از ۰.۹۵۸ به ۰.۹۳۱ کاهش داد. این نتیجهگیری سخت و عدد-محور ثابت کرد که این لینک صرفاً ساختاری است: این پیوند روی یک لبه در گراف فراخوانی (که توسط process_checkout فراخوانی میشود) قرار دارد و در متن هیچ تابعی نوشته نشده است.
همانطور که در تحلیل قبلی ما دربارهی مرزهای RAG اشاره کردیم، این معماری قصد دارد «شکاف معنایی» را پر کند؛ جایی که ترفندهای برداری نمیتوانند عباراتی را که از نظر لغوی بیربط هستند اما از نظر عملکردی متصلاند، به هم وصل کنند. این چالش دقیقاً همان دلیلی است که جستوجوی برداری خالص در محیطهای صنعتی شکست میخورد و نیاز به مدلهای ترکیبی را ایجاد میکند. برای مثال، در حالی که یک ایندکس برداری میتواند تابعی که «رمزهای عبور را رمزنگاری میکند» بدون دانستن نام تابع پیدا کند (و به hash_password برسد)، اما نمیتواند تابعی را پیدا کند که صرفاً توسط تابع دیگر فراخوانی شده است بدون اینکه کلیدواژه معنایی مشترکی داشته باشند. این موضوع ایجاب میکند که از یک الگوریتم واحد «بهترین» به سمت یک سیستم تریاژ تخصصی حرکت کنیم.
سه قلمرو بازیابی داده
این معماری، دانش کدبیس را به سه سیگنال مجزا و غیرهمپوشان تقسیم میکند. این بخش، زیربنای معماری است و هر جزء آن متکی بر یک عدد تجربی است.
- بازیابی برداری (Vector Retrieval): قلمرو پرسوجوهای با شباهت معنایی. این روش در مقاله ۰۳ تثبیت و در چهار مقاله بعدی تایید شد. خط پایه (Baseline) استفاده از تکهبندی در سطح تابع AST + جایگذاری کد خام، به Recall@5 معادل ۰.۹۵۸ رسید. این روش در پرسوجوهایی مانند «یک قابلیت را با زبان طبیعی توصیف کن و پیادهسازی آن را بیاب» عالی عمل میکند. مرز آن کاملاً مشخص است: هیچ ترفند برداری نمیتواند
calculate_order_totalو «پرداخت Stripe» را به هم نزدیک کند، زیرا آنها در دنیای واقعی اساساً دو چیز متفاوت هستند. - بازیابی گرافی (Graph Retrieval): قلمرو پرسوجوهای رابطه ساختاری. مقاله ۰۵ ثابت کرد که اطلاعات گراف فراخوانی، برداری از حقیقت است که روشهای برداری به آن دسترسی ندارند. مورد Q8 در نهایت با دو گام پیمایش (2-hop) از طریق لبه فراخوانی
process_checkout$ \rightarrow $calculate_order_totalبازیابی شد. با این حال، گسترش ساده BFS دو-گامه باعث حجیم شدن مجموعه کاندیداها میشود و میتواند نتایج درست مانندverify_password(مورد Q1) را بیرون براند و هیچ سودی نرساند. این ثابت میکند سیگنال گراف ارزشمند است، اما گسترش ساده روش غلطی است. - بازیابی نمادین (Symbol Retrieval): قلمرو پرسوجوهای تطبیق دقیق. وقتی یک پرسوجو یک نماد دقیق است — مثلاً «تابع
validate_jwt_tokenکجا تعریف شده است» یا «کدام فایلهاredis.Redisرا وارد (import) کردهاند» — تطبیق دقیق حروف مورد نیاز است. یک جدول نمادین (Symbol Table) اینها را در کمتر از ۱۰ میلیثانیه برمیگرداند، در حالی که جستوجوی برداری در اینجا مانند «استفاده از پتک برای شکستن گردو» است و مستعد خطاهای تخمینی معنایی است.
این قلمروها با چارچوب «چهار لایه دانش» در مقاله ۰۱ همسو هستند: نحو (AST/نماد)، معنایی (بردار)، معماری (گراف) و قصد (تاریخچه Git). اصل اول یک سیستم تولیدی این است که بپذیرد این چهار سیگنال مستقل از یکدیگرند و هیچکدام قابل چشمپوشی نیستند.

اصل اول: موازیسازی مسیرها
این سیستم جستوجو برای یافتن یک روش بازیابی غالب را رد میکند. بعد از پنج مقاله پرسش درباره اینکه «آیا بردار/BM25/ترکیبی کار میکند؟» و پنج بار شکست، نتیجه روشن است: هیچ مسیر واحدی بر هر چهار لایه غلبه نمیکند. برای پرسوجوهای ساختاری مانند Q8، مسیر متنی فیزیکی دسترسی ندارد. در عوض، سیستم با بردار، گراف و نماد به عنوان متخصصان مستقل که به صورت موازی کار میکنند، برخورد میکند.
این یک رویکرد تریاژ مهندسی است. درست همانطور که بیمارستان شکستگیها را به ارتوپد و دنداندردها را به دندانپزشک میفرستد، این سیستم پرسوجوها را تریاژ میکند. استفاده از جستوجوی برداری برای تطبیق دقیق نماد، شبیه این است که دندانپزشکی جراحی بایپاس قلب انجام دهد؛ او ممکن است بداند قلب چیست، اما ابزار درستی برای این کار نیست. این اصل طراحی مستقیماً از شکستهای مقالات ۰۵ و ۰۷ گرفته شده است، جایی که اجبار یک سیگنال به انجام کاری که در آن ضعیف بود، منجر به شکست کامل شد.
نکته: «بازیابی چندمسیره» یک توصیه طراحی مشتق شده از مقالات ۰۵/۰۷ است. در حالی که قلمروهای مسیرهای مجزا از نظر تجربی پشتیبانی میشوند (بردار ۰.۹۵۸، گراف برای اصلاح Q8)، Recall ترکیبی کلی هنوز دادههای تجربی سر-به-سر (end-to-end) ندارد.
اصل دوم: مسیریابی هوشمند بر اساس قصد
برای جلوگیری از «آلودگی مسیر» — جایی که یک نتیجه بد از یک ایندکس، نتیجه عالی ایندکس دیگر را پایین میکشد — معماری از یک مسیریاب پرسوجو (Query Router) استفاده میکند. مقاله ۰۷ این را نشان داد: در مورد Q7، بردار نمره ۱.۰۰ گرفت اما BM25 به دلیل تطبیق بیش از حد با کلمه عمومی «execute»، آن را به ۰.۶۷ کشاند. هنگامی که RRF آنها را ادغام کرد، سیگنال باکیفیت آلوده شد. ادغام کورکورانه اجازه میدهد مسیری که در یک پرسوجو بد است، مسیری را که در آن خوب است پایین بکشد.
منطق مسیریابی
پیادهسازی نیازی به LLM در ابتدا ندارد؛ اکثر قصدها با قوانین شناسایی میشوند:
- شناسههای دقیق: حضور نام توابع با استایل snake_case/camelCase یا نام ماژولهای وارد شده $ \rightarrow $ اولویت با مسیر نمادین.
- کلیدواژههای ساختاری: کلمات کلیدی مانند «چه کسی فراخوانی میکند»، «به چه چیزی وابسته است»، «زنجیره فراخوانی» یا «جریان کامل» $ \rightarrow $ فعالسازی مسیر گرافی.
- زبان طبیعی: توصیفات کلی از قابلیتها $ \rightarrow $ فعالسازی مسیر برداری.
- پرسوجوهای ترکیبی: پرسوجوهایی که شامل هر دو قصد ساختاری و توصیفات معنایی هستند $ \rightarrow $ فعالسازی مشترک چندین مسیر.
موارد خاصی (Edge cases) که قوانین نمیتوانند طبقهبندی کنند، میتوانند برای کمک به یک مدل کوچک ارجاع شوند، اگرچه قوانین اکثریت قریب به اتفاق سناریوها را پوشش میدهند. توجه داشته باشید که این مسیریابی یک مفهوم طراحی در سطح شبهکد است و دقت آن هنوز به صورت تجربی تایید نشده است.
اصل سوم: گراف به عنوان شهروند درجه یک
بازیابی گرافی به جای اینکه یک افزونه پس-پردازشی باشد، به عنوان یک نقطه ورود اصلی بازیابی ارتقا یافته است. شکست در مقاله ۰۵ به این دلیل رخ داد که جریان به این صورت بود: پرسوجو $ \rightarrow $ برترینهای برداری (top-k) $ \rightarrow $ گسترش BFS $ \rightarrow $ رتبهبندی مجدد. این کار با گراف به عنوان یک «وصله» برخورد میکرد و اجازه میداد توابع مرتبط ساختاری اما بیربط به پرسوجو، رتبهبندی را آلوده کنند.
در معماری تولیدی، ایندکس گراف و ایندکس بردار به طور موازی ساخته میشوند و به عنوان نقاط ورود مستقل عمل میکنند:
- اجرای مستقل: مسیر گراف نام توابع/ماژولها را در پرسوجو شناسایی کرده و لبههای CALLS/CALLED_BY را مستقل از مسیر برداری پیمایش میکند.
- ادغام وزنی: نتایج از طریق ادغام وزنی (Weighted Fusion) به جای RRF کور-به-زمینه ترکیب میشوند. در پرسوجوهای ساختاری، گراف وزن بیشتری دارد و در پرسوجوهای معنایی، بردار وزن بیشتری میگیرد.
- کاهش نویز: با استفاده از منطق بازیابی مستقل و شرایط فعالسازی سختگیرانه (مثلاً تنها ۱ گام پیمایش، فیلتر شده توسط قوانین کسبوکار)، سیستم
calculate_order_totalدر مورد Q8 را بازیابی میکند بدون اینکه نویزی وارد استخر برداری کند که باعث جایگزینیverify_passwordدر مورد Q1 شود.
نکته: این جهتگیری یک اصلاح طراحی بر اساس شکست تایید شده در مقاله ۰۵ است. وزنهای ادغام خاص و شرایط فعالسازی نیاز به تنظیم بیشتر در پیادهسازی عملی دارند.
اصل چهارم: بهروزرسانیهای افزایشی با Git Diff
کدبیسهای واقعی با صدها کامیت روزانه تکامل مییابند. بازسازی کامل یک ایندکس برداری برای صدها هزار خط کد میتواند دهها دقیقه زمان ببرد که برای توسعهدهندگان غیرقابل قبول است. این معماری بهروزرسانیهای مبتنی بر Git diff را پیاده میکند.
مکانیسمهای بهروزرسانی
- تشخیص تغییرات: در هنگام کامیت، سیستم دقیقاً محاسبه میکند کدام فایلها و توابع تغییر کردهاند و فقط همان نقاط را دوباره ایندکس میکند. توابع دستنخورده نیازی به محاسبه مجدد بردارها یا گرههای گراف ندارند.
- انتشار تغییرات: تغییر یک تابع ایزوله نیست. اگر امضایی مانند
create_payment_intentتغییر کند، سیستم از ساختار گراف به عنوان مسیر انتشار برای بهروزرسانی تمام همسایگان آسیبدیده در طول گراف فراخوانی استفاده میکند. این کار از گراف به عنوان شهروند درجه یک برای حفظ دقت بهره میبرد. - اسنپشاتهای نسخهای: پشتیبانی از پرسوجو در نسخههای تاریخی با استفاده از هش کامیت، که به «لایه قصد» (مقاله ۰۱) متصل میشود تا به سوالاتی مانند «این تابع سه ماه پیش چه شکلی بود؟» یا «کدام کامیت این خط را معرفی کرد؟» پاسخ دهد.
نکته: بهروزرسانی افزایشی یک توصیه طراحی خالص است. تمام مقالات قبلی از یک مجموعه داده استاتیک استفاده کردند. این پاسخی است به نکته مقاله ۰۱ که میگوید پویا بودن (Dynamism) بزرگترین چالش در ایندکسگذاری کدبیس است.
پیادهسازی و ابزارها
استقرار این سیستم در سه مرحله توصیه میشود که هر مرحله ارزش مستقلی ارائه میدهد:
فاز ۱: بردار + نماد (MVP)
ابتدا ارزانترین مسیرها را عرضه کنید. از ripgrep و یک جدول نمادین AST (نام تابع $ \rightarrow $ فایل $ \rightarrow $ شماره خط) برای پرسوجوهای دقیق استفاده کنید. برای معناییها از تکهبندی سطح تابع AST + بردار کد خام (Recall@5 = ۰.۹۵۸) بهره ببرید. قوانین مسیریابی ساده را اعمال کنید: شناسههای دقیق به نماد، در غیر این صورت به بردار. این فاز اکثریت نیازهای بازیابی روزانه را پوشش میدهد.
فاز ۲: گراف فراخوانی (ساختاری)
پارس کردن AST را برای ساخت جداول لبه CALLS/CALLED_BY اضافه کنید. این را به عنوان یک مسیر مستقل درجه یک سیمکشی کنید. مسیریاب را برای شناسایی کلمات کلیدی «زنجیره فراخوانی» ارتقا دهید و ادغامکننده را به ادغام وزنی بر اساس نوع پرسوجو تغییر دهید. این کار نقطه کور ساختاری Q8 را که مسیرهای متنی نمیتوانند به آن برسند، میپوشاند.
فاز ۳: تاریخچه Git و تولیدیسازی
لایه قصد را برای پرسوجوهای تاریخی و بهروزرسانیهای افزایشی Git diff پیاده کنید. خط لوله ایندکسگذاری را برای پشتیبانی از انتشار تغییرات به همسایگان بازسازی کنید. این کار «دمو» را به سیستمی تبدیل میکند که میتواند در برابر تکامل یک پروژه واقعی دوام بیاورد.
توصیههای پشته مهندسی (Stack)
- ذخیرهسازی برداری: pgvector یا Qdrant. اینها به دلیل پشتیبانی از فیلتر کردن متادیتا انتخاب شدهاند تا کاربران بتوانند جستوجو را به ماژولها یا فایلهای خاص محدود کنند.
- ذخیرهسازی گراف: در صورت امکان از Neo4j اجتناب کنید. یک دیکشنری پایتون (
{function_name: [called functions]}) یا یک جدول لبه SQLite برای گرافهای فراخوانی کد کافی و به مراتب سبکتر است. - ایندکس نمادین: ripgrep (نسخه ۱۵.۱.۰ برای پشتیبانی از PCRE2 + JIT) برای تطبیق دقیق متن کامل، ترکیب شده با یک جدول نماد پارس شده توسط AST.
- تریگرها: Git pre-push hooks برای بهروزرسانیهای محلی یا مراحل خط لوله CI/CD برای ایندکسهای مشترک تیمی.
ماتریس هزینههای پیادهسازی
| نوع ایندکس | هزینه ساخت | هزینه بهروزرسانی | تأخیر پرسوجو | پرسوجوهای کاربردپذیر |
|---|---|---|---|---|
| نمادین | کم (ثانیه) | بسیار کم | < ۱۰ میلیثانیه | نمادهای دقیق |
| برداری | متوسط (دقیقه) | متوسط | ~۱۰۰ میلیثانیه | معنایی |
| گرافی | کم (ثانیه) | کم | < ۵۰ میلیثانیه | ساختاری |
| تاریخچه Git | زیاد (ابتدایی) | کم | متغیر | قصد/تاریخچه |
نکته: هزینهها و تأخیرها تخمینهای مرتبه بزرگی بر اساس تجربه دمو هستند، نه SLAهای تولیدی. ابتدا مسیرهای ارزان نمادین و گرافی را عرضه کنید.
پیادهسازی آماده: codebase-memory-mcp
سرور codebase-memory-mcp یک پیادهسازی زنده از این معماری است. این سرور بازیابی چندمسیره (اصل اول) را با ترکیب ایندکسهای برداری، گراف فراخوانی و نمادین پیاده میکند. این سیستم از طریق پروتکل MCP در معرض Claude Code قرار گرفته و ابزارهایی مانند search_graph (جستوجوی نمادین/معنایی)، trace_path (ردیابی زنجیره فراخوانی) و query_graph (پرسوجوهای گرافی) را فراهم میکند.
این تغییر در رویکرد، این فرض رایج در این حوزه را که «بردارهای بهتر» میتوانند RAG کدبیس را حل کنند، تغییر میدهد. این ثابت میکند که برخی پیوندها ذاتاً غیرمتنی هستند و تنها راه حل، معماریای است که به لایههای مختلف دانش کد احترام بگذارد: نحو، معنا، معماری و قصد.




گفتگو