/v1/chat/completions المتوافقة مع OpenAI، وتقبل رموز حاملك الخاصة، وتعيد توجيه الطلبات إلى Venice، وتدعم استجابات البث، وتصدر امتدادات ومقاييس OpenTelemetry مفيدة.
هل أنت مهتم بتنفيذ الكود الكامل؟ اطلع على مستودع GitHub.
المتطلبات المسبقة
- Rust 1.92+
- Docker و Docker Compose
- مفتاح Venice API
curl- إلمام أساسي بخدمات ويب Rust
ما الذي نبنيه
التنفيذ المرجعي هو خدمة Rust صغيرة مع بعض الأجزاء الواضحة:| الجزء | ما يقوم به |
|---|---|
| موجه Axum | يخدم /healthz و /v1/chat/completions |
| مستخرج المصادقة | يتحقق من رموز حامل البوابة مقابل المفاتيح المُجزَّأة في Postgres |
| محدد المعدل | يستخدم نوافذ ثابتة مخزنة في Postgres |
| عميل Venice | يقوم بالوكيل لطلبات إكمال الدردشة غير المتدفقة والمتدفقة |
| القياس عن بُعد | يسجل امتدادات GenAI واستخدام الرموز وتكلفة الفوترة في Venice والزمن وتوقيتات البث |
| Docker Compose | يشغل Postgres والبوابة محليًا |

إنشاء خدمة Rust
ابدأ بمشروع Rust ثنائي جديد:Cargo.toml - تمت إضافة الشروحات في مقتطف الكود:
تحميل التكوين
تقرأ البوابة كل شيء من متغيرات البيئة. بالنسبة لكود البنية التحتية مثل هذا، تُعد متغيرات البيئة افتراضًا جيدًا لأنه يمكن تشغيل نفس الملف الثنائي محليًا، أو في Docker Compose، أو في بيئة مستضافة دون تنسيق ملف تكوين منفصل. بالإضافة إلى ذلك، سيسمح لك العديد من المزودين بتخزين متغيرات البيئة الخاصة بك كأسرار في وقت تشغيل الحاوية الخاص بهم. غالبًا ما يكون هذا أكثر أمانًا من محاولة استخدام شيء مثلdotenv (أو dotenvy في Rust، حيث أن صندوق dotenv الأصلي مهجور في الغالب).
قم بإنشاء src/config.rs:
- رابط قاعدة البيانات
- مفتاح Venice API الخاص بك
VENICE_BASE_URL يشير إلى https://api.venice.ai/api/v1، و CAPTURE_GENAI_CONTENT يكون افتراضيًا false بحيث لا يتم تسجيل محتوى المطالبة إلا إذا قمت بتمكينه عن قصد.
هذا الافتراضي الثاني هو الأكثر أهمية. يمكن للبوابة أن ترى كل مطالبة واستجابة تتدفق من خلالها، لكن قابلية المراقبة يجب ألا تصبح تلقائيًا التقاطًا للمحتوى. في معظم أنظمة الإنتاج، تُعد أعداد الرموز، والزمن، وأسماء النماذج، ورموز الحالة، وبيانات الفوترة الوصفية كافية للعمليات. بشكل عام، قد لا يكون تسجيل المطالبات والمحادثات في الإنتاج مسؤولية خصوصية فحسب - بل يمكن أن يكون أيضًا مسؤولية تخزين. تضيف إضافتها إنشاء امتدادات وآثار بمستوى عالٍ للغاية من التعدد (أي تفرد البيانات داخل مجموعة البيانات). يمكن أن يجعل هذا البحث في بيانات المراقبة الخاصة بك مكلفًا للغاية، بالإضافة إلى احتمال إعاقة الأداء عند البحث في البيانات.
إنشاء مخطط قاعدة البيانات
بعد ذلك، قم بإنشاءmigrations/0001_api_keys.sql.
سنقوم بتخزين أول 12 حرفًا فقط من كل مفتاح API للبوابة كبادئة بحث، بالإضافة إلى تجزئة SHA-256 للمفتاح الكامل. يتيح ذلك للبوابة العثور على صف مرشح بسرعة دون تخزين بيانات الاعتماد الأولية.
البادئة ليست سرية. وهي موجودة للفهرسة. التجزئة هي ما يثبت أن المستدعي قدم المفتاح الكامل. هذا هو نفس الشكل الأساسي الذي تستخدمه العديد من أنظمة مفاتيح API: عرض المفتاح الأولي مرة واحدة، وتخزين تمثيل غير قابل للعكس، والاحتفاظ ببادئة قصيرة للبحث وسير عمل الدعم.
- لا يتم تخزين مفاتيح API أبدًا بنص عادي.
- يجب أن تكون إعدادات حد المعدل موجبة.
- لا يمكن أن تظل المفاتيح المُبطلة نشطة.
- يتم تحديد نافذة حد المعدل بشكل فريد بواسطة المفتاح ووقت البدء وطول النافذة.
بناء عميل Venice
بعد ذلك، سنقوم بإنشاءsrc/venice.rs. يحتاج العميل فقط إلى معرفة رابط إكمال الدردشة العلوي، ومفتاح Venice API، وعدد مرات إعادة محاولة الإخفاقات العابرة.
الحفاظ على هذا الغلاف صغيرًا أمر مقصود - يجب ألا تعيد البوابة تنفيذ واجهة برمجة تطبيقات Venice بالكامل. على المستوى الأساسي، تتمثل مهمة البوابة في إرفاق بيانات الاعتماد من جانب الخادم، وتطبيق مهلة، وإعادة محاولة الطلبات الآمنة للإعادة، وإرجاع الاستجابة العلوية في شكل يمكن للموجه إعادة توجيهه.
EventSource من نفس الطلب:
serde_json::Value. هذا اختيار توافق متعمد. إذا قمنا بنمذجة كل حقل إكمال دردشة ممكن في Rust، فعلينا الاستمرار في تحديث البوابة في كل مرة تضيف فيها واجهة برمجة التطبيقات العلوية خيارًا مفيدًا. من خلال تحليل ما نحتاجه فقط في مكان آخر، نسمح لمعلمات Venice الأحدث بالمرور دون إصدار بوابة.
مشاركة حالة التطبيق
قم بإنشاءsrc/state.rs:
PgPool هو بالفعل مقبض تجمع مشترك، و Arc<Config> يبقي التكوين رخيصًا أيضًا.
يمنح هذا كل معالج الوصول إلى نفس الأشياء الثلاثة: التكوين غير القابل للتغيير، واتصالات قاعدة البيانات المجمعة، وعميل Venice. يسهل الاحتفاظ بها في AppState واحد الاختبار لاحقًا لأن المعالجات تتلقى تبعياتها من خلال حالة Axum بدلاً من قراءة المتغيرات العامة.
مصادقة مفاتيح API للبوابة
يرسل العميل مفتاح البوابة الخاص به على النحو التالي:src/auth.rs وتنفيذ مستخرج Axum. يتيح المستخرج للمعالجات المحمية أن تصرح بأنها تتطلب مفتاحًا مصادقًا عليه:
- تحليل رمز الحامل.
- أخذ أول 12 بايت كبادئة مفتاح.
- تجزئة الرمز المرشح الكامل باستخدام SHA-256.
- تحميل صف المفتاح النشط بواسطة البادئة.
- مقارنة التجزئة المخزنة والتجزئة المرشحة في وقت ثابت.
AuthenticatedApiKey تخطي المصادقة عن طريق الخطأ داخل جسم الوظيفة؛ يجب على Axum بناء هذه القيمة قبل تشغيل المعالج. هذا يجعل المسار المحمي سهل التدقيق.
إضافة حدود معدل النافذة الثابتة
قم بإنشاءsrc/rate_limit.rs. يستخدم محدد المعدل عبارة SQL واحدة لإدراج نافذة جديدة أو زيادة النافذة الموجودة:
WHERE api_key_rate_limit_windows.request_count < $3 هي الجزء المهم. عندما تكون النافذة ممتلئة بالفعل، لا يقوم Postgres بتحديث الصف ولا ينتج RETURNING أي صف. يمكن للمعالج تحويل ذلك إلى استجابة 429 Too Many Requests مع رأس Retry-After.
النافذة الثابتة ليست أكثر محددات المعدل تطورًا، لكنها سهلة الشرح، وسهلة الفحص، وجيدة بما يكفي لبرنامج تعليمي عن البوابة. المفاضلة هي أن حركة المرور يمكن أن تتراكم حول حدود النافذة. إذا كنت بحاجة إلى سلوك أكثر سلاسة على نطاق واسع، فإن محدد سلة الرموز أو محدد النافذة المنزلقة المدعوم بـ Redis هو خطوة تالية طبيعية.
إرجاع أخطاء بأسلوب OpenAI
قم بإنشاءsrc/error.rs واجعل أخطاء التطبيق تنفذ IntoResponse:
بناء الموجه
الآن يمكننا توصيل مسارات HTTP فيsrc/router.rs:
AuthenticatedApiKey. إذا فشلت المصادقة، فلن يدخل Axum جسم المعالج أبدًا:
model و messages و stream. يمر كل شيء آخر في جسم JSON إلى Venice. هذا يبقي البوابة متوافقة مع ميزات المزود التي قد ترغب في استخدامها لاحقًا.
يجعل المعالج أيضًا وضعي الاستجابة صريحين. تنتظر الطلبات غير المتدفقة أن يقوم Venice بإرجاع استجابة JSON كاملة، ثم تسجل بيانات الاستجابة الوصفية قبل إرسال البايتات إلى المصدر. تعيد طلبات البث فورًا مع جسم text/event-stream مدعومًا بتدفق غير متزامن. يبقي هذا التقسيم المسار غير المتدفق بسيطًا مع إعطاء مسار البث تحكمًا كافيًا لمراقبة القطع أثناء مرورها.
دعم استجابات البث
تستخدم إكمالات دردشة البث الأحداث المرسلة من الخادم. يرسل Venice بيانات SSE، وتقوم البوابة بترحيل تلك البيانات إلى العميل. يجب على البوابة تجنب تخزين البث بالكامل مؤقتًا لأن ذلك سيهزم الغرض من البث. يهتم المستخدمون بالوقت اللازم للحصول على الرمز الأول، وليس فقط بالوقت اللازم للرمز النهائي. من خلال إعادة توجيه كل حدث علوي عند وصوله، يمكن للعملاء عرض المخرجات الجزئية بينما لا يزال النموذج يولد. قم بإنشاءsrc/sse.rs:
data: ...، وينتهي البث بـ data: [DONE].
مراقب البث هو أيضًا المكان الذي يمكننا فيه جمع البيانات الوصفية دون تغيير ما يراه العميل. يتم إعادة توجيه كل قطعة بتنسيق SSE، ولكن لا يزال بإمكان البوابة مراقبة معرفات الاستجابة، وأسباب الإنهاء، واستخدام الرموز، وحقول التكلفة، ومعلومات التوقيت أثناء مرور تلك القطع.
تسجيل قياسات GenAI عن بُعد
البوابات مفيدة لأن كل طلب يمر عبر مكان واحد. هذا يجعلها مكانًا رائعًا لتسجيل النموذج، والزمن، واستخدام الرموز، وأسباب الإنهاء، وتكلفة الفوترة، وتوقيتات البث. قم بإنشاءsrc/telemetry.rs وابدأ بتحليل الطلب:
id استجابة Venice:
بدء تشغيل الخادم
الآن قم بتوصيل كل شيء معًا فيsrc/main.rs:
- قراءة التكوين.
- تهيئة القياس عن بُعد.
- الاتصال بـ Postgres.
- تشغيل ترحيلات SQLx.
- بناء حالة التطبيق المشتركة.
- بدء تشغيل خادم Axum.
docker compose up يمكنه إحضار المكدس بأكمله إلى حالة عمل. في نشر إنتاج أكبر، قد تفضل تشغيل الترحيلات كخطوة إصدار منفصلة بحيث تتم مراجعة تغييرات المخطط وتطبيقها قبل بدء تشغيل مثيلات بوابة جديدة.
زرع مفتاح بوابة محلي
للتطوير المحلي، قم بإنشاءscripts/seed_api_key.sh. يقوم البرنامج النصي بإدراج مفتاح API للبوابة في Postgres عن طريق تخزين بادئته وتجزئة SHA-256 الخاصة به:
التشغيل محليًا
للتشغيل محليًا، سنستخدم Docker Compose لبدء كل من البوابة و Postgres. يبقي هذا البرنامج التعليمي قابلاً للتكرار: لا يحتاج القراء إلى قاعدة بيانات مُهيأة يدويًا، ويمكن للبوابة استخدام نفس شكلDATABASE_URL الذي ستستخدمه في نشر حاوي.
-d إذا كنت تريد استخدام الطرفية الخاصة بك لأشياء أخرى بعد ذلك (ثم استخدم docker compose down لإزالته).
في طرفية أخرى، قم بزرع مفتاح بوابة التطوير:
5432، فقم بإزالة تعيين منفذ المضيف لخدمة Postgres في Compose. تحتاج البوابة فقط إلى الوصول إلى Postgres على شبكة Docker الداخلية.
الشيء المهم الذي يجب مراعاته هو أن مفتاح Venice API ينتمي فقط إلى بيئة البوابة. يجب أن تستخدم طلبات العميل مفتاح البوابة المزروع. هذا الفصل هو الهدف الكامل من وضع بوابة أمام مزود النموذج.
اختبار البوابة
أولاً، تحقق من الحالة الصحية:توسيع هذه البوابة
هذه البوابة صغيرة عن قصد، لكنها تمنحك أساسًا صلبًا. تشمل الخطوات التالية الجيدة:- إضافة ميزانيات لكل موضوع وحدود إنفاق شهرية.
- دعم مزودين علويين متعددين خلف نفس واجهة برمجة التطبيقات المتوافقة مع OpenAI.
- تخزين البيانات الوصفية للطلب لسجلات التدقيق مع إبقاء تسجيل المطالبات معطلاً افتراضيًا.
- إضافة واجهة برمجة تطبيقات إدارية لإنشاء مفاتيح البوابة وإبطالها وتدويرها.
- إضافة قوائم النماذج المسموح بها لكل مفتاح API.
- إضافة Redis أو مخزن مشترك آخر إذا كنت بحاجة إلى تحديد معدل بزمن أقل عبر العديد من مثيلات البوابة.