unbleep एक OpenAI-संगत API है, जिसका व्यवहार में मतलब है कि माइग्रेशन दो लाइनों का है और कोई नई डिपेंडेंसी नहीं। आपका आधिकारिक SDK, आपकी रीट्राई लॉजिक, आपका स्ट्रीमिंग लूप, आपकी टोकन गिनती और आपके एरर हैंडलर — सब वैसे के वैसे रहते हैं। बदलता सिर्फ़ यह है कि रिक्वेस्ट कहाँ जाती हैं और कौन-सी कुंजी उन्हें प्रमाणित करती है। यह पोस्ट पहले वह अदला-बदली बताती है, फिर वे चार चीज़ें जो इतनी अलग हैं कि पता न हो तो कुछ न कुछ तोड़ देंगी।
दो लाइनों में OpenAI-संगत API पर माइग्रेशन
import os
from openai import OpenAI
client = OpenAI(
base_url="https://unbleep.ai/v1",
api_key=os.environ["UNBLEEP_API_KEY"],
)
resp = client.chat.completions.create(
model="unbleep",
messages=[{"role": "user", "content": "Summarise this incident report."}],
)
print(resp.choices[0].message.content)अगर आप कोड को बिल्कुल छूना नहीं चाहते, तो SDK दोनों मान एनवायरनमेंट से पढ़ लेता है, इसलिए कॉन्फ़िग बदलना ही काफ़ी है:
export OPENAI_BASE_URL="https://unbleep.ai/v1"
export OPENAI_API_KEY="ub_live_9f2c..."यही जोड़ी SDK के ऊपर बने ज़्यादातर इकोसिस्टम को कवर कर लेती है — LangChain, LlamaIndex, Instructor, Vercel AI SDK, और जो भी चीज़ base-URL की सेटिंग देती है। कुंजियों में प्रीफ़िक्स होता है ताकि लीक सीक्रेट स्कैनरों को साफ़ दिखें: ub_live_ और ub_test_ दोनों एक ही प्रीपेड क्रेडिट से, एक ही प्रति-टोकन दर पर बिल होती हैं। टेस्ट कुंजी एक अलग, रद्द की जा सकने वाली क्रेडेंशियल है जिसकी सीमा कम है — खाते के 60 की जगह 15 रिक्वेस्ट/मिनट — यह फ़्री टियर नहीं है। दोनों को सर्वर-साइड ही रखें।
GET /v1/models काम करता है, इसलिए ड्रॉपडाउन भरने के लिए मॉडलों की सूची निकालने वाली टूलिंग को अलग से संभालने की ज़रूरत नहीं।
मॉडल चुनना
तीन टियर। तारीख़ वाली id — unbleep-250811, unbleep-high-250811, unbleep-mini-250811 — उपनाम (alias) के तौर पर स्वीकार की जाती हैं, पर आज वे उसी बिल्ड पर जाती हैं जिस पर सादी id, और रिस्पॉन्स में सादी id ही वापस आती है। उन्हें आगे के लिए संगत वर्तनी मानें, पुनरुत्पादन (reproducibility) की गारंटी नहीं; अगर किसी इवैल्यूएशन को दोहराना ज़रूरी है, तो मॉडल id नहीं, आउटपुट रिकॉर्ड करें।
| मॉडल | कॉन्टेक्स्ट | क़ीमत इन / आउट प्रति 1M | नोट्स | | --- | --- | --- | --- | | unbleep | 256K | $3.00 / $3.00 | डिफ़ॉल्ट। रीज़निंग टियर। | | unbleep-high | 1M* | $5.00 / $5.00 | सबसे बड़े काम। रीज़निंग टियर। | | unbleep-mini | 32K | $1.00 / $1.00 | सस्ता और तेज़। सीधे जवाब देता है, कोई रीज़निंग ट्रेस नहीं। |
*रिक्वेस्ट बॉडी की सीमा 2,000,000 बाइट है — लगभग 500k टोकन — इसलिए एक अकेली कॉल असल में 1M विंडो नहीं भर सकती; उससे बड़ी कोई भी चीज़ 413 payload_too_large के साथ लौटती है।
unbleep-mini की 32K की छत वही है जो 128K-कॉन्टेक्स्ट वाले मॉडल से माइग्रेट करने वालों को पकड़ती है: जो प्रॉम्प्ट पहले समा जाता था, वह अब अस्वीकार हो जाएगा। अगर आप लागत के हिसाब से रूट करते हैं, तो लंबाई के हिसाब से भी करें।
def pick_model(prompt_chars: int) -> str:
"""~4 chars/token is a deliberate under-estimate; leave room for the completion."""
est_tokens = prompt_chars // 4
if est_tokens < 24_000:
return "unbleep-mini"
return "unbleep" if est_tokens < 200_000 else "unbleep-high"स्ट्रीमिंग
stream=True सेट करें और आपको मानक Server-Sent Events मिलते हैं: हर इवेंट एक chat.completion.chunk है जो delta लिए होता है, और स्ट्रीम एक शाब्दिक data: [DONE] के साथ ख़त्म होती है। आपका मौजूदा लूप बिना बदलाव के काम करता है।
curl -N https://unbleep.ai/v1/chat/completions \
-H "Authorization: Bearer $UNBLEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "unbleep",
"messages": [{"role": "user", "content": "Explain the residual stream."}],
"stream": true,
"stream_options": {"include_usage": true}
}'टोकन की गिनती आपको मिलती है, चाहे आप माँगें या न माँगें: unbleep हमेशा बैकएंड से usage माँगता है और उसे आगे भेज देता है, और सिर्फ़ एक स्पष्ट stream_options: {"include_usage": false} ही उसे बाहर जाते समय हटाता है। दोनों ही स्थितियों में स्ट्रीम एक आख़िरी चंक के साथ ख़त्म होती है जिसमें choices ऐरे ख़ाली होता है — और जब तक आपने ऑप्ट-आउट न किया हो, भरा हुआ usage ऑब्जेक्ट होता है — इसीलिए नीचे का लूप choices को छूने से पहले उसे जाँचता है।
reasoning_content फ़ील्ड
स्कीमा में यही एक असली जोड़ है। unbleep और unbleep-high जवाब देने से पहले सोचते हैं, और वह सोचने की शृंखला (chain of thought) reasoning_content में लौटती है — मैसेज (नॉन-स्ट्रीमिंग) या डेल्टा (स्ट्रीमिंग) पर content के बगल वाला फ़ील्ड। अपस्ट्रीम बैकएंड इस पर एकमत नहीं हैं कि इसे reasoning कहा जाए या reasoning_content; API इसे reasoning_content पर सामान्यीकृत कर देता है ताकि आपको हमेशा एक ही आकार संभालना पड़े।
चूँकि यह OpenAI स्कीमा का हिस्सा नहीं है, यह SDK के टाइप स्टब में भी नहीं है। रिस्पॉन्स मॉडल अतिरिक्त फ़ील्ड की अनुमति देते हैं, इसलिए रनटाइम पर एट्रिब्यूट एक्सेस काम करता है — पर इसे getattr से पढ़ें, ताकि mini का रिस्पॉन्स, जिसमें कोई ट्रेस नहीं होता, एरर न फेंके:
import sys
stream = client.chat.completions.create(
model="unbleep",
messages=[{"role": "user", "content": "Why did this detection rule misfire?"}],
stream=True,
stream_options={"include_usage": True},
)
usage = None
for chunk in stream:
if not chunk.choices: # final usage-only chunk
usage = chunk.usage
continue
delta = chunk.choices[0].delta
thought = getattr(delta, "reasoning_content", None)
if thought: # trace to stderr, answer to stdout
sys.stderr.write(thought)
if delta.content:
sys.stdout.write(delta.content)
if usage:
details = usage.completion_tokens_details
print(f"\nreasoning tokens: {getattr(details, 'reasoning_tokens', 0)}")तीन व्यावहारिक नतीजे:
- रीज़निंग टोकन आउटपुट टोकन हैं। वे
completion_tokensमें दिखते हैं और आउटपुट दर पर बिल होते हैं।usage.completion_tokens_details.reasoning_tokensबताता है कि बिल का कितना हिस्सा सोचने में गया। - वे
max_tokensभी खाते हैं। रीज़निंग टियर पर कसी हुई सीमा पूरी की पूरी ट्रेस में ख़र्च हो सकती है, और आपके हाथ कटा हुआ जवाब या ख़ालीcontentलगता है। दोनों के लिए बजट रखें, या थिंकिंग बंद कर दें। - जब ज़रूरत न हो, इसे बंद कर दें। छोटी या बड़ी-मात्रा वाली कॉल के लिए
extra_bodyके ज़रिए"thinking": falseभेजें।reasoning_effortभी आगे पास हो जाता है।
resp = client.chat.completions.create(
model="unbleep",
messages=[{"role": "user", "content": "phishing or benign?"}],
max_tokens=4,
extra_body={"thinking": False}, # spend the budget on the answer, not the trace
)reasoning_content को कभी भी बाद के टर्न में असिस्टेंट कंटेंट के रूप में वापस न भेजें। यह डायग्नोस्टिक आउटपुट है, बातचीत का इतिहास नहीं, और इसे दोहराने से अगला रिस्पॉन्स बिगड़ता है।
एरर और दो असली गड़बड़ियाँ
एरर OpenAI का एनवलप इस्तेमाल करते हैं — {"error": {"type", "code", "message"}} — इसलिए आपके मौजूदा except ब्लॉक काम करते रहते हैं। स्टेटस कोड वैसे ही मैप होते हैं जैसी आप उम्मीद करेंगे: 401 ग़लत कुंजी, 422 policy: strict द्वारा ब्लॉक, 429 रेट लिमिट, 5xx रीट्राई करने लायक़ अपस्ट्रीम एरर।
जो स्टेटस आपने शायद कभी संभाला ही नहीं, वह है 402, क्रेडिट ख़त्म। खाते प्रीपेड हैं, इसलिए न कोई ओवरेज है, न कोई इनवॉइस — जब तक आप टॉप-अप न करें, रिक्वेस्ट बस रुक जाती हैं। OpenAI कोटा ख़त्म होने को 429 से बताता है, जिसका मतलब है कि भोले-भाले माइग्रेशन में आपकी बैकऑफ़ लॉजिक 402 को हमेशा के लिए रीट्राई करती रहेगी। इसे अंतिम (terminal) मानें और इस पर अलर्ट लगाएँ।
दूसरी गड़बड़ी: system_fingerprint वापस नहीं आता। यह सर्विंग बैकएंड की पहचान बताता है, इसलिए इसे बाक़ी वेंडर फ़ील्ड के साथ हटा दिया जाता है। अगर आपका कैश या पुनरुत्पादन की जाँच इस पर टिकी है, तो आपको अपना वर्ज़न मार्कर चाहिए होगा: तारीख़ वाली मॉडल id मौजूदा बिल्ड के उपनाम हैं, जमे हुए स्नैपशॉट नहीं, इसलिए बैकएंड बदलने पर वे आपको नहीं बताएँगी।
रेट लिमिट हर रिस्पॉन्स पर हेडर के रूप में आती हैं — x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests — ताकि बैच जॉब उस छत से टकराकर उसे खोजने की बजाय अपनी रिक्वेस्ट दर ख़ुद संभाल सके। एक दूसरी छत भी है जिसे हेडर नहीं बताते: प्रति खाता एक समय में ज़्यादा से ज़्यादा 8 रिक्वेस्ट चल सकती हैं, और नौवीं 429 के साथ too_many_concurrent_requests कोड लेकर लौटती है। स्ट्रीमिंग कॉल स्ट्रीम ख़त्म होने तक अपना स्लॉट पकड़े रखती है, इसलिए अपने वर्कर पूल की सीमा 8 रखें।
आप किससे जुड़ रहे हैं
साफ़ कह देना ठीक है: इस एंडपॉइंट के पीछे के मॉडल abliterated हैं, यानी उनका इनकार करने का व्यवहार वेट के स्तर पर हटा दिया गया है। यही तो मक़सद है — यह सुरक्षा शोध, रेड-टीमिंग और इवैल्यूएशन के लिए बना डेवलपर API है, जहाँ इनकार एक माप की त्रुटि है। इसका मतलब यह भी है कि ख़राब प्रॉम्प्ट पकड़ने के लिए सामान्य गार्डरेल यहाँ नहीं हैं, इसलिए आउटपुट के लिए किसी इंसान को जवाबदेह रखें और स्वीकार्य उपयोग नीति पढ़ें। क़ानूनी उपयोग आपकी ज़िम्मेदारी है।
API कुंजी लें — माइग्रेशन सचमुच दो लाइनों का है।