یک جریان SSE، هفت ارائهدهنده LLM
من یک اپلیکیشن Next.js ساختم که از هفت ارائهدهنده مختلف LLM پشتیبانی میکند.
OpenAI، Claude، Gemini، Ollama، Mistral، Groq و Azure.
من یک قانون سختگیرانه تعیین کردم: مرورگر باید برای هر ارائهدهنده دقیقاً از یک مسیر کد یکسان استفاده کند.
این کار دشوار است زیرا این APIها یکسان نیستند. آنها از روشهای انتقال متفاوتی استفاده میکنند. ساختارهای دادهای متفاوتی ارسال میکنند. برخی از SSE استفاده میکنند در حالی که برخی دیگر از NDJSON استفاده میکنند.
اگر اجازه دهید این تفاوتها به رابط کاربری (UI) شما برسند، کد شما به مجموعهای آشفته از دستورات "if" تبدیل میشود. هر بار که یک ارائهدهنده جدید اضافه میکنید، فرانتاند شما پیچیدهتر میشود.
من این مشکل را با ایجاد یک قرارداد واحد حل کردم. هر ارائهدهنده باید این فرمت را به مرورگر ارسال کند:
• data: {"delta":"
مرورگر فقط نیاز دارد سه چیز را درک کند: delta، error و [DONE].
در اینجا نحوه ساخت آن آمده است:
۱. استفاده از Async Generatorها من با هر ارائهدهنده مانند یک generator برخورد میکنم که متن ساده (plain text) تولید میکند. این کار پیچیدگی API را پنهان میکند.
۲. الگوی Wrapper
من یک تابع wrapper به نام createSSEStream ایجاد کردم. این wrapper فرمت انتقال (wire format) را مدیریت میکند. همچنین تضمین میکند که جریان (stream) همیشه پایان یابد. حتی اگر یک ارائهدهنده در میانه راه با خطا مواجه شود، wrapper یک خطا و سیگنال [DONE] ارسال میکند. این کار از معلق ماندن (hanging) کلاینت جلوگیری میکند.
۳. گروهبندی APIهای مشابه OpenAI، Mistral، Groq و Azure همگی از الگوی یکسانی استفاده میکنند. من یک پیادهسازی برای همه آنها نوشتم. اکنون اضافه کردن یک ارائهدهنده سازگار جدید تنها با یک خط کد امکانپذیر است.
۴. مدیریت موارد استثنا Anthropic و Ollama متفاوت عمل میکنند. Anthropic از رویدادهای تایپشده (typed events) خاصی استفاده میکند. Ollama از NDJSON استفاده میکند. من پارسرهای سفارشی برای آنها نوشتم، اما هر دو متن را به همان wrapper تحویل میدهند. مرورگر هرگز متوجه تفاوت نمیشود.
حریم خصوصی و سادگی این اپلیکیشن از مدل "Bring Your Own Key" استفاده میکند.
• کاربران کلید API خود را وارد میکنند. • کلید در local storage باقی میماند. • سرور به عنوان یک پروکسی خالص عمل میکند. • کلید هرگز در پایگاه داده ذخیره نمیشود.
این رویکرد نیاز به احراز هویت (auth) پیچیده یا مدیریت کلیدهای مخفی را از بین میبرد و میزبانی خودکار (self-host) اپلیکیشن را آسان میکند.
درس ساده است: هر یکپارچهسازی را به عنوان یک generator از آنچه واقعاً میخواهید مدلسازی کنید. آن را یک بار wrap کنید. اجازه دهید تغییرات در generatorها باقی بمانند تا منطق اصلی شما تمیز بماند.
Optional learning community: https://t.me/GyaanSetuAi