बहुतेक इंजिनिअरिंग टीम्स 'retrieval-augmented generation' (RAG) मध्ये एकाच अडचणीचा सामना करतात. ते ट्युटोरियलमधील पद्धतींचे अनुसरण करतात: दस्तऐवजांचे (documents) ५१२ किंवा १०२४ टोकन्सच्या ठराविक तुकड्यांमध्ये (fixed chunks) विभाजन करणे, त्यांना एकाच एम्बेडिंग मॉडेलमधून (embedding model) पाठवणे आणि साध्या top-k लुकअपसह वेक्टर डेटाबेस (vector database) कॉल करणे. प्रेझेंटेशनमध्ये हे उत्तम दिसते, पण प्रत्यक्ष वापरामध्ये (production) ते कोलमडते.

ठराविक आकाराचे तुकडे (Fixed chunks) मजकुराची पर्वा करत नाहीत. ते कायदेशीर कराराचे (legal contract) वाक्य अर्धवट तोडून टाकू शकतात, ज्यामुळे दायित्व कलमे (liability clauses) दोन असंबद्ध मजकुरांमध्ये विभागली जातात. ते संपूर्ण API एंडपॉइंटचे वर्णन एका मोठ्या तुकड्यात टाकू शकतात, ज्यामुळे वापरकर्त्याने विचारलेला विशिष्ट पॅरामीटर (parameter) गोंधळात हरवून जातो. आणि जेव्हा माहिती शोधणे (retrieval) संथ होते, तेव्हा प्रत्येक मिलिसेकंदाचा विलंब (latency) थेट वापरकर्त्याच्या अनुभवावर परिणाम करतो. आम्हाला हा अनुभव कडू पद्धतीने आला. त्यानंतर आम्ही आमचा 'retrieval layer' पूर्णपणे बदलून पुन्हा तयार केला. आमचा 'recall at ten' ७८% वरून ९५% वर पोहोचला. विलंब (latency) वाढला नाही, उलट तो कमालीचा कमी झाला.

कॉपी-पेस्ट RAG मधील समस्या

मानक RAG स्टॅक आता एक प्रकारची डीफॉल्ट सेटिंग बनली आहे. लहान तुकडे, एक एम्बेडिंग मॉडेल, वेक्टर सर्च, बस एवढेच. ही पद्धत केवळ 'डेमो'मध्ये टिकते कारण डेमोमध्ये स्वच्छ प्रश्न आणि सुव्यवस्थित दस्तऐवज वापरले जातात. प्रत्यक्ष वापराचा डेटा (production data) कधीही सुव्यवस्थित नसतो.

कायदेशीर दस्तऐवजांची रचना श्रेणीबद्ध (hierarchical) असते. विभागांमध्ये उपविभाग असतात, उपविभागांमध्ये कलमे (clauses) असतात. जर तुम्ही केवळ टोकन काउंटर वापरून त्यांचे तुकडे केले, तर तुम्ही त्यातील महत्त्वाचे संबंध नष्ट करता ज्याचा वापर मॉडेल तर्क करण्यासाठी करते. API डॉक्युमेंटेशनचीही रचना असते, पण ती वेगळी असते. एक फंक्शन सिग्नेचर, त्याचे पॅरामीटर्स, त्याचे रिटर्न व्हॅल्यू आणि एक उदाहरण मिळून एक तार्किक घटक (logical unit) तयार होतो. जर तुम्ही याला ठराविक टोकन विंडोमध्ये बसवण्याचा प्रयत्न केला, तर एकतर उदाहरण कापले जाते किंवा तुकडा असंबद्ध फंक्शन्सनी भरला जातो. सपोर्ट तिकिटे विस्कळीत, संवादात्मक आणि विषयांच्या अचानक बदलांनी भरलेली असतात. विकी (Wikis) विस्तारलेले आणि एकमेकांशी संबंधित असतात. एकच 'chunking strategy' या सर्वांसाठी वापरता येत नाही, तरीही टीम्स नेहमी तीच पद्धत वापरतात. आम्ही तसे भासवणे थांबवले.

धोरणात्मक चंकिंग (Strategic Chunking): पद्धतीचा विषयाशी मेळ घाला

आम्ही 'content-aware chunking' कडे वळलो. कायदेशीर दस्तऐवजांसाठी, आम्ही 'recursive chunking' वापरतो जे दस्तऐवजाची श्रेणीबद्ध रचना जपते. हे कलमे अखंड ठेवते आणि विभागांमधील पालक-अपत्य (parent-child) संबंध टिकवून ठेवते. API डॉक्युमेंटेशनसाठी, आम्ही 'function-aware chunking' तयार केले आहे जे प्रत्येक फंक्शन किंवा एंडपॉइंटला एक सीमा (boundary) मानते. जर एखाद्या पॅरामीटरचे वर्णन मोठे असेल, तर तुकडा टोकन मर्यादेऐवजी त्या फंक्शनच्या आसपास विस्तारतो. सपोर्ट तिकिटांसाठी, आम्ही 'semantic chunking' वापरतो जे विषयांच्या नैसर्गिक सीमा ओळखते. जेव्हा ग्राहक अचानक बिलिंग तक्रारीकडून तांत्रिक त्रुटीकडे (technical bug) वळतो, तेव्हा तुकडा त्याच ठिकाणी विभागला जातो. विकी आणि विस्कळीत ज्ञानकोशांसाठी (unstructured knowledge bases), आम्ही 'agentic chunking' वापरतो जिथे एक हलके LLM मजकूर तपासते आणि अर्थपूर्ण सीमा कुठे असावी हे ठरवते. हे 'character split' पेक्षा सेट करायला संथ आहे, पण माहिती शोधणे (retrieval) जे प्रभावीपणे काम करते आणि जे केवळ अंदाज लावते, यातील फरक हाच आहे.

हायब्रिड रिट्रिव्हल (Hybrid Retrieval): केवळ वेक्टर सर्च पुरेसा का नाही?

वेक्टर सर्च अर्थ समजून घेतो, पण तो अचूक जुळणी (exact matches) चुकवू शकतो. जर वापरकर्त्याने ERR_CONNECTION_RESET_0x5F3 सारखा एरर कोड पेस्ट केला, तर 'semantic similarity' त्याला केवळ नेटवर्क त्रुटींबद्दल चर्चा करणाऱ्या परिच्छेदांपेक्षा कमी रँक देऊ शकते. दुसरीकडे, BM25 अचूक स्ट्रिंग्स शोधते पण वैचारिक संबंध (conceptual relatedness) misses करते. तुम्हाला दोन्हीची गरज आहे.

आम्ही वेक्टर सर्च आणि BM25 समांतरपणे (in parallel) चालवतो. त्यानंतर आम्ही 'Reciprocal Rank Fusion' (RRF) वापरून निकाल एकत्र करतो, जे दोन वेगवेगळ्या सर्च स्पेसमधील स्कोअरना एकाच स्केलमध्ये न आणता सामान्य (normalize) करते. फ्युजननंतर, आम्ही सर्वोत्तम उमेदवारांना (top candidates) 'cross-encoder reranker' कडे पाठवतो. यामुळे थोडा विलंब (latency) वाढतो, पण अचूकतेमध्ये (precision) मोठी सुधारणा होते. रँकर क्वेरी आणि प्रत्येक उमेदवार एकत्र वाचतो आणि एक 'relevance score' देतो जो सुरुवातीच्या एम्बेडिंगच्या 'cosine similarity' पेक्षा कितीतरी अधिक अचूक असतो. व्यवहारात, हे संयोजन अशा अचूक एरर कोड्सना पकडते जे केवळ वेक्टर सर्चला मिळत नाहीत, आणि त्याच वेळी अशा तांत्रिक उपायांना समोर आणते जे कीवर्ड सर्चकडे दुर्लक्ष करेल.

क्वेरी एक्सपान्शन (Query Expansion): इंडेक्सपर्यंत पोहोचण्यापूर्वी वापरकर्त्याचे इनपुट सुधारणे

वापरकर्ते परिपूर्ण सर्च क्वेरी लिहीत नाहीत. ते 'multi-hop' प्रश्न विचारतात, जसे की "माझे शेवटचे डिप्लॉयमेंट का अयशस्वी झाले आणि मी ते रोलबॅक कसे करू?", ज्यासाठी दोन वेगळ्या माहितीचे स्रोत शोधणे आणि त्यांना जोडणे आवश्यक असते. किंवा ते असे अस्पष्ट प्रश्न विचारतात ज्यांचा इंडेक्सशी संबंध जोडणे कठीण असते.

आम्ही शोध घेण्यापूर्वी क्वेरीजमध्ये बदल करतो. एका मल्टी-हॉप (multi-hop) प्रश्नाचे उप-प्रश्नांमध्ये विभाजन केले जाते. एका अस्पष्ट हेतूचे रूपांतर अनेक विशिष्ट शोध क्वेरीजमध्ये केले जाते. आम्हाला असे आढळले की एका युजर क्वेरीचे पाच वेगवेगळ्या शोध क्वेरीजमध्ये विस्तार केल्यास रिकॉल (recall) ७८ टक्क्यांवरून ९६ टक्क्यांपर्यंत वाढू शकतो. हे LLM ला अधिक कडक प्रॉम्प्टिंग (prompting) करण्याबद्दल नाही. हे रिट्रिव्हल सिस्टमला (retrieval system) योग्य संदर्भ शोधण्यासाठी अधिक संधी देण्याबद्दल आहे. प्रत्येक तयार केलेली क्वेरी एक वेगळा दृष्टिकोन किंवा संज्ञा (terminology) पकडते आणि एकत्रित निकाल एक पूर्ण चित्र स्पष्ट करतात.

Bayesian Optimization: अंदाज लावणे थांबवा

एकदा तुमच्याकडे अनेक चंकिंग स्ट्रॅटेजीज (chunking strategies), हायब्रिड रिट्रिव्हल (hybrid retrieval) आणि क्वेरी एक्सपान्शन (query expansion) उपलब्ध झाले की, तुम्हाला एका नवीन समस्येचा सामना करावा लागतो. येथे खूप सारे 'नॉब्स' (knobs) आहेत. चंक साईज (chunk size), ओव्हरलॅप टक्केवारी (overlap percentage), वेक्टर वेट विरुद्ध BM25 वेट, रँकिंग थ्रेशोल्ड्स (reranking thresholds) आणि top-k व्हॅल्यूज हे सर्व नॉन-लिनियर (nonlinear) पद्धतीने एकमेकांशी संवाद साधतात. मॅन्युअल ट्यूनिंग (manual tuning) हे केवळ एक अंदाज लावण्याचे खेळ बनून राहते.

आम्ही अंदाज लावणे थांबवले. आम्ही...