انتقل إلى المحتوى الرئيسي

الوحدة 10 — مشروع: عرض توضيحي لنموذج لغوي

الوحدات التسع السابقة قدّمت قطع لغز. هذه الوحدة تُركّبها في مشروع واحد: مساعد لغويّ منشور على مساحة Hugging Face، بعنوان دائم، مع أمثلة وبثّ وقائمة انتظار وجمع ملاحظات. الهدف ليس اكتشاف ميزة جديدة، بل رؤية كيف تتزاوج الميزات في تطبيق قابل للاستعمال يوميّاً.

المتطلّبات وقرارات التصميم

قبل أيّ سطر شيفرة، قرارات صريحة:

  • الجمهور: طلّاب علوم البيانات في العالم العربيّ.
  • حالة الاستعمال: طرح أسئلة تعليميّة حول الشبكات العصبيّة والتعلّم الآليّ.
  • النموذج: gpt-4o-mini عبر واجهة سحابيّة، حسّاسة الكلفة لكنّها ممتازة على العربيّة.
  • العتاد: CPU basic على مساحة Hugging Face — النموذج سحابيّ، فلا حاجة لـ GPU.
  • الحدود المُعلَنة: مساعد تعليميّ، لا يُقدّم استشارات طبّيّة أو قانونيّة، ولا يعتمد رأيه.
  • الميزانيّة الشهريّة: 20 دولارًا (USD) كحدّ أعلى؛ ما زاد يُوقف الخدمة.

هذه القرارات مكتوبة في README.md من البداية، فتحكم كلّ اختيار لاحق.

بنية المشروع

mon-assistant/
├── app.py # تطبيق Gradio
├── requirements.txt # الاعتماديّات
├── README.md # بيانات المساحة والوصف
├── examples/
│ └── questions.json # أمثلة قابلة للتحميل
└── data/
└── flags.jsonl # ملاحظات المستخدمين (نمو تلقائيّ)

الشيفرة الكاملة app.py

import gradio as gr
import os, json
from openai import OpenAI
from pathlib import Path

# 1. إعداد العميل
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
MODEL = "gpt-4o-mini"
DEFAULT_SYSTEME = (
"أنت مساعد تعليميّ في علوم البيانات والذكاء الاصطناعيّ. "
"تُجيب بالعربيّة الفصحى، بإيجاز، مع مثال شيفرة عند الحاجة. "
"لا تُقدّم استشارات طبّيّة أو قانونيّة. تصرّح صراحةً عندما لا تعرف."
)

# 2. دالّة الجواب مع بثّ تدريجيّ
def repondre(message, historique, systeme, temperature):
messages = [{"role": "system", "content": systeme}] + historique + [
{"role": "user", "content": message},
]
try:
flux = client.chat.completions.create(
model=MODEL,
messages=messages,
temperature=temperature,
max_tokens=800,
stream=True,
)
accumule = ""
for morceau in flux:
delta = morceau.choices[0].delta.content or ""
accumule += delta
yield accumule
except Exception as e:
yield f"حدث خطأ: {str(e)[:200]}. حاول لاحقًا."

# 3. أمثلة تُظهر النجاحات والحدود
EXEMPLES = [
"اشرح لي مبدأ الانتشار العكسي بشيفرة PyTorch مبسّطة.",
"ما الفرق العمليّ بين Adam وSGD؟",
"هل النموذج قادر على تشخيص مرضي؟", # يظهر حدّ الرفض
"لخّص مقالة أكاديميّة عن Transformers لطالب مبتدئ.",
"اكتب لي شيفرة لتحميل جدول CSV وحساب متوسّط عمود.",
]

# 4. جمع ملاحظات المستخدمين
FICHIER_FLAG = Path("data/flags.jsonl")
FICHIER_FLAG.parent.mkdir(exist_ok=True)

def enregistrer_flag(message, reponse, evaluation):
ligne = {
"message": message,
"reponse": reponse,
"evaluation": evaluation,
}
with FICHIER_FLAG.open("a", encoding="utf-8") as f:
f.write(json.dumps(ligne, ensure_ascii=False) + "\n")
return "شكرًا! تمّ تسجيل ملاحظتك."

# 5. الواجهة الكاملة
with gr.Blocks(title="مساعد علوم البيانات") as demo:
gr.Markdown("# مساعد علوم البيانات\nاسأل بالعربيّة، اطّلع على شيفرة، تعلّم.")

systeme = gr.Textbox(
value=DEFAULT_SYSTEME,
label="رسالة النظام (قابلة للتحرير)",
lines=3,
rtl=True,
)
temperature = gr.Slider(0.0, 1.0, value=0.2, step=0.1, label="درجة الحرارة")

chat = gr.ChatInterface(
fn=repondre,
additional_inputs=[systeme, temperature],
examples=EXEMPLES,
title=None,
description=None,
)

with gr.Row():
avis_msg = gr.Textbox(label="آخر رسالة", visible=False)
avis_rep = gr.Textbox(label="آخر جواب", visible=False)
note = gr.Radio(["مفيد", "غير كافٍ", "خاطئ"], label="قيّم آخر جواب")
b_flag = gr.Button("أرسل التقييم")
merci = gr.Textbox(label="", interactive=False)

b_flag.click(
fn=enregistrer_flag,
inputs=[avis_msg, avis_rep, note],
outputs=merci,
)

demo.queue(default_concurrency_limit=3, max_size=20).launch()

requirements.txt

gradio>=5.0
openai>=1.30

README.md مع بيانات وصفيّة

---
title: مساعد علوم البيانات
emoji: 📚
colorFrom: blue
colorTo: purple
sdk: gradio
sdk_version: 5.0.0
app_file: app.py
pinned: false
license: mit
short_description: مساعد عربيّ لعلوم البيانات مع بثّ تدريجيّ
---

# مساعد علوم البيانات — نموذج لغويّ عربيّ

مساعد تعليميّ للطلّاب باللغة العربيّة. مبنيّ على `gpt-4o-mini`
مع Gradio 5. البثّ التدريجيّ مُفعَّل، وقائمة الانتظار محدودة بـ 3
مستخدمين متزامنين.

## الحدود
- ليس بديلًا لطبيب أو محامٍ.
- الأجوبة قد تحتوي أخطاء؛ راجع دائمًا مصادر أوّليّة.
- المحادثات لا تُخزَّن، لكنّ التقييمات المُرسَلة تُحفظ.

## المصاريف
تُموَّل المساحة شخصيًّا؛ حدّ شهريّ 20 دولارًا (USD). عند تجاوز الحدّ،
تُوقَف الخدمة إلى الشهر التالي.

قراءة الكلفة الحقيقيّة

قبل نشر رابط الخدمة، تقدير كلفة صريح:

بندالقيمة
كلفة gpt-4o-mini (2026)حوالى 0.15 دولار لكلّ مليون رمز إدخال؛ 0.60 دولار لكلّ مليون رمز إخراج
متوسّط تبادل200 رمز إدخال + 500 رمز إخراج ≈ 0.00033 دولار لكلّ تبادل
ميزانيّة شهريّة20 دولارًا ÷ 0.00033 ≈ 60,000 تبادل شهريًّا
العتاد على Hugging FaceCPU basic مجّانيّ

هذا يعني أنّ مساعدًا يخدم 200 مستخدم يوميًّا بمعدّل 10 أسئلة لكلّ منهم يبقى ضمن الميزانيّة. قراءة الأرقام قبل الإطلاق تمنع مفاجأة الفاتورة.

تعقّب الاستعمال

بجانب ملفّ flags.jsonl، أضف تسجيلًا خفيفًا لكلّ نداء:

import logging
logging.basicConfig(
filename="data/usage.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
)

def repondre(message, historique, systeme, temperature):
logging.info(f"MSG_LEN={len(message)} HIST={len(historique)}")
# ...

هذه السطور تكفي لمعرفة: كم مستخدمًا يوميًّا؟ متى الذروة؟ متوسّط طول الأسئلة؟ الإجابات تُغذّي الوحدة القادمة من العرض.

قراءة الملاحظات

بعد أسبوع، سكربت صغير يُلخّص الملاحظات:

import json, collections

with open("data/flags.jsonl") as f:
lignes = [json.loads(l) for l in f]

compte = collections.Counter(l["evaluation"] for l in lignes)
print(compte)

نسبة «خاطئ» تفوق 20% إشارة على أنّ رسالة النظام تحتاج مراجعة، أو أنّ النموذج لا يجيد المجال. نسبة «غير كافٍ» عالية تعني ضرورة رفع max_tokens أو إضافة تعليمات أوضح.

اختبار الحمل قبل الإطلاق

مباشرة قبل مشاركة الرابط، السكربت من الوحدة 7 يعطي مؤشّرًا:

python test_charge.py --concurrent 5 --total 20

إذا كانت النتائج تُظهر زمن استجابة أكبر من 20 ثانية عند 5 مستخدمين متزامنين، أعِد ضبط concurrency_limit أو استعمل نموذجًا أخفّ. الاختبار قبل الإطلاق أسهل من إنقاذ الوضع بعد استقبال شكاوى.

من العرض إلى المنتج

هذه المساحة مساعد عرض، لا خدمة إنتاج. الفرق: إنتاج يحتاج نُسَخًا احتياطيّة، ومراقبة على مدار الساعة، وSLA، وإدارة إصدارات، وأمن معتمد. عرض يكفي فيه ما رأينا. المرور من الأوّل إلى الثاني هو موضوع دورات DevOps وMLOps (الدورة 20)، ولا يجب خلطه بمهمّة هذه الدورة.

قائمة تحقّق ما قبل النشر

  • README.md يذكر الحدود والكلفة والغرض.
  • Secrets تحوي OPENAI_API_KEY، لا شيفرة تحوي المفتاح.
  • رسالة نظام تُشير إلى عدم تقديم استشارات حسّاسة.
  • أمثلة مختلطة (نجاح + رفض + حدّ).
  • cache_examples="lazy" لتخفيف كلفة الأمثلة.
  • queue() مع concurrency_limit مناسب.
  • بثّ تدريجيّ مُفعَّل.
  • جمع الملاحظات في flags.jsonl.
  • اختبار حمل بـ 5 مستخدمين متزامنين قبل المشاركة.
  • رابط تجريبيّ للفريق قبل الإعلان العامّ.

الخلاصة

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

الوحدة الأخيرة: خلاصة الدورة كلّها، ومقارنة مع Streamlit من الدورة 38، وإعلان امتحان الأربعين سؤالًا.