ابنِ بوابة API آمنة للذكاء الاصطناعي باستخدام Node.js

Share:

ابنِ بوابة صغيرة لـ API الذكاء الاصطناعي باستخدام Node.js تستقبل JSON مُتحقَّقاً منه، وتُسند معرّفات للطلبات، وتطبّق حداً محلياً لكل عميل، وتُمرّر الطلبات المعتمدة إلى خدمة نماذج على الخادم من دون كشف بيانات اعتماد المزوّد في كود المتصفح.

المتطلبات المسبقة

  • Node.js ١٨ أو إصدار أحدث. يستخدم هذا الدرس التقني إمكانات وقت التشغيل المدمجة في http وcrypto وfetch.
  • إلمام أساسي بـ JavaScript وJSON وطلبات HTTP ومتغيرات البيئة والطرفية.
  • خدمة نماذج مسموح لبوابتك باستدعائها عبر HTTP. لا تتضمن البوابة نموذجاً أو SDK لمزوّد نماذج.
  • عميل REST مثل curl أو Postman أو Bruno أو Insomnia أو إضافة VS Code REST Client.

ما الذي ستبنيه

يبني هذا الدرس التقني بوابة صغيرة عمداً لـ API الذكاء الاصطناعي باستخدام Node.js. يرسل العميل طلب JSON إلى POST /api/chat. تتحقق البوابة من أن الطلب يطابق البنية المتوقعة، وترفض الحمولات كبيرة الحجم، وتُسند معرّف طلب، وتطبّق تحديداً محلياً للمعدل، ثم تمرّر الطلب المتحقق منه إلى خدمة نماذج منفصلة عبر HTTP. بعد ذلك، تعيد البوابة استجابة خدمة النماذج إلى المستدعي.

ترتكز الفكرة المعمارية على فصل شائع للمسؤوليات: إذ تخدم الواجهة الخلفية للويب واجهات HTTP API، بينما تؤدي خدمة النماذج المهمة التنبؤية الأساسية. يصف السياق المتحقق منه مثالاً لمكدس يتضمن واجهة ويب أمامية، وموازن تحميل، وواجهة خلفية لـ REST API باستخدام Node.js، وقائمة مهام موزعة، وخدمة نماذج. تطبق هذه المقالة حدود بوابة Node.js فقط. ولا تدّعي أنها تفرض معمارية إنتاجية مكتملة، أو مورداً محدداً للنماذج، أو إطار عمل معيناً لخدمة النماذج.

إن إبقاء البوابة منفصلة عن خدمة النماذج يمنح التطبيق موضعاً واحداً لتطبيق قواعد الإدخال والضوابط التشغيلية قبل وصول العمل إلى نظام ذكاء اصطناعي. كما يعني أن تطبيقات المتصفح تستدعي خدمتك بدلاً من تلقي بيانات اعتماد حساسة للخدمة المصدرية. وتعتمد متطلبات المصادقة والتفويض والاحتفاظ بالبيانات ومراجعة السلامة والنشر الدقيقة على مؤسستك وحالة الاستخدام لديك، ولذلك لم تُمثَّل كإعدادات افتراضية عامة في هذا المثال.

يتجنب التنفيذ استدعاءات SDK الخاصة بالمزوّد التي لم يتم التحقق منها، وأسماء النماذج، وحدود الرموز المميزة، ومخططات استجابات النماذج. وبدلاً من ذلك، يعرّف عقد JSON صغيراً تملكه هذه التطبيق. وهذا مفيد عندما تكون خدمة النماذج خدمة داخلية، أو API استدلال تُشغَّل بصورة منفصلة، أو محولاً تحتفظ به في مكان آخر.

مسؤوليات البوابة وحدودها

ينبغي أن تكون مهمة البوابة محددة. في هذا الدرس التقني، تؤدي خمسة أمور: تستقبل طلبات HTTP، وتُحلل JSON محدود الحجم، وتتحقق من الحقول المطلوبة، وتسجل معرّف طلب معتماً، وتمرّر مدخلات آمنة إلى خدمة نماذج مصدرية. ولا تحاول تقرير ما إذا كانت الإجابة المولدة صحيحة. ولا تدرّب نموذجاً. ولا تضمّن المفاتيح في حزمة الواجهة الأمامية. كما أنها لا تفترض أن كل خدمة نماذج تستخدم بنية الطلب أو الاستجابة نفسها.

عقد الطلب العام لدينا بسيط عمداً:

{
  "messages": [
    { "role": "user", "content": "Explain an API gateway." }
  ]
}

تقبل البوابة ما يصل إلى ٢٠ رسالة، وتسمح فقط بالأدوار system وuser وassistant، وتحد كل رسالة بـ ١٢٬٠٠٠ حرف افتراضياً. هذه ضوابط على مستوى التطبيق وليست قياسات للرموز المميزة. فالأحرف ورموز النموذج المميزة ليست الوحدة نفسها، لذا لا ينبغي تقديم حد الأحرف على أنه حد دقيق للتكلفة أو الاستخدام.

تتلقى الخدمة المصدرية في هذا الدرس التقني غلافاً يحتوي على معرّف طلب والرسائل المتحقق منها. والاستجابة الناجحة المتوقعة منها هي JSON. لا تحوّل البوابة JSON هذا إلى تنسيق إكمال محايد للمزوّد، لأنه لم يتم التحقق من أي تنسيق لاستجابة مزوّد في السياق المتاح. إن امتلاك عقد داخلي صغير وموثّق أكثر أماناً من التخمين بشأن API لمزوّد ما.

الخطوة ١: أنشئ المشروع وملف البيئة

أنشئ دليلاً جديداً للمشروع. لا يستخدم هذا الإصدار أي تبعية خارجية، مما يجعل المثال سهل الفحص ويحافظ على سطح وقت تشغيل محدود. يكفي خادم HTTP المدمج في Node.js لعرض بوابة مركّز.

mkdir nodejs-ai-api-gateway
cd nodejs-ai-api-gateway
npm init -y
npm pkg set type=module
npm pkg set scripts.start="node src/server.js"
npm pkg set scripts.dev="node --watch src/server.js"
npm pkg set engines.node=">=18.0.0"
mkdir -p src

أنشئ ملف .gitignore قبل إنشاء الإعدادات المحلية. لا ترفع بيانات الاعتماد أو ملفات البيئة الخاصة بالنشر إلى نظام التحكم بالمصادر مطلقاً.

node_modules
.env
.env.local
.env.production
logs
coverage
npm-debug.log*
.DS_Store

بعد ذلك، أنشئ .env. يُعد MODEL_SERVICE_URL الإعداد المصدر الوحيد المطلوب. يجب أن يشير الرابط إلى خدمة يمكن لبيئة النشر الوصول إليها ومصرح لها باستخدامها. يستخدم المثال رابط loopback كقيمة للتطوير المحلي فقط.

PORT=3001
MODEL_SERVICE_URL=http://127.0.0.1:8080/generate
ALLOWED_ORIGIN=http://localhost:3000
MAX_BODY_BYTES=262144
MAX_MESSAGE_CHARS=12000
MAX_CONVERSATION_MESSAGES=20
REQUESTS_PER_MINUTE=30
UPSTREAM_TIMEOUT_MS=30000

لا تضع بيانات اعتماد الخدمة المصدرية في متغيرات بيئة مكشوفة للمتصفح. إذا كانت خدمة النماذج تتطلب بيانات اعتماد، فاحتفظ بها في بيئة النشر من جهة الخادم للبوابة وأرفقها على الخادم فقط. لا يتضمن هذا الدرس التقني ترويسة تفويض لأن اسمها وتنسيقها ودورة حياة بيانات الاعتماد الخاصة بها غير محددة في السياق المتحقق منه.

الخطوة ٢: تحقّق من الإعداد عند بدء التشغيل

أنشئ src/config.js. تصل متغيرات البيئة كسلاسل نصية، لذلك تحلل الوحدة القيم الرقمية صراحةً وتفشل مبكراً عندما يكون الإعداد المطلوب غير صالح. يُفضَّل التحقق عند بدء التشغيل على اكتشاف منفذ أو رابط مشوهين بعد وصول حركة المرور فقط.

import process from "node:process";

function readPositiveInteger(name, fallback, minimum, maximum) {
  const raw = process.env[name] ?? String(fallback);
  const value = Number.parseInt(raw, 10);

  if (!Number.isInteger(value) || value < minimum || value > maximum) {
    throw new Error(`${name} must be an integer from ${minimum} to ${maximum}.`);
  }

  return value;
}

function readUrl(name) {
  const raw = process.env[name];

  if (!raw) {
    throw new Error(`${name} is required.`);
  }

  try {
    return new URL(raw).toString();
  } catch {
    throw new Error(`${name} must be a valid URL.`);
  }
}

export const config = Object.freeze({
  port: readPositiveInteger("PORT", 3001, 1, 65535),
  modelServiceUrl: readUrl("MODEL_SERVICE_URL"),
  allowedOrigin: process.env.ALLOWED_ORIGIN ?? "http://localhost:3000",
  maxBodyBytes: readPositiveInteger("MAX_BODY_BYTES", 262144, 1024, 1048576),
  maxMessageChars: readPositiveInteger("MAX_MESSAGE_CHARS", 12000, 1, 100000),
  maxConversationMessages: readPositiveInteger(
    "MAX_CONVERSATION_MESSAGES",
    20,
    1,
    100
  ),
  requestsPerMinute: readPositiveInteger(
    "REQUESTS_PER_MINUTE",
    30,
    1,
    10000
  ),
  upstreamTimeoutMs: readPositiveInteger(
    "UPSTREAM_TIMEOUT_MS",
    30000,
    1000,
    120000
  )
});

للتطوير...

تابع القراءة

سجل دخولك مجاناً لقراءة المقال كاملاً والوصول إلى أدوات الذكاء الاصطناعي.

تسجيل الدخول / إنشاء حساب

هل كان هذا الشرح مفيداً؟

مرشد بوابة الذكاء الاصطناعي
نشط للخدمة
مرحباً بك في بوابة الذكاء الاصطناعي! أنا مرشدك هنا لمساعدتك في فهم خدمات المنصة، باقات التشغيل وخطط الضمان المالي. كيف يمكنني إرشادك اليوم؟