अनुसंधान

हमारे एजेंट उपयोग में लाना को लगातार बेहतर बनाना

Stefan Heule & Jediah Katz14 मिनट में पढ़ें

हम Cursor एजेंट उपयोग में लाना के निर्माण को उसी तरह लेते हैं, जैसे किसी भी महत्वाकांक्षी सॉफ़्टवेयर उत्पाद के निर्माण को लेते हैं। इस काम का बड़ा हिस्सा विज़न-आधारित होता है, जहाँ हम इस स्पष्ट सोच से शुरू करते हैं कि आदर्श एजेंट अनुभव कैसा होना चाहिए।

इसके बाद, हम उस विज़न के और करीब पहुँचने के बारे में परिकल्पनाएँ बनाते हैं, उन्हें परखने के लिए प्रयोग चलाते हैं, और evals तथा वास्तविक उपयोग से मिलने वाले मात्रात्मक और गुणात्मक संकेतों का इस्तेमाल करके लगातार सुधार करते हैं। यह प्रक्रिया सही ऑनलाइन और ऑफ़लाइन इंस्ट्रुमेंटेशन पर निर्भर करती है, ताकि हम समझ सकें कि कोई बदलाव वास्तव में उपयोग में लाना को बेहतर बनाता है या नहीं।

जब हमें नए मॉडल्स का अर्ली एक्सेस मिलता है, तो ये सभी तरीके एक जगह आकर मिलते हैं। हम हफ्तों तक किसी मॉडल की खूबियों और विचित्रताओं के हिसाब से अपने उपयोग में लाना को अनुकूलित करते हैं, जब तक कि हमारे खास तौर पर ट्यून किए गए उपयोग में लाना के भीतर वही मॉडल साफ़ तौर on अधिक तेज़, अधिक स्मार्ट और अधिक दक्ष न हो जाए।

कभी-कभी हमें बड़े स्तर के सुधार दिखाई देते हैं। लेकिन ज़्यादातर समय, उपयोग में लाना को बेहतर बनाना कई छोटे-छोटे अनुकूलनों को लगातार जोड़ने का काम होता है, जो मिलकर एजेंट्स को सॉफ़्टवेयर बनाने में बेहतर बनाते हैं।

कॉन्टेक्स्ट विंडो का विकास

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

इस विंडो को भरने और प्रबंधित करने का हमारा तरीका Cursor के इतिहास में काफ़ी बदल चुका है।

जब हमने 2024 के आखिर में अपना पहला कोडिंग एजेंट विकसित किया, तब मॉडल अपने लिए सही संदर्भ चुनने में काफ़ी कमज़ोर थे। इसलिए हमने guardrails बनाने के लिए context engineering पर बहुत काम किया—जैसे, हर edit के बाद एजेंट को lint और type errors दिखाना, जब वह बहुत कम लाइनों का अनुरोध करता था तो उसके file reads को फिर से लिखना, और यहाँ तक कि एक turn में वह अधिकतम कितने टूल कॉल कर सकता है, इसकी सीमा तय करना।

हम एजेंट को हर सत्र की शुरुआत में काफ़ी मात्रा में static context भी उपलब्ध कराते थे। अलग-अलग समय पर इसमें कोडबेस का फ़ोल्डर लेआउट, query से अर्थगत रूप से मेल खाने वाले code snippets, और उन फ़ाइलों के compressed versions शामिल थे जिन्हें उपयोगकर्ता ने मैन्युअली संलग्न किया था।

अब इनमें से ज़्यादातर चीज़ें पीछे छूट चुकी हैं।

हम अब भी कुछ उपयोगी static context शामिल करते हैं (जैसे operating system, git status, और मौजूदा व हाल ही में देखी गई फ़ाइलें)। लेकिन मॉडल की बढ़ती क्षमता के साथ हमने guardrails कम किए हैं और अधिक dynamic context देना शुरू किया है, जिसे एजेंट काम करते समय खुद fetch कर सकता है। एक पहले की पोस्ट में, हमने dynamic context के पीछे की अपनी कुछ तकनीकों पर विस्तार से चर्चा की थी, जिनमें से कई को बाद में दूसरे कोडिंग एजेंट्स ने भी अपनाया। अब हमारा ज़्यादातर काम एजेंट को संदर्भ को dynamic तरीके से pull करने और बाहरी दुनिया के साथ इंटरैक्ट करने के अधिक तरीके देने पर केंद्रित है।

Dynamic context के साथ, मॉडल यह तय कर सकता है कि अतिरिक्त जानकारी—जैसे पिछली बातचीत, सक्रिय टर्मिनल सत्र, या प्रासंगिक टूल—को कॉन्टेक्स्ट विंडो में कब शामिल करना है।Dynamic context के साथ, मॉडल यह तय कर सकता है कि अतिरिक्त जानकारी—जैसे पिछली बातचीत, सक्रिय टर्मिनल सत्र, या प्रासंगिक टूल—को कॉन्टेक्स्ट विंडो में कब शामिल करना है।

उपयोग में लाना परिवर्तनों का आकलन करने के दो तरीके

उपयोग में लाना और मॉडल मिलकर तय करते हैं कि एजेंट कितना अच्छा है, लेकिन “अच्छा” वास्तव में क्या है, इसे सटीक रूप से तय करना आसान नहीं है। इसे समझने के लिए, हमने मापन की कई परतें बनाई हैं।

हम अपने eval suite, CursorBench, के साथ सार्वजनिक benchmarks भी बनाए रखते हैं, जो हमें गुणवत्ता का तेज़ और मानकीकृत आकलन देता है और समय के साथ तुलना करने में मदद करता है। लेकिन सर्वश्रेष्ठ benchmarks भी वास्तविक उपयोग का केवल एक अनुमान ही दे पाते हैं, इसलिए अगर हम पूरी तरह उन्हीं पर निर्भर रहें, तो कई महत्वपूर्ण संकेत छूट जाएँगे।

इसीलिए हम online experiments भी चलाते हैं, जिनमें हम उपयोग में लाना के दो या अधिक रूपांतरों को साथ-साथ deploy करते हैं और वास्तविक उपयोग पर उनका A/B परीक्षण करते हैं। इन परीक्षणों में हम अलग-अलग मेट्रिक्स के ज़रिए एजेंट की गुणवत्ता मापते हैं। कुछ मेट्रिक्स सीधे-सादे होते हैं, जैसे latency, token efficiency, tool call count, और cache hit rate। ये रुझान समझने में उपयोगी होते हैं, लेकिन फिर भी इस ज़्यादा महत्वपूर्ण और थोड़े अमूर्त सवाल का जवाब नहीं देते कि एजेंट ने वास्तव में अच्छा काम किया या नहीं। इसे हम दो तरीकों से मापते हैं।

पहला तरीका एजेंट-जनरेट किए गए कोड का “Keep Rate” है। एजेंट द्वारा प्रस्तावित कोड परिवर्तनों के किसी दिए गए सेट के लिए, हम ट्रैक करते हैं कि तय समयांतराल के बाद उनमें से कितना हिस्सा उपयोगकर्ता के कोडबेस में बना रहता है। इससे हमें समझने में मदद मिलती है कि उपयोगकर्ताओं को एजेंट के आउटपुट में कब मैन्युअल बदलाव करने पड़ते हैं, या कब उन्हें दोहराकर काम करना पड़ता है और एजेंट से चीज़ें ठीक करवानी पड़ती हैं। यह इस बात का संकेत है कि एजेंट की शुरुआती प्रतिक्रिया की गुणवत्ता कम थी।

दूसरा, हम एक language model का उपयोग करके एजेंट के शुरुआती आउटपुट पर उपयोगकर्ता की प्रतिक्रियाएँ पढ़ते हैं, ताकि अर्थगत रूप से यह समझा जा सके कि उपयोगकर्ता संतुष्ट था या नहीं। यदि उपयोगकर्ता अगले फ़ीचर पर बढ़ जाता है, तो यह एक मज़बूत संकेत है कि एजेंट ने अपना काम कर दिया। वहीं, अगर उपयोगकर्ता कोई stack trace पेस्ट करता है, तो यह एक विश्वसनीय संकेत है कि एजेंट अपना काम नहीं कर पाया।

कभी-कभी ये online tests हमें किसी ऐसे विचार को अलग रख देने का संकेत देते हैं जो शुरुआत में आशाजनक लगता है। एक experiment में, हमने संदर्भ सारांशण के लिए एक अधिक महँगा मॉडल आज़माया और पाया कि उससे एजेंट की गुणवत्ता में बहुत मामूली फ़र्क पड़ा, जो अतिरिक्त लागत के लायक नहीं था।

गिरावटों को ट्रैक करना और ठीक करना

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

एजेंट के टूल्स बग्स के लिए सबसे बड़े स्रोतों में से एक हैं, और टूल कॉल त्रुटियाँ Cursor में किसी सत्र के लिए बेहद नुकसानदेह हो सकती हैं। हालांकि एजेंट अक्सर खुद को सुधार सकता है, त्रुटियाँ संदर्भ में बनी रहती हैं, टोकन्स बर्बाद करती हैं, और “context rot” पैदा करती हैं—जहाँ जमा होती गलतियाँ मॉडल के बाद के फ़ैसलों की गुणवत्ता को खराब करती जाती हैं।

कभी-कभी, किसी असफल टूल कॉल के बाद एजेंट पूरी तरह अटक सकता है या बिल्कुल पटरी से उतर सकता है। हालांकि टूल कॉल वॉल्यूम और त्रुटि दर जैसे मेट्रिक्स सीधे यह नहीं मापते कि एजेंट ने अच्छा काम किया या नहीं, लेकिन वे ऐसे संकेतक होते हैं जो किसी बड़ी समस्या की ओर इशारा कर सकते हैं।

कोई भी अज्ञात त्रुटि उपयोग में लाना में एक बग का संकेत है, और हम उसके साथ वैसा ही व्यवहार करते हैं। लेकिन कई त्रुटियाँ “अपेक्षित” होती हैं—उदाहरण के लिए, मॉडल का कभी-कभी गलत संपादन सुझाना या ऐसी फ़ाइल पढ़ने की कोशिश करना जो मौजूद ही नहीं है। हम इन अपेक्षित त्रुटियों को उनके कारण के आधार पर वर्गीकृत करते हैं। InvalidArguments और UnexpectedEnvironment मॉडल की गलतियों और कॉन्टेक्स्ट विंडो में मौजूद विरोधाभासों को दर्ज करते हैं, जबकि ProviderError GenerateImage या WebSearch जैसे टूल्स से जुड़ी प्रदाता-स्तरीय आउटेज को दर्ज करता है।

हमारे पास UserAborted और Timeout जैसे कई अन्य वर्गीकरण भी हैं, जो मिलकर अधिकांश अपेक्षित त्रुटियों को कवर करते हैं।

इस साल की शुरुआत में एक केंद्रित स्प्रिंट के दौरान, हमने सभी टूल कॉल्स को कम-से-कम 2 और अक्सर 3 नाइन्स की विश्वसनीयता तक पहुँचा दिया।इस साल की शुरुआत में एक केंद्रित स्प्रिंट के दौरान, हमने सभी टूल कॉल्स को कम-से-कम 2 और अक्सर 3 नाइन्स की विश्वसनीयता तक पहुँचा दिया।

हम इन मेट्रिक्स के आधार पर अलर्ट तय करते हैं, ताकि उत्पादन तक पहुँचने वाले महत्वपूर्ण रिग्रेशन को पकड़ा जा सके। चूँकि अज्ञात त्रुटियाँ हमेशा बग्स होती हैं, इसलिए किसी भी टूल की अज्ञात त्रुटि दर तय सीमा से ऊपर जाते ही हम अलर्ट ट्रिगर करते हैं। लेकिन यह समझना मुश्किल हो सकता है कि अपेक्षित त्रुटियाँ उपयोग में लाना में बग का संकेत हैं या वे वास्तव में अपेक्षित व्यवहार हैं।

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

हम एक साप्ताहिक स्वचालन भी चलाते हैं, जिसमें एक ऐसा skill होता है जो मॉडल को हमारे लॉग्स में खोज करना, नई या हाल में तेज़ी से बढ़ी समस्याओं को सामने लाना, और जांच के साथ backlog में tickets बनाना या अपडेट करना सिखाता है। हम एक साथ कई समस्याओं के fixes शुरू करने के लिए क्लाउड एजेंट्स पर बहुत ज़्यादा निर्भर रहते हैं, और उन्हें सीधे Linear से ट्रिगर भी कर सकते हैं।

यह प्रक्रिया हमारे एजेंट उपयोग में लाना के लिए एक स्वचालित “software factory” को साकार करने के तरीकों में से एक है। इस साल की शुरुआत में एक केंद्रित स्प्रिंट के दौरान, हमने अप्रत्याशित टूल कॉल त्रुटियों को दस गुना तक घटा दिया।

अलग-अलग मॉडल्स के लिए उपयोग में लाना को अनुकूलित करना

हमारे सभी उपयोग में लाना abstractions मॉडल-अज्ञेय हैं और जिन मॉडल्स का हम समर्थन करते हैं, उनमें से हर एक के लिए इन्हें काफ़ी गहराई से अनुकूलित किया जा सकता है। उदाहरण के लिए, OpenAI के मॉडल्स को patch-based प्रारूप का इस्तेमाल करके फ़ाइलें संपादित करने के लिए प्रशिक्षित किया गया है, जबकि Anthropic के मॉडल्स को string replacement पर प्रशिक्षित किया गया है। कोई भी मॉडल किसी भी टूल का उपयोग कर सकता है, लेकिन उसे कोई अपरिचित टूल देने पर अतिरिक्त तर्क टोकन खर्च होते हैं और गलतियाँ भी ज़्यादा होती हैं। इसलिए, अपने उपयोग में लाना में हम हर मॉडल को वही टूल प्रारूप उपलब्ध कराते हैं जो उसके प्रशिक्षण के दौरान इस्तेमाल हुआ था।

यह अनुकूलन काफ़ी गहराई तक जाता है, और इसमें अलग-अलग प्रदाताओं तथा यहाँ तक कि अलग-अलग मॉडल संस्करणों के लिए कस्टम प्रॉम्प्टिंग भी शामिल है। OpenAI के मॉडल्स निर्देशों का पालन करने में ज़्यादा शाब्दिक और सटीक होते हैं, जबकि Claude थोड़ा अधिक सहज है और कम सटीक निर्देशों को बेहतर ढंग से संभाल लेता है।

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

इस tuning प्रक्रिया का बड़ा हिस्सा उपयोग में लाना को किसी नए मॉडल की खूबियों के अनुरूप अनुकूलित करने पर केंद्रित होता है, लेकिन कभी-कभी हमें मॉडल की वास्तविक विचित्रताएँ भी मिलती हैं, जिन्हें हम उपयोग में लाना के ज़रिए कम कर सकते हैं। उदाहरण के लिए, हमने एक मॉडल में वह व्यवहार देखा जिसे हमने context anxiety कहना शुरू किया: जैसे-जैसे उसकी कॉन्टेक्स्ट विंडो भरती जाती थी, वह काम करने से मना करने लगता था और यह कहकर झिझकता था कि कार्य बहुत बड़ा लग रहा है। हम प्रॉम्प्ट में बदलाव करके इस व्यवहार को घटाने में सफल रहे।

चैट के बीच में मॉडल स्विचिंग को आसान बनाना

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

जब कोई उपयोगकर्ता मॉडल बदलता है, तो Cursor स्वचालित रूप से उस मॉडल के लिए उपयुक्त उपयोग में लाना पर स्विच कर देता है, जिसमें उस मॉडल के लिए अनुकूलित प्रॉम्प्ट्स और टूल्स का सेट होता है। हालांकि, मॉडल को फिर भी उन टूल्स को ऐसे बातचीत इतिहास पर लागू करना होता है, जो किसी दूसरे मॉडल ने बनाया था और जो उसके प्रशिक्षण वितरण से बाहर है।

इससे निपटने के लिए, हम कस्टम निर्देश जोड़ते हैं जो मॉडल को बताते हैं कि वह कब चैट के बीच में किसी दूसरे मॉडल से काम संभाल रहा है। ये निर्देश उसे उन टूल्स को कॉल करने से भी रोकते हैं जो बातचीत इतिहास में दिखाई देते हैं, लेकिन उसके अपने टूल सेट का हिस्सा नहीं हैं।

मॉडल्स को ऐसे टूल्स कॉल करने से रोकना जो उसके टूलसेट में नहीं हैंमॉडल्स को ऐसे टूल्स कॉल करने से रोकना जो उसके टूलसेट में नहीं हैं

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

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

उपयोग में लाना और सॉफ़्टवेयर विकास का भविष्य

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

इसे प्रभावी ढंग से काम कराने की मूल चुनौती उपयोग में लाना की है। सिस्टम को यह पता होना चाहिए कि किस एजेंट को भेजना है, उस एजेंट की क्षमताओं के मुताबिक कार्य को कैसे सौंपना है, और परिणामों को जोड़कर एक सुसंगत वर्कफ़्लो कैसे तैयार करना है। इस तरह के समन्वय को संचालित करने की क्षमता किसी एक एजेंट में नहीं, बल्कि उपयोग में लाना में होगी। इसका मतलब है कि एजेंट की सफलता के लिए उपयोग में लाना इंजीनियरिंग हमेशा से महत्वपूर्ण रही है, लेकिन आगे चलकर इसकी अहमियत और भी बढ़ने वाली है।