हमने Stripe, WorkOS और Slack क्लाइंट SDK का उपयोग बंद कर दिया है और बाकी को माइग्रेट करने की प्रक्रिया में हैं। इसके बजाय, हम सीधे उनके REST APIs को एक छोटे रैपर क्लास के माध्यम से कॉल करते हैं जिसे हम HttpBaseClient कहते हैं।
यह उल्टा लगता है। SDKs को आपका समय बचाना चाहिए। लेकिन आप AI के बारे में सुन रहे हैं जो अधिक कोड को नेटिव रूप से चलाने के लिए पोर्ट कर रहा है। यही विचार SDKs पर भी लागू होता है। क्यों?
- SDKs कच्चे विवरण छिपाते हैं, जैसे HTTP रिस्पॉन्स हेडर और रॉ रिस्पॉन्स बॉडी, जो उन एजेंट्स के लिए महत्वपूर्ण हैं जो समस्याओं को डीबग कर रहे हैं या जांच के लिए अपस्ट्रीम टीम को विवरण भेज रहे हैं।
- वे उचित प्रतिक्रियाओं की अपेक्षा करते हैं, लेकिन वास्तविकता में फायरवॉल, लोड बैलेंसर और गेटवे से खराब प्रतिक्रियाएं आती हैं। यह हमारे लिए आवश्यक डीबगिंग विवरणों को छुपा देता है।
- SDKs रिट्राइज़, एरर हैंडलिंग और ऑब्ज़र्वेबिलिटी के लिए एक केंद्रीकृत एंट्री पॉइंट की अनिवार्यता को दरकिनार कर देते हैं, जिससे एजेंट अपने स्वयं के पैटर्न फिर से लिख सकते हैं।
- वे भारी ब्लोट लेकर आते हैं, आमतौर पर OpenAPI से ऑटो-जनरेटेड, जबकि हम एंडपॉइंट्स के एक छोटे से हिस्से का ही उपयोग करते हैं।
सोचिए कि SDKs की शुरुआत क्यों हुई। वे इसलिए बने क्योंकि कंपनियां एडॉप्शन को आसान बनाना चाहती थीं: एक शेयर्ड लाइब्रेरी शिप करें, ग्राहकों का समय बचाएं, और दोहराए जाने वाले इंटीग्रेशन काम को छुपाएं। लेकिन AI ने उस लागत वक्र को बदल दिया। अब SDK के साथ इंटीग्रेट करना अक्सर उतना ही काम है जितना कि सीधे HTTP API को कॉल करना, और हमारे द्वारा उपयोग किए जाने वाले एंडपॉइंट्स के लिए एक अनुकूलित REST क्लाइंट लिखना सस्ता है, साथ ही बेहतर यूनिफाइड ऑब्ज़र्वेबिलिटी प्रदान करता है।
अंतहीन व्हैक-ए-मोल का खेल

ऐसे प्यारे "JSON" रिस्पॉन्स
हमारा Sentry एरर डैशबोर्ड पहले ऐसा दिखता था। हम पाते थे कि किसी कोड पाथ से कुछ अनुरोध विफल हो रहे हैं, फिर हम उस पाथ को मंकी-पैच करते थे ताकि उपयोगकर्ता के लिए SDK की खराबी को संभाला जा सके। अगले दिन कोई दूसरा कोड पाथ एक अप्रत्याशित त्रुटि उत्पन्न करता; हम उसे भी पैच करते, और यह खेल कभी खत्म नहीं होता था।
SDKs एक सामान्य प्रोडक्शन फेल्योर मोड में नाजुक होते हैं: सर्वर कहता है कि कुछ गलत हुआ, लेकिन रिस्पॉन्स वह JSON आकार नहीं है जिसकी SDK को उम्मीद थी।
एक ओवरलोडेड Nginx गेटवे अप्रत्याशित HTML लौटाता है। Cloudflare एक अनुरोध को ब्लॉक करता है। एक फायरवॉल रेट-लिमिट पेज दिखाता है। वेंडर API आमतौर पर JSON होता है, लेकिन इसके सामने की चीज़ हमेशा वेंडर API नहीं होती है।
जब ऐसा होता है, तो कई SDKs रिस्पॉन्स को पार्स करने का प्रयास करते हैं, एक सामान्य पार्सिंग त्रुटि के साथ विफल होते हैं, और उपयोगी बिट्स को फेंक देते हैं। कच्चा बॉडी गायब हो जाता है। स्टेटस टेक्स्ट दब जाता है। HTTP हेडर अक्सर उपयोगी तरीके से वापस नहीं आते हैं। यदि वेंडर सपोर्ट HTTP हेडर से एक अनुरोध आईडी मांगता है, तो हमारी किस्मत खराब है क्योंकि SDK इसे वापस नहीं करता है।
एजेंट SDKs की सीधी पहुंच का दुरुपयोग करते हैं
हमारे क्लाइंट SDK कॉल एक बेकार इलाका थे। Stripe का एक पैटर्न था। WorkOS का दूसरा। Slack की अपनी विचित्रताएं थीं। अन्य इंटीग्रेशन में रॉ fetch, SDK कॉल, एकमुश्त रिट्राई लॉजिक, या बिल्कुल भी रिट्राई लॉजिक नहीं था।
SDKs ने गलत काम को आसान बना दिया। एक एजेंट हमेशा सीधे वेंडर क्लाइंट के पास जा सकता था और किसी भी रूट से stripe.customers.create(...) कॉल कर सकता था। यह उत्पादक लगता है, लेकिन यह उस साझा स्थान को दरकिनार करता है जहाँ ऑथ, रिट्राइज़, मीट्रिक्स, लॉग और एरर ट्रांसलेशन रहना चाहिए। हमारे कोडबेस में इस तरह के क्लोज़र रैपर बिखरे हुए थे:
1const response = await catchRateLimitError(() =>2 stripe.customers.retrieve(stripeCustomerId)3);
यदि आप कोई स्थान चूक गए और SDK कॉल को रैप करना भूल गए, तो आपका दिन खराब था। Stripe रेट लिमिट और WorkOS रेट लिमिट का हमारे उत्पाद के लिए एक ही मतलब है: अपस्ट्रीम हमें धीमा करने के लिए कह रहा है। लेकिन टाइप स्तर पर, वे पूरी तरह से अलग ऑब्जेक्ट थे। कुछ कोड SDK-विशिष्ट अपवादों को पकड़ता था। कुछ सामान्य Error को पकड़ता था। कुछ कुछ नहीं पकड़ता था। यह Sentry व्हैक-ए-मोल में बदल गया: एक कॉल साइट पर एक 429 को ठीक करें, और कहीं और उसी प्रकार की विफलता की प्रतीक्षा करें।
SDKs में ब्लोट है
NPM पैकेज openai और anthropic-ai/sdk Stainless द्वारा OpenAPI स्पेक्स से ऑटो-जनरेटेड हैं। Stripe भी Stripe के OpenAPI स्पेक से जनरेट होता है। इस तरह आप एक बड़े API के लिए दर्जनों प्रोग्रामिंग भाषाओं में एक सार्वजनिक SDK के रखरखाव को स्केल करते हैं।
लेकिन एक बेहतरीन सार्वजनिक SDK को सभी की सेवा करनी होती है। हमारे बैकएंड को सभी के SDK की आवश्यकता नहीं है। इसे हमारे आठ Stripe एंडपॉइंट्स, हमारे WorkOS उपयोगकर्ता और संगठन एंडपॉइंट्स, और उन Slack विधियों की आवश्यकता है जिन्हें हम वास्तव में कॉल करते हैं। Stripe 6.5 MB है, workos-inc/node 6.9 MB है, slack/web-api 7.7 MB है, और linear/sdk 34 MB है। अत्यधिक मामले में, googleapis 198 MB तक पहुँचता है।
जनरेटेड SDKs पूरे प्लेटफॉर्म को अपने साथ लाते हैं: सैकड़ों विधियाँ, ओवरलोड, पेजिनेशन हेल्पर, रिट्राई व्यवहार, एनवायरनमेंट डिटेक्शन, कम्पैटिबिलिटी शिम, और पुरानी सतहें जो गायब नहीं हो सकतीं क्योंकि कोई, कहीं, अभी भी उन पर निर्भर है। अपने स्वयं के बैकएंड के अंदर, वह सामान्यता आमतौर पर ब्लोट होती है। इससे भी बुरा, यह हमारे और वायर के बीच बैठता है।
हम भूल जाते हैं कि HTTP APIs API कॉन्ट्रैक्ट हैं
लोग कभी-कभी HTTP APIs के बारे में ऐसे बात करते हैं जैसे वे निचले स्तर के कार्यान्वयन विवरण हों और SDKs ही वास्तविक स्थिर इंटरफ़ेस हों। सार्वजनिक REST API एक अनुबंध है। वेंडर इसे आसानी से तोड़ नहीं सकते। SDK लेखक भी यह जानते हैं, क्योंकि वे हर ग्राहक को अपग्रेड करने के लिए मजबूर नहीं कर सकते। कई ग्राहक प्रोडक्शन में वर्षों पुराने SDK संस्करण चलाते रहते हैं, जिसका अर्थ है कि पुराने वायर कॉन्ट्रैक्ट को वैसे भी काम करते रहना होगा।
हमारा अपना HttpBaseClient
HttpBaseClient क्लाइंट SDKs के लिए हमारा प्रतिस्थापन है। प्रदाता उपवर्ग वेंडर-विशिष्ट भागों की आपूर्ति करते हैं: बेस URL, ऑथ हेडर, कंटेंट टाइप, एरर मैपिंग, और हमारे द्वारा वास्तव में उपयोग किए जाने वाले एंडपॉइंट्स के लिए संकीर्ण विधियाँ। HttpBaseClient बाकी का मालिक है: सीरियलाइज़ेशन, पार्सिंग, ट्रांसपोर्ट एरर, स्ट्रक्चर्ड लॉग, मीट्रिक्स, स्टेटस मैपिंग, और अवधि ट्रैकिंग। यह ऑब्ज़र्वेबिलिटी को एकीकृत करता है ताकि हर वेंडर सुसंगत मानकों का पालन करे। यहाँ सरलीकृत आकार है:
1abstract class HttpBaseClient<TEndpoint extends string> {2 protected abstract readonly baseUrl: string;34 protected constructor(private readonly dependency: string) {}56 protected abstract buildAuthHeaders(): Promise<Record<string, string>>;78 protected async request<TBody, TResponse>(config: {9 method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';10 path: string;11 endpoint: TEndpoint;12 body?: TBody;13 }): Promise<TResponse> {14 const url = `${this.baseUrl}${config.path}`;15 const headers = await this.buildAuthHeaders();16 const labels = {17 dependency: this.dependency,18 endpoint: config.endpoint,19 method: config.method,20 };21 const start = performance.now();2223 logInfo('upstream request starting', labels);2425 try {26 const response = await callWithMetrics(27 () =>28 fetch(url, {29 method: config.method,30 headers,31 body: config.body === undefined ? undefined : JSON.stringify(config.body),32 }),33 this.dependency,34 labels35 );3637 const body = await parseBody(response);38 if (!response.ok) throw this.mapHttpError(response, body);3940 logInfo('upstream request succeeded', {41 ...labels,42 statusCode: response.status,43 durationMs: performance.now() - start,44 });4546 return body as TResponse;47 } catch (cause) {48 logWarn('upstream request failed', {49 ...labels,50 durationMs: performance.now() - start,51 cause,52 });53 throw cause;54 }55 }56}5758// Then a Stripe wrapper becomes small and explicit:59enum StripeEndpoint {60 CustomersCreate = 'stripe/customers/create',61}6263class StripeHttpClient extends HttpBaseClient<StripeEndpoint> {64 protected readonly baseUrl = 'https://api.stripe.com';6566 constructor(private readonly apiKey: string) {67 super(ClientVendor.Stripe);68 }6970 createCustomer(body: { email: string; name: string }) {71 return this.request<typeof body, Stripe.Customer>({72 method: 'POST',73 path: '/v1/customers',74 endpoint: StripeEndpoint.CustomersCreate,75 body,76 });77 }7879 // More endpoints go here.80}
यही तरकीब है। क्लास Stripe के सभी मॉडल बनाने की कोशिश नहीं करता है। यह उस HTTP व्यवहार को मॉडल करता है जो हम हर वेंडर कॉल के लिए चाहते हैं। Stripe को अभी भी फॉर्म एन्कोडिंग मिलती है। WorkOS को अभी भी बियरर ऑथ और JSON बॉडी मिलते हैं। Slack को अभी भी HTTP 200 पर अपना अजीब ok: false व्यवहार मिलता है। लेकिन हमारा बाकी बैकएंड एक सुसंगत आकार देखता है।
आप यहाँ हमारे HttpBaseClient का लंबा संस्करण देख सकते हैं, जिसे उदाहरण कोड के रूप में पढ़ने में आसान बनाने के लिए जेनेरिक और संशोधित किया गया है।
जहाँ हम अभी भी SDKs का उपयोग करते हैं
हमारा वर्तमान दृष्टिकोण हाइब्रिड है: रनटाइम पर अपने स्वयं के HTTP क्लाइंट का उपयोग करें, लेकिन SDKs को वहाँ रखें जहाँ उनके प्रकार अभी भी समय बचाते हैं। StripeHttpClient Stripe.Customer लौटा सकता है, SlackHttpClient slack/web-api आर्गुमेंट प्रकार उधार ले सकता है, और WorkOS प्रकार अभी भी वायर रिस्पॉन्स का वर्णन कर सकते हैं।
मुझे उम्मीद है कि हम समय के साथ इसे भी चरणबद्ध तरीके से हटा देंगे। जैसे-जैसे AI हमारे लिए आवश्यक सटीक अनुरोध और प्रतिक्रिया प्रकार उत्पन्न करने और बनाए रखने में बेहतर होता जाता है, केवल प्रकारों के लिए एक पूरा SDK पैकेज रखने का मामला कमजोर होता जाता है। लेकिन रनटाइम व्यवहार दर्दनाक हिस्सा है, इसलिए यहीं से माइग्रेशन शुरू होता है।
हम अभी भी SDKs का उपयोग करते हैं जब SDK उत्पाद की सीमा होती है, न कि केवल REST के आसपास एक रैपर। ऑब्ज़र्वेबिलिटी सबसे स्पष्ट उदाहरण है। Sentry के लिए, SDK रनटाइम इंस्ट्रूमेंटेशन, एरर कैप्चर, स्कोप प्रोपेगेशन, रिलीज़ मेटाडेटा और इंटीग्रेशन को संभालता है जिसे हम हाथ से नहीं बनाना चाहेंगे। यह एक वेंडर SDK को सामान्य बैकएंड HTTP कॉल के लिए एक पतले क्लाइंट के रूप में उपयोग करने से अलग है।
हर API HTTP पर REST नहीं है, और यह ठीक है। डेटाबेस कॉल एक अच्छा उदाहरण है। हमारा निचले स्तर का एब्स्ट्रैक्शन BaseClient है: यह प्रत्येक क्लाइंट को समान मीट्रिक्स, लॉगिंग और एरर-हैंडलिंग कॉन्ट्रैक्ट देता है, साथ ही एक चाइल्ड क्लास को यह ओवरराइड करने की अनुमति देता है कि उसके ट्रांसपोर्ट के लिए "fetch" का क्या अर्थ है।
हम कहाँ जा रहे हैं
डेवलपर्स API डॉक्स को वास्तविक इंटीग्रेशन गाइड के रूप में मानेंगे और SDKs को संदर्भ कार्यान्वयन के रूप में: ऑथ, पेलोड, पेजिनेशन, रिट्राइज़ और एज केस के लिए टेम्पलेट। "SDK शिप करें" का अगला संस्करण "एजेंट स्किल शिप करें" हो सकता है जो एजेंटों को सिखाता है कि API को सही तरीके से कैसे कॉल करें, सही पैटर्न का पुन: उपयोग करें, और हर रनटाइम कॉल को वेंडर पैकेज के माध्यम से धकेलने से बचें।





