सामग्री पर जाएं
Chandler Nguyen
AI7 मिनट पढ़ने का समय

AI

मेरे AI App के Offline Mode को AI की ज़रूरत नहीं थी। उसे boring चीज़ की ज़रूरत थी।

मैं DIALØGUE के iOS app के लिए एक clever, सिर्फ़-AI-वाला offline mode बनाने ही वाला था। फिर मैंने एक ज़्यादा boring सवाल पूछा — iOS मुझे पहले से क्या दे रहा है? — और पता चला कि असली काम तो clever बनने की इच्छा को रोकना था। यह है वो standard, बिल्कुल भी चमक-दमक रहित मशीनरी जो "एक episode download करो और plane में सुनो" के पीछे है — और क्यों boring रास्ता चुनना ही ज़्यादा समझदारी थी।

हाल ही में मैंने DIALØGUE के iOS app के लिए offline downloads ship किए — वही हिस्सा जिसे अपनी पिछली पोस्ट में मैंने बस "resilient offline" कहकर हाथ हिला दिया था, बिना कभी यह बताए कि इसका मतलब क्या है। यह पोस्ट वही explanation है। और इसकी शुरुआत होती है मेरे लगभग ग़लत चीज़ बना बैठने से।

यह रहा confession। DIALØGUE एक AI podcast app है: आप इसे कोई topic या एक PDF देते हैं, और यह दो hosts के बीच की बातचीत generate कर देता है। तो जब मैंने offline mode पर काम शुरू किया, मेरा दिमाग सीधे किसी AI-जैसी चीज़ की तरफ़ कूद गया। शायद "offline" का मतलब है device पर ही audio को पहले से generate करना। शायद model का output cache करना, या कोई clever pipeline जो बिना network के एक episode दोबारा बना दे। मेरे दिमाग में एक पूरी architecture बनने लगी थी। मैं तो लगभग वैसा ही design doc लिख ही चुका था।

फिर मैं रुका और एक ज़्यादा boring सवाल पूछा: इसके लिए iOS मुझे पहले से क्या दे रहा है?

और जवाब निकला — "लगभग सब कुछ।" और इस feature का असली काम कुछ smart बनाना नहीं था। असली काम था उस इच्छा को रोकना।

उस boring सवाल ने मेरा एक महीना बचा लिया

यह वो बात है जो मैं पोस्ट-दर-पोस्ट बार-बार सीखता हूँ: complexity आपकी दोस्त नहीं है। ख़ासकर तब, जब आप अकेले इंसान हों जो शाम और weekends में बना रहा हो।

एक AI podcast, एक बार generate हो जाने के बाद, बस एक audio file है जो किसी URL पर पड़ी है। "Offline" का मतलब उसे दोबारा generate करना नहीं है। मतलब है उस file को download करना और बाद में play करना। यह कोई AI problem नहीं है। यह वो problem है जिसे Apple, App Store के होने से भी पहले से solve करता आ रहा है।

तो एक clever pipeline के बजाय, मैंने box का सबसे boring और सबसे आज़माया हुआ tool उठाया: एक background URLSession। अगर आप इससे कभी नहीं मिले — idea simple है: आप download को operating system को सौंप देते हैं, और वह चलता रहता है, चाहे आपका app background में हो या kill हो चुका हो, और फिर वह आपके app को दोबारा launch करके तैयार file आपको थमा देता है।

let config = URLSessionConfiguration.background(withIdentifier: "com.yourapp.downloads")
config.sessionSendsLaunchEvents = true

लगभग दो lines, और अब genuinely मुश्किल हिस्से operating system के पास हैं। मैंने कोई download engine नहीं लिखा। मैंने Apple का उधार ले लिया।

जहाँ भी मैंने देखा, वही कहानी दोहराई गई:

  • Download बीच में टूट गया? iOS आपको "resume data" देता है — एक छोटा-सा blob जिससे download शून्य से शुरू होने के बजाय वहीं से आगे बढ़ता है जहाँ रुका था। मैं बस उसे disk पर लिख देता हूँ (Caches folder में, क्योंकि वह disposable है) और अगली बार वापस pass कर देता हूँ।
  • User का cellular data नहीं फूँकना चाहते? एक line: request.allowsCellularAccess = !wifiOnly। उसके बाद operating system ख़ुद WiFi का इंतज़ार करता है। मैं connectivity monitor नहीं करता। मैं किसी चीज़ की polling नहीं करता।
  • दिखाना है कि downloads कितनी जगह घेर रहे हैं? ByteCountFormatter एक byte count को सही localized units के साथ "23.4 MB" में बदल देता है। वो भी मैंने नहीं लिखा।

मैं ईमानदारी से बताना चाहता हूँ कि यह कितना अच्छा लगता है, क्योंकि यह instinct के ख़िलाफ़ जाता है। पीछे मुड़कर देखूँ तो सही चाल ज़्यादा clever code नहीं थी। सही चाल थी यह मान लेना कि यह एक solved problem है और उस काम को उधार ले लेना, बजाय उससे ज़्यादा होशियार बनने की कोशिश करने के।

लेकिन "boring" का मतलब "आसान" नहीं था

अब मुझे एक बात माननी है, क्योंकि अगर मैं आपसे कहूँ कि standard practices ने इसे trivial बना दिया, तो मैं झूठ बोलूँगा — और इसे पढ़ने वाला कोई भी iOS developer क़रीब दस सेकंड में मुझे पकड़ लेगा।

Standard होना trivial होने जैसा नहीं है। Boring tools इसलिए boring हैं क्योंकि वे ख़ूब घिसे-पिटे हैं, इसलिए नहीं कि उनमें कोई रगड़ नहीं है। ये रहीं वो चीज़ें जिन्होंने सचमुच मेरा time खाया, शायद यह list आपका कुछ बचा ले:

1. Temp file आपकी आँखों के सामने ही ग़ायब हो जाती है। जब एक background download पूरा होता है, iOS आपको callback देता है और एक temporary location पर पड़ी file की तरफ़ इशारा करता है। वो catch जो कोई नहीं बताता: जिस पल आपका callback return करता है, उसी पल वो file delete हो जाती है। आपको उसे synchronously किसी permanent जगह पर ले जाना पड़ता है, ठीक वहीं उसी function में, किसी और चीज़ से पहले। और भी अटपटा यह है कि वो callback main thread के बाहर चलता है और सिर्फ़ इतना जानता है कि कौन-सा download पूरा हुआ, यह नहीं कि मेरे app की भाषा में वो कौन-सा episode है। तो code एक भद्दा-सा two-step करता है: file को फ़ौरन एक temporary नाम पर move करो, फिर main thread पर hop करके उसे rename करो जब पता चल जाए कि वो है क्या। सुंदर नहीं है। पर alternative यह है कि file save करने से पहले ही जा चुकी हो।

2. Simulator में background sessions चुपचाप कुछ नहीं करतीं। मैंने एक शर्मनाक लंबा वक़्त इस यक़ीन में बिताया कि मेरा code टूटा हुआ है। टूटा नहीं था। Background URLSession iOS Simulator में बस भरोसे से काम नहीं करती — न error, न crash, कुछ download नहीं होता। Fix भद्दा है और मैं इसे अपना मानता हूँ:

#if DEBUG
// Use a default session in the simulator — background sessions can silently fail
session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
#else
session = URLSession(configuration: .background(withIdentifier: identifier), delegate: self, delegateQueue: nil)
#endif

वो #if DEBUG elegant नहीं है। पर यह दिखावा करना कि simulator असली device की तरह बर्ताव करता है, मुझे इसे मानने से कहीं ज़्यादा महँगा पड़ता कि वो ऐसा नहीं करता।

3. Swift 6 ने मुझसे ठीक-ठीक कहलवाया कि कौन किस thread पर चलता है। Download callbacks एक background thread पर आते हैं। मेरी UI state — वो छोटे progress rings, हरा "Downloaded" checkmark — को main thread पर update होना होता है। Swift 6 की सख़्त concurrency checking ने उस boundary पर मुझे लापरवाह होने ही नहीं दिया। आख़िर में मेरे पास एक cleanup commit था जिसका title शब्दशः था "resolve Swift 6 actor-isolation warnings." उस वक़्त चिढ़ हुई, पीछे देखने पर सही था। Compiler सही था और मैं ग़लत।

4. Server हमेशा file का नाम साफ़-सुथरा नहीं रखता। Audio कभी audio/mpeg के रूप में आता है, कभी m4a, कभी-कभी कुछ और। तो एक छोटा-सा, नीरस function है जो response के content type से file extension निकालता है, न मिले तो URL पर fall back करता है, और default में .mp3 रखता है। चमक-दमक वाला नहीं। ज़रूरी।

इनमें से कोई भी AI problem नहीं है। हर एक "computers असल में काम कैसे करते हैं" वाली problem है। यही boring रास्ते का tax है। यह असली है — पर मैं इसे हर बार ख़ुशी से चुकाऊँगा, बजाय किसी ऐसी clever चीज़ को maintain करने की क़ीमत के जो मैंने ख़ुद ईजाद की हो।

जो मैंने जान-बूझकर नहीं बनाया

मुझे लगता है इस पूरे feature का सबसे useful हिस्सा उन चीज़ों की list है जो मैंने नहीं कीं:

  • मैंने कोई download engine नहीं लिखा। (Background URLSession।)
  • मैंने कोई connectivity monitor नहीं लिखा। (allowsCellularAccess।)
  • मैंने कोई resume protocol ईजाद नहीं किया। (OS का resume data।)
  • मैंने सिर्फ़-AI-वाली कोई चीज़ नहीं बनाई। (यह एक file है। यह download होती है।)

जिस चीज़ पर मैंने सचमुच मेहनत लगाई वो छोटी और बिना चमक वाली थी: एक नन्हीं-सी queue ताकि एक बार में तीन से ज़्यादा downloads न चलें और बाक़ी अपनी बारी का इंतज़ार करें; episode के titles को persist करना ताकि cold launch के बाद storage screen पर "Episode, Episode, Episode" के बजाय असली नाम दिखें; उस screen को सबसे बड़ी file पहले के हिसाब से sort करना, ताकि जब आप जगह ख़ाली करने की कोशिश कर रहे हों तो सबसे भारी मुजरिम सबसे ऊपर बैठें। और असली decisions — कितने slots ख़ाली हैं, list कैसे sort हो — मैंने बिना किसी URLSession वाले plain functions में निकाल लीं, ताकि उन्हें ठीक से unit-test कर सकूँ। Boring. Testable. Boring इसलिए कि testable।

सबक़, क़रीब सौवीं बार

अब तक मैं इस पोस्ट का कोई-न-कोई version लिख चुका हूँ — अपनी website दोबारा बनाने पर, एक $54 के API bill पर, इसी app को दोबारा बनाने पर। सबक़ हमेशा वही रहता है, और मुझे उसे बार-बार सीखने की ज़रूरत पड़ती रहती है: clever solution अक्सर महँगा solution होता है। "Platform मुझे पहले से क्या दे रहा है?" — एक line code लिखने से पहले मैं यही सबसे क़ीमती सवाल पूछ सकता हूँ।

ख़ासकर एक solo builder के लिए: हर clever चीज़ जो आप ईजाद करते हैं, वो एक ऐसी चीज़ है जिसे अकेले आपको ही maintain करना होगा जब वो सबसे ख़राब वक़्त पर टूटे। हर boring, standard चीज़ जो आप उधार लेते हैं, उसे हज़ारों engineers वाली एक company maintain करती है। मेरे अनुभव में, यह सौदा क़रीब-क़रीब हमेशा करने लायक़ होता है — और सच कहूँ तो, यह सौदा करने का अनुशासन उस होशियारी से ज़्यादा मुश्किल है जिसकी वो जगह लेता है। हालाँकि हो सकता है मैं इस बारे में ग़लत होऊँ कि वो रेखा ठीक-ठीक कहाँ खिंचती है। ज़रूर ऐसी problems होंगी जहाँ platform आपको कुछ नहीं देता और मुश्किल हिस्सा आपको ख़ुद बनाना पड़ता है — मुझे बस बार-बार यही दिखता है कि ऐसा मेरी instinct के ज़ोर देने से कहीं ज़्यादा कम होता है।

DIALØGUE इस्तेमाल करने वाले कुछ लोग अब एक episode download करके, plane में चढ़कर, बिना signal के उसे सुन सकते हैं। यह एक मायने रखने वाला feature लगता है। और इसके पीछे का code लगभग ज़बरदस्ती हद तक मामूली है। मैंने इससे सुलह कर ली है। मुझे लगता है यही तो काम है।

क्या आप भी ऐसा करते हैं — ख़ुद को clever version की तरफ़ हाथ बढ़ाते पकड़ते हैं, जबकि boring वाला ठीक वहीं रखा था? या बस मैं ही धीमा हूँ, वही सबक़ बार-बार सीखता रहता हूँ? मैं सचमुच जानना चाहूँगा कि बाक़ी लोग इस instinct से कैसे लड़ते हैं।

शुभकामनाओं सहित, Chandler