बहुतेक RAG डेमो लॅपटॉपवर अतिशय उत्कृष्ट दिसतात. एका स्क्रिप्टला वीस पानांची PDF द्या, एक प्रश्न विचारा आणि तो योग्य परिच्छेद उद्धृत (cite) करताना पहा. पण त्याच पाइपलाइनला प्रोडक्शनमध्ये नेणे ही खरी कसोटी असते. कायदेशीर कागदपत्रे वाक्यांच्या स्तरावर मधूनच कापली जातात. जाड API संदर्भ महत्त्वाच्या माहितीला अनावश्यक माहितीच्या (boilerplate noise) गोंधळात बुडवतात. लॅटन्सी (Latency) वाढते. वापरकर्ते वाट पाहतात, त्रस्त होतात आणि निघून जातात. आम्हाला याचा मोठा फटका बसला. म्हणून आम्ही रिट्रिव्हल लेयर (retrieval layer) पूर्णपणे मोडीत काढून तो मोजता येण्याजोगा आणि ट्यून करण्यायोग्य (tunable) सिस्टम म्हणून पुन्हा तयार केला. याचा परिणाम असा झाला की, युजर एक्सपिरियन्स (user experience) संथ न करता ९५% रिकॉल (recall) देणारी पाइपलाइन तयार झाली.
प्रोडक्शनमध्ये RAG डेमो का अपयशी ठरतो
छंद जोपासणारे प्रकल्प (hobby projects) आणि सुरुवातीच्या टप्प्यातील उत्पादने यांमध्ये स्टँडर्ड स्टॅक आश्चर्यकारकपणे सारखाच असतो: फिक्स्ड टोकन चंक्स (fixed token chunks), ऑफ-द-शेल्फ एम्बेडिंग्स (off-the-shelf embeddings) आणि एक सिंगल वेक्टर सर्च कॉल. ही साधेपणा मोहक वाटते आणि जेव्हा तुमचा डेटा स्वच्छ, लहान आणि व्याकरणाच्या दृष्टीने अंदाज लावण्यायोग्य असतो, तेव्हा ते काम करते. परंतु प्रोडक्शन डेटा यापैकी काहीच नसतो. ५१२ टोकनचा एक फिक्स्ड चंक SaaS करारातील नुकसान भरपाईच्या (indemnification clause) कलमाच्या मधून सहज कापला जाऊ शकतो. अचानक तुमचे रिट्रिव्हल लेयर लँग्वेज मॉडेलला कायदेशीर जबाबदारीचा अर्धा भाग देतो आणि त्याला दायित्वाच्या (liability) प्रश्नाचे उत्तर देण्यास सांगतो. संदर्भ तुटलेला असल्यामुळे मॉडेल हॅल्युसिनेशन (hallucinate) करते.
मोठी तांत्रिक कागदपत्रे ही समस्या अधिक वाढवतात. API डॉक्युमेंटेशन फंक्शन सिग्नेचर, टेबल्स आणि कोड ब्लॉक्सने भरलेले असते. एक फिक्स्ड विंडो कदाचित TypeScript इंटरफेसचा मध्य भाग पकडू शकेल, परंतु त्याच्या वरचे फंक्शन नाव आणि खाली दिलेले वापराचे उदाहरण (usage example) सुटू शकते. एम्बेडिंग वेक्टर वापरकर्त्याने विचारलेल्या वास्तविक क्षमतेऐवजी केवळ सिंटॅक्सचे तुकडे आणि अनावश्यक माहिती दर्शवतो. Garbage in, hallucination out.
टोकन काउंटऐवजी स्ट्रक्चरनुसार चंकिंग करा
आम्ही केलेला पहिला बदल म्हणजे चंक्सना केवळ टोकनची पिशवी म्हणून पाहणे थांबवणे. चंक्स हे सिमेंटिक युनिट्स (semantic units) असतात. योग्य रणनीती पूर्णपणे तुम्ही काय इंडेक्स करत आहात यावर अवलंबून असते.
कायदेशीर कागदपत्रांसाठी, आम्ही डॉक्युमेंट हायरार्कीचा (hierarchy) आदर करणारे रिकर्सिव्ह चंकिंग (recursive chunking) वापरण्यास सुरुवात केली. हे विभाग, उपविभाग आणि कलमांना (clauses) सीमा मानतो. एखादे कलम अखंड राहते कारण कलम हे अर्थाचे एक युनिट आहे. जर तुम्ही ते मधून कापले, तर कायदेशीर तर्क (legal logic) विस्कळीत होतो.
API डॉक्ससाठी, स्ट्रक्चर-अवेअर चंकिंग (structure-aware chunking) फंक्शन्स, क्लासेस आणि एंडपॉइंट्सना अॅटॉमिक (atomic) मानतो. एका चंकमध्ये फंक्शन सिग्नेचर, त्याचे आर्ग्युमेंट्स आणि त्याचे डॉकस्ट्रिंग (docstring) असू शकते. केवळ टोकन काउंटर वाढला म्हणून ते पुढच्या युटिलिटी फंक्शनमध्ये विनाकारण पसरत नाही. यामुळे एम्बेडिंग एखाद्या विशिष्ट क्षमतेवर लक्ष केंद्रित ठेवते.
सपोर्ट तिकिटे अधिक गुंतागुंतीची असतात. ती संवादात्मक, थ्रेडेड आणि नॉन-लिनिअर असतात. फिक्स्ड चंक्स एकाच थ्रेडमधून इंजिनिअरचे स्टेटस अपडेट आणि ग्राहकाची तक्रार दोन्ही उचलतील आणि ते एक सुसंगत युनिट आहे असे भासतील. आम्ही सिमेंटिक चंकिंगकडे (semantic chunking) वळलो, जिथे टोकन बजेट संपल्यावर नाही, तर विषय किंवा बोलणारा व्यक्ती बदलल्यावर विभागणी केली जाते.
अंतर्गत विकी (Internal wikis) अनेकदा संस्थेतील सर्वात विस्कळीत डेटा असतो. फॉरमॅटिंग विसंगत असते, हेडर्स नसतात आणि विभाग एकमेकांत मिसळलेले असतात. यांच्यासाठी, आम्ही LLM-आधारित चंकिंग वापरतो. एम्बेडिंग तयार करण्यापूर्वी एक लहान मॉडेल पुढे वाचते आणि तार्किक सीमा (logical boundaries) ओळखते. यासाठी कॅरेक्टर स्प्लिटपेक्षा जास्त खर्च येतो, परंतु रिट्रिव्हलची गुणवत्ता त्याचे मूल्य लगेच भरून काढते.
हायब्रिड रिट्रिव्हल: सिग्नल्स एकत्र करा
वेक्टर सर्च शक्तिशाली आहे पण त्याच्या काही मर्यादा (blind spots) आहेत. ERR_CONNECTION_REFUSED_0x800 सारखा अचूक एरर कोड पेस्ट केल्यावर, सिमिलॅरिटी सर्च एखाद्या असंबंधित मॉड्यूलसाठी ट्रबलशूटिंग गाईड देऊ शकतो, कारण एम्बेडिंग स्पेसने त्यांना एकमेकांच्या जवळ क्लस्टर केले असते. अचूक मॅचेस (exact matches) महत्त्वाचे असतात आणि केवळ वेक्टर सर्च त्यांना दुर्लक्षित करू शकतो.
BM25 सह कीवर्ड सर्च (Keyword search) ही अचूक-मॅचची समस्या उत्तम प्रकारे सोडवते. परंतु ते संकल्पनात्मक अंतराबाबत (conceptual distance) अपयशी ठरते. जर वापरकर्त्याने "performance degradation under heavy load" बद्दल विचारले, तर BM25 "slow throughput during traffic spikes" चे वर्णन करणारी डायग्नोस्टिक नोट मिस करेल, कारण तिथे कीवर्ड्सचा पुरेसा ओव्हरलॅप नाही.
आम्ही बाजू निवडणे थांबवले आणि दोन्ही समांतरपणे चालवण्यास सुरुवात केली. वेक्टर आणि कीवर्ड सर्च प्रत्येकजण स्वतःच्या रँक्ड लिस्ट (ranked lists) परत करतात. आम्ही त्यांना Reciprocal Rank Fusion (RRF) वापरून एकत्र करतो. RRF प्रभावीतेमध्ये साधे आणि अत्यंत अचूक आहे. ते प्रत्येक डॉक्युमेंटला प्रत्येक लिस्टमध्ये त्याचे स्थान पाहून स्कोअर देते. दोन्ही सिस्टममध्ये वरच्या बाजूला असणाऱ्या डॉक्युमेंट्सना मोठा बूस्ट मिळतो. ज्या डॉक्युमेंट्सना फक्त एकच इंजिन शोधते, त्यांनाही अंतिम उमेदवार संचामध्ये (candidate set) स्थान मिळते.
फ्युजननंतर, आम्ही टॉप कॅंडिडेट्सना cross-encoder reranker मधून चालवतो. हे मोफत नाही. यामुळे अंदाजे ५० मिलीसेकंदचा अतिरिक्त कम्प्युट वेळ लागतो. परंतु, यामुळे रिकॉल (recall) १५% ने वाढतो. cross-encoder संपूर्ण क्वेरी आणि प्रत्येक कॅंडिडेट चंकची एकत्रितपणे तपासणी करते, ज्यामुळे bi-encoder एम्बेडिंगपेक्षा कितीतरी पटीने अधिक सूक्ष्म आणि अचूक रिलेव्हन्स स्कोअर (relevance score) मिळतो. तो अतिरिक्त ५० मिलीसेकंदचा वेळ अत्यंत फायदेशीर आहे. यामुळे तुम्ही LLM ला चुकीचा कॉन्टेक्स्ट पाठवणे टाळता आणि गोंधळलेल्या किंवा हॅलुसिनेटेड (hallucinated) उत्तरासाठी दोन सेकंद वाट पाहण्यापासून वाचता.
शोध घेण्यापूर्वी क्वेरीमध्ये सुधारणा करा
वापरकर्ते सर्च इंजिनिअर्ससारख्या क्वेरी लिहीत नाहीत. ते "app broken" असे टाईप करतात. ते गूढ लॉग फ्रॅग्मेंट्स (log fragments) पेस्ट करतात. ते अस्पष्ट आणि संदिग्ध प्रश्न विचारतात. जर तुम्ही त्या कच्च्या स्ट्रिंग्स (raw strings) थेट इंडेक्सला पाठवल्या, तर तुम्हाला कचरा माहिती मिळेल.
रिट्रिव्हल इंजिनपर्यंत (retrieval engine) पोहोचण्यापूर्वी आम्ही प्रत्येक क्वेरीमध्ये बदल करतो.
पहिले म्हणजे, क्वेरी एक्सपान्शन (query expansion). सिस्टीम एका छोट्या प्रश्नातून अनेक शोध संज्ञा (search terms) तयार करते. वापरकर्ता विचारतो, "How do I fix the timeout?" इंजिन त्याचे रूपांतर connection timeouts, read timeouts, gateway timeouts आणि retry logic मध्ये करते. केवळ या पद्धतीमुळे आमचा रिकॉल ७८% वरून ९६% वर पोहोचला.
दुसरे म्हणजे, क्वेरी डिकंपोझिशन (query decomposition). जटिल प्रश्नांचे लहान उप-प्रश्नांमध्ये विभाजन केले जाते. "What's the refund policy for enterprise customers past 90 days and how does it differ from monthly plans?" सारख्या क्वेरीचे एका मोठ्या एम्बेडिंग लूकअपऐवजी (embedding lookup) दोन केंद्रित शोधांमध्ये रूपांतर होते. प्रत्येक उप-प्रश्न स्वतंत्रपणे इंडेक्सला हिट करतो. त्यानंतर निकाल पुन्हा एकत्र जोडले जातात. यामुळे शोध प्रक्रिया मर्यादित आणि अचूक राहते, ज्यामुळे एकाच एम्बेडिंगद्वारे एकाच वेळी डझनभर संकल्पना शोधण्याचा प्रयत्न करताना माहितीची विरळता (dilution) थांबते.
तुमच्या पाइपलाइनला Bayesian Search द्वारे ट्यून होऊ द्या
जर तुम्ही अजूनही चंक साईज (chunk size), ओव्हरलॅप रेशो (overlap ratios) आणि रिट्रिव्हल वेट्स (retrieval weights) मॅन्युअली ट्यून करत असाल, तर तुम्ही उत्तम परफॉर्मन्स गमावत आहात. आम्ही अंदाज लावणे थांबवले.
आम्ही एक 'सर्च स्पेस' (search space) परिभाषित केली जिथे चंक साईज, ओव्हरलॅप टक्केवारी, vector-versus-BM25 वेट्स आणि reranker थ्रेशोल्ड हे सर्व व्हेरिएबल्स आहेत. त्यानंतर आम्ही Bayesian optimization लागू केले. शेकडो रँडम कॉन्फिगरेशन्समधून ग्रिड-सर्च (grid-searching) करण्याऐवजी, Bayesian search काय काम करते याचे एक संभाव्यता मॉडेल (probabilistic model) तयार करते. ते एक कॉन्फिगरेशन सुचवते, रिकॉल आणि लेटन्सी (latency) पाहते, स्वतःच्या विश्वासांमध्ये सुधारणा करते आणि पुढचे कॉन्फिगरेशन सुचवते. कालांतराने, ते अशा संतुलनावर पोहोचते जे एखादा माणूस मॅन्युअली कधीच शोधू शकला नसता.
याने असे कॉम्बिनेशन शोधले जे आम्ही कधीच प्रयत्न केले नसते. जास्त ओव्हरलॅपसह लहान चंक्स (smaller chunks). डेंस वेक्टर सर्चवर (dense vector search) थोडे कमी वेट आणि अधिक आक्रमक reranker थ्रेशोल्ड. या गैर-स्पष्ट तडखोडीमुळे (non-obvious tradeoffs) आम्हाला उच्च रिकॉल आणि कमी लेटन्सी दोन्ही मिळाले.
हे एकदाच करायचे काम नाही. आम्ही दरमहा हायपरपॅरामीटर ऑप्टिमायझेशन (hyperparameter optimization) पुन्हा करतो. तुमचा कॉर्पस (corpus) बदलत जातो. वापरकर्त्याचे वर्तन बदलत जाते. तुमच्या पाइपलाइनने स्थिर राहण्याऐवजी स्वतःला बदलले पाहिजे.
फळ (The Payoff)
या पुनर्बांधणीचा (rebuild) प्रत्यक्ष निकाल वादातीत आहे.
१० व्या स्थानावरील रिकॉल ७८% वरून ९५% वर गेला. जेव्हा योग्य उत्तर आमच्या नॉलेज बेसमध्ये असते, तेव्हा आम्ही ते वीस वेळांपैकी १९ वेळा समोर आणतो. ९५ व्या पर्सेंटाइलवरील लेटन्सी ८५० मिलीसेकंदवरून ३२० मिलीसेकंदवर आली. चॅट आता संथ वाटण्याऐवजी त्वरित वाटते.
चांगल्या रिट्रिव्हलमुळे लँग्वेज मॉडेलला अधिक चांगल्या प्रकारे ग्राउंडिंग (grounding) मिळाले. हॅलुसिनेशन रेट (hallucination rate) १२% वरून ३% वर खाली आला. जेव्हा मॉडेलसमोर योग्य कॉन्टेक्स्ट असतो, तेव्हा ते तथ्ये स्वतःच्या मनाने तयार करणे थांबवते. प्रति क्वेरी खर्च ३८% ने कमी झाला. जलद आणि अचूक रिट्रिव्हलचा अर्थ असा की अनावश्यक कॉन्टेक्स्ट, रिट्राय लूप्स आणि निरर्थक पण विस्तृत प्रॉम्प्ट्सवर कमी टोकन्स वाया जातात.
इन्फ्रास्ट्रक्चरप्रमाणे बांधा
जर तुम्ही प्रोटोटाइपमधून प्रोडक्शनकडे (production) जात असाल, तर रिट्रिव्हलला केवळ कॉन्फिगरेशन न मानता इन्फ्रास्ट्रक्चर कोड (infrastructure code) म्हणून माना.
