OctoHub 0.8.0: प्रॉक्सी में एकीकृत मीडिया API उग आई

एक महीने पहले जिस सवाल ने हमसे OctoHub बनवाया, वह यह था: आपके एजेंट ने चालीस मॉडल कॉल कीं — उसने क्या भेजा, उसकी कीमत क्या रही, और किस अपस्ट्रीम ने जवाब दिया?

अब वही सवाल, बस पैसा किसी और जगह खिसक गया है। आपके एजेंट ने एक डिज़ाइन पर काम करते हुए ग्यारह इमेज बनाईं, फिर नौ सेकंड का एक वीडियो, फिर सारांश को एक सिंथेटिक आवाज़ में पढ़कर सुनाया। इसमें चार प्रोवाइडर शामिल थे। उनमें से तीन ऐसी इकाइयों में बिल बनाते हैं जो टोकन नहीं हैं — GPU-सेकंड, वीडियो-सेकंड, अक्षर। और उनमें से एक ने तो बताया ही नहीं कि लागत क्या रही। अब बताइए: बिल किस ग्राहक को भेजेंगे?

chat completions के लिए हम इसका जवाब दे चुके थे। बाकी हर चीज़ के लिए जवाब अब भी एक स्प्रेडशीट था।

OctoHub 0.8.0 यह खाई पाट देता है। इमेज जनरेशन, वीडियो जनरेशन, स्पीच सिंथेसिस और ट्रांसक्रिप्शन अब उसी प्रॉक्सी, उन्हीं क्लाइंट कुंजियों, उन्हीं अनुमति-सूचियों, उसी अनुरोध लॉग और उसी लागत कॉलम से होकर गुज़रते हैं जिनसे /v1/completions गुज़रता है। पाँच प्रोवाइडर — fal, ElevenLabs, Replicate, Runway और OpenRouter — एक ही JSON envelope के पीछे।


वह श्रम-विभाजन जिसने इसे छोटा रखा

यह सब octolib 0.36.1 के ऊपर बना, जो उसी दिन दोपहर को टैग हुई थी और जहाँ असल मीडिया स्टैक रहता है: चारों टास्क के लिए typed request structs, प्रोवाइडर अडैप्टर, job lifecycle, capability descriptors, और एक reference rate table उन चीज़ों की कीमत लगाने के लिए जिनकी कीमत प्रोवाइडर खुद नहीं बताते। उस रिलीज़ को हमने सितंबर की शुरुआत के राउंड-अप में कवर किया था।

OctoHub वाला हिस्सा एक ही नियम से निकलता है: जो कुछ मॉडलों के बारे में है वह octolib में, जो कुछ टेनेंट्स के बारे में है वह यहाँ। रूटिंग व्याकरण, अडैप्टर, job handles और प्राइसिंग octolib के हैं। कुंजियाँ, कोटा, persistence, मेट्रिक्स और wire API OctoHub के।

इसी नियम की वजह से कॉन्फ़िग में कोई नया सिंटैक्स नहीं आया। मीडिया अलियास वही चीज़ है जो मॉडल अलियास है:

[media_models]
"flux" = ["fal:fal-ai/flux/dev", "replicate:black-forest-labs/flux-1.1-pro"]
"veo"  = ["openrouter:google/veo-3.1"]
"tts"  = ["elevenlabs:eleven_flash_v2_5"]

[providers.fal]        # concurrency and rate windows, unchanged
concurrency = 8
requests_per_minute = 60

वही provider:model व्याकरण, वही अर्थ कि सूची का मतलब लोड बैलेंसिंग है, वही प्रति-प्रोवाइडर लिमिटर। मीडिया tokens_per_minute को कभी फीड नहीं करता — मीडिया प्रोवाइडर कोई टोकन रिपोर्ट ही नहीं करते — लेकिन ये विंडो प्रोवाइडर के नाम से बँधी हैं और completions के साथ साझा हैं, इसलिए जिस प्रोवाइडर को आप दोनों कामों के लिए इस्तेमाल करते हैं, वहाँ जो टोकन बजट आपका chat ट्रैफ़िक पहले ही खर्च कर चुका है, वह किसी मीडिया अनुरोध को लौटा देगा।

खराब कॉन्फ़िगरेशन पहले पैसे वाले अनुरोध पर नहीं, बूट पर ही फेल होता है: अनजान प्रोवाइडर, बिगड़ा हुआ provider:model, खाली मिरर सूची, या [models] से टकराने वाला अलियास — इनमें से कोई भी सर्वर को शुरू नहीं होने देता।


चार टास्क, एक envelope

POST /v1/images/generations     generate | edit | inpaint | variation
POST /v1/videos                 text_to_video | image_to_video | reference_to_video | extend | edit
POST /v1/audio/speech
POST /v1/audio/transcriptions
GET  /v1/media/{id}             fetch or advance a job
POST /v1/media/{id}/cancel
GET  /v1/media/models           capabilities, parameters, reference price

ये क्लाइंट एंडपॉइंट हैं, ठीक completions की तरह ऑथेंटिकेट होते हैं — वही bearer कुंजी, वही प्रति-कुंजी मॉडल अनुमति-सूची, वही X-Request-Id से जुड़ाव:

curl -sX POST http://127.0.0.1:8080/v1/images/generations \
  -H "Authorization: Bearer <client-key>" \
  -d '{"model":"flux","prompt":"a red panda astronaut","count":2,"size":"1024x1024"}'

हर प्रतिक्रिया — इमेज, वीडियो, आवाज़, ट्रांसक्रिप्ट, पूरी हो चुकी हो या अभी चल रही — एक ही ऑब्जेक्ट होती है:

{
  "id": "med_9f3c1e0b…", "object": "media", "task": "text_to_image",
  "status": "succeeded", "model": "fal-ai/flux/dev", "provider": "fal",
  "progress": 1.0,
  "artifacts": [ { "kind": "image", "media_type": "image/png",
                   "source": { "type": "url", "value": "https://…" },
                   "size_bytes": 812345, "expires_at": 1767225600 } ],
  "usage": { "cost": 0.08, "cost_source": "provider", "currency": "USD",},
  "warnings": [], "safety": { "status": "passed",}, "error": null
}

एक ही आकार का मतलब है persistence के लिए एक ही कोड पथ, लॉग के लिए एक ही row फ़ॉर्मैट, और आपके क्लाइंट को parse करने के लिए एक ही चीज़। ट्रांसक्रिप्शन इकलौता टास्क है जिसका payload कोई artifact नहीं है, इसलिए वह text, language, segments और words वाला एक result ऑब्जेक्ट जोड़ देता है।

OpenAI के API से दो विचलन जानबूझकर किए गए हैं, और हम उन्हें पहले ही बता देते हैं, बजाय इसके कि आप उन्हें बाद में खोजें: सब कुछ JSON है, कभी multipart/form-data नहीं — बाइनरी इनपुट {"type":"url"…} या {"type":"base64"…} ऑब्जेक्ट होते हैं — और इमेज edit, inpaint तथा variation अलग-अलग पथ नहीं, बल्कि एक mode फ़ील्ड हैं। /v1/images/generations OpenAI का पथ और उसकी model / prompt / size वाली वर्तनी उधार लेता है, मगर उसका wire फ़ॉर्मैट नहीं — इमेज की गिनती count है, n नहीं, और जवाब में OpenAI के {"created", "data"} की जगह ऊपर वाला envelope आता है। कोई OpenAI SDK इसे parse नहीं कर पाएगा; इसे सादे HTTP से या एक पतले रैपर से कॉल कीजिए।


वह job जो उसे शुरू करने वाले अनुरोध से ज़्यादा जीती है

यही वह हिस्सा है जो chat completion को प्रॉक्सी करने से सचमुच अलग है, और डिज़ाइन के फ़ैसले भी यहीं हैं।

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

Row इंतज़ार से पहले लिखी जाती है, बाद में नहीं। कतार-आधारित प्रोवाइडर के स्वीकार करते ही — fal, Replicate, Runway, OpenRouter वीडियो — OctoHub रिकॉर्ड और credential-रहित JobHandle सहेज देता है, और उसके बाद ही इंतज़ार शुरू करता है। रीस्टार्ट, टाइमआउट, या कनेक्शन छोड़ देने वाला क्लाइंट: इनमें से कोई भी उस job को अनाथ नहीं कर सकता जिसका पैसा आप चुका चुके हैं। Handle आपके डेटाबेस में है और उसी से job को दोबारा आगे बढ़ाया जा सकता है। (ElevenLabs और OpenRouter के सिंक्रोनस एंडपॉइंट के पास कोई कतार ही नहीं है जो बदले में handle थमाए — वे पूरा काम submit कॉल के भीतर ही निपटा देते हैं, इसलिए बीच में टूटने की कोई गुंजाइश ही नहीं बचती और दोबारा शुरू करने को कुछ रहता ही नहीं।)

202 कोई विफलता नहीं है। wait: false भेजिए और प्रोवाइडर के स्वीकार करते ही id मिल जाती है। wait: true भेजिए और server.upstream_timeout_secs पार कर जाइए, तो वही 202 मिलता है, status: "queued" या "running" के साथ। रिमोट काम चलता रहता है; id ज़िंदा है; कुछ खोया नहीं। जब तैयार हों, GET /v1/media/{id} पर poll कर लीजिए।

job को आगे polling ही बढ़ाती है — कोई background worker नहीं है। यह जानबूझकर तय किया गया non-goal है: worker का मतलब होता एक शेड्यूलर, leases, और उन jobs के लिए एक दूसरा failure mode जिनका कोई इंतज़ार ही नहीं कर रहा। नतीजा छिपाया नहीं गया, डॉक्स में लिखा है: जिस job को आप कभी poll नहीं करते वह queued पड़ी रहती है और उसकी लागत कभी दर्ज नहीं होती। पहले से पूरी हो चुकी job को पढ़ना मुफ़्त है और दोबारा बिल कभी नहीं बनाता — terminal row डेटाबेस से परोसी जाती है, कोई अपस्ट्रीम कॉल होती ही नहीं।

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

Failover submit पर होता है, जहाँ वह सुरक्षित है। server.failover_on_error चालू कीजिए — completions की तरह यह भी डिफ़ॉल्ट रूप से बंद है — और submit पर आई प्रोवाइडर की गड़बड़ी उस कैंडिडेट को हटा देती है और अनुरोध अलियास के अगले मिरर को सौंप देती है। वह गड़बड़ी उस प्रोवाइडर की लगातार विफलताओं में भी गिनी जाती है: server.provider_error_cooldown_secs सेट कीजिए (डिफ़ॉल्ट 0, यानी बंद) और प्रोवाइडर की तरफ़ से लगातार तीन विफलताएँ उसे कूलडाउन में डाल देती हैं, जो उसे रोकता नहीं, बस स्वस्थ कैंडिडेटों के पीछे कर देता है। डिफ़ॉल्ट पर छोड़ दीजिए तो गड़बड़ी सीधे कॉल करने वाले के पास लौट जाती है। दोनों ही सूरतों में, job के स्वीकार हो जाने के बाद failover करने को कुछ बचता ही नहीं — उसका पैसा जा चुका है।

रिकॉर्ड उसी कुंजी तक सीमित हैं जिसने उन्हें बनाया। किसी दूसरे टेनेंट की id पर 404 मिलता है, 403 नहीं — आपको यह जानने को नहीं मिलता कि वह id मौजूद भी है।


पैरामीटर वाली समस्या, और उसका ईमानदार जवाब

हर मीडिया प्रोवाइडर की अपनी राय है कि अनुरोध दिखता कैसा है। fal को num_inference_steps और guidance_scale चाहिए; कुछ एंडपॉइंट प्रॉम्प्ट को text कहते हैं; Runway क्रेडिट बेचता है और अपने ही मॉडल नामों में सोचता है। एकीकृत API को इस बारे में कुछ तय करना ही पड़ता है, और दो खराब जवाब मौजूद हैं: सिर्फ़ साझा हिस्सा उजागर कीजिए (बेकार), या एक अनुवाद परत गढ़ लीजिए जो दिखावा करे कि सब एक जैसा है (झूठ, वह भी महँगा)।

OctoHub का जवाब तीन हिस्सों में है:

एक portable कोर, हर जगह एक ही वर्तनीprompt, count, seed, size, duration_secs, negative_prompt, output_formatsize "1024x1024" या "16:9" लेता है; इसके अलावा कुछ भी 400 है। Portable का मतलब एक ही नाम है, हर जगह सपोर्ट नहीं: Runway के पास count, negative_prompt या output_format का कोई समकक्ष नहीं है, और OpenRouter के वीडियो एंडपॉइंट के पास भी नहीं, इसलिए डिफ़ॉल्ट सख़्त नीति के तहत उन प्रोवाइडरों पर ये चुपचाप नज़रअंदाज़ होने के बजाय 400 बनकर लौटते हैं — और वह नीति ही नीचे वाला तीसरा हिस्सा है।

एक escape hatch जो कुछ भी जस का तस आगे पहुँचा देता है, प्रोवाइडर के हिसाब से namespace किया हुआ:

"provider_options": {
  "fal": { "input": { "num_inference_steps": 28, "guidance_scale": 3.5 },
           "field_map": { "prompt": "text" } }
}

field_map किसी portable नाम को उस नाम पर मैप कर देता है जो एंडपॉइंट असल में इस्तेमाल करता है — इसलिए portable prompt उस एंडपॉइंट पर भी चलता रहता है जिसका फ़ील्ड text है। जब कोई अलियास कई प्रोवाइडरों पर फैला हो, तो सारे namespace एक साथ भेज दीजिए; सिर्फ़ जीतने वाले कैंडिडेट का namespace आगे जाता है और बाकी गिरा दिए जाते हैं — यही चीज़ मल्टी-प्रोवाइडर अलियास को इस्तेमाल लायक बनाती है।

एक नीति कि जब कोई पैरामीटर पूरा न किया जा सके तो क्या हो। unsupported_parameters: "error" (डिफ़ॉल्ट) पैसा खर्च होने से पहले फेल होता है — प्रोडक्शन के लिए यही सही है। "warn_and_drop" उसे गिरा देता है और एक चेतावनी लौटाता है — तब काम का, जब एक अलियास असमान सपोर्ट वाले प्रोवाइडरों पर पंखे की तरह फैलता हो। चुनाव आप प्रति अनुरोध करते हैं, क्योंकि आपके अलावा कोई नहीं जानता कि आपका मतलब कौन-सा था।

और GET /v1/media/models आपको कुछ भी खर्च करने से पहले बता देता है कि कौन क्या है: हर कॉन्फ़िगर किए गए कैंडिडेट के execution और पैरामीटर capability फ़्लैग, सीमाएँ, उसके अडैप्टर का अपना provider_options JSON Schema, और reference कीमत। 0.8.0 में दो खुरदुरे किनारे, जो आपको वैसे भी मिल ही जाते: discovery हर कैंडिडेट को इमेज अडैप्टर के ज़रिए टटोलती है, इसलिए वीडियो अलियास पर भी tasks फ़ील्ड में ["text_to_image"] ही पढ़ने को मिलता है, और किसी elevenlabs कैंडिडेट के पास इमेज अडैप्टर है ही नहीं — वह कीमत के साथ लौटता है, मगर descriptor null रहता है। कई capability फ़ील्डों में ईमानदारी से unknown लिखा होता है — अडैप्टर हर एंडपॉइंट का स्कीमा जान ही नहीं सकते, और यह कह देना आत्मविश्वास से भरे गलत जवाब से बेहतर है। escape hatch ठीक इसीलिए मौजूद है।


वह लागत जो अंदाज़ा लगाने से इनकार करती है

असल में यह रिलीज़ इसी फ़ीचर के बारे में है, और यही वह जगह है जहाँ हम सबसे ज़्यादा ज़िद्दी रहे।

usage.cost वह संख्या है जिसका बिल बना। usage.cost_source बताता है कि वह आई कहाँ से:

cost_source मतलब
provider अपस्ट्रीम ने असली डॉलर लौटाए। OpenRouter और Replicate ऐसा करते हैं।
estimate octolib की reference rate table से लोकली निकाली गई।
unavailable कोई इसकी कीमत लगा ही नहीं सका — cost null है।

null शून्य नहीं है। जिस अनुरोध की कीमत कोई नहीं लगा सका, वह बिना कीमत के दर्ज होता है, मुफ़्त के रूप में कभी नहीं। वह octohub_media_cost_unknown_total में दिखता है और cost_unavailable चेतावनी साथ लाता है — बजाय इसके कि वह चुपचाप आपके कुल खर्च को नीचे खींचे और डैशबोर्ड को हकीकत से बेहतर दिखाए।

अनुमान octolib की reference table से आते हैं, जो इकाइयों को समझती है क्योंकि प्रोवाइडर भी समझते हैं: ElevenLabs अक्षरों का बिल बनाता है, Runway क्रेडिट से बदले गए वीडियो-सेकंड बेचता है, fal GPU wall-clock का सहारा लेता है क्योंकि उसकी queue मेट्रिक्स सिर्फ़ यही मात्रा बताती हैं। जहाँ rate एक अंदाज़ा भर होता, वहाँ कोई rate है ही नहीं — Replicate के कम्युनिटी मॉडल किसी अनजान GPU क्लास पर GPU-सेकंड का बिल बनाते हैं, इसलिए उनके लिए कोई rate निकलता ही नहीं और वे किसी भरोसेमंद दिखने वाली संख्या की मुहर लगवाने के बजाय बिना कीमत के रह जाते हैं।

एक ही ज्ञात कमी, खुलकर कही जा रही है: ElevenLabs की ट्रांसक्रिप्शन बिना कीमत के है। Scribe इनपुट-ऑडियो की अवधि का बिल बनाता है, जो वापस रिपोर्ट नहीं होती, और कोई reference rate उसे ढँकता नहीं। बाकी जगह ट्रांसक्रिप्शन को संख्या मिल ही जाती है — fal अपने प्रति-GPU-सेकंड वाले सर्वग्राही नियम पर आ जाता है, और Replicate तथा OpenRouter अपस्ट्रीम जो डॉलर बताता है उसी से कीमत लगाते हैं। जब हम Scribe की कीमत लगा सकेंगे, लगाएँगे; तब तक वह unavailable है, 0.00 नहीं।

एग्रीगेट की तरफ़, GET /v1/admin/usage को media_count और total_cost मिलते हैं — जो अब completions, embeddings और मीडिया को जोड़कर वही एक संख्या बनाते हैं जिसे आप सचमुच किसी इनवॉइस पर लिखेंगे। GET /v1/admin/media अलग-अलग रिकॉर्ड सूचीबद्ध करता है, बाकी दोनों जैसे ही फ़िल्टरों के साथ, नए पहले, और चल रही jobs भी शामिल — गैर-टर्मिनल स्टेटस और null completed_at के साथ। job अपना ही रिकॉर्ड है, बस पहले वाले स्टेटस पर; जाँचने के लिए कोई अलग कतार है ही नहीं।

Prometheus, प्रति task, model और provider — और metrics.per_key चालू होने पर request काउंटर पर एक api_key_id लेबल भी। लागत वाले काउंटर कुंजी के हिसाब से बिना लेबल के रहते हैं; प्रति-टेनेंट खर्च GET /v1/admin/usage से आता है, जहाँ वह सैंपल किया हुआ नहीं, सटीक होता है:

octohub_media_requests_total{task,model,provider,status}
octohub_media_duration_seconds{task,model,provider}
octohub_media_cost_microusd_total{task,model,provider,source}
octohub_media_cost_unknown_total{task,model,provider}

लागतें माइक्रो-USD में गिनी जाती हैं, क्योंकि $0.003 वाली इमेज के लिए डॉलर का काउंटर राउंडिंग-एरर पैदा करने की मशीन है। बकाया jobs का कोई gauge जानबूझकर नहीं है: सही gauge को डेटाबेस की गैर-टर्मिनल rows गिननी पड़तीं, और इन-प्रोसेस काउंटर उसी पल गलत हो जाता जब किसी job को कोई दूसरी replica poll कर ले या वह रीस्टार्ट झेलकर बच जाए।


जिन चीज़ों को हमने ना कहा

जो फ़ीचर यहाँ नहीं हैं, वे उतना ही भार उठाते हैं जितना वे जो हैं:

OctoHub blob स्टोर नहीं बनता। न कोई ऑब्जेक्ट स्टोर है, न CDN, न चलाने को कोई artifact lifecycle, और वह आपकी तरफ़ से कभी कोई artifact लाकर नहीं देता। जो प्रोवाइडर URL लौटाता है, वह URL के रूप में ही सहेजा जाता है — row में एक लिंक और मेटाडेटा रहता है, बस इतना ही, और ज़्यादातर ट्रैफ़िक यही है। जो प्रोवाइडर payload खुद लौटाता है, वही वह अपवाद है जिसके लिए आपको जगह का हिसाब रखना चाहिए: ElevenLabs की स्पीच हमेशा ऐसा करती है, और fal, Replicate या OpenRouter कभी-कभी। उन बाइट्स को base64 में बदलकर प्रतिक्रिया में भेजा जाता है, और वही base64 रिकॉर्ड के result कॉलम में सहेजा भी जाता है — क्योंकि बाद में आने वाला GET /v1/media/{id} अपस्ट्रीम को छुए बिना ठीक उसी row को दोबारा परोसता है। media टेबल में पड़ी एक MP3 अपने असल आकार से करीब 4/3 गुना जगह खाती है, इसलिए भारी टेक्स्ट-टू-स्पीच डिप्लॉयमेंट को ऐसी टेबल के लिए तैयार रहना चाहिए जो आधी बहीखाता है, आधी मीडिया लाइब्रेरी।

क्लाइंट के दिए फ़ाइल पथ अस्वीकार होते हैं। octolib का MediaSource file, provider_file और object_storage सपोर्ट करता है; यहाँ तीनों 400 लौटाते हैं। किसी सर्वर को भेजे अनुरोध में पड़ा पथ दरअसल सर्वर के फ़ाइल सिस्टम को पढ़ने की माँग है — एक SSRF/LFI छेद, कोई फ़ीचर नहीं। Inline base64 का आकार किसी प्रोवाइडर तक कुछ भी पहुँचने से पहले media.max_source_bytes (डिफ़ॉल्ट 20 MiB) के हिसाब से जाँचा जाता है, और payload कभी डेटाबेस में नहीं उतरता: सहेजा गया अनुरोध आकार और बाइट गिनती रखता है, बाइट नहीं।

अपस्ट्रीम क्रेडेंशियल सर्वर पर ही रहते हैं। प्रोवाइडर कुंजियाँ सर्वर के एनवायरनमेंट से आती हैं (FAL_API_KEY, ELEVENLABS_API_KEY, वगैरह) और क्लाइंट से कभी स्वीकार नहीं की जातीं। provider_options.<provider>.cost_estimate सिरे से अस्वीकार है — कीमत सर्वर की तरफ़ तय होती है, और क्लाइंट को यह बताने का हक़ नहीं कि उस पर आपका कितना बकाया है।

0.8.0 में यह भी नहीं है: स्ट्रीमिंग TTS, और multipart/form-data। जब किसी को सचमुच इनकी ज़रूरत पड़ेगी, दोनों बाद में जोड़े जा सकते हैं — हालाँकि स्ट्रीमिंग को पहले लागत का अपना जवाब चाहिए होगा, क्योंकि octolib का स्ट्रीमिंग स्पीच पथ कोई usage रिपोर्ट करता ही नहीं।


अपग्रेड

media टेबल बाकी टेबलों के साथ स्टार्टअप पर ही बन जाती है — SQLite, MySQL और PostgreSQL पर, बिना किसी माइग्रेशन कदम के। मौजूदा कॉन्फ़िग चलता रहता है: अगर आप कोई [media_models] नहीं जोड़ते, तो नए एंडपॉइंट के पास रूट करने को कुछ नहीं होता और बाकी प्रॉक्सी ठीक वैसे ही बर्ताव करती है जैसे 0.7 में करती थी।

# Linux x86_64, static musl build
curl -fsSL https://github.com/Muvon/octohub/releases/download/0.8.0/octohub-0.8.0-x86_64-unknown-linux-musl.tar.gz | tar xz
./octohub

बाइनरी छह targets के लिए आती हैं (Linux musl, macOS, Windows — x86_64 और ARM64), या स्रोत से cargo build --release

रिलीज़ एक ही वजह से breaking चिह्नित है: Storage trait में मीडिया मेथड उग आए। यह सिर्फ़ तभी मायने रखता है जब आप OctoHub के internals के हिसाब से अपना storage बैकएंड बनाए रखते हैं; अगर आप बाइनरी चलाते हैं, तो बदलने को कुछ नहीं है। एक छोटी बात जानने लायक: 0.7.12 से OctoHub attribution हेडर शुरू से आख़िर तक फ़ॉरवर्ड करता है, और इस साइकल से वह अपस्ट्रीम पर खुद को octolib के सामान्य डिफ़ॉल्ट के बजाय Octohub/<version> बताता है — ताकि प्रोवाइडर की तरफ़ के डैशबोर्ड उस प्रॉक्सी का नाम लें जिसने कॉल की।


असल बात

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

0.8.0 से "यह वीडियो किस ग्राहक ने बनाया, किस प्रोवाइडर पर, और उसकी कीमत क्या रही" का जवाब आपके अपने डेटाबेस की एक row है — chat completions के ठीक बगल में, उसी मुद्रा में, और एक ऐसे कॉलम के साथ जो किसी को पता न होने पर यह बात साफ़-साफ़ कह देता है।

— Don

OctoHub Apache-2.0 के तहत ओपन सोर्स है, Muvon Un Limited द्वारा विकसित। इसे GitHub पर लीजिए — issues और pull requests का स्वागत है। पूरा मीडिया डॉक्युमेंटेशन: doc/11-media.md