استدعاء نموذج
درس 1 من 10
1استدعاء نموذج
الصورة الكبيرةاستدعاء نموذج لغوي يبدو سطرًا واحدًا، لكن خلفه أربعة قرارات تحدّد جودة نظامك وتكلفته: كيف ترتّب الرسائل، وأي حرارة تختار، وكم رمزًا ترسل في كل طلب، وهل تطلب نصًّا حرًّا أم بيانات منظّمة يتحقّق منها الكود.
ماذا تشاهد في الرسم: الدرجات لم تتغيّر، والحرارة وحدها رفعت احتمال كلمة خاطئة من صفر إلى 11%. في نظام يجيب عن حالة طلب، هذا يعني ردًّا من كل تسعة تقريبًا قد يقول «مرفوض» لطلب مقبول، ولهذا تبدأ الأنظمة التي تحضر من مستندات بحرارة منخفضة.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
استدعاء نموذج من برنامجك مثل إرسال رسالة إلى مستشار وانتظار رده. تحدد في الرسالة دوره، وتكتب سؤالك، وتختار هل تريده دقيقًا أم مبدعًا. وكل كلمة تكلّف مالًا، ولو طلبت الرد في نموذج محدد (مثل استمارة) يسهل على برنامجك أن يقرأه.
الطلب قائمة رسائل system وuser وassistant، وtemperature تتحكم في العشوائية، وmax_tokens يحد الطول، والتكلفة تُحسب برموز المدخل والمخرج. والناتج المنظّم structured output يُلزم النموذج بمخطط JSON Schema تتحقق منه بمكتبة مثل pydantic.
قبل أن تبدأ
✂️الترميز
تعرف أن السعر وحدود السياق بالرموز لا بالكلمات، وأن العربية تأخذ رموزًا أكثر لنفس المعنى.
🔨ابنِ محوّلًا صغيرًا
رأيت الحرارة تقسم الدرجات قبل softmax في دالة generate، وهي نفس الحرارة التي ترسلها هنا.
الفكرة الجوهرية
استدعاء النموذج سطر واحد، لكن خلفه أربعة قرارات. في هذا الدرس تبني الرسائل وتحسب تكلفتها، ثم ترسل طلبًا حقيقيًّا، ثم تضبط الحرارة، ثم تطلب بيانات يتحقّق منها الكود.
1الرسائل والتكلفة
المحادثة قائمة رسائل لكل منها دور: system ثم user وassistant بالتناوب. النموذج بلا ذاكرة، فكل طلب يعيد إرسال القائمة كلها. انظر إلى الرسم: السؤال الجديد يسافر ومعه كل ما قبله.
والتكلفة تُحسب بالرموز: رموز الإدخال في سعرها، ورموز الإخراج في سعرها، والإخراج أغلى عادة بثلاث إلى خمس مرات.
messages = [
{"role": "system", "content": "You answer questions about the leave policy. Be brief."},
{"role": "user", "content": "How many days of annual leave do I get?"},
{"role": "assistant", "content": "21 working days per year."},
{"role": "user", "content": "And after five years?"},
]
def rough_tokens(text):
return max(1, round(len(text) / 4)) # about 4 characters per English token
prompt_tokens = sum(rough_tokens(m["content"]) + 4 for m in messages)
answer_tokens = 60
price_in, price_out = 0.50, 1.50 # dollars per million tokens, an example price
cost = prompt_tokens * price_in / 1e6 + answer_tokens * price_out / 1e6
print("turns sent:", len(messages))
print("prompt tokens ~", prompt_tokens)
print(f"cost of this call: ${cost:.6f}")
print(f"cost of 100,000 such calls: ${cost * 100_000:.2f}")
الطلب الرابع يرسل الرسائل الأربع كلها. التقدير هنا تقريبي (أربعة حروف للرمز في الإنجليزية، وأقل بكثير في العربية كما رأيت في درس التقطيع)، لكن الضرب في عدد الطلبات هو ما يهمّ: كسور السنت تصير مئات الدولارات. احسب هذا قبل أن تختار نموذجًا، لا بعد أن تصلك الفاتورة.
2استدعاء حقيقي
الآن ترسل الرسائل فعلًا. ثلاثة أشياء تهمّك في كل طلب: من أين يأتي المفتاح، وما الحد الأعلى للرد، وكم رمزًا حوسبت عليه.
أغلب المزوّدين يقبلون نفس الشكل المتوافق مع OpenAI. هذا يحتاج مفتاح API، فهو للقراءة هنا.
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.deepseek.com", api_key=os.environ["DEEPSEEK_API_KEY"])
reply = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "system", "content": "أجب عن أسئلة سياسة الإجازات باختصار."},
{"role": "user", "content": "كم يوم إجازة سنوية لي؟"}],
temperature=0.2,
max_tokens=200,
)
print(reply.choices[0].message.content)
print(reply.usage) # prompt_tokens, completion_tokens: what you are billed for
المفتاح من متغيّر بيئة لا من الكود، وmax_tokens حدّ أعلى يحميك من رد طويل بلا داعٍ، وusage هو الرقم الحقيقي الذي تُحاسب عليه. سجّله مع كل طلب من اليوم الأول.
3الحرارة
النموذج يعطي كل كلمة تالية محتملة درجة، ثم يحوّل الدرجات إلى احتمالات بـsoftmax. الحرارة تقسم الدرجات قبل ذلك: قرب الصفر يأخذ الأقوى كل شيء، وفوق 1 تتسطّح الاحتمالات.
انظر إلى الرسم: كلما ارتفعت الحرارة نزلت approved وصعدت rejected، فيقترب الخطّان.
نفس الدرجات لخمس كلمات تالية محتملة، بأربع حرارات.
import numpy as np
words = ["approved", "accepted", "pending", "rejected", "banana"]
logits = np.array([3.0, 2.6, 1.5, 1.0, -2.0])
def probs(t):
z = logits / t
e = np.exp(z - z.max())
return e / e.sum()
for t in [0.2, 0.7, 1.0, 1.5]:
p = probs(t)
row = " ".join(f"{w}={x:.2f}" for w, x in zip(words, p))
print(f"T={t}: {row}")
بحرارة 0.2 تأخذ approved 88% والباقي يختفي تقريبًا. وبحرارة 1.5 تصل rejected إلى 11%: في نظام يجيب عن حالة طلب، هذا يعني أن واحدًا من كل تسعة ردود تقريبًا قد يقول «مرفوض» لطلب مقبول. ولاحظ أن banana لا تختفي تمامًا عند الحرارة العالية. لهذا تبدأ الأنظمة التي تحضر من مستندات بحرارة منخفضة.
القاعدة العملية: من 0 إلى 0.3 للإجابات من المستندات، وأعلى من ذلك للكتابة الإبداعية فقط.
4المخرجات المنظّمة
الكود لا يقرأ نثرًا. اطلب JSON بمخطّط محدّد، وتحقّق من كل حقل ونوعه، وإن فشل التحقّق فأعد الطلب ومعه سبب الرفض.
انظر إلى الرسم: ثلاث محاولات، كل رفض يرجع إلى النموذج بسببه، والثالثة تمرّ.
ثلاثة ردود محتملة من نموذج طُلب منه JSON. الكود يتحقّق من كل رد، ويرفض ويعيد السؤال مع سبب الرفض.
import json
replies = [
'Sure! Here is the data: {"name": "Sara", "days": 3}',
'{"name": "Sara", "days": "three"}',
'{"name": "Sara", "days": 3, "type": "sick"}',
]
schema = {"name": str, "days": int, "type": str}
def parse(reply):
try:
data = json.loads(reply)
except json.JSONDecodeError as e:
return None, f"not JSON: {e.msg}"
for key, kind in schema.items():
if key not in data:
return None, f"missing field {key!r}"
if not isinstance(data[key], kind):
return None, f"{key!r} should be {kind.__name__}"
return data, None
for attempt, reply in enumerate(replies, 1):
data, error = parse(reply)
if data:
print(f"attempt {attempt}: ok -> {data}")
break
print(f"attempt {attempt}: rejected ({error}), asking again with the error")
الرد الأول فيه مقدّمة مهذّبة قبل JSON، والثاني كتب العدد بالحروف. كلاهما يبدو «صحيحًا» لإنسان ويكسر الكود. المزوّدون اليوم يقدّمون «وضع JSON» أو «المخرجات المنظّمة» الذي يقيّد التوليد بالمخطّط، فاستخدمه حين يتوفّر، وأبقِ التحقّق في الكود مع ذلك، فالمخطّط يضمن الشكل لا المعنى.
كيف يعمل، خطوة بخطوة: كيف يسير طلب واحد إلى نموذج
تبني قائمة الرسائل: رسالة النظام الثابتة أولًا، ثم المحادثة السابقة، ثم سؤال المستخدم الجديد.
ترسلها مع temperature منخفضة وmax_tokens كسقف للرد، والمفتاح من متغيّر بيئة.
تتحقّق من الرد: إن طلبت JSON تحلّله وتفحص كل حقل ونوعه.
إن فشل التحقّق تعيد الطلب مع سبب الخطأ مرتين أو ثلاثًا على الأكثر، وتسجّل usage مع كل طلب.
مثال محلول
النموذج لا يتذكّر شيئًا بين الطلبات. كل طلب يحمل المحادثة كاملة من أولها: رسالة النظام التي تحدّد الدور، ثم رسائل المستخدم والمساعد بالتناوب. ولهذا كل رسالة قديمة تبقيها تدفع ثمنها مرة أخرى في كل طلب جديد.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| النموذج يتذكّر المحادثة بين الطلبات. | النموذج بلا ذاكرة، وكل طلب يحمل المحادثة كلها من أولها، فتدفع ثمن كل رسالة قديمة في كل طلب جديد. |
| الحرارة صفر تضمن إجابة صحيحة. | الحرارة المنخفضة تجعل الإجابة أكثر ثباتًا فقط. إن كان الخيار الأرجح خاطئًا، فستحصل على الخطأ نفسه كل مرة. |
| وضع JSON في المزوّد يغني عن التحقّق في الكود. | المخطّط يضمن الشكل لا المعنى. حقل days قد يأتي رقمًا صحيح النوع وخاطئ القيمة، فأبقِ التحقّق في كودك. |
جرّب بنفسك
نظام يستخرج تاريخ ومبلغ الفاتورة من نص. أي حرارة تختار؟
الاستخراج له إجابة واحدة صحيحة، والتنوّع هنا أخطاء. الحرارة المنخفضة تجعل النموذج يختار الأرجح في كل مرة.
النموذج أعاد JSON صحيح الشكل لكن حقل days فيه "three". ماذا تفعل؟
التحقّق في الكود هو خط الدفاع. إعادة الطلب مع سبب الرفض تصلح أغلب الأخطاء من المحاولة الثانية، والحدّ الأقصى يمنع حلقة مكلفة.
بطاقات مراجعة سريعة
system وuser وassistant. النموذج بلا ذاكرة، فكل طلب يحمل المحادثة كلها.تحقق من فهمك
لماذا يكلّف الطلب العشرون في محادثة طويلة أكثر من الأول بكثير، مع أن سؤال المستخدم بنفس الطول؟
لأنه يحمل التسعة عشر تبادلًا السابقة كلها كرموز إدخال. تكلفة المحادثة تنمو تقريبًا مع مربّع عدد الأدوار إن أبقيت كل شيء. الحل في درس هندسة السياق: احتفظ بآخر بضعة أدوار وملخّص لما قبلها.
لماذا تضع التعليمات الثابتة في رسالة النظام أول الطلب بدل آخره؟
لأن كثيرًا من المزوّدين يخزّنون بداية الطلب المتكرّرة ويحاسبون عليها بسعر أقل، بشرط أن تكون متطابقة حرفيًّا بين الطلبات. الثابت أولًا والمتغيّر آخرًا.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي1
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.