من دفتر إلى خدمة
درس 1 من 6
1من دفتر إلى خدمة
الصورة الكبيرةنموذج يعمل في دفتر Jupyter ليس نظامًا. النظام نقطة نهاية (Endpoint) يرسل لها أي برنامج طلبًا ويتلقّى ردًّا، ونموذج يُحمَّل مرة واحدة عند البدء لا مع كل طلب، وتحقّق من المدخلات قبل أن تصل للنموذج، وفحص صحة يخبر من يراقب الخدمة أنها حيّة وجاهزة.
ماذا تشاهد في الرسم: الطلب يمرّ على التحقّق قبل أن يلمس النموذج، فالنص الفارغ يعود بـ422 والجسم غير الصالح بـ400. والنموذج في المنتصف حُمّل عند بدء الخدمة لا مع كل طلب، والرد يحمل نسخته.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
النموذج في دفتر التجارب مثل وصفة في دفتر طبّاخ: تعمل عنده فقط. لكي يستخدمه الناس، تفتح مطعمًا: نافذة تستقبل الطلبات، وتجهّز المطبخ مرة واحدة في الصباح لا مع كل طلب، وتضع لافتة تقول «مفتوح» يراها المارة.
تغلّف النموذج بواجهة مثل FastAPI بنقطة POST /predict ومخطط مدخل ومخرج بـpydantic، وتحمّل النموذج مرة واحدة عند بدء الخدمة لا في كل طلب. ونقطة صحة GET /health تعيد حالة الخدمة وإصدار النموذج لأدوات المراقبة والموازنة.
قبل أن تبدأ
📞استدعاء نموذج
استدعيت نماذج عبر API، وتعرف شكل الطلب والرد بـJSON.
🐍دوال بايثون
تكتب دالة تأخذ مدخلًا وتعيد نتيجة، وتعرف الاستثناءات.
الفكرة الجوهرية
الخدمة نقطة يرسل لها أي برنامج طلبًا ويأخذ ردًّا. تبني هنا تحميل النموذج مرة واحدة، ثم معالج الطلبات، ثم رموز الحالة، ثم فحص الجاهزية مع FastAPI.
1حمّل النموذج مرة واحدة
تحميل النموذج يأخذ ثوانيَ إلى دقائق ويستهلك ذاكرة كثيرة. لو حدث مع كل طلب لصارت الخدمة غير صالحة للاستخدام.
انظر إلى الرسم: أربعة طلبات. التحميل داخل الطلب يعني أربع مرات، والتحميل عند البدء يعني مرة واحدة يتشاركها الجميع.
2معالج الطلب: تحقّق ثم توقّع
النقطة عنوان مثل POST /predict يأخذ JSON ويردّ JSON بشكل ثابت وموثّق. وقبل أن يصل أي شيء إلى النموذج نتحقّق من النوع والطول والحقول المطلوبة.
قبل FastAPI، لنرَ ما يفعله أي إطار: يستقبل نصًّا، ويتحقّق منه، ويستدعي النموذج المحمّل مسبقًا، ويعيد رمز حالة ورد JSON.
import json
class SentimentModel:
loads = 0
def __init__(self):
SentimentModel.loads += 1 # in real life: seconds of reading weights from disk
self.version = "sentiment-2026-05-01"
def predict(self, text):
return "positive" if any(w in text for w in ["ممتاز", "شكرا", "رائع"]) else "negative"
MODEL = SentimentModel() # loaded once, when the service starts
def handle(body):
try:
data = json.loads(body)
except json.JSONDecodeError:
return 400, {"error": "body must be JSON"}
text = data.get("text")
if not isinstance(text, str) or not text.strip():
return 422, {"error": "field 'text' must be a non-empty string"}
if len(text) > 2000:
return 422, {"error": "text is longer than 2000 characters"}
return 200, {"label": MODEL.predict(text), "model_version": MODEL.version}
def health():
return 200, {"status": "ok", "model_version": MODEL.version}
for body in ['{"text": "خدمة ممتاز وشكرا"}', '{"text": ""}', 'not json', '{"text": "تأخر الطلب"}']:
print(handle(body))
print(health())
print("model loads after 4 requests:", SentimentModel.loads)
أربعة طلبات والنموذج حُمّل مرة واحدة. والرموز تقول للعميل ما حدث: 200 نجاح، و400 الطلب نفسه معطوب، و422 الطلب سليم الشكل لكن محتواه غير مقبول. وكل رد ناجح يحمل نسخة النموذج، فحين يشتكي أحد من إجابة تعرف أي نموذج أعطاها.
3رموز الحالة
رمز الحالة يقول للعميل ما حدث قبل أن يقرأ الرد. انظر إلى الرسم: ثلاثة رموز تكفي لمعظم الحالات.
400 يعني أن الطلب نفسه معطوب، مثل JSON غير صالح. و422 يعني أن الطلب سليم الشكل لكن محتواه غير مقبول، مثل نص فارغ أو طويل جدًا.
4FastAPI وفحص الجاهزية
الخدمة حيّة بمجرد أن يعمل الخادم، لكنها ليست جاهزة حتى ينتهي تحميل النموذج. لذلك نفصل فحصين: /health/live و /health/ready.
انظر إلى الرسم: منصّة النشر لا ترسل الطلبات إلا بعد أن يردّ فحص الجاهزية بـ 200.
هذا ما ستكتبه فعليًّا. يحتاج pip install fastapi uvicorn، فهو للقراءة هنا.
from contextlib import asynccontextmanager
from fastapi import FastAPI
from pydantic import BaseModel, Field
state = {}
@asynccontextmanager
async def lifespan(app):
state["model"] = load_model("/models/sentiment-v3") # once, before the first request
yield
state.clear()
app = FastAPI(lifespan=lifespan)
class PredictIn(BaseModel):
text: str = Field(min_length=1, max_length=2000)
class PredictOut(BaseModel):
label: str
model_version: str
@app.post("/predict", response_model=PredictOut)
def predict(body: PredictIn):
model = state["model"]
return PredictOut(label=model.predict(body.text), model_version=model.version)
@app.get("/health/ready")
def ready():
return {"status": "ok", "model_version": state["model"].version}
lifespan يحمّل النموذج قبل أول طلب، وPydantic يتحقّق من المدخلات ويعيد 422 بتفاصيل الخطأ تلقائيًّا، وتوثيق تفاعلي يظهر على /docs دون سطر إضافي. شغّله بـuvicorn app:app وجرّبه من المتصفح.
كيف يعمل، خطوة بخطوة: كيف تخدم الخدمة طلبًا
عند البدء يُحمَّل النموذج مرة واحدة، و/health/ready لا يعيد 200 إلا بعد اكتمال التحميل.
يصل POST /predict بجسم JSON، فيُفحص نوعه وطوله وحقوله المطلوبة.
إن كان الجسم معطوبًا يعود 400، وإن كان المحتوى غير مقبول يعود 422 برسالة تقول ما الخطأ.
إن نجح الفحص يتنبّأ النموذج المحمّل، ويعود الرد بشكل ثابت فيه النتيجة ونسخة النموذج.
أعمق قليلًا
للنماذج اللغوية الكبيرة لا تكتب الخدمة من الصفر. خوادم مخصّصة مثل vLLM تتولّى التحميل، والتجميع الذكي للطلبات، وإدارة ذاكرة السياق، وتعرض واجهة متوافقة مع OpenAI مباشرة. FastAPI مناسب لنماذجك الصغيرة والمتوسطة ولطبقة منطق العمل حول نموذج لغوي يعمل في خادم مخصّص.
مثال محلول
الخطأ الأول في كل خدمة نموذج تقريبًا: تحميل النموذج داخل دالة الطلب. تحميل نموذج يستغرق ثواني إلى دقائق، فإن حدث مع كل طلب صارت الخدمة غير قابلة للاستخدام. الصحيح أن يُحمَّل مرة عند بدء الخدمة ويبقى في الذاكرة.
async def تستدعي نموذجًا ثقيلًا متزامنًا. هذا يجمّد حلقة الأحداث كلها أثناء التنبّؤ، فلا يُخدم أي طلب آخر ولا حتى فحص الصحة. استخدم def العادية للكود الحسابي المتزامن، وFastAPI يشغّلها في خيط منفصل.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| الخدمة جاهزة ما دام الخادم يعمل. | حيّة نعم، لكنها ليست جاهزة حتى يكتمل تحميل النموذج، وقد يستغرق دقيقة. لهذا فحصان: حيّة وجاهزة. |
async def أسرع دائمًا في FastAPI. | مع نموذج ثقيل متزامن يجمّد حلقة الأحداث فلا يُخدم أي طلب آخر. استخدم def عادية للحساب المتزامن. |
| خطأ في المدخل يستحق 500. | 500 تعني خطأ في الخادم. مشكلة في الطلب نفسه تعيد 400 أو 422 حتى يعرف العميل أن عليه إصلاح مدخله. |
جرّب بنفسك
الخدمة بطيئة جدًّا: كل طلب يستغرق 8 ثوانٍ، والنموذج نفسه يتنبّأ في 50 ملّي ثانية. ما السبب المرجّح؟
الفرق الهائل بين زمن التنبّؤ وزمن الطلب يعني عملًا ثابتًا مع كل طلب، والتحميل أشهر مثال. انقله إلى بدء الخدمة.
أي رمز حالة تعيده لطلب JSON سليم حقل text فيه فارغ؟
500 يعني خطأ في الخادم، والمشكلة هنا في الطلب. 422 يخبر العميل أن يصلح مدخله، ومعه رسالة تقول ما الخطأ.
بطاقات مراجعة سريعة
POST /predict يستقبل JSON ويعيد JSON، بشكل ثابت موثّق يعتمد عليه أي عميل.GET /health يعيد 200 حين تكون الخدمة جاهزة فعلًا والنموذج محمّلًا. منصّات النشر تستخدمه لتعرف متى ترسل الطلبات ومتى تعيد التشغيل.تحقق من فهمك
لماذا يحمل كل رد نسخة النموذج؟
لأنك ستنشر نسخًا جديدة، وقد تعمل نسختان معًا أثناء الانتقال. حين يشتكي مستخدم من إجابة، أو ترى انخفاضًا في مقياس، تحتاج أن تعرف أي نموذج أعطاها. بدون النسخة في الرد والسجل، التشخيص تخمين.
لماذا تضع حدًّا أقصى لطول النص، مثل 2000 حرف؟
لأن نصًّا بمئة ألف حرف قد يأكل ذاكرة الخادم أو يحجز النموذج دقائق عن الجميع. الحد مع رسالة مفهومة يحمي الخدمة وكل مستخدميها.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي1
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.