Skip to content
This page has been auto-translated and may contain errors.View in English

कोड से मॉडल को कॉल करना

docs.scrimba.com

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

आप एक प्रॉम्प्ट टाइप करते हैं, आपका कोड इसे कहीं भेजता है, और टेक्स्ट वापस आता है। ऐसा लगता है कि मॉडल आपके प्रोग्राम में रहता है। यह नहीं करता, और यह देखना कि यह वास्तव में कहाँ चलता है, लागत, गति, और विचित्रताओं को समझाता है जो आप मिलेंगे।

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

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

उदाहरण आधिकारिक OpenAI लाइब्रेरी का उपयोग करते हैं। अन्य प्रदाता विवरण में भिन्न होते हैं, लेकिन आकार लगभग हर जगह समान है: आप संदेशों की एक सूची भेजते हैं, आप एक संदेश वापस पाते हैं। वह समानता लॉक-इन के विरुद्ध आपका बीमा है, क्योंकि एक ही कोड एक ओपन मॉडल की ओर इशारा कर सकता है जिसे आप कहीं और होस्ट या किराए पर लेते हैं, अक्सर केवल बेस URL में परिवर्तन के साथ (खुले और बंद मॉडल उस पसंद को कवर करते हैं)।

आप एक लाइब्रेरी फ़ंक्शन को कॉल करते हैं, कुछ सेकंड गुजरते हैं, और टेक्स्ट वापस आता है। लाइब्रेरी इसे स्थानीय लगता है, लेकिन मॉडल के बारे में कुछ भी आपकी मशीन पर नहीं है।

समझने के लिए कदम यह है कि फ़ंक्शन क्या लपेट रहा है। आपका कोड अनुरोध को JSON के रूप में पैकेज करता है, प्रदाता को एक HTTPS कनेक्शन खोलता है, और प्रतीक्षा करता है जबकि उनका हार्डवेयर भविष्यवाणी लूप चलाता है और टोकन वापस स्ट्रीम करता है। SDK (प्रदाता की सॉफ्टवेयर लाइब्रेरी, जैसे openai) एक POST अनुरोध पर एक पतली सुविधा परत है।

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

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

एक मॉडल कॉल एक फ़ंक्शन कॉल जैसा दिखता है और अस्थिर बुनियादी ढांचे पर एक दूरस्थ प्रक्रिया की तरह व्यवहार करता है। वह बेमेल वह है जहाँ उत्पादन समस्याएँ आती हैं, तो शुरुआत से वास्तविक चित्र पकड़ना लायक है।

SDK के तहत, हर कॉल एक HTTPS POST है: एक JSON बॉडी ऊपर जाता है, प्रदाता अपने हार्डवेयर पर अनुमान चलाता है, और टोकन वापस आते हैं, वैकल्पिक रूप से खुले कनेक्शन पर स्ट्रीम किए जाते हैं। SDK (प्रदाता क्लाइंट लाइब्रेरी) आपको प्रमाणीकरण, पुनः प्रयास, टाइपिंग, और स्ट्रीमिंग पार्सिंग खरीदता है, और यह आपको एक कोड द्वारा किए गए कोड के एक परत को खर्च करता है जिसे आप नियंत्रित नहीं करते हैं। इसे एक लपेटने के रूप में व्यवहार करें जिसे आप देख सकते हैं, एक काली पेटी नहीं।

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

अनुरोध

pip install openai के साथ लाइब्रेरी स्थापित करें, एक क्लाइंट बनाएँ, और इसे कॉल करें। क्लाइंट एक पर्यावरण चर से आपकी API कुंजी पढ़ता है, इसलिए कुंजी कभी भी आपके कोड में दिखाई नहीं देती है।

python
from openai import OpenAI

# पर्यावरण से OPENAI_API_KEY पढ़ता है
client = OpenAI()

# वह एक पंक्ति है जिसे आप मॉडल स्वैप करने के लिए बदलते हैं
MODEL = "gpt-4o-mini"

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "You are a concise assistant. Answer in one sentence."},
        {"role": "user", "content": "Why is the sky blue?"},
    ],
)

दो चीजें आवश्यक हैं: model, वह मॉडल का नाम जिसे आप चलाना चाहते हैं, और messages, पिछले अध्याय से भूमिका-टैग किए गए संदेशों की सूची। बाकी सब कुछ के पास एक डिफॉल्ट है।

मॉडल नाम एक एकल स्थिरांक में रहता है पर ध्यान दें। एक क्षेत्र में जहाँ एक बेहतर, सस्ता मॉडल हर कुछ महीनों में उतरता है, यह जानबूझकर है। मॉडल नाम को एक जगह रखें ताकि मॉडल स्वैप करना एक पंक्ति परिवर्तन हो। यह अभ्यास में परिवर्तन नियम है: अस्थिर विशिष्ट को जहाँ आप पा सकते हैं, अपने कोड में बिखरे हुए नहीं।

Junoअनुरोध एक कॉल को केवल दो चीजों की आवश्यकता है: model और messages। मॉडल नाम एक एकल स्थिरांक में होना चाहिए, क्योंकि हर कुछ महीनों में एक सस्ता या बेहतर मॉडल दिखाई देता है और आप इसे नौ फाइलों में पेस्ट किए जाने के लिए शिकार नहीं करना चाहते। मुझे धीमे तरीके से सीखने में बहुत समय लगा, एक मॉडल का नाम बदलने के बाद और मुझे इसे नौ फाइलों में पाया।

दो फ़ील्ड आवश्यक हैं, model और messages, और बाकी सब कुछ के पास एक डिफॉल्ट है। एक स्थिरांक में मॉडल नाम एक शैली बिंदु नहीं है: यह वह एक अस्थिर विस्तार है जिसे आप एक जगह चाहते हैं, क्योंकि एक सस्ता या बेहतर मॉडल अक्सर आता है काफी हद तक कि फाइलों के पार इसे हार्डकोड करना एक स्वैप को एक शिकार में बदल देता है।

अब एक स्तर नीचे देखें। वह create कॉल एक JSON बॉडी में सीरियलाइज़ करता है और इसे POST करता है। स्पष्ट करते हुए, वायर फ़ॉर्मेट वह सटीक अनुरोध है जो प्रदाता प्राप्त करता है:

python
import httpx, os

# वही अनुरोध जो SDK भेजता है, हाथ से
resp = httpx.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
    json={
        "model": "gpt-4o-mini",
        "messages": [
            {"role": "system", "content": "You are a concise assistant."},
            {"role": "user", "content": "Why is the sky blue?"},
        ],
    },
    timeout=20,
)
# SDK जो ऑब्जेक्ट में पार्स करता है वही आकार
data = resp.json()

आप वास्तविक कोड में SDK के लिए पहुँचेंगे: यह प्रमाणीकरण, पुनः प्रयास, और आपके लिए टाइपिंग को हैंडल करता है। लेकिन यह देखना कि बॉडी एक सादा डिक्ट है, बाकी कंक्रीट बनाता है। URL और फ़ील्ड नाम यहाँ OpenAI के हैं, और सटीक एंडपॉइंट और कुंजियाँ प्रदाता द्वारा भिन्न होती हैं, लेकिन कदम, JSON में, बाहर, हर जगह समान है।

Junoअनुरोध आवश्यक फ़ील्ड model और messages हैं, कुछ नहीं। SDK create कॉल हुड के तहत एक JSON POST है, तो जब कुछ गलत लगता है तो आप तार में उतर सकते हैं और सटीक बॉडी को निरीक्षण कर सकते हैं। मॉडल नाम को एक स्थिरांक में रखें: यह वह हिस्सा है जो बदलता है, और आप इसे एक जगह चाहते हैं।

आवश्यक फ़ील्ड model और messages हैं। दिलचस्प सवाल यह नहीं है कि वे क्या हैं बल्कि वे आपके कोड में कहाँ रहते हैं, क्योंकि model अनुरोध में सबसे अस्थिर मूल्य है और SDK इसके चारों ओर सबसे अस्थिर निर्भरता है।

create कॉल एक JSON POST में सीरियलाइज़ करता है। यह मायने रखता है क्योंकि इसका अर्थ है कि अनुरोध वह डेटा है जिसके आप मालिक हैं, जादू नहीं जो SDK प्रदर्शन करता है। आप बॉडी बना सकते हैं, इसे लॉग कर सकते हैं, रन्स के पार इसे भिन्न कर सकते हैं, या जब SDK एक नया प्रदाता फीचर को लैग करता है तो इसे हाथ से भेज सकते हैं। वायर फ़ॉर्मेट को जानना वह है जो आपको एक कॉल को डिबग करने देता है जो अब्स्ट्रक्शन आपसे छिपा रहा है।

उत्पादन आदत हर कॉल को अपने एक संकीर्ण फ़ंक्शन के माध्यम से रूट करना है:

python
def complete(messages, *, model=DEFAULT_MODEL, **params):
    # एक चोक बिंदु: प्रदाता स्वैप करें, लॉगिंग जोड़ें, यहाँ पुनः प्रयास बदलें
    return client.chat.completions.create(model=model, messages=messages, **params)

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

Junoअनुरोध केवल model और messages आवश्यक हैं, और दोनों ही सीमा के पीछे रहने के लायक हैं जिसे आप नियंत्रित करते हैं। हर कॉल को एक फ़ंक्शन के माध्यम से रूट करें: यह वह जगह है जहाँ एक मॉडल स्वैप एक पंक्ति है, जहाँ लॉगिंग जाता है, और जहाँ प्रदाता परिवर्तन आपके कॉलर्स तक पहुँचने से पहले रुकता है। जबकि आप हैं मॉडल संस्करण को पिन करें, क्योंकि "latest" आपके नीचे स्थानांतरित होना एक प्रतिगमन है जिसे आप तैनात नहीं करते और देख नहीं सकते।

प्रतिक्रिया पढ़ना

उत्तर कुछ संरचना में लपेटा हुआ वापस आता है। टेक्स्ट जो आप चाहते हैं वह अंदर एक पाथ है, लेकिन बाकी संरचना जानने के लायक है, क्योंकि यह बताता है कि क्या हुआ।

python
choice = response.choices[0]

# उत्तर टेक्स्ट
print(choice.message.content)
# "stop"  -> मॉडल अपने आप समाप्त हो गया
print(choice.finish_reason)
# Usage(prompt_tokens=24, completion_tokens=18, total_tokens=42)
print(response.usage)

वहाँ तीन चीजें मायने रखती हैं। message.content टेक्स्ट है, choices[0] के अंदर बैठा है क्योंकि API एक से अधिक विकल्प लौटा सकता है, हालांकि आप लगभग हमेशा एक पूछते हैं और पहले को पढ़ते हैं।

finish_reason बताता है कि मॉडल क्यों रुका। "stop" का अर्थ है यह अपना विचार समाप्त कर गया, जबकि "length" का अर्थ है यह आपकी टोकन कैप में भाग गया और आधे-अधूरे उत्तर में कट गया। उस फ़ील्ड को चेक करना यह है कि आप कोड में एक काटे गए उत्तर को कैसे पकड़ते हैं आँख से नहीं।

और usage टोकन की रिपोर्ट करता है जो कॉल ने खर्च किया, प्रॉम्प्ट में विभाजित जो आप भेजते हैं और समाप्ति जिसे यह लिखता है। वह उपयोग ऑब्जेक्ट आपका बिल है, आइटमाइज़्ड। हर कॉल आपको बिल्कुल बताता है कि टोकन में इसकी लागत क्या है, जो संख्या है जिसे देखना है जब आप बनाते हैं।

Junoप्रतिक्रिया पढ़ना उत्तर टेक्स्ट response.choices[0].message.content पर है, लेकिन दो पड़ोसी भी मायने रखते हैं। finish_reason बताता है कि यह क्यों रुका: "stop" का अर्थ है किया गया, "length" का अर्थ है यह आपकी टोकन कैप को मार गया और कट गया। और response.usage टोकन में आपका बिल है, प्रॉम्प्ट और समाप्ति को विभाजित किया गया, तो आपको कभी अनुमान लगाने की आवश्यकता नहीं है कि एक कॉल की कीमत क्या है।

तीन फ़ील्ड संकेत ले जाते हैं। choices[0].message.content टेक्स्ट है। finish_reason यह है कि मॉडल क्यों रुका: "stop" एक स्वच्छ समाप्ति है, "length" का अर्थ है यह आपकी टोकन कैप को मार गया और काट दिया गया, तो यह चेक करना कैसे है कि आप कोड में एक काटे गए उत्तर को पकड़ें आधे उत्तर को भेजने के बजाय। और usage टोकन गणना है, प्रॉम्प्ट और समाप्ति अलग।

यह कदम है जो यहाँ भुगतान करता है usage को वास्तविक पैसे में बदलना। उपयोग ऑब्जेक्ट प्रति-कॉल बिलिंग डेटा है, तो आप लागत को तुरंत गणना कर सकते हैं:

python
# प्रदाता कीमतें प्रति-टोकन हैं; वर्तमान दरें जांचें, वे बदलती हैं
# प्रॉम्प्ट टोकन के लिए प्रति टोकन डॉलर
PROMPT_RATE = 0.15 / 1_000_000
# समाप्ति टोकन के लिए प्रति टोकन डॉलर
COMPLETION_RATE = 0.60 / 1_000_000

u = response.usage
cost = u.prompt_tokens * PROMPT_RATE + u.completion_tokens * COMPLETION_RATE
print(f"{u.total_tokens} tokens, ${cost:.6f}")

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

Junoप्रतिक्रिया पढ़ना तीन फ़ील्ड पढ़ें: टेक्स्ट के लिए content, कोड में एक "length" काटन को पकड़ने के लिए finish_reason, और टोकन के लिए usageusage को तुरंत डॉलर में बदलें, प्रॉम्प्ट और समाप्ति को अलग से कीमत दिया गया, और कॉल के अनुसार इसे लॉग करें। आप महँगी सुविधा को इनवॉयस से पहले बहुत पहले देखेंगे।

प्रतिक्रिया तीन फ़ील्ड ले जाती है जो आप कार्य करते हैं: content, finish_reason, और usage। वह जो निर्माता छोड़ते हैं और खेद करते हैं वह finish_reason है। एक "length" मूल्य का अर्थ है कि मॉडल आपके max_tokens को मार गया और आउटपुट काट दिया गया, अक्सर खराब JSON या आधे-अधूरे वाक्य के रूप में जो आपका डाउनस्ट्रीम पार्सर फिर घुट जाता है। "stop" के अलावा कोई भी मूल्य एक विफल कॉल के रूप में व्यवहार करें और इसे हैंडल करें, content पूर्ण है यह अनुमान न लगाएँ।

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

python
u = response.usage
log.info("llm_call", feature="invoice_extract", model=MODEL,
         prompt_tokens=u.prompt_tokens, completion_tokens=u.completion_tokens,
         finish_reason=response.choices[0].finish_reason)

इसके साथ जगह में, प्रति फ़ीचर लागत और प्रति उपयोगकर्ता एक अनुमान के बजाय एक प्रश्न बन जाता है, और एक भाग्यहीन प्रॉम्प्ट जो चुपचाप तीन गुना आकार में बढ़ता है एक आश्चर्य शुल्क के बजाय एक मीट्रिक के रूप में दिखाई देता है। असममिति को देखें: इनपुट टोकन सस्ते हैं और समानांतर प्रक्रिया करने के लिए, आउटपुट टोकन कैसे LLMs काम करते हैं में कवर किया गया महँगा, सीरियल हिस्सा है, तो एक सुविधा जो लंबे उत्तर उत्सर्जित करती है उसकी प्रॉम्प्ट आकार से अधिक लागत और लैग करता है। विशेषता नाम OpenAI के हैं और प्रदाता द्वारा स्थानांतरित होते हैं, तो गणना को अपनी सीमा के पीछे लॉग करें बजाय response.usage.prompt_tokens को ऐप के पार बिखेरने के।

Junoप्रतिक्रिया पढ़ना कोई भी `finish_reason` जो `"stop"` नहीं है उसे एक विफल कॉल के रूप में व्यवहार करें: एक `"length"` काटन आपके पार्सर को खराब JSON सौंपता है और आप बग को डाउनस्ट्रीम का पीछा करेंगे। `usage` को अपने लॉग में फ़ीचर द्वारा टैग किए गए वायर करें, प्रॉम्प्ट और समाप्ति को विभाजित किया गया, तो प्रति फ़ीचर लागत एक प्रश्न है और एक बढ़ता प्रॉम्प्ट एक मीट्रिक है, बिलिंग आश्चर्य नहीं। आउटपुट टोकन महँगा सीरियल हिस्सा है, तो एक बातूनी सुविधा इसकी प्रॉम्प्ट से अधिक लागत करता है।

जानने के लायक पैरामीटर

model और messages से परे, दो वैकल्पिक सेटिंग्स लगातार आती हैं।

temperature कैसे LLMs काम करते हैं से यादृच्छिकता डायल है, ठोस बनाया गया। निम्न मूल्य (0 के पास) मॉडल को इसकी शीर्ष चुनावों के पास रखते हैं: केंद्रीभूत और दोहराए जाने योग्य, जो आप निष्कर्षण और वर्गीकरण के लिए चाहते हैं। उच्च मूल्य (लगभग 0.7 से 1) इसे कम संभावना टोकन के लिए पहुँचने देते हैं: अधिक विविध और रचनात्मक, और भटकने के लिए अधिक प्रवण। आप अनुकूल कर रहे हैं कि मॉडल कितनी बोल्डली नमूने, कुछ नहीं।

max_tokens कैप करता है कि उत्तर कितने टोकन तक चल सकता है। यह आपको एक भाग्यहीन उत्तर और भाग्यहीन बिल से बचाता है। max_tokens को बहुत कम सेट करें और मॉडल कट जाता है, जो बिल्कुल पिछले सेक्शन से "length" समाप्ति कारण है, तो वास्तविक हेडरूम छोड़ दें।

python
response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    # केंद्रीभूत और सुसंगत
    temperature=0.2,
    # उत्तर की लंबाई को कैप करें
    max_tokens=300,
)
Junoजानने के लायक पैरामीटरtemperature अनुकूल करता है कि मॉडल कितनी बोल्डली नमूने: केंद्रीभूत, दोहराए जाने योग्य उत्तर के लिए कम, विविध, रचनात्मक लोगों के लिए उच्च। max_tokens उत्तर की लंबाई को कैप करता है आपके बिल को बचाने के लिए, लेकिन बहुत कम सेट करें यह "length" कटन को ट्रिगर करता है, तो हेडरूम छोड़ दें। निष्कर्षण और वर्गीकरण के लिए, हर बार कम तापमान के लिए पहुँचें।

दो पैरामीटर अधिकांश वजन लेते हैं: temperature और max_tokens। यांत्रिकी कैसे LLMs काम करते हैं में हैं; यहाँ कदम मूल्य को कार्य से मेल खाना है बजाय जो कुछ भी डिफॉल्ट भेज रहा है उसे विश्वास करने के।

temperature को एक प्रति-कार्य सेटिंग के रूप में सोचें, एक वैश्विक नहीं। इसे उस से चुनें जो कार्य की आवश्यकता है:

python
TEMPERATURE_BY_TASK = {
    # एक सही उत्तर, हर बार इसे चाहते हैं
    "extraction": 0.0,
    # एक लेबल चुनें, कोई रचनात्मकता नहीं चाही
    "classification": 0.0,
    # ज्यादातर वफादार, एक बिट शब्द स्वतंत्रता
    "summary": 0.3,
    # विविधता पूरी बात है
    "brainstorm": 0.9,
}

कुछ भी जो आप बाद में पार्स करते हैं, संरचित डेटा, एक श्रेणी, एक दस्तावेज़ से खींचे गए नंबर, 0 पर जाएँ और यादृच्छिकता के बारे में सोचना बंद करें। ड्राफ़्टिंग और विचारमंथन के लिए जहाँ विविधता मूल्य है, ऊपर जाएँ।

max_tokens एक छत है, लक्ष्य नहीं: यह खर्च और भाग्यहीन उत्तर को कैप करता है, लेकिन कार्य की आवश्यकता के तहत सेट करें और आप पिछले सेक्शन से "length" काटन पाते हैं, जो थोड़ी बड़ी बिल से बदतर है। इसे सबसे लंबे वैध उत्तर प्लस हेडरूम के आकार के लिए आकार दें। ये दो फ़ील्ड नाम प्रदाता के पार स्थिर हैं; कुछ max_tokens का नाम बदलते हैं, तो जब आप स्विच करते हैं कुंजी की पुष्टि करें।

Junoजानने के लायक पैरामीटरtemperature को कार्य के अनुसार सेट करें, एक बार विश्व नहीं: 0 कुछ भी आप बाद में पार्स करते हैं, ड्राफ़्टिंग के लिए उच्च जहाँ विविधता बिंदु है। max_tokens एक सुरक्षा छत है, तो सबसे लंबे वास्तविक उत्तर प्लस हेडरूम के आकार; इसे बहुत तंग सेट करें और आप एक छोटी बिल बचत के लिए एक काटे गए उत्तर में व्यापार। SDK भेज रहे डिफॉल्ट पर विश्वास करना बंद करें।

temperature और max_tokens डायल की तरह दिखते हैं और लागत और विश्वसनीयता के साथ नीति निर्णयों की तरह व्यवहार करते हैं। यहाँ गहराई यह है कि वे अन्य सब कुछ के साथ कैसे इंटरैक्ट करते हैं जिसे आप भेज रहे हैं।

temperature को अपनी विश्वसनीयता अनुबंध के हिस्से के रूप में व्यवहार करें। किसी भी आउटपुट के लिए जिसे आप पार्स या स्टोर करते हैं, 0 पर चलाएँ तो कैसे LLMs काम करते हैं से नमूनाकरण-संचालित विचरण प्रदाता अनुमति के रूप में छोटा है। यह 0 पर पूरी तरह से नियतिवादी नहीं है, तो सटीक-मिलान कैशिंग या बाइट-समान परीक्षणों को इसके ऊपर न बनाएँ, लेकिन यह फर्श है जिसे आप संरचित कार्य के लिए चाहते हैं। उच्च तापमान को सतहों के लिए आरक्षित करें जहाँ विविधता उत्पाद है, और उस विचरण को कुछ के रूप में व्यवहार करें जिसे आपके eval रन्स के पार मापते हैं, एक एकल पास नहीं जिसे आप ध्यान देते हैं।

max_tokens एक लेटेंसी और लागत लीवर है, केवल सुरक्षा जाल नहीं। आउटपुट टोकन पीढ़ी का सीरियल, महँगा हिस्सा है, तो कैपिंग लंबाई आपके सबसे खराब-केस बिल और सबसे खराब-केस प्रतीक्षा दोनों को बाध्य करता है।

जाल आँख से आकार दे रहा है: बहुत तंग वैध उत्तर को "length" विफलता में काटता है जो आपके पार्सर को खराब आउटपुट सौंपता है, बहुत ढीला एक पतित लूप को कुछ भी बंद करने से पहले लागत और लेटेंसी को चलाने देता है। प्रति-कार्य सबसे लंबे वैध उत्तर प्लस मार्जिन का आकार, और लॉग करें जब आप कैप को मारते हैं तो एक सुविधा जो छंटनी करती रहती है दृश्यमान हो जाती है। दोनों पैरामीटर नाम OpenAI के हैं और कुछ प्रदाता उनका नाम बदलते हैं, तो सीमा फ़ंक्शन के पीछे उन्हें सेट करें प्रत्येक कॉल साइट पर नहीं।

Junoजानने के लायक पैरामीटरtemperature एक विश्वसनीयता अनुबंध है: 0 कुछ भी आप पार्स करते हैं, उच्च केवल जहाँ विचरण उत्पाद है, और 0 के ऊपर कभी सटीक-मिलान कैश न करें क्योंकि यह बाइट-नियतिवादी नहीं है। `max_tokens` आपके सबसे खराब-केस लागत और लेटेंसी को बाध्य करता है, तो सबसे लंबे वास्तविक उत्तर प्लस मार्जिन का आकार और लॉग करें जब आप इसे मारते हैं, क्योंकि एक सुविधा जो छंटनी करती रहती है एक पैरामीटर के रूप में छिपी बग है। दोनों को अपनी सीमा के पीछे सेट करें, प्रत्येक कॉल साइट पर नहीं।

स्ट्रीमिंग

डिफॉल्ट रूप से आप पूरे उत्तर को समाप्त होने तक प्रतीक्षा करते हैं, फिर यह एक बार में आता है। चूँकि मॉडल एक समय में टोकन उत्पन्न करता है, एक लंबा उत्तर मतलब एक लंबी प्रतीक्षा कुछ नहीं देख रहे हैं।

स्ट्रीमिंग प्रत्येक टोकन को आपको उसी क्षण भेजता है जब यह उत्पन्न होता है, तो टेक्स्ट तुरंत दिखाई देता है और लाइव भर जाता है, जिस तरह चैट ऐप्स एक उत्तर को टाइप करते हुए दिखाते हैं।

python
stream = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    stream=True,
)

for chunk in stream:
    piece = chunk.choices[0].delta.content
    if piece:
        print(piece, end="", flush=True)

कुल पीढ़ी समय दोनों तरीकों से समान है; मॉडल तेजी से नहीं है। स्ट्रीमिंग केवल बदलता है कब आप टोकन देखते हैं, इसे जैसे ही यह पैदा होता है सतह के रूप में इसे अंत तक सभी को वापस रखने के बजाय।

आप इसे चंक्स पर पुनरावृत्ति करके और टेक्स्ट के प्रत्येक delta को प्रिंट करके हैंडल करते हैं। ट्रेड-ऑफ थोड़ी अधिक कोड एक बहुत बेहतर प्रतीक्षा के लिए है। स्ट्रीमिंग बदलता है कब आप टोकन देखते हैं, कितनी तेजी से नहीं आते हैं।

Junoस्ट्रीमिंग मॉडल एक समय में टोकन उत्पन्न करता है, तो डिफॉल्ट रूप से एक लंबा उत्तर एक लंबी चुप्पी प्रतीक्षा है। स्ट्रीमिंग प्रत्येक टोकन को जैसे ही यह आता है भेजता है, तो उत्तर लाइव भर जाता है, जो यह है कि चैट ऐप्स प्रतिक्रियाशील महसूस करते हैं। समान कुल समय, आप चंक्स पर पुनरावृत्ति करके और प्रत्येक `delta` को प्रिंट करके टोकन को पहले देखते हैं।

स्ट्रीमिंग प्रत्येक टोकन को आगे करता है जब मॉडल इसे उत्पन्न करता है, तो उपयोगकर्ता तुरंत टेक्स्ट देखता है पूरे उत्तर की प्रतीक्षा के बजाय। कुल पीढ़ी समय अपरिवर्तित है; जो गिरता है वह है पहले टोकन के लिए समय, जो "महसूस करने योग्य तेजी से" अधिकांश है।

स्वच्छ संस्करण दो चीजें हैंडल करता है जिन्हें खिलौना लूप छोड़ता है: बिना टेक्स्ट के चंक्स, और पूरे उत्तर को जमा करना जबकि आप इसे प्रदर्शित करते हैं।

python
stream = client.chat.completions.create(model=MODEL, messages=messages, stream=True)

full = []
for chunk in stream:
    delta = chunk.choices[0].delta
    piece = delta.content
    if piece is None:          # भूमिका-केवल और अंतिम चंक्स कोई टेक्स्ट नहीं ले जाते
        continue
    full.append(piece)
    print(piece, end="", flush=True)

# संपूर्ण संदेश, इतिहास या भंडारण के लिए
reply = "".join(full)

खाली delta जांच मायने रखती है: पहला चंक अक्सर भूमिका-केवल होता है और अंतिम चंक खाली सामग्री के साथ finish_reason ले जा सकता है। None को छोड़ दें और आप अपने आउटपुट में "None" को प्रिंट करने से बचते हैं।

full में संचय आपको इतिहास के लिए पूरा संदेश देता है, क्योंकि स्ट्रीमिंग टेक्स्ट प्रदर्शित करता है लेकिन यह आपके लिए सहेजता नहीं है। जानने के लिए एक ट्रेड-ऑफ: एक स्ट्रीम किया हुआ कॉल डिफॉल्ट रूप से usage ब्लॉक नहीं लौटाता है, तो यदि आपको टोकन गणना की आवश्यकता है तो आप उन्हें स्पष्ट रूप से अनुरोध करते हैं या स्वयं गणना करते हैं। स्ट्रीमिंग प्रदाताओं के पार मौजूद है, लेकिन चंक आकार, delta, content, जहाँ finish_reason ठहरता है, प्रदाता-विशिष्ट है।

Junoस्ट्रीमिंग स्ट्रीमिंग कुल समय नहीं, पहले टोकन के लिए समय में कटौती करता है। खाली `delta` के लिए गार्ड: पहला चंक अक्सर भूमिका-केवल होता है और अंतिम एक कोई टेक्स्ट के साथ `finish_reason` ले जाता है, तो `None` को छोड़ दें या आप उत्तर में "None" को प्रिंट करते हैं। इतिहास के लिए टुकड़ों को अपने आप जमा करें, और याद रखें एक स्ट्रीम किया हुआ कॉल आप इसके लिए पूछते हैं जब तक `usage` को छोड़ता है।

स्ट्रीमिंग एक बेहतर लेटेंसी प्रोफाइल के लिए एक सरल प्रतिक्रिया का व्यापार करता है: आप पहले टोकन के लिए समय में कटौती करते हैं, लेकिन आप एकल स्वच्छ प्रतिक्रिया ऑब्जेक्ट देते हैं और चंक्स आते हैं जब स्थिति असेंबल करना लेते हैं। किसी भी मानव घड़ियों के लिए लायक है और एक बैकएंड कॉल के लिए शायद ही कभी लायक है जिसे आप पूरा पार्स करते हैं।

उत्पादन में काटने का विवरण चंक सीमाओं पर है। पहला चंक आमतौर पर भूमिका-केवल होता है, अंतिम चंक खाली सामग्री के साथ finish_reason ले जाता है, और आपको टेक्स्ट को अपने आप असेंबल करना होता है क्योंकि कुछ भी आपको असेंबल किया संदेश सौंपता है:

python
full, finish = [], None
for chunk in stream:
    choice = chunk.choices[0]
    if choice.delta.content:
        full.append(choice.delta.content)
    if choice.finish_reason:        # अंतिम चंक पर आता है
        finish = choice.finish_reason
reply = "".join(full)
if finish == "length":              # काटन अभी भी मध्य-स्ट्रीम होता है
    handle_truncated(reply)

तीन उत्पादन वास्तविकताएँ:

  • finish_reason स्ट्रीम के अंत में आता है, तो "length" काटन गायब नहीं होता जब आप स्ट्रीम करते हैं, आप इसे बाद में नोटिस करते हैं, और आपने असेंबल किए गए टेक्स्ट पर विश्वास करने से पहले इसकी जांच की।
  • एक स्ट्रीम मध्य-उड़ान में टूट सकता है: कनेक्शन चालीस में से दस टोकन के बाद गिरता है, आपको एक आंशिक उत्तर छोड़ते हैं, तो स्ट्रीम किए गए कॉल को अपने स्वयं के आंशिक-विफलता हैंडलिंग की आवश्यकता है और स्वच्छ रूप से पुनः प्रयास करना मुश्किल है।
  • स्ट्रीम किए गए प्रतिक्रियाएँ आमतौर पर usage को छोड़ती हैं, तो आप एक झंडा पास करते हैं इसे शामिल करने के लिए या लागत ट्रैकिंग के लिए असेंबल किए गए टेक्स्ट को टोकनाइज़ करते हैं।

SSE फ्रेमिंग और चंक स्कीमा प्रदाता-विशिष्ट हैं, तो स्ट्रीम पार्सिंग को बाकी के पीछे रखें और कॉलर्स को असेंबल किया परिणाम देखने दें, कच्चे चंक्स नहीं।

Junoस्ट्रीमिंग स्ट्रीमिंग पहले टोकन के लिए समय खरीदता है और आपको स्वच्छ प्रतिक्रिया ऑब्जेक्ट को खर्च करता है, तो इसे उपयोग करें जहाँ एक मानव देख रहा है और बैकएंड पार्सिंग को छोड़ दें। अंतिम चंक अभी भी `finish_reason` ले जाता है, तो एक `"length"` काटन मध्य-स्ट्रीम वास्तविक है, टेक्स्ट पर विश्वास करने से पहले इसकी जांच करें। और एक स्ट्रीम चालीस में से दस टोकन के बाद मर सकता है, तो आंशिक उत्तर को हैंडल करें और प्रदाता-विशिष्ट चंक पार्सिंग को अपनी सीमा के पीछे रखें।

हर कॉल स्वतंत्र है

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

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

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

python
history = [
    {"role": "system", "content": "You are a friendly travel assistant. Keep answers short."},
]

def chat(user_text):
    history.append({"role": "user", "content": user_text})

    # पूरा इतिहास हर बार ऊपर जाता है; सर्वर कुछ नहीं याद करता है
    response = client.chat.completions.create(model=MODEL, messages=history)
    reply = response.choices[0].message.content

    # अगली बार के लिए उत्तर रखें
    history.append({"role": "assistant", "content": reply})
    return reply

chat("I have a weekend in दिल्ली. What should I see?")
# केवल काम करता है क्योंकि पहली बातचीत इतिहास में है और टर्न एक भेजा जाता है
chat("Which of those is best for kids?")

दूसरा सवाल केवल समझदारी में आता है क्योंकि पहली बातचीत और इसका उत्तर अभी भी history में है और साथ भेजा जाता है। आप स्मृति हैं। यह एक विचार, कि आप हर बार पूर्ण संदर्भ को असेंबल और पुनः भेजते हैं, बातचीत को आधार करता है, और बाद में यह उपकरण उपयोग, पुनर्प्राप्ति, और एजेंट को भी आधार करता है। वे सभी यह तय करने के भिन्नताएँ हैं कि प्रत्येक स्वतंत्र कॉल से पहले उन संदेशों की सूची में क्या जाता है।

Junoहर कॉल स्वतंत्र है API स्टेटलेस है: प्रत्येक अनुरोध बिल्कुल उन संदेशों पर चलता है जिन्हें आप भेजते हैं, और सर्वर विनिमय के बाद कुछ नहीं रखता है। एक बातचीत आपके कोड में रहती है, आप बातचीत को पुनः भेजते हैं इसलिए नकली निरंतरता, जो यह है कि लंबी चैट समय के साथ अधिक लागत क्यों करते हैं, प्रत्येक कॉल हर पूर्व मोड़ को इनपुट टोकन के रूप में पुनः भेजता है।

API स्टेटलेस है: सर्वर बिल्कुल आप जिन संदेशों को भेजते हैं उन पर चलता है और बाद में कुछ नहीं रखता है। बातचीत आपके कोड में रहती है, और आप हर बार पूरा इतिहास पुनः भेजते हैं नकली निरंतरता के लिए, जो यह है कि क्यों एक लंबी चैट प्रति संदेश अधिक लागत करता है यहाँ तक कि जब नया सवाल छोटा हो।

यह वृद्धि एक समस्या है जिसे आप प्रबंधित करते हैं, एक जिसे आप अनदेखा नहीं कर सकते, क्योंकि इतिहास अंततः संदर्भ विंडो को ओवरफ्लो करता है। पहली कदम एक स्लाइडिंग विंडो है: सिस्टम संदेश प्लस सबसे हाल के मोड़ रखें।

python
def trim(history, keep_recent=10):
    # हमेशा सिस्टम संदेश रखें
    system = history[:1]
    # सबसे नए मोड़ केवल
    recent = history[1:][-keep_recent:]
    return system + recent

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

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

API स्टेटलेस है: आप बातचीत के मालिक हैं और हर कॉल पर इसे पुनः भेजते हैं। भोली संस्करण, सर्वदा जोड़ें और बहुत भेजें, स्केल पर दो तरीकों से विफल होता है: यह अंततः संदर्भ विंडो को ओवरफ्लो करता है, और यह हर मोड़ को अधिक महँगा और धीमा बनाता है, क्योंकि इनपुट बिना बाध्य बढ़ता है।

तो इतिहास प्रबंधन एक बजटिंग समस्या है तीन मानक रणनीतियों के साथ, प्रत्येक अपने तरीके से विफल:

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

प्रति उपयोग केस चुनें और इसे उपकरण करें, क्योंकि प्रत्येक अंततः गलत संदर्भ की सतह करेगा।

python
def build_messages(system, history, user_msg, budget_tokens=6000):
    msgs = [system]
    # उपयोगकर्ता संदेश जोड़ने के लिए आवश्यक टोकन
    running = count_tokens(system) + count_tokens(user_msg)
    for turn in reversed(history):           # सबसे नया पहले
        t = count_tokens(turn)
        if running + t > budget_tokens:
            break                             # बजट के तहत पुराने मोड़ गिरें
        msgs.insert(1, turn)
        running += t
    msgs.append(user_msg)
    return msgs

टोकन गणना के अनुसार बजट, बदल गणना के अनुसार नहीं, क्योंकि मोड़ लंगड़ी से भिन्न होते हैं और मोड़-गणना विंडो जब कोई एक दस्तावेज़ पेस्ट करता है आपके संदर्भ बजट को उड़ाता है। मॉडल के अपने के साथ गणना करें। यह वही संदर्भ-निर्माण काम है जो संकेत और RAG बनाता है, अब बातचीत पर स्वयं।

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

जब चीजें गलत हो जाती हैं

एक मॉडल कॉल किसी और के सर्वर पर एक नेटवर्क कॉल है, तो यह सभी तरीकों से विफल हो जाता है नेटवर्क कॉल विफल होते हैं। शुरुआत से डिजाइन करें।

  • त्रुटियाँ और दर सीमाएँ। प्रदाता सीमा करता है कि आप कितने अनुरोध भेज सकते हैं एक विंडो में। कैप को हिट करें और कॉल दर-सीमा त्रुटि के साथ विफल। मानक प्रतिक्रिया प्रतीक्षा है और पुनः प्रयास, प्रत्येक विफलता के बाद प्रतीक्षा को लंबा करना (बैकऑफ) तो आप व्यस्त सेवा को हथौड़ा नहीं करते हैं।
  • समय सीमाएँ। एक कॉल लंबित हो सकता है। एक समय सीमा निर्धारित करें तो एक धीमी अनुरोध आपके ऐप को फ्रीज नहीं करता।
  • कुंजियाँ सर्वर पर रहती हैं। आपकी API कुंजी एक पासवर्ड है जो आपके पैसे खर्च करता है। कभी API कुंजी को ब्राउज़र कोड में न डालें। कोई भी dev उपकरण खोल सकता है और पढ़ सकता है। ब्राउज़र आपके बैकएंड से बात करता है, और आपका बैकएंड, कुंजी रखते हुए, मॉडल से बात करता है।
  • मान लें कि कोई भी उत्तर गलत हो सकता है। यहाँ तक कि एक सफल कॉल भ्रम या खराब आउटपुट लौटा सकता है, तो अगले अध्याय परिणाम को कुछ के बारे में हैं जिस पर आपका कोड विश्वास कर सकता है।
python
def ask_model(messages):
    try:
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            # 20 सेकंड के बाद हार मान जाएँ
            timeout=20,
        )
        return response.choices[0].message.content
    except Exception as err:
        print("Model call failed:", err)
        return "Sorry, something went wrong. Please try again."
Junoजब चीजें गलत हो जाती हैं एक मॉडल कॉल किसी और के सर्वर पर एक नेटवर्क कॉल है, तो दर सीमा, समय सीमा, और सादी विफलताओं की अनुमति दें, और हर कॉल को फिर से प्रयास-और-बैकऑफ के साथ त्रुटि हैंडलिंग में लपेटें। अपनी API कुंजी को सर्वर पर रखें, कभी ब्राउज़र में नहीं, क्योंकि यह असली पैसे खर्च करता है और dev उपकरण इसे पढ़ेंगे। और यहाँ तक कि एक सफल उत्तर को संभवतः गलत मानें, जिसे अगले अध्याय हैंडल करने में मदद करते हैं।

एक मॉडल कॉल नेटवर्क कॉल विफल करता है: दर सीमाएँ, समय सीमाएँ, क्षणिक सर्वर त्रुटियाँ। कदम विशिष्ट विफलताओं को पकड़ना है और जो लायक हैं उन्हें पुनः प्रयास करना, बजाय सब कुछ एक नंगे except में लपेटने के जो वास्तविक बग को छिपाता है।

टाइप किए गए अपवाद पकड़ें और बैकऑफ लागू करें, हर विफलता के बाद लंबी प्रतीक्षा तो आप व्यस्त सेवा को हथौड़ा करना बंद करते हैं:

python
import time
from openai import RateLimitError, APITimeoutError, APIError

def ask_model(messages, retries=3):
    for attempt in range(retries):
        try:
            r = client.chat.completions.create(model=MODEL, messages=messages, timeout=20)
            return r.choices[0].message.content
        except (RateLimitError, APITimeoutError, APIError) as err:
            if attempt == retries - 1:
                raise
            # 1s, 2s, 4s: घातीय बैकऑफ
            wait = 2 ** attempt
            print(f"retry {attempt + 1} after {err}")
            time.sleep(wait)

अपवाद प्रकार बताते हैं कि क्या करना है: RateLimitError और APITimeoutError क्षणिक हैं और पुनः प्रयास के लायक हैं, जबकि एक बुरा अनुरोध (खराब संदेश, अज्ञात मॉडल) आपकी बग है और जोर से विफल होना चाहिए, पुनः प्रयास नहीं। हर प्रयास के बाद प्रतीक्षा को दोगुना करना आपको एक सेवा पर पहले से ही संघर्ष कर रहे ढेर से बचाता है। SDK कुछ विफलताओं को आपके लिए पुनः प्रयास करता है, तो अपनी खुद की परत जोड़ने से पहले इसके डिफॉल्ट की जांच करें।

कुंजी को सर्वर-साइड रखें, और एक सफल उत्तर को संभवतः गलत मानें, जिसे अगले अध्याय संबोधित करते हैं। अपवाद वर्ग नाम यहाँ OpenAI के हैं; अन्य SDK अपने स्वयं को उठाते हैं, तो उन्हें अपनी सीमा पर मैप करें।

Junoजब चीजें गलत हो जाती हैं टाइप किए गए अपवाद पकड़ें, नंगे except नहीं: RateLimitError और APITimeoutError क्षणिक हैं और एक पुनः प्रयास के लायक हैं, एक बुरा अनुरोध आपकी बग है और जोर से विफल होना चाहिए। घातीय रूप से बैकऑफ, प्रतीक्षा को दोगुना, तो आप एक संघर्ष सेवा को हथौड़ा करना बंद करते हैं। अपनी खुद की परत जोड़ने से पहले जांचें कि क्या SDK पहले से ही पुनः प्रयास करता है, और कुंजी को सर्वर-साइड रखें।

विफलता हैंडलिंग है जहाँ यह एक फ़ंक्शन कॉल बंद हो जाता है और वितरित-सिस्टम काम बन जाता है। कॉल ज्ञात तरीकों से विफल हो जाता है, दर सीमाएँ, समय सीमाएँ, क्षणिक 5xx त्रुटियाँ, और असुरक्षित हिस्सा पुनः प्रयास नहीं है, एक पुनः प्रयास कितनी लागत करता है यह सोचना है।

जाल इडेमपोटेंसी (क्या एक ऑपरेशन को दो बार चलाना एक बार चलाने के समान प्रभाव रखता है)। एक समय सीमा के बाद भोली पुनः प्रयास आपको दोहरा-बिल कर सकता है: पहला अनुरोध प्रदाता के पक्ष पर समाप्त हो सकता है और टोकन उत्पन्न कर सकता है, प्रतिक्रिया आपको कभी नहीं पहुँची, तो आपका पुनः प्रयास दूसरी पूर्ण पीढ़ी के लिए भुगतान करता है। एक पढ़ने-शैली कॉल के लिए यह बर्बाद पैसे है, कुछ भी एक पार्श्व प्रभाव के साथ यह एक दोहराया कार्य है। जहाँ प्रदाता एक इडेमपोटेंसी कुंजी का समर्थन करता है, एक भेजें तो एक पुनः प्रयास एक ही अनुरोध के रूप में मान्यता प्राप्त होता है; जहाँ यह नहीं करता, दोहराने के लिए सुरक्षित बनाएँ।

python
import random, time
from openai import RateLimitError, APITimeoutError, APIError

RETRYABLE = (RateLimitError, APITimeoutError, APIError)

def complete(messages, *, retries=3, **params):
    for attempt in range(retries):
        try:
            return client.chat.completions.create(messages=messages, model=MODEL, **params)
        except RETRYABLE as err:
            if attempt == retries - 1:
                raise
            # बैकऑफ + jitter
            time.sleep((2 ** attempt) + random.random())

तीन अधिक उत्पादन वास्तविकताएँ:

  • Jitter, हर प्रतीक्षा में जोड़ा गया यादृच्छिक अंश, क्लाइंट्स के एक बेड़े को लॉकस्टेप में पुनः प्रयास करने से रोकता है और दर सीमा को पुनः-स्पाइक करते हैं (गड़गड़ाहट-झुंड समस्या)।
  • कनेक्शन पुनः उपयोग भार के तहत मायने रखता है: एक क्लाइंट बनाएँ और साझा करें, तो कनेक्शन पूल किए जाते हैं हर कॉल के लिए एक ताजा TLS हैंडशेक भुगतान करने के बजाय, और स्वतंत्र कॉल को समवर्ती चलाएँ बजाय एक धीमी सीरियल लूप।
  • पुनः प्रयास से पहले वर्गीकृत करें: एक RateLimitError क्षणिक है, एक खराब-अनुरोध त्रुटि आपकी बग है और पुनः प्रयास समय और पैसे बर्बाद करता है एक कॉल पर जो कभी नहीं सफल होगा।

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

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