वेक्टर सर्च समानता के हिसाब से रैंक करता है। हाइब्रिड सर्च समानता के साथ कीवर्ड ओवरलैप जोड़कर रैंक करता है। पर कोड सर्च से आपको असल में चाहिए प्रासंगिकता — और समानता प्रासंगिकता नहीं है। जिस स्निपेट में आपकी क्वेरी के सबसे ज़्यादा टोकन मिलते हैं, अक्सर वही कोड नहीं होता जो उसका जवाब देता है।

पूछिए "हम कहाँ तय करते हैं कि किसी रिक्वेस्ट को दोबारा भेजा जा सकता है", और समानता पर चलने वाला रैंकर ख़ुशी-ख़ुशी आपको रिट्राई कॉन्फ़िग स्ट्रक्ट, रिट्राई कॉन्स्टेंट और वह टेस्ट थमा देगा जिसमें यह शब्द चार बार आता है — जबकि वह अकेला फ़ंक्शन जो यह फ़ैसला लेता है, सातवें नंबर पर बैठा रहेगा। हर टोकन मिला। जवाब कुछ नहीं मिला।

PageIndex ने यही बात दस्तावेज़ों के लिए रखी थी: टॉप-k समानता की जगह एक LLM लाओ जो तर्क करे कि कौन-से सेक्शन प्रासंगिक हैं। हम यही कोड के लिए चाहते थे, जहाँ संरचना किसी PDF की विषय-सूची से कहीं समृद्ध है — यहाँ सिंबल हैं, कॉल ग्राफ़ है, असली फ़ंक्शन बॉडी हैं।

तो हमने इसे बनाया: एक वैकल्पिक रीज़निंग स्टेप जो हाइब्रिड रिट्रीवल के बाद बैठता है, कैंडिडेट कोड पढ़ता है, और इस आधार पर री-रैंक करता है कि वह क्वेरी का जवाब देता है या नहीं। यह डिफ़ॉल्ट रूप से बंद है, एक कॉन्फ़िग फ़्लैग के पीछे है, और structural_search को वही सीधा-सादा डिटरमिनिस्टिक grep रहने देता है जो उसे होना चाहिए। हमारे बेंचमार्क पर यह हर मेट्रिक में बड़ी जीत है। यह पोस्ट ईमानदार रिपोर्ट है — आँकड़े, हमने ट्यूनिंग कैसे की, और वे दो चीज़ें जो काम नहीं आईं।


मूल विचार, और वह एक तरकीब जिसने इसे चलाया

पाइपलाइन छोटी है। क्वेरी पहले की तरह ही हाइब्रिड रिट्रीवल से गुज़रती है — लोकल इंडेक्स पर वेक्टर समानता प्लस कीवर्ड मैचिंग। शीर्ष कैंडिडेट अपनी पूरी कोड बॉडी के साथ लौटते हैं। एक LLM उन्हें पढ़ता है और इस आधार पर क्रम देता है कि वे सवाल का जवाब देते हैं या नहीं। फिर दोनों रैंकिंग फ़्यूज़ होती हैं और शीर्ष परिणाम लौटाए जाते हैं। कोई नया इंडेक्स नहीं, एम्बेडिंग का दूसरा पास नहीं, री-इंडेक्सिंग नहीं।

उस बीच वाले स्टेप का सीधा-सरल संस्करण ज़ाहिर है: कैंडिडेट लो, पूछो "इनमें से कौन क्वेरी का जवाब देते हैं, क्रम में", और वही क्रम इस्तेमाल कर लो। पहले हमने यही किया। रिकॉल के लिए यह आपदा थी।

127 क्वेरी पर शुद्ध रीज़निंग री-रैंकर का हाल:

मेट्रिक सिर्फ़ हाइब्रिड शुद्ध रीज़निंग Δ
MRR 0.595 0.752 +0.157
NDCG@10 0.658 0.758 +0.100
Hit@10 0.913 0.843 −0.071
Recall@10 0.886 0.811 −0.075

रैंकिंग मेट्रिक उछल गईं — सही जवाब ऊपर खिंच आया। पर Hit@10 और Recall@10 गिर गए, क्योंकि मॉडल छाँटता है: जिन्हें वह प्रासंगिक मानता है उतने ही लौटाता है और बाक़ी को चुपचाप हटा देता है, उन सही परिणामों समेत जो रैंक 6–10 पर बैठे थे। जो री-रैंकर रिकॉल फेंककर प्रिसिज़न बढ़ाता है, वह प्रोडक्शन में भेजने लायक री-रैंकर नहीं है। जिन क्वेरी पर वह सही होता है वहाँ शानदार दिखता है, और मुश्किल क्वेरी को बेजवाब बना देता है।

तोड़ छोटा है और यही पूरी वजह है कि यह काम करता है: LLM को रैंकिंग की जगह मत लेने दो — उसे Reciprocal Rank Fusion से हाइब्रिड रैंकिंग के साथ मिलाओ।

score(candidate) = 1 / (k + hybrid_rank)
                 + reasoning_weight * 1 / (k + reasoning_rank)

k वही सामान्य RRF डैम्पिंग कॉन्स्टेंट है जो किसी एक रैंक पोज़िशन को पूरे स्कोर पर हावी नहीं होने देता। असल बात यह है कि हर पद क्या करता है। हाइब्रिड रैंक हमेशा योगदान देता है, इसलिए वह रिकॉल का फ़र्श बन जाता है — LLM द्वारा नीचे धकेला या छोड़ा गया सही परिणाम नीचे जाता है, मिटता नहीं। भार सहित रीज़निंग रैंक सूची के शीर्ष को चलाता है। रैंकिंग का फ़ायदा भी रहता है और रिकॉल का रिसाव भी रुक जाता है।

इसी एक बदलाव ने शुद्ध रीज़निंग के समझौते को हर मेट्रिक पर साफ़ जीत में बदल दिया।


आँकड़े

बेंचमार्क: एक तय कमिट पर पिन किए गए Octocode कोडबेस के ख़िलाफ़ 127 हाथ से एनोटेट की गई क्वेरी (100 सामान्य + 27 जानबूझकर कठिन, जो प्राकृतिक भाषा में हैं और जिनका कोई कीवर्ड ओवरलैप नहीं)। लोकल fastembed एम्बेडिंग, हाइब्रिड सर्च चालू, दोनों तरफ़ वही क्वेरी, रीज़निंग मॉडल के रूप में deepseek:deepseek-v4-flash। ग्राउंड ट्रुथ है सत्यापित सोर्स लोकेशनों के साथ लाइन-रेंज ओवरलैप।

मेट्रिक सिर्फ़ हाइब्रिड + रीज़निंग Δ
MRR 0.595 0.809 +0.214 (+36%)
NDCG@10 0.658 0.833 +0.175 (+27%)
Hit@5 0.827 0.953 +0.126
Hit@10 0.913 0.969 +0.055
Recall@5 0.777 0.924 +0.147
Recall@10 0.886 0.944 +0.058

हर मेट्रिक ऊपर। "मुझे वह कोड ढूँढो जो X करता है" के लिए जो दो सबसे ज़्यादा मायने रखती हैं — MRR (पहला सही परिणाम कितना ऊपर आता है) और NDCG@10 (क्या सबसे प्रासंगिक परिणाम सबसे ऊपर रैंक करते हैं) — वे 36% और 27% चढ़ीं। Hit@5 0.83 से 0.95 पर पहुँचा: बीस में से उन्नीस बार जवाब अब टॉप पाँच में है।

यही आख़िरी आँकड़ा एजेंट का व्यवहार बदलता है। जब जवाब भरोसे के साथ टॉप पाँच में होता है, एजेंट पाँच परिणाम पढ़ता है और काम कर देता है। जब नहीं होता, एजेंट सर्च चौड़ी करता है, और फ़ाइलें पढ़ता है, उन कैंडिडेट्स पर कॉन्टेक्स्ट जलाता है जो कभी प्रासंगिक थे ही नहीं, और कभी-कभी हार मानकर grep कर लेता है। ऊपर की रैंकिंग गुणवत्ता नीचे बचा हुआ कॉन्टेक्स्ट है।


हमने इसे कैसे ट्यून किया (और कहाँ ज़्यादा का मतलब बुरा था)

हमने कॉन्फ़िग का अंदाज़ा नहीं लगाया। साझा इंडेक्स पर, एक बार में एक ही आयाम, हमने स्वीप किया।

रीज़निंग वेट — फ़्यूज़न LLM पर कितना झुके बनाम हाइब्रिड फ़र्श पर। 1, 2, 3, 5 पर स्वीप। वेट के साथ रैंकिंग चढ़ती है और 2–3 के आसपास सपाट हो जाती है; 5 कुछ नहीं देता और थोड़ा रिकॉल ले लेता है, क्योंकि तब तक हाइब्रिड फ़र्श मायने रखना बंद कर देता है और आप फिर अकेले मॉडल पर भरोसा कर रहे होते हैं। पूरे बेंचमार्क पर 2.0 सबसे संतुलित रहा।

कितने कैंडिडेट पर तर्क कराएँ। 15, 25, 35, 40 पर स्वीप। ज़्यादा बेहतर नहीं है — 40 मापने लायक हद तक 25 से बुरा था। मॉडल को ज़्यादा कैंडिडेट देना फ़ैसले को पतला करता है, और बुरे नतीजे के लिए ज़्यादा टोकन ख़र्च कराता है। 25 सही जगह है।

मॉडल हर कैंडिडेट का कितना हिस्सा देखे। यह निर्णायक निकला। सिर्फ़ सिग्नेचर, एक स्निपेट, या पूरी बॉडी:

कॉन्टेक्स्ट Hit@5 MRR Recall@5
सिग्नेचर 0.933 0.806 0.900
स्निपेट 0.933 0.849 0.883
पूरी बॉडी 1.000 0.857 0.967

(30 क्वेरी का सबसेट; सबसेट पर निरपेक्ष आँकड़े ऊँचे आते हैं, पर बात क्रम की है।)

पूरी कोड बॉडी बहुत बड़े अंतर से जीतती है। प्रासंगिकता आँकने के लिए मॉडल को असली कोड देखना पड़ता है — नाम और सिग्नेचर काफ़ी नहीं हैं। सिर्फ़-सिग्नेचर सबसे बुरा विकल्प रहा, और इस पर ठहरना ज़रूरी है: यही सबसे सस्ता विकल्प है, टोकन बचाने के लिए हाथ इसी पर जाता है, और यह ठीक वही जानकारी फेंक देता है जिसके लिए रीज़निंग स्टेप बना है।

LLM तापमान। हमने मान लिया था कि कम (ज़्यादा डिटरमिनिस्टिक) बेहतर रैंक करेगा। ग़लत — तापमान 1.0 ने 0.3 और 0.0 को हराया। सामान्य सैंपलिंग पर मॉडल बेहतर तर्क करता है।


जो काम नहीं आया: इंडेक्स-टाइम कॉन्टेक्स्चुअल विवरण

रिकॉल ऊपर धकेलने का ज़ाहिर तरीक़ा है इंडेक्स को समृद्ध करना: हर चंक के लिए LLM से एक पंक्ति में "यह कोड क्या करता है" लिखवाओ और उसे कोड के साथ एम्बेड करो (Anthropic के contextual retrieval ने ठीक यही करके असफल रिट्रीवल में बड़ी गिरावट दर्ज की थी)। मशीनरी हमारे पास पहले से थी, तो हमने इसे परखा — विवरणों के साथ पूरा री-इंडेक्स, फिर वही A/B।

नतीजा थोड़ा बुरा रहा:

Hit@5 MRR NDCG@10 Recall@10
सादा इंडेक्स (+रीज़निंग) 0.953 0.809 0.833 0.944
कॉन्टेक्स्चुअल इंडेक्स (+रीज़निंग) 0.945 0.836 0.859 0.925
Δ −0.008 +0.027 +0.026 −0.019

यह रिकॉल देकर रैंकिंग ख़रीदता है: MRR और NDCG थोड़े ऊपर, पर Hit और Recall नीचे। कोड के आगे LLM का विवरण जोड़ना एम्बेडिंग में कोड के अपने टोकन को पतला करता है, और साफ़, अच्छी तरह संरचित कोड पर ठीक-ठाक कोड एम्बेडर के साथ यह रिकॉल को नीचे खिसका देता है। Anthropic ने जो रिकॉल बढ़त मापी थी वह गद्य और मिश्रित कॉर्पस पर थी — इस पर नहीं। और रैंकिंग का जो फ़ायदा कॉन्टेक्स्चुअल देता? उसे रीज़निंग स्टेप पहले ही पकड़ लेता है, जिससे उसके ऊपर कॉन्टेक्स्चुअल जोड़ना बेकार से लेकर नुक़सानदेह तक बन जाता है।

तो हमने इसे शिप नहीं किया। यह बंद ही रहता है, और इस फ़ैसले की क़ीमत साफ़ कहनी चाहिए: कॉन्टेक्स्चुअल इंडेक्स का मतलब है हर री-इंडेक्स पर हर चंक के लिए एक LLM कॉल — उस नतीजे के लिए जो मापने पर बुरा निकला। यह खुलकर कहना ज़रूरी है क्योंकि "कॉन्टेक्स्चुअल रिट्रीवल जोड़ लो" हर जगह कार्गो-कल्ट की तरह दोहराया जाता है — यह सार्वभौमिक जीत नहीं है, और कोड पर यह आपको महँगा पड़ सकता है।


इसे चालू कैसे करें

रीज़निंग डिफ़ॉल्ट रूप से बंद है (हर सर्च पर एक LLM कॉल की क़ीमत है)। इसे config.toml में चालू करें:

[search.reasoning]
enabled          = true
model            = "deepseek:deepseek-v4-flash"  # कोई भी provider:model
max_candidates   = 25       # 25 ने 40 को हराया — ज़्यादा से फ़ैसला पतला होता है
context_level    = "full"   # पूरी बॉडी जीतती है; स्निपेट/सिग्नेचर बदतर हैं
reasoning_weight = 2.0      # हाइब्रिड रिकॉल फ़र्श के मुक़ाबले RRF वेट
final_top_k      = 10

हर पैरामीटर टेम्प्लेट में परिभाषित है — सख़्त कॉन्फ़िग, कोड में कोई छिपा डिफ़ॉल्ट नहीं। deepseek-v4-flash सस्ता और तेज़ है; कोई भी प्रोवाइडर चलेगा, और ऊपर के आँकड़े वही हैं जो एक सस्ता मॉडल देता है, कोई फ़्रंटियर मॉडल नहीं। यह सिर्फ़ सिमैंटिक सर्च पाथ में चलता है — structural_search शुद्ध grep ही रहता है, बिना AI के, जैसा होना चाहिए। अगर आपको डिटरमिनिस्टिक, शून्य-लागत सर्च चाहिए, फ़्लैग बंद रहने दीजिए और आपके मौजूदा सेटअप में कुछ नहीं बदलेगा।


ईमानदार छत

हम Hit@5 0.953 पर हैं। इस बेंचमार्क पर 1.0 तक पहुँचना यथार्थवादी नहीं है — आख़िरी 27 क्वेरी जानबूझकर प्रतिकूल हैं, और कुछ ग्राउंड ट्रुथ सचमुच अस्पष्ट है। बची हुई खाई पूल रिकॉल की नहीं है (कॉन्टेक्स्चुअल टेस्ट ने पुष्टि की कि कैंडिडेट पूल पहले ही अच्छी तरह ढका है), यह सचमुच कठिन क्वेरी की है। प्रोडक्शन में एक मज़बूत बेस एम्बेडर (लोकल fastembed डिफ़ॉल्ट की जगह कोड के लिए बना एम्बेडिंग मॉडल) फ़र्श को और ऊपर उठाएगा, पर रीज़निंग वाला तरीक़ा ख़ुद यहाँ अपने व्यावहारिक सर्वोत्तम पर है।

इस नतीजे में हमें जो पसंद है: यह असली संश्लेषण है। हाइब्रिड सर्च सस्ते और डिटरमिनिस्टिक ढंग से रिकॉल देता है। रीज़निंग वहाँ प्रासंगिकता देती है जहाँ आप पहले से LLM पर ख़र्च कर रहे हैं। RRF दोनों को मिला देता है ताकि चुनना न पड़े। न नया इंडेक्स, न वेक्टर-रहित पुनर्लेखन, न री-इंडेक्सिंग — एक फ़्लैग, एक LLM कॉल, और सही कोड ऊपर आ जाता है।

FAQ

एक वाक्य में रीज़निंग रिट्रीवल क्या है?

हाइब्रिड रिट्रीवल के बाद का एक वैकल्पिक स्टेप, जिसमें एक LLM कैंडिडेट कोड बॉडी पढ़ता है, उन्हें इस आधार पर क्रम देता है कि वे क्वेरी का जवाब देते हैं या नहीं, और वह क्रम हाइब्रिड रैंकिंग की जगह लेने के बजाय Reciprocal Rank Fusion से उसके साथ मिला दिया जाता है।

क्या यह वेक्टर या हाइब्रिड सर्च की जगह लेता है?

नहीं, और यही पूरी बात है। रैंकिंग LLM को सौंपने पर हमें Recall@10 के 7 अंक गँवाने पड़े। हाइब्रिड रैंक स्कोर में रिकॉल फ़र्श की तरह बना रहता है, इसलिए जिस सही परिणाम को मॉडल ने अनदेखा किया वह नीचे जाता है, मिटता नहीं।

इसकी लागत क्या है?

हर सिमैंटिक सर्च पर एक अतिरिक्त LLM कॉल, ज़्यादा से ज़्यादा 25 कैंडिडेट पर। यह डिफ़ॉल्ट रूप से बंद है, और बेंचमार्क के आँकड़े deepseek:deepseek-v4-flash से आए — एक सस्ता, तेज़ मॉडल। ज़्यादा ख़र्च करना हो तो कोई भी provider:model चलेगा।

क्या यह structural_search को धीमा या बदलता है?

नहीं। रीज़निंग सिर्फ़ सिमैंटिक सर्च पाथ में चलती है। structural_search बिना किसी LLM के डिटरमिनिस्टिक grep ही रहता है।

क्या मुझे इंडेक्स-टाइम कॉन्टेक्स्चुअल विवरण भी चालू करने चाहिए?

कोड पर हमारी माप कहती है नहीं: MRR और NDCG थोड़े सुधरे पर Hit@5 और Recall@10 की क़ीमत पर, और रैंकिंग का फ़ायदा रीज़निंग स्टेप पहले ही दे रहा है। ऊपर से यह हर री-इंडेक्स पर हर चंक के लिए एक LLM कॉल जोड़ता है।

इसे इस्तेमाल करने के लिए क्या री-इंडेक्स करना पड़ेगा?

नहीं। रीज़निंग आपके मौजूदा इंडेक्स के ऊपर क्वेरी-टाइम स्टेप है। फ़्लैग चालू करें, रीस्टार्ट करें, हो गया।

— Don


Octocode Apache-2.0 के तहत ओपन सोर्स है — बेंचमार्क, ग्राउंड ट्रुथ और कच्चे नतीजे शामिल हैं, ताकि आप ये आँकड़े अपने कोडबेस पर दोबारा निकाल सकें। यही Octomind के पीछे का कोड-सर्च इंजन है; दोनों की बातचीत MCP सर्वर के ज़रिए होती है।