स्ट्रीमिंग LLM गार्डरेल: भेजा गया टोकन लौटता नहीं
स्ट्रीमिंग LLM गार्डरेल: भेजा गया टोकन लौटता नहीं। NeMo, Guardrails AI और Bedrock रफ़्तार व सुरक्षा का सौदा कैसे करते हैं, और वाक्य-गेट हर दावा कैसे जाँचता है।
LLM जो टोकन स्ट्रीम कर चुका है, उसे वापस नहीं लिया जा सकता। टेक्स्ट एक बार पाठक तक पहुँच गया, तो उस पर आपत्ति करने वाला गार्डरेल सिर्फ़ आगे आने वाले हिस्से को रोक सकता है। इसलिए स्ट्रीम पर लगे गार्डरेल को असल में एक ही फ़ैसला करना होता है: रिलीज़ से पहले जाँचे और पाठक को इंतज़ार कराए, या पहले रिलीज़ करे और मान ले कि रोका गया उत्तर पढ़ा जा चुका है। काम के तीन तरीक़े हैं: रिलीज़ के बाद जाँच, तय आकार के चंक पर गेट, या पूरे वाक्य पर गेट। इस साइट का सहायक, जिसे मैंने बनाया है, हर वाक्य पर गेट लगाता है। इसकी क़ीमत एक वाक्य जितना इंतज़ार है। इसकी सीमा यह है कि यह जाँचता है कि दावा समर्थित है या नहीं, यह नहीं कि वह सच है या नहीं।
यह लेख उस आख़िरी जाँच के बारे में है जो स्रोतों पर आधारित (grounded) उत्तर के रिलीज़ होने से पहले होती है। उससे पहले की परतें, दायरे से बाहर के सवालों को ठुकराने से लेकर रिट्रीवल स्कोर की न्यूनतम सीमा तक, RAG हैलुसिनेशन गार्डरेल: पाँच परतों वाला डिज़ाइन में हैं।
स्ट्रीम हो चुका टोकन वापस क्यों नहीं लिया जा सकता
बाद में जाँचने वाला (post-hoc) वैलिडेटर पूरा उत्तर तभी देखता है जब पाठक उसे पढ़ चुका होता है। अगर तीसरा वाक्य कोई तारीख़ गढ़ देता है, तो बाद में उसे मिटाने से रिकॉर्ड बदलता है, वह नहीं जो पढ़ा जा चुका है। Christopher M. Frost का arXiv पर आया एक हालिया प्रीप्रिंट इस सीमा को सटीक शब्दों में रखता है: “जो नियंत्रण किसी निषिद्ध पैटर्न को, उसे पूरा करने वाला चंक रिलीज़ करने के बाद पकड़ता है, वह उसे वापस नहीं ले सकता”।
वेंडर भी अपने डॉक्यूमेंटेशन में यही सीमा बताते हैं। Azure OpenAI का asynchronous filter, जिसे अलग से चालू करना पड़ता है, टेक्स्ट तुरंत लौटा देता है और उल्लंघन का सिग्नल उल्लंघन वाली सामग्री से लगभग 1,000 कैरेक्टर के दायरे में ही मिलने की गारंटी देता है। उसकी ट्रबलशूटिंग सलाह है: “पहले से दिखाई जा चुकी सामग्री हटाने के लिए अपने ऐप्लिकेशन में content redaction लागू करें”। OWASP की LLM09:2025 प्रविष्टि ऐसे टूल्स और प्रक्रियाओं की सिफ़ारिश करती है जो “मुख्य आउटपुट को स्वचालित रूप से सत्यापित करें”। खुला सवाल यह है कि रिलीज़ के सापेक्ष सत्यापन कब चलता है।
स्ट्रीम पर गार्डरेल के तीन तरीक़े: post-hoc, चंक-गेट और वाक्य-गेट
तरीक़ा | पाठक को टेक्स्ट कब दिखता है | रोका गया उत्तर | उदाहरण |
|---|---|---|---|
Post-hoc (पहले रिलीज़) | अपनी जाँच से पहले (NeMo: हर चंक बफ़र होते ही; Azure और Bedrock के asynchronous मोड: जनरेट होते ही) | रुकने से पहले उसका कुछ हिस्सा पढ़ा जा चुका होता है | NeMo, जब |
चंक-गेट | हर चंक पास होने के बाद | रोका गया चंक कभी नहीं दिखता | Azure और Bedrock के डिफ़ॉल्ट; NeMo, जब |
वाक्य-गेट | हर वाक्य पास होने के बाद | रोका गया वाक्य कभी नहीं दिखता | इस साइट का सहायक |
NeMo Guardrails में stream_first से क्या मिलता है, क्या छूटता है
NeMo Guardrails में output rails की स्ट्रीमिंग डिफ़ॉल्ट रूप से बंद रहती है। चालू करने पर यह output rails, यानी जनरेट हुए टेक्स्ट पर NeMo की जाँचें, डिफ़ॉल्ट रूप से 200 टोकन के चंक पर चलाती है और हर चंक से 50 टोकन का संदर्भ अगले चंक में ले जाती है। stream_first फ़्लैग का डिफ़ॉल्ट True है: “output rails के किसी चंक को प्रोसेस करने से पहले ही क्लाइंट को टोकन का वह चंक मिल जाता है। इससे time-to-first-token कम होता है, लेकिन अगर कोई rail सामग्री को ब्लॉक करती है, तो उपयोगकर्ता को टोकन पहले ही मिल चुके होते हैं।” इसके बाद स्ट्रीम एक JSON error के साथ ख़त्म होती है।
False होने पर “उपयोगकर्ता ब्लॉक की गई सामग्री कभी नहीं देखता, लेकिन time-to-first-token हर चंक पर rail के चलने में लगे समय जितना बढ़ जाता है।” सौदा यही है: पहला टोकन दिखने में लगने वाला समय (time to first token, TTFT) एक तरफ़, और ब्लॉक किए गए टेक्स्ट का पाठक तक पहुँच जाना दूसरी तरफ़। जब output rails की स्ट्रीमिंग चालू हो, तो NeMo डिफ़ॉल्ट रूप से पहले रिलीज़ करता है; नीचे के दोनों क्लाउड फ़िल्टर पहले जाँचते हैं। अगर कोई rail ऐसी चीज़ रोकने के लिए है जो कभी दिखनी ही नहीं चाहिए, तो डिफ़ॉल्ट बदल दें।
Guardrails AI के वाक्य-स्तरीय वैलिडेटर से क्या मिलता है, क्या छूटता है
Guardrails AI इकाई के रूप में वाक्य चुनता है: “डिफ़ॉल्ट रूप से, वैलिडेटर सत्यापन चलाने से पहले तब तक रुकते हैं जब तक LLM से एक वाक्य जितनी सामग्री जमा न हो जाए।” पूरे उत्तर के बजाय अलग-अलग वाक्यों का सत्यापन “उन वैलिडेटर की लागत बचाने में मदद करता है जिन्हें महँगे inference की ज़रूरत होती है”।
स्ट्रीमिंग में सभी on_fail टाइप समर्थित नहीं हैं, और कई वैलिडेटर के सुधार (fixes) आपस में मिलाए जाने से पहले “सभी वैलिडेटर के पर्याप्त चंक जमा कर लेने” का इंतज़ार करते हैं। डॉक्यूमेंटेशन साफ़ तौर पर नहीं बताता कि सत्यापन से पहले कच्चे टोकन उपयोगकर्ता तक पहुँचते हैं या नहीं, इसलिए इसे अपने इंटीग्रेशन में ख़ुद जाँचें।
डिफ़ॉल्ट रूप से बफ़र: Azure OpenAI और Amazon Bedrock
Azure OpenAI डिफ़ॉल्ट रूप से completion टेक्स्ट को बफ़र करता है, बफ़र पर फ़िल्टर चलाता है, और अलग-अलग टोकन के बजाय “कंटेंट चंक” लौटाता है। Amazon Bedrock Guardrails अपने डिफ़ॉल्ट synchronous मोड में चंक भेजने से पहले उन्हें स्कैन करता है, जिससे “कुछ latency आती है” लेकिन “बेहतर सटीकता मिलती है”। Bedrock के asynchronous मोड में चंक में “guardrails का स्कैन पूरा होने तक अनुचित सामग्री हो सकती है”, और संवेदनशील जानकारी को मास्क नहीं किया जा सकता।
वाक्य-गेट स्ट्रीम को कैसे सत्यापित करता है
इस साइट का सहायक केवल साइट की अपनी सामग्री से उत्तर देता है, और जो सवाल मेरे बारे में नहीं है और जिसका समर्थन कोई भी अंश (passage) नहीं करता, वह मॉडल तक पहुँचता ही नहीं। वह मेरे बारे में अन्य पुरुष (third person) में बात करता है। जिस साइट पर मेरा नाम है, वहाँ सबसे बुरी विफलता कोई गढ़ी हुई योग्यता (credential) है, और योग्यता का ज़िक्र एक दावा है। दावा रखने वाली सबसे छोटी इकाई वाक्य है, इसलिए गेट वाक्य-सीमा तक बफ़र करता है, जाँचता है, और उसके बाद ही रिलीज़ करता है। लैब का RAG बनाम बिना रिट्रीवल डेमो भी इसी स्ट्रीमिंग एंडपॉइंट को कॉल करता है।
टोकन आते रहने के दौरान वाक्य कहाँ ख़त्म होता है
वाक्य-समापन चिह्न:
.!?…, साथ ही अरबी लिपि का प्रश्नचिह्न؟और पूर्ण विराम۔, और चीनी व हिंदी के लिए。!?, दंड (पूर्ण विराम)।और दोहरा दंड॥।?!जैसी शृंखला एक ही वाक्य-सीमा मानी जाती है। अरबी लिपि का अर्धविराम؛उपवाक्यों को जोड़ता है, इसलिए उसे जान-बूझकर बाहर रखा गया है।उसके बाद ख़ाली जगह (whitespace) ज़रूरी है। यही एक नियम
3.14औरexample.comको टूटने नहीं देता। अपवाद हैं चीनी पूर्ण विराम, विस्मयादिबोधक चिह्न और प्रश्नचिह्न, जिनके बाद कोई स्पेस नहीं आता। किसी उद्धरण के भीतर ये वाक्य वहाँ ख़त्म करते हैं जहाँ उद्धरण बंद होता है, और अगर उसके बाद अल्पविराम आए तो ख़त्म करते ही नहीं।हर पूर्ण विराम वाक्य ख़त्म नहीं करता। जिसके बाद लैटिन का छोटा अक्षर आए, वह वाक्य ख़त्म नहीं करता (
et al. and), और न ही एक छोटी सूची के संक्षिप्त रूपों का पूर्ण विराम, जैसेe.g.,उदा.या जर्मनbzw., और न ही जर्मन तारीख़ में दिन के बाद का पूर्ण विराम (am 17. September)।अगला कैरेक्टर फ़ैसला करता है। वाक्य-सीमा तभी पक्की होती है जब ऐसा कैरेक्टर आ जाए जो न स्पेस हो और न उद्धरण-मार्कर, क्योंकि पूर्ण विराम के बाद आने वाले मार्कर अपने से पहले वाले वाक्य का हिस्सा हैं: सभी, चाहे
[S1][S2]हों या[S1] [S2], सिर्फ़ पहला नहीं।वाक्य-सीमा के बाद आया सूची-चिह्न वाक्य नहीं है। वरना उत्तर या नए वाक्य की शुरुआत में आया
1.बिना उद्धरण वाला दावा माना जाता और स्रोतों पर आधारित उत्तर को उसके पहले बुलेट पर ही रोक देता।एक ऊपरी हद। अगर एक हज़ार कैरेक्टर तक कोई वाक्य-सीमा न मिले, तो बफ़र आख़िरी स्पेस पर काटा जाता है और फिर भी जाँचा जाता है।
ख़ाली पंक्ति बनी रहती है। जिस वाक्य से पहले ख़ाली पंक्ति थी, वह रिलीज़ भी ख़ाली पंक्ति के बाद होता है, इसलिए दो पैराग्राफ़ वाला उत्तर दो पैराग्राफ़ में ही रहता है।
अगले कैरेक्टर का इंतज़ार आम तौर पर एक कैरेक्टर की देरी लाता है, और पूर्ण विराम के बाद मार्कर आएँ तो थोड़ी ज़्यादा। आख़िरी वाक्य स्ट्रीम ख़त्म होने पर जाँचा जाता है। तुलना करते समय फ़ारसी अंकों और विभाजकों (۳٫۱۴ का मिलान 3.14 से होता है) और अरबी अक्षर-रूपों को एक समान रूप में बदल दिया जाता है, अरबी के ह्रस्व स्वर-चिह्न हटा दिए जाते हैं, और zero-width non-joiner को स्पेस माना जाता है: ये वही रूपांतर हैं जिन्हें फ़ारसी RAG के लिए हाइब्रिड सर्च रिट्रीवल की तरफ़ सँभालता है।
चार नियतात्मक जाँचें: उद्धरण, मान और उत्तम पुरुष
उद्धरण का दायरा। छह स्रोत दिए गए हों और मार्कर
[S7]हो, तो वह किसी गढ़े हुए स्रोत का नाम ले रहा है।उद्धरण अनिवार्य। आठ या उससे ज़्यादा शब्दों वाले वाक्य को, या ऐसे वाक्य को जिसमें कोई भी संख्या, URL या ईमेल पता हो, मार्कर चाहिए। छोटे जोड़ने वाले वाक्य पास हो जाते हैं।
अक्षरशः समर्थन। हर संख्या, URL और ईमेल पता उस वाक्य द्वारा उद्धृत किसी स्रोत में मौजूद होना चाहिए। मिलान अंकों की सीमाओं का ध्यान रखता है, इसलिए स्रोत में लिखा कोई वर्ष उस प्रतिशत की पुष्टि नहीं कर सकता जिसके अंक उस वर्ष के आख़िरी अंकों से मिलते हों, और जो दशमलव संख्याएँ सिर्फ़ आख़िरी शून्यों में अलग हों, वे एक ही मान हैं:
20.00वही है जो20। जो अल्पविराम हज़ारों को अलग नहीं करता, वह दशमलव चिह्न है:2,5का मतलब2.5है,25नहीं।उत्तम पुरुष। उत्तम पुरुष (first person) का कोई भी शब्द स्ट्रीम रोक देता है। अंग्रेज़ी सूची छोटी और तय है: उत्तम पुरुष सर्वनाम और उनके संक्षिप्त रूप (contractions)। फ़ारसी सूची चुनकर बनाई गई है: सर्वनाम और कुछ ज़्यादा जोखिम वाले क्रिया-रूप, जैसे फ़ारसी में “मैं हूँ” और “मेरे पास है” के रूप। फ़ारसी में क्रिया का पुरुष उसके अंतिम प्रत्यय से पता चलता है, और ऐसे प्रत्यय साधारण संज्ञाओं के अंत में भी आ सकते हैं, इसलिए हर प्रत्यय को पकड़ने लायक व्यापक नियम संज्ञाओं को भी पकड़ लेगा। साइट की बाक़ी भाषाओं की सूचियों में सर्वनाम और उनके संबंधकारक रूप (possessives) हैं, और स्पैनिश, पुर्तगाली, रूसी, अरबी और हिंदी की सूचियों में कुछ क्रियाएँ भी हैं, क्योंकि ये भाषाएँ “मैं” अक्सर सिर्फ़ क्रिया से कहती हैं; चीनी में वाक्य में कहीं भी 我 आने की जाँच होती है, सिवाय 自我 (“स्व-”) से बने किसी संयुक्त शब्द के, जैसे 自我修正 (“स्व-सुधार”)। कौन-सी सूचियाँ लागू होंगी, यह इस पर निर्भर है कि उत्तर किस भाषा में माँगा गया था। अंग्रेज़ी, फ़ारसी, अरबी, रूसी, हिंदी और चीनी की सूचियाँ हर उत्तर पर लागू होती हैं। अंग्रेज़ी मॉडल के निर्देशों (instructions) की भाषा है, इसलिए माँगी गई भाषा से भटका उत्तर सबसे ज़्यादा उसी में जा सकता है; बाक़ी पाँच ऐसी लिपियों में लिखी जाती हैं जिनमें कोई लैटिन नाम या acronym उनका कोई शब्द नहीं बना सकता। अपवाद अरबी में माँगा गया उत्तर है: उसमें फ़ारसी के “मैं” और “हम” वाले शब्द नहीं खोजे जाते, क्योंकि अरबी में वही शब्द “से” और “क्या” हैं। जर्मन, फ़्रेंच, स्पैनिश और पुर्तगाली की सूचियाँ सिर्फ़ उन्हीं उत्तरों पर लागू होती हैं जो उसी भाषा में माँगे गए हों: उनके छोटे शब्द नाम भी होते हैं और दूसरी भाषाओं के आम शब्द भी, जैसे “Ma et al.” का “Ma”। कोई भी सूची पढ़ने से पहले बड़े अक्षरों (capitals) में लिखे acronyms, “et al.” से पहले का उपनाम और “Annex I” का रोमन अंक अलग कर दिए जाते हैं।
कोई वाक्य जाँच में विफल हो तो क्या होता है
विफलता से पहले पास हुआ हर वाक्य रिलीज़ होता है, उसी बैच में पहले आए वाक्य भी।
गेट रुक जाता है और रुका ही रहता है: एक असमर्थित वाक्य बाक़ी उत्तर को भी अविश्वसनीय बना देता है।
एक
guardइवेंट उल्लंघन के कारण और पहले से तय, स्थानीय भाषा में लिखा संदेश ले जाता है कि बाक़ी उत्तर रोक लिया गया है, और आख़िरीdoneइवेंट इस रुकावट को दर्ज करता है। रोका गया वाक्य कभी भेजा नहीं जाता; सर्वर लॉग में सिर्फ़ उसकी लंबाई लिखता है।प्रोवाइडर की स्ट्रीम बंद कर दी जाती है, और इसी तरह यह डिज़ाइन अपस्ट्रीम अनुरोध रद्द करता है। रुकने से पहले जो जनरेट हुआ, वह फिर भी इनवॉइस में आता है, इसलिए ख़र्च की मासिक सीमा में भी दर्ज होता है। दोनों प्रोवाइडर प्रोटोकॉल output usage को एक अंतिम इवेंट में रिपोर्ट करते हैं, जहाँ बीच में कटी स्ट्रीम कभी नहीं पहुँचती, इसलिए सेवा मिला हुआ टेक्स्ट रखती है और प्रोवाइडर ने जो रिपोर्ट नहीं किया, उसे हर UTF-8 बाइट के लिए एक टोकन की दर से भरती है; यह गिनती ग़लत हो, तो ज़्यादा की ओर होती है। कोई reasoning मॉडल सोचने में जो टोकन ख़र्च करता है, वे कभी टेक्स्ट के रूप में नहीं आते, इसलिए यह अनुमान उन्हें छोड़ देता है। ख़र्च की सीमा पाँच परतों वाले लेख में समझाई गई है।
चैट पैनल उस टर्न को रोके गए उत्तर के रूप में चिह्नित करता है और उसका रिलीज़ हुआ टेक्स्ट बातचीत के इतिहास में रखता है, क्योंकि रुकने की जगह तक वह टेक्स्ट समर्थित है।
ट्रंकेशन, यानी उत्तर का बीच में कट जाना, एक अलग विफलता है, और इस सहायक के एक पुराने संस्करण ने दोनों को आपस में मिला दिया था। जब मॉडल के पास आउटपुट की जगह ख़त्म हो जाती थी, तो गेट कटे हुए टुकड़े को पूरा वाक्य मानकर जाँचता था और बिना उद्धरण वाला बताकर रोक लेता था। पाठक से कहा जाता था कि जो उत्तर सिर्फ़ बीच में रुका था, उसका समर्थन स्रोतों से नहीं हो सकता। और जब कोई टेक्स्ट लौटता ही नहीं था, तो टर्न बिना किसी संदेश के ग़ायब हो जाता था। अब अधूरा टुकड़ा बिना जाँचे हटा दिया जाता है और उत्तर कट जाने की सूचना दिखाई जाती है। बीच में कटा वाक्य असमर्थित वाक्य नहीं है। अगर दोनों एक साथ हों, तो गार्ड वाला रुकना ही मान्य होता है।
उद्धरण मार्कर अंदरूनी हैं
प्रॉम्प्ट हर उस वाक्य पर [Sn] मार्कर माँगता है जो कोई तथ्य बताता है, और गेट हर ठोस वाक्य पर उद्धरण वाला नियम लागू करता है। वाक्य पास होते ही उसके मार्कर हटा दिए जाते हैं और स्पेसिंग ठीक कर दी जाती है, फ़ारसी विराम-चिह्नों से पहले भी, इसलिए पाठक मार्कर नहीं, सहज गद्य देखते हैं। अगर इसके बजाय प्रॉम्प्ट से ही मार्कर हटा दिए जाते, तो हर ठोस वाक्य उद्धरण वाले नियम पर विफल हो जाता।
जब मॉडल के पास कहने को कुछ न हो
जब स्रोत सवाल का जवाब नहीं देते, तो प्रॉम्प्ट मॉडल से एक वाक्य नहीं, बल्कि एक तय टोकन माँगता है। मॉडल का लिखा इनकार भी बाक़ी दावों जैसा ही एक दावा होता, और वह भी बिना उद्धरण वाला: गेट उसे ऐसे संदेश के साथ रोक देता जो दोष स्रोतों पर डालता, जबकि उस सवाल का जवाब देना स्रोतों का काम था ही नहीं।
गेट यह टोकन कभी रिलीज़ नहीं करता। जब तक अब तक आया टेक्स्ट यह टोकन बन सकता है, उसे रोके रखा जाता है; जो पहला कैरेक्टर इस संभावना को ख़त्म कर दे, वह टेक्स्ट को सामान्य रास्ते पर भेज देता है, इसलिए साधारण उत्तर को ज़रा भी ज़्यादा इंतज़ार नहीं करना पड़ता। अगर उत्तर इसी टोकन से शुरू होता है, तो सर्विस उसे साइट के तय इनकार-संदेश से बदल देती है, यानी वही टेक्स्ट जो हर दूसरे इनकार में दिखता है। अगर टोकन पहले से रिलीज़ हो चुके वाक्यों के बाद आता है, तो वे वाक्य बने रहते हैं और उत्तर वहीं ख़त्म हो जाता है।
“समर्थित” की जाँच “सच” की जाँच नहीं है
स्टैनफ़र्ड के नेतृत्व वाले एक अध्ययन में Magesh और उनके सहयोगियों ने वह किया जिसे वे AI क़ानूनी शोध टूल्स का पहला पूर्व-पंजीकृत (preregistered) मूल्यांकन बताते हैं। LexisNexis और Thomson Reuters के AI शोध टूल्स में से हर एक ने 17% और 33% के बीच मामलों में हैलुसिनेट किया। किसी उत्तर को हैलुसिनेटेड तब गिना गया जब वह “या तो ग़लत हो या misgrounded (ग़लत आधार वाला)”, और misgrounded का अर्थ है कि “प्रमुख तथ्यात्मक कथनों का हवाला तो दिया गया है, पर वे स्रोत की ग़लत व्याख्या करते हैं या किसी ऐसे स्रोत का संदर्भ देते हैं जो लागू नहीं होता”। ग़लत आधार वाले उत्तरों में उद्धरण होते हैं। लीगल AI प्रोडक्ट्स के लिए इसका क्या मतलब है, यह एक अलग लेख में है।
स्ट्रिंग पर चलने वाला गेट ग़लत आधार (misgrounding) को नहीं देख सकता, और इस गेट के कुछ ज्ञात ब्लाइंड स्पॉट हैं:
दो स्रोतों का उद्धरण देने वाला वाक्य दोनों में से किसी से भी मान ले सकता है। जाँच वाक्य के स्तर पर होती है, उपवाक्य के स्तर पर नहीं।
दशमलव बिंदु को सीमा माना जाता है। स्रोत में लिखे
14.52से52%या14की पुष्टि हो सकती है, और हज़ार का विभाजक भी यही करता है:1,200से200की पुष्टि हो जाती है।शब्दों की जाँच नहीं होती। बिना संख्या वाला ऐसा वाक्य जो किसी भूमिका या संगठन को बदल दे, दायरे के भीतर के किसी भी मार्कर के साथ पास हो जाता है।
बिना उद्धरण वाले छोटे वाक्य पास हो जाते हैं। आठ से कम शब्दों वाले जिस वाक्य में कोई संख्या, URL या पता न हो, उसे मार्कर की ज़रूरत नहीं। चीनी में शब्दों के बीच स्पेस नहीं होता, इसलिए वहाँ कैरेक्टर गिने जाते हैं, हर तीन कैरेक्टर दो शब्दों के बराबर: सिर्फ़ चीनी कैरेक्टरों से बने वाक्य को बारह कैरेक्टर से कम होने पर मार्कर की ज़रूरत नहीं।
तय शब्द-सूचियों में कमियाँ रह जाती हैं। अंग्रेज़ी में
we'dऔरlet'sपास हो जाते हैं, और पूरी तरह बड़े अक्षरों में लिखा सर्वनाम भी, जैसेWEयाUS, क्योंकि उसे acronym माना जाता है। फ़ारसी में उत्तम पुरुष की पकड़ काफ़ी हद तक प्रॉम्प्ट पर टिकी है: उत्तम पुरुष की जो क्रिया चुनी हुई सूची में नहीं है, वह पास हो जाती है। बाक़ी भाषाओं की सूचियाँ भी चुनकर बनाई गई हैं, और किसी दूसरी भाषा में माँगे गए उत्तर में जर्मन, फ़्रेंच, स्पैनिश या पुर्तगाली का उत्तम पुरुष प्रॉम्प्ट के भरोसे छोड़ा गया है।बंद करने वाले विराम-चिह्न वाक्यों को जोड़ सकते हैं। समापन चिह्न पर बंद होने वाले उद्धरण-चिह्न, जैसे
?»या.", के बाद केवल बड़ा अक्षर या नई पंक्ति वाक्य ख़त्म करती है, और कोष्ठक, जैसे.), के बाद केवल नई पंक्ति या उद्धरण-मार्कर। इसलिए दो वाक्यों को एक मानकर जाँचा जा सकता है: बड़े अक्षर से पहले.)के बाद, अरबी, फ़ारसी और हिंदी में؟»के बाद, क्योंकि इनमें बड़े अक्षर नहीं होते, और जर्मन उद्धरण-चिह्नों„…?“और»…?«के बाद, जिन्हें स्प्लिटर बंद करने वाले चिह्न मानता ही नहीं। चीनी चिह्नों पर ऊपर बताया गया उनका अपना नियम लागू होता है।
ग़लती उलटी दिशा में भी होती है। जो संक्षिप्त रूप सूची में नहीं है, उसके बाद बड़ा अक्षर आए तो “वाक्य” समय से पहले ख़त्म हो जाता है, जैसे किसी नाम से पहले Mr., और उद्धरण-चिह्नों में लिखा वह प्रश्न भी जिसके बाद कोई नाम आए, या जर्मन में कोई भी संज्ञा, और अगर उस टुकड़े में आठ या उससे ज़्यादा शब्द हों और कोई मार्कर न हो, तो स्रोतों पर आधारित उत्तर रोक लिया जाता है। जिस सूची से पहले की पंक्ति कोलन पर ख़त्म होती है, उसमें पहले सूची-चिह्न से पहले कोई वाक्य-सीमा नहीं होती। स्प्लिटर ठीक 1. के बाद काटता है, और वह टुकड़ा उद्धरण वाले नियम पर विफल हो जाता है। दोनों ही स्थितियों में गेट सुरक्षित पक्ष में विफल होता है (fails closed): वह बिना जाँचा उत्तर रिलीज़ करने के बजाय स्रोतों पर आधारित उत्तर को रोक लेता है।
कॉर्पस भी बदलता रहता है। प्रकाशित लेख सहायक के लिए इंडेक्स किए जाते हैं, इसलिए उनके आँकड़े, इस लेख के 17% और 33% भी, ऐसे मान बन जाते हैं जिनकी पुष्टि गेट कर देगा। समर्थित का अर्थ है “वाक्य जिन स्रोतों का उद्धरण देता है, उनमें कहीं मौजूद”, न कि “इसी विषय के बारे में”।
गेट रिलीज़ से पहले की आख़िरी जाँच है, इकलौती नहीं। Joren और उनके सहयोगियों ने पाया कि संदर्भ पर्याप्त न होने पर बड़े मॉडल “अक्सर उत्तर देने से परहेज़ करने के बजाय ग़लत उत्तर देते हैं”, और Google Research लिखता है कि RAG आम तौर पर कुल प्रदर्शन तो सुधारता है, लेकिन “विरोधाभासी रूप से, उचित स्थिति में उत्तर देने से परहेज़ करने की मॉडल की क्षमता घटा देता है”। इसीलिए इनकार और स्कोर की न्यूनतम सीमा पहले आते हैं, जैसा पाँच परतों वाला डिज़ाइन बताता है। Frost का प्रीप्रिंट अपने नतीजे की सीमा ऐसे शब्दों में बताता है जो इस गेट पर भी लागू होते हैं: “एक नियतात्मक (deterministic) बैकस्टॉप के लिए नीति-अनुरूपता, न कि सिमैंटिक सुरक्षा या नीति की पूर्णता का प्रमाण”।
LLM स्ट्रीमिंग के लिए SSE: fetch, प्रॉक्सी और कैंसलेशन
वाक्य-गेट तभी काम का है जब रिलीज़ हुआ हर वाक्य पाठक तक तुरंत पहुँचे। यह ट्रांसपोर्ट की चार बारीकियों पर निर्भर करता है।
EventSource के बजाय fetch और ReadableStream क्यों
WHATWG स्पेसिफ़िकेशन EventSource कंस्ट्रक्टर को एक URL और सिर्फ़ एक विकल्प, withCredentials, देता है: न कोई method, न कोई body। भाषा (locale) और बातचीत के इतिहास के साथ भेजा गया सवाल एक body है, इसलिए एंडपॉइंट एक POST है जो Server-Sent Events लौटाता है। ब्राउज़र इसे fetch, ReadableStream के reader और AbortController से पढ़ता है, जिसका abort(), MDN के शब्दों में, “fetch अनुरोधों, किसी भी response body की खपत, और स्ट्रीम्स को रद्द कर सकता है”।
हाथ से लिखे पार्सर को ये नियम चाहिए:
अलग-अलग read के बीच बफ़र रखें। एक चंक एक संदेश नहीं है।
ख़ाली पंक्ति आने पर इवेंट डिस्पैच करें। उसके आने तक इवेंट अधूरा है।
कमेंट पंक्तियों को नज़रअंदाज़ करें, यानी वे जो
:से शुरू होती हैं।कई पंक्तियों वाला data जोड़ें: एक इवेंट की सभी
data:पंक्तियाँ line feed से जुड़ती हैं।CRLF, LF और CR तीनों को पंक्ति का अंत मानें।
कोलन के बाद एक स्पेस हटाएँ, और सिर्फ़ एक।
डिकोड करने के लिए TextDecoder को {stream: true} के साथ इस्तेमाल करें; MDN के मुताबिक़ यह सेटिंग “अगर डेटा को चंक में प्रोसेस किया जा रहा हो” तब के लिए है। वरना दो चंक में बँटा फ़ारसी कैरेक्टर replacement characters के रूप में डिकोड होगा।
प्रॉक्सी बफ़रिंग: nginx और Caddy
nginx डिफ़ॉल्ट रूप से प्रॉक्सी किए गए responses को बफ़र करता है, और X-Accel-Buffering response header इसे बंद कर सकता है। सहायक की स्ट्रीम X-Accel-Buffering: no और Cache-Control: no-cache, no-store, no-transform भेजती है; no-transform इसलिए है क्योंकि body को दोबारा कंप्रेस करने वाला प्रॉक्सी कई चंक को एक में मिला सकता है। Caddy text/event-stream responses को तुरंत फ़्लश करता है, और साइट का Caddy कॉन्फ़िगरेशन चैट रूट पर flush_interval -1 भी सेट करता है। Service worker स्ट्रीम के लिए कोई रूट रजिस्टर नहीं करता, क्योंकि कोई caching strategy response को उसके ख़त्म होने तक रोके रखेगी।
खुली स्ट्रीम के भीतर keep-alive और एरर
SSE स्पेसिफ़िकेशन उन पुराने (legacy) प्रॉक्सी से बचाव के लिए, जो कम टाइमआउट के बाद कनेक्शन गिरा देते हैं, “हर 15 सेकंड या उसके आसपास” एक कमेंट पंक्ति भेजने का सुझाव देता है, और सर्वर इसी अंतराल पर ping भेजता है। पहला byte निकलते समय status पहले ही 200 हो चुका होता है, इसलिए विफलताएँ स्ट्रीम के भीतर error इवेंट के रूप में जाती हैं।
ऐसा कैंसलेशन जो बिल भी रोके
ब्राउज़र में रोकने पर fetch रद्द हो जाता है। हर टोकन आने पर सर्वर जाँचता है कि क्लाइंट का कनेक्शन टूटा तो नहीं। टूटा हो, तो वह लूप से बाहर निकलता है और प्रोवाइडर की स्ट्रीम बंद कर देता है, जो इस डिज़ाइन में अपस्ट्रीम अनुरोध रद्द करता है। दो बातें मान लेने के बजाय टेस्ट करने लायक हैं। पहली, ब्राउज़र और आपके API के बीच का हर प्रॉक्सी और CDN क्लाइंट का डिस्कनेक्ट आगे पहुँचाता है या नहीं। इस रास्ते को टेस्ट करें; कॉन्फ़िगरेशन पर भरोसा न करें। दूसरी, आपका प्रोवाइडर usage कब रिपोर्ट करता है: अगर output usage सिर्फ़ आख़िरी चंक के साथ आता है, तो जल्दी बंद की गई स्ट्रीम को वह कभी नहीं मिलता, और रिपोर्ट किए गए usage को गिनने वाली ख़र्च-सीमा, जितना जनरेट हुआ उससे कम गिनेगी।
स्ट्रीमिंग गार्डरेल को टेस्ट कैसे करें
टेस्ट गेट को तीन स्तरों पर परखते हैं:
तय स्रोतों पर गेट के यूनिट टेस्ट। गढ़े हुए मान, दायरे से बाहर के मार्कर, बिना उद्धरण वाले दावे और उत्तम पुरुष के शब्द (साइट की हर भाषा में) स्ट्रीम रोकते हैं; समर्थित मान और छोटे जोड़ने वाले वाक्य पास होते हैं; बाद में आया ख़राब वाक्य पहले के सही वाक्यों को नहीं मिटाता।
स्प्लिटर टेस्ट। दशमलव संख्याएँ और डोमेन वाक्य-सीमा नहीं हैं, पीछे आने वाले मार्कर अपने वाक्य के साथ रहते हैं, और
؟वाक्य ख़त्म करता है। चीनी टेक्स्ट बिना स्पेस के भी वाक्यों में बँटता है। जिसकी नक़ल करना फ़ायदेमंद है, वह यह है: तीन-तीन कैरेक्टर के टुकड़ों में डाला गया टेक्स्ट ठीक वैसे ही बँटना चाहिए जैसे पूरा टेक्स्ट।स्क्रिप्टेड प्रोवाइडर के साथ स्ट्रीम टेस्ट, जो उत्तर को छोटे टुकड़ों में दोबारा चलाता है: गढ़ा हुआ मान कभी नेटवर्क (wire) तक नहीं पहुँचता, गार्ड का रुकना या डिस्कनेक्ट अपस्ट्रीम स्ट्रीम बंद करता है, और ट्रंकेशन को ट्रंकेशन के रूप में ही रिपोर्ट किया जाता है।
दो मुख्य टेस्ट का एक ख़ाका, जो साइट से लिया नहीं गया बल्कि इसी लेख के लिए लिखा गया है:
# Sketch only: SentenceSplitter and SentenceGate stand for your own classes.
import pytest
ANSWER = "The reading room opens early [S1]. Is the café open? Yes, on weekdays [S2]."
def pieces(text, size):
return [text[i:i + size] for i in range(0, len(text), size)]
def split_streamed(text, size):
splitter = SentenceSplitter()
sentences = [s for piece in pieces(text, size) for s in splitter.feed(piece)]
tail = splitter.flush()
return sentences + ([tail] if tail else [])
@pytest.mark.parametrize("size", [1, 2, 3, 7])
def test_piece_size_does_not_change_sentences(size):
assert split_streamed(ANSWER, size) == split_streamed(ANSWER, len(ANSWER))
def test_unsupported_value_never_reaches_the_reader():
gate = SentenceGate(sources=["The library opens at nine."])
released = ""
for piece in pieces("It opens at nine [S1]. Late fees ended in 2019 [S1]. More", 7):
outcome = gate.feed(piece)
released += outcome.text
if outcome.stopped:
break
assert "2019" not in released and gate.stoppedइनमें से कोई भी टेस्ट किसी लाइव मॉडल की रेड-टीमिंग नहीं है। मैंने गेट की अपनी ग़लतियाँ मापी हैं: प्रोडक्शन पर रिकॉर्ड किए गए उत्तर, जिन्हें ऑफ़लाइन दोबारा गेट से गुज़ारा गया ताकि पता चले कि उसने स्रोतों पर आधारित कौन-सा उत्तर कहाँ रोका। यह एक मॉडल और सवालों के एक सेट पर ग़लत रुकावटों की गिनती है, यह नहीं कि असली मॉडल कितनी बार कुछ गढ़ता है, इसलिए मैं इसे किसी दर के रूप में प्रकाशित नहीं कर रहा।
चेकलिस्ट: स्ट्रीम किए गए उत्तर पर गार्डरेल लागू करना
तय करें कि क्या कभी नहीं दिखना चाहिए। इसी से तय होता है कि पहले जाँच होगी या पहले रिलीज़, आपके फ़्रेमवर्क का डिफ़ॉल्ट चाहे जो हो।
इकाई चुनें और इंतज़ार को नाम दें: तय आकार का चंक या एक वाक्य।
जहाँ स्ट्रिंग काफ़ी हो, वहाँ नियतात्मक जाँचें इस्तेमाल करें, और पहले अंकों व अक्षर-रूपों को एक समान करें। मॉडल पर आधारित rail हर इकाई में अपने चलने का समय जोड़ती है।
वाक्य-सीमा को तभी पक्का मानें जब अगला कैरेक्टर आ जाए, और बफ़र की ऊपरी हद तय करें।
उल्लंघन होने पर जो पास हुआ उसे रिलीज़ करें, रुकें, पहले से तय संदेश दिखाएँ, रोका गया टेक्स्ट कभी न दोहराएँ, और अपस्ट्रीम स्ट्रीम बंद करें।
ट्रंकेशन को उल्लंघन से अलग रखें, और वह गिनें जो जनरेट हुआ, वह नहीं जो दिखाया गया।
ट्रांसपोर्ट:
fetch, स्ट्रीम reader औरAbortControllerके साथ POST; स्ट्रीमिंग डिकोडर; कमेंट पंक्तियों वाले ping; एरर इवेंट के रूप में।प्रॉक्सी:
X-Accel-Buffering: no,no-transform, event-stream की फ़्लशिंग, टेस्ट किया हुआ डिस्कनेक्ट का रास्ता, और स्ट्रीम पर कोई service worker नहीं।टेस्ट: उत्तरों को छोटे टुकड़ों में दोबारा चलाएँ और assert करें कि रोका गया मान कभी नेटवर्क तक न पहुँचे।
अगर आप स्रोतों पर आधारित कोई सहायक डिज़ाइन कर रहे हैं और चाहते हैं कि उसके रिलीज़ पाथ को कोई और भी एक बार देख ले, तो वह बातचीत परियोजना निदान पेज से शुरू होती है।
अक्सर पूछे जाने वाले सवाल
क्या स्ट्रीमिंग छोड़े बिना स्ट्रीम हो रहे LLM आउटपुट का सत्यापन किया जा सकता है?
हाँ। ऐसी इकाई तक बफ़र करें जिसे आप जाँच सकें, और हर इकाई पास होते ही उसे रिलीज़ करें। पाठक को टेक्स्ट टोकन-दर-टोकन नहीं, बल्कि वाक्य-दर-वाक्य या चंक-दर-चंक आता दिखता है।
क्या वाक्य तक बफ़र करने से time to first token बढ़ता है?
यह दिखने वाले आउटपुट को एक वाक्य जितना टाल देता है: टेक्स्ट तब दिखता है जब पहला वाक्य पूरा होकर जाँच लिया जाए। जनरेशन की तुलना में स्ट्रिंग जाँचें सस्ती हैं, लेकिन मैंने इस साइट पर यह अंतर मापा नहीं है, इसलिए कोई संख्या नहीं दे रहा।
क्या NeMo Guardrails में stream_first बंद कर देना चाहिए?
अगर ब्लॉक किया गया चंक कभी नहीं दिखना चाहिए, तो इसे बंद करें। NeMo का डॉक्यूमेंटेशन इसकी क़ीमत बताता है: time to first token हर चंक पर rail के चलने के समय जितना बढ़ जाता है। डिफ़ॉल्ट तभी रखें जब आपका प्रोडक्ट यह स्वीकार कर सके कि पाठक ऐसे टोकन देख ले जिन्हें कोई rail बाद में ब्लॉक करे।
LLM स्ट्रीमिंग के लिए EventSource क्यों नहीं?
EventSource एक URL और credentials का एक फ़्लैग लेता है, इसलिए वह request body नहीं भेज सकता। इतिहास के साथ भेजा गया चैट अनुरोध एक POST है, इसलिए स्ट्रीम reader के साथ fetch इस्तेमाल करें, जिससे AbortController के ज़रिये कैंसलेशन भी मिल जाता है।
क्या उद्धरण की जाँच हैलुसिनेशन रोक देती है?
नहीं। यह गढ़े हुए स्रोत-क्रमांक, बिना उद्धरण वाले ठोस वाक्य और वाक्य द्वारा उद्धृत स्रोतों में न मिलने वाले मान रोकती है। यह नहीं बता सकती कि उद्धृत स्रोत दावे का समर्थन करता है या नहीं, और यही ग़लत आधार (misgrounded) वाली विफलता है। इसे इनकार और रिट्रीवल की न्यूनतम सीमा के पीछे खड़े एक बैकस्टॉप की तरह देखें।