आजकल RAG को कभी-कभी बुरा समझा जाता है. कुछ लोगों को अब लगता है कि यह बिल्कुल मामूली है—शुरू करना आसान है, पर इसे बड़े स्तर पर ले जाना नहीं—जबकि कुछ मानते हैं कि ‘एजेंट-आधारित प्रणालियां’ इससे आगे निकल चुकी हैं. हालांकि, थोड़ा गहराई से देखने पर इनमें से कई प्रणालियां बहुत जल्द RAG जैसी ही दिखने लगती हैं...
यह ब्लॉग एक-दो व्यावहारिक उदाहरणों के जरिए दिखाता है कि हम इन आम चुनौतियों से कैसे निपटते हैं:
मिश्रित टेक्स्ट और संख्यात्मक डेटा को संभालना और यह साधारण RAG को क्यों विफल कर देता है: कीवर्ड आपस में टकराते हैं और संख्याओं का अपना कोई अर्थगत आशय नहीं होता.
पहले सारांश तैयार करके एम्बेडिंग बनाना क्यों उपयोगी है: हर खंड का छोटा वर्णनात्मक सारांश बनाएं, फिर उसी सारांश को एम्बेड करके उस पर क्वेरी चलाएं.
संदर्भयुक्त सारांश कैसे बनाएं: मूल दस्तावेज का संदर्भ शामिल करें, ताकि एक जैसे दिखने वाले आंकड़ों के बीच अंतर स्पष्ट रहे.
कोड और Pydantic मॉडल पर कब भरोसा करें: जहां सामग्री हूबहू चाहिए, वहां विश्वसनीयता के लिए कस्टम कोड और/या Pydantic मॉडल को LLM कॉल के साथ इस्तेमाल करें.
बुनियादी बातें
RAG प्रणालियां सहायता बॉट से लेकर आंतरिक ज्ञान सहायकों तक, कई तरह के अनुप्रयोगों को शक्ति देती हैं.
इनके भीतर आम तौर पर आप ये काम करते हैं:
अपने स्रोत दस्तावेजों के खंड बनाएं
हर खंड को वेक्टर स्पेस में एम्बेड करें
क्वेरी के समय शीर्ष-K खंड पुनर्प्राप्त करें
उन खंडों के आधार पर उत्तर तैयार करें
LangChain, LlamaIndex और OpenAI का Filestore जैसे लोकप्रिय टूलकिट इन चरणों को लगभग मामूली बना देते हैं. लेकिन वास्तविक पाइपलाइन में ऐसा डेटा भी मिलता है जो केवल सघन टेक्स्ट नहीं होता, और बुनियादी RAG को उससे निपटने में कठिनाई हो सकती है. अगले खंडों में हम डेटा संबंधी चुनौतियों के ठोस उदाहरण दिखाएंगे और जटिलता बढ़ने के साथ धीरे-धीरे समाधान तैयार करेंगे.
जब आपका डेटा केवल टेक्स्ट न हो—जो वास्तव में काफी आम है
गेमिंग के संदर्भ में डेटा के इस खंड पर विचार करें:
JSON
एम्बेडिंग इसलिए काम करती हैं क्योंकि शब्दों के अर्थ और व्याकरण के जरिए उनके बीच के संबंध सीखे जाते हैं. ऊपर के डेटा में टेक्स्ट और संख्याएं मिली हुई हैं. इस खास संदर्भ से बाहर उन संख्याओं का शब्दों से कोई संबंध नहीं है. इसलिए कहा जा सकता है कि डेटा का यह खंड कुछ वर्णनात्मक शब्दों और उनके बाद आने वाली कुछ बेतरतीब संख्याओं का मेल भर है.
अगर हमारे पास केवल इसी तरह का डेटा होता तो यह समस्या नहीं होती, क्योंकि हम उपलब्ध कुछ वर्णनात्मक शब्दों की एम्बेडिंग से इसे फिर भी पुनर्प्राप्त कर सकते थे या बस text-to-sql इस्तेमाल कर सकते थे. लेकिन क्या होगा अगर यह खंड टेक्स्ट से भरे कई ऐसे खंडों के बीच दबा हो, जिनमें यही शब्द भी आते हों. उदाहरण के लिए:
JSON
अब मान लें कि हम यह पुनर्प्राप्त करना चाहते हैं: “ड्रैकोनिक असेंशन के साथ हमले की सीमा कितनी है.” बहुत संभव है कि हम मनचाहा प्रासंगिक खंड पुनर्प्राप्त न कर पाएं, क्योंकि वही कीवर्ड रखने वाले दूसरे खंडों के शोर में वह काफी गहराई तक दबा है.
मूल समस्या यह है कि एक ही विषय पर अलग-अलग प्रकार की जानकारी होने के बावजूद हम डेटा के इन खंडों में ठीक से अंतर नहीं कर सकते. क्या हम इसे किसी तरह अधिक समृद्ध या बेहतर बना सकते हैं. बिल्कुल बना सकते हैं:smile:
अपने डेटा का सारांश बनाकर उसे समृद्ध करें—जी हां, आपने सही पढ़ा
खंड को सीधे एम्बेड करने के बजाय हम पहले उसके डेटा का वर्णन करने वाला सारांश बना सकते हैं, फिर उसी सारांश को एम्बेड करके उसके आधार पर पुनर्प्राप्ति कर सकते हैं. उत्तर तैयार करते समय हम फिर भी सारांश से जुड़ा मूल डेटा ही इस्तेमाल करेंगे.
इसलिए ऊपर दिखाए गए दोनों खंडों के लिए हम कुछ ऐसे सारांश बनाएंगे:
हमले की सीमा, गति और क्षति के आंकड़े—डिफॉल्ट और ड्रैकोनिक असेंशन के साथ.
ड्रैकोनिक असेंशन का वर्णन और विवरण, जिसमें सक्रिय होने की शर्तें, दृश्य प्रभाव और कथा-संसार शामिल हैं.
फिर हम क्वेरी को भी बेहतर बनाते हैं, ताकि वह सारांश के अनुरूप हो जाए. मिसाल के लिए, हम “ड्रैकोनिक असेंशन के साथ हमले की सीमा कितनी है.” को बदलकर “ड्रैकोनिक असेंशन के साथ हमले की सीमा का आंकड़ा क्या है.” कर देंगे. यह खास तौर पर तब जरूरी है जब पुनर्प्राप्ति क्वेरी तकनीकी क्षेत्र से बाहर के ऐसे उपयोगकर्ताओं से आती है, जो ~~“मनमाने अंदाज”~~ सामान्य मानवीय भाषा में पूछते हैं. आखिर RAG अधिकतम सटीकता और रिकॉल कैसे हासिल करता है, यह जानना न उनकी विशेषज्ञता है और न चिंता.


बातों को संदर्भ से बाहर न लें—यह जीवन में भी आम तौर पर लागू होता है
अब अगली स्थिति में हमें नीचे जैसे एक-दूसरे से मिलते-जुलते ढेरों डेटा खंडों से निपटना है:
Plain Text
अगर हम यही तरीका अपनाए रखें, तो कल्पना करें कि कोई पूछे, “किरदार X के हमले की सीमा कितनी है.” अभी बनाए गए सारांश भी एक जैसे दिखेंगे, इसलिए हमें किस्मत के भरोसे अनुमान लगाना पड़ेगा. तो हम उनमें अंतर कैसे कर सकते हैं.
सीधा जवाब है: संदर्भ दें. हम डेटा खंड में उसके मूल दस्तावेज का संदर्भ जोड़ सकते हैं. मसलन, इस मामले में {”किरदार”: “X”}. अब किरदार Y और Z के लिए भी वही डेटा होने पर हम किरदार X का सही डेटा सटीक रूप से पुनर्प्राप्त कर पाएंगे.
हालांकि, खंड का संदर्भयुक्त सारांश बनाना अधिक बेहतर और व्यापक रूप से लागू होने वाला तरीका होगा. यानी केवल डेटा खंड का सारांश बनाने के बजाय हम उसके मूल दस्तावेज और खंड, दोनों को देकर एक सामान्य संदर्भयुक्त सारांश बनवा सकते हैं. उसमें यह भी बताया जाएगा कि खंड अपने मूल दस्तावेज में कैसे फिट होता है. उदाहरण के लिए:
यह खंड किरदार X के लिए ... के विस्तृत आंकड़े देता है. यह खंड पूरे दस्तावेज में दिखाता है कि हमले की गति के मामले में X कितना शक्तिशाली है...
यह खंड किरदार Y के लिए ... के विस्तृत आंकड़े देता है. यह खंड पूरे दस्तावेज में दिखाता है कि विशेष क्षमता के साथ Y के आंकड़े कितने बढ़ जाते हैं...
यह खंड किरदार Z के लिए ... के विस्तृत आंकड़े देता है. यह खंड पूरे दस्तावेज में दिखाता है कि Z के आंकड़े उसे टीम मुकाबलों में टैंक की भूमिका के लिए कैसे उपयुक्त बनाते हैं...
यह तरीका (जो कुछ हद तक Anthropic से प्रेरित है) ऊपर के उदाहरण के लिए जरूरत से ज्यादा लग सकता है. हालांकि, यह उन खंडों के लिए बहुत प्रभावी है जिनका “संदर्भ से बाहर” गलत अर्थ निकाला जा सकता है. साथ ही, यह सभी खंडों पर काम करने वाला एकीकृत तरीका देकर इंजीनियरिंग पाइपलाइन को सुव्यवस्थित रखता है.


जब आपको ~~हर चीज नियंत्रित करने वाला~~ पूरी तरह कठोर होना पड़े
आमतौर पर हमें पूरा डेटा मिलता है और हम RAG प्रणाली के लिए उसे खंडों में बांटते हैं. इस उदाहरण में हम कुछ अलग दिखाते हैं. यहां डेटा खंडों में बंटा है, लेकिन वे खराब खंड हैं—दरअसल वे एक तार्किक खंड के बेतरतीब हिस्से हैं, जिन्हें वापस साथ जोड़ना जरूरी है. तार्किक खंड से आशय ऐसी सामग्री से है जो स्वाभाविक रूप से साथ होनी चाहिए, जैसे दस्तावेज का कोई उपखंड या कोई सुसंगत अनुच्छेद.


इस डेटा के साथ अपने पहले प्रयास में हमने सब कुछ LLM कॉल को दिया, उससे उचित ढंग से समूह बनाने को कहा और फिर समूहीकृत सामग्री लौटाने को कहा. LLM को इसमें काफी अच्छा होना चाहिए, है न. जवाब हां भी है और नहीं भी.
हमने कई अन्य मौकों पर भी पाया है कि LLM सुस्त ढंग से काम करने लगते हैं और पूरी, हूबहू सामग्री चाहिए होने पर भरोसेमंद नहीं रहते—खासकर जब संदर्भ लंबा हो. और यह बिल्कुल स्वाभाविक है. लेकिन इस खास उपयोग के लिए यह अस्वीकार्य था, क्योंकि हमें शब्द-दर-शब्द हूबहू सामग्री चाहिए थी—न सारांश, न मूल सामग्री का कोई हिस्सा छोड़ना. हम कोई भी विवरण नहीं छोड़ सकते.
और जवाब का “हां” वाला हिस्सा यह था कि उसने टूटे हुए खंडों के अर्थ और संरचना को बहुत अच्छी तरह समझा. काश वह हूबहू सामग्री दोहराने से इनकार न करता. धत्त तेरे की:/
तो हम LLM की खूबियों का लाभ उठाते हुए उसकी अविश्वसनीय क्षमताओं से कैसे बच सकते थे. हमने अपने पुराने भरोसेमंद साथी कोड का सहारा लिया—यानी एक कस्टम Python फ़ंक्शन. और एक ऐसा Pydantic मॉडल, जो इससे सरल हो ही नहीं सकता. समाधान यह है:
मौजूदा तार्किक खंड को कायम रखते हुए सभी अनुभागों पर क्रम से जाएं.
हर अनुभाग पर LLM से पूछें: क्या यह अनुभाग मौजूदा तार्किक खंड का हिस्सा है. Pydantic मॉडल के अनुसार हां या नहीं में उत्तर दें.
अगर हां, तो अनुभाग को खंड से जोड़ दें. अगर नहीं, तो मौजूदा तार्किक खंड पूरा होने के कारण उसे ज्यों का त्यों निकाल दें और इस अनुभाग से नया खंड शुरू करें.


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