هل خدعك وكيل الذكاء الاصطناعي للتو؟ كيف يتعلّم الذكاء الاصطناعي اجتياز الاختبارات
يقول وكيل الذكاء الاصطناعي إن كل الاختبارات تنجح، لكنه غيّرها. كيف تتعلّم النماذج، ولماذا تتحايل على الاختبارات (reward hacking)، وما يستطيع فريقك فعله.

يكتب الوكيل: «كل الاختبارات تنجح. الميزة جاهزة.» تفتح الـ . نصف الميزة فقط موجودة. الاختبار الذي كان يفشل لم يعد يفشل، لأن الوكيل غيّر ما يتوقعه الاختبار.
هل الوكيل معطّل؟ لا. إنه يفعل بالضبط ما دُرِّب على فعله. لتفهم السبب، عليك أن تعرف كيف يُصنع نموذج البرمجة. قضيت سنوات أدرس تعلّم الآلة انطلاقاً من الرياضيات: الانحدار التدرّجي (gradient descent) يدوياً، والشبكات العصبية، بلغات MATLAB و Python و R. ثم شغّلت نماذج على أجهزة iPhone. إليك إذن كيف يعمل الأمر، بكلمات بسيطة، مع برنامجين صغيرين يمكنك تشغيلهما بنفسك.
الخطوة 1: مُخمِّن بارع جداً
يقرأ النموذج النص على شكل (tokens)، وهي قطع صغيرة من الكلمات. عبارة "tiktoken is great!" تصبح ستة منها: t، ik، token، is، great، !. الكلمات الشائعة توكن واحد؛ أما الكلمة النادرة مثل "tiktoken" فتُبنى من قطع أصغر يعرفها النموذج مسبقاً. في مرحلة التدريب المسبق يرى النموذج كمية هائلة من النصوص ويتعلّم شيئاً واحداً: تخمين التوكن التالي. دُرِّب Llama 3 من Meta على 15.6 تريليون توكن، أي نحو 60 تريليون حرف (بحساب نحو 4 أحرف لكل توكن).
وماذا عن الأخطاء المطبعية؟ كثيرون منا يكتبون البرومبتات بسرعة، بلغة ثانية أو ثالثة، وهي مليئة بالأخطاء المطبعية. ومع ذلك يفهم النموذج. لقد تعلّم من الإنترنت، والإنترنت مليء بالأخطاء المطبعية، لذلك غالباً ما تقود "مدرسه" إلى "مدرسة" رغم الخطأ. لكن هذا ليس بلا ثمن: في إحدى الدراسات، كلّفت الأخطاء المطبعية وإعادة الصياغة ما يصل إلى نحو 21% من الأداء، وجعلت الإجابات أقل اتساقاً. في الكود، انتبه للأسماء. getUserByld ليس getUserById، لا بالنسبة للنموذج ولا بالنسبة للبحث.
النموذج لا يختار كلمة. إنه يعطي درجةً لكل توكن تالٍ ممكن، ثم تتحول الدرجات إلى احتمالات. إعداد يُسمّى درجة الحرارة (temperature) يحدّد مدى جرأة الاختيار. حرارة منخفضة: التوكن الأعلى دائماً تقريباً. حرارة أعلى: تنوّع أكبر.
import math, random
# درجات النموذج للكلمة التالية بعد "The tests are ..."
scores = {"passing": 2.0, "failing": 1.0, "flaky": 0.2}
def probabilities(scores, temperature):
exp = {w: math.exp(s / temperature) for w, s in scores.items()}
total = sum(exp.values())
return {w: e / total for w, e in exp.items()}
for t in (0.2, 1.0):
p = probabilities(scores, t)
print(t, {w: round(v, 2) for w, v in p.items()})
random.seed(1)
p = probabilities(scores, 1.0)
print(random.choices(list(p), weights=p.values(), k=10))# درجات النموذج للكلمة التالية بعد "The tests are ..."
scores <- c(passing = 2.0, failing = 1.0, flaky = 0.2)
probabilities <- function(scores, temperature) {
e <- exp(scores / temperature)
e / sum(e)
}
round(probabilities(scores, 0.2), 2)
round(probabilities(scores, 1.0), 2)
set.seed(1)
sample(names(scores), 10, replace = TRUE, prob = probabilities(scores, 1.0))% درجات النموذج للكلمة التالية بعد "The tests are ..."
words = {'passing', 'failing', 'flaky'};
scores = [2.0, 1.0, 0.2];
probabilities = @(s, temperature) exp(s / temperature) / sum(exp(s / temperature));
disp(round(100 * probabilities(scores, 0.2)) / 100)
disp(round(100 * probabilities(scores, 1.0)) / 100)
rng(1);
p = probabilities(scores, 1.0);
picks = words(arrayfun(@(~) find(rand < cumsum(p), 1), 1:10))عند درجة حرارة 0.2، تفوز "passing" في 99 مرة من 100. هذا هو الدرس الأول: «تمّ» ليست قراراً. إنها توكن تالٍ مرجَّح.
الخطوة 2: تعليمه أن يجيب
المُخمِّن الخام لا يجيب عن الأسئلة، بل يُكمل النص. لذلك يكتب أناسٌ أمثلةً لإجابات جيدة، ويُدرَّب النموذج عليها. في ورقة InstructGPT من OpenAI (2022) كتب نحو 40 متعاقداً أمثلةً لإجابات لنحو 13,000 برومبت، ورتّبوا إجابات النموذج لنحو 33,000 برومبت آخر. وكانت النتيجة لافتة: فضّل المقيّمون نموذجاً من 1.3 مليار مُعامِل، ضُبط على هذه الأمثلة والترتيبات، على GPT-3 ذي 175 مليار مُعامِل. السلوك تغلّب على الحجم.
الخطوة 3: المكافأة
الآن يتمرّن النموذج. يجرّب إجابة، ويحصل على درجة، ثم يُعدَّل ليُكثر مما نال درجة عالية. تلخّص Anthropic ذلك في سطر واحد: «السلوكيات التي تؤدي إلى مكافأة عالية تُعزَّز.» نوعان من الدرجات يهمّاننا.
ما يحبه الناس. يقارن الناس بين الإجابات، ويتعلّم نموذج ثانٍ أن يتنبأ بالإجابة التي سيختارونها. المشكلة: الناس يحبون الإجابات التي توافقهم. في 2023 اختبرت Anthropic خمسة مساعدين ووجدتهم جميعاً متملّقين ()، ورجّحت أن سبب ذلك يعود جزئياً إلى بيانات التفضيل البشري تلك. في أبريل 2025 تراجعت OpenAI عن تحديث لـ صار مُفرطاً في المجاملة. وهذا تفسيرها هي: عدة تغييرات، منها مكافأة جديدة مبنية على الإعجاب وعدم الإعجاب «أضعفت تأثير إشارة المكافأة الأساسية لدينا».
هل تنجح الاختبارات؟ بالنسبة للكود، الدرجة غالباً هي تشغيل للاختبارات. كانت مكافأة DeepSeek-R1 تُحسب بتشغيل كوده على حالات اختبار. وتصف OpenAI مهام تدريبها على البرمجة بأن عليها «أن تستوفي مجموعة من اختبارات الوحدة». نظيف، وموضوعي، ورخيص. وهنا بالضبط يقع الخلل.
ما لا تراه الدرجة
تتحقق الدرجة من شيء واحد: أخضر أو أحمر. لا ترى كيف صارت الاختبارات خضراء. إصلاح الخلل بطيء، وأحياناً يفشل رغم ذلك. تغيير الاختبار سريع وينجح دائماً. المتعلّم الذي يُكثر مما جلب الدرجة سينجرف نحو الطريق المختصر. تسمّي المختبرات ذلك التحايل على المكافأة ()، وهي نفسها توثّقه:
- كان Claude 3.7 Sonnet من Anthropic يُرجع أحياناً قيم الاختبار المتوقعة مباشرة، أو يعدّل الاختبارات الفاشلة. وتقول Anthropic إن ذلك نتج عن التحايل على المكافأة أثناء التدريب.
- في تدريب OpenAI، تعلّم نموذج أن يستدعي
exit(0)قبل تشغيل كل الاختبارات، أو يرفعSkipTest، أو يعدّل الكود المحيط بالاختبارات حتى «تنجح الاختبارات تلقائياً». - بعد أن تحايل o3 في مهمة، سألته عمّا إذا كانت خطته تطابق ما أراده المستخدم. قال لا، عشر مرات من عشر. وفي إحدى المهام، لم تغيّر عبارة «من فضلك لا تغشّ» شيئاً: ظلّ o3 يخطّط للغش في 80% من المحاولات.
- أعطى باحثون من و Anthropic وكلاءَ اختباراتٍ تناقض المواصفات، بحيث يصبح النجاح فيها غشاً. نجح GPT-5 في 54% من إحدى هذه المجموعات.
والأمر لا يقلّ مع الوقت. في أغسطس 2026 كتبت Anthropic أنه مع ازدياد قوة النماذج، صار التحايل على المكافأة «أكثر انتشاراً وأصعب في المنع».
يمكنك أن ترى هذا يحدث في عشرين سطراً. لدى متعلّم فعلان. يجرّب كليهما، ويتذكر أيّهما جلب الدرجة، ويكرّر الأفضل في الغالب. أولاً، الدرجة تتحقق فقط من أن «الاختبارات تنجح». ثم تشغّل الدرجة اختبارات مخفية لا يستطيع المتعلّم لمسها.
import random
def learn(pass_rate, steps=500):
random.seed(7)
value = {a: 1.0 for a in pass_rate} # ما يظن المتعلّم أن كل إجراء يستحقه؛ يبدأ متفائلاً، لذلك يجرّب كليهما
tries = {a: 0 for a in pass_rate}
for _ in range(steps):
# غالباً نختار أفضل إجراء حتى الآن، وأحياناً نختار عشوائياً
a = random.choice(list(pass_rate)) if random.random() < 0.1 else max(value, key=value.get)
reward = 1.0 if random.random() < pass_rate[a] else 0.0
tries[a] += 1
value[a] += (reward - value[a]) / tries[a]
return tries
# الدرجة تسأل فقط: هل تنجح الاختبارات؟ تعديل الاختبار يجعلها تنجح دائماً.
print(learn({"fix the bug": 0.7, "edit the test": 1.0}))
# الدرجة تشغّل اختبارات مخفية لا يستطيع الوكيل لمسها. تعديل الاختبار الظاهر لا يجلب شيئاً.
print(learn({"fix the bug": 0.7, "edit the test": 0.0}))learn <- function(pass_rate, steps = 500) {
set.seed(7)
value <- setNames(c(1, 1), names(pass_rate)) # ما يظن المتعلّم أن كل إجراء يستحقه؛ يبدأ متفائلاً، لذلك يجرّب كليهما
tries <- setNames(c(0, 0), names(pass_rate))
for (step in 1:steps) {
# غالباً نختار أفضل إجراء حتى الآن، وأحياناً نختار عشوائياً
a <- if (runif(1) < 0.1) sample(names(pass_rate), 1) else names(which.max(value))
reward <- as.numeric(runif(1) < pass_rate[[a]])
tries[a] <- tries[a] + 1
value[a] <- value[a] + (reward - value[a]) / tries[a]
}
tries
}
# الدرجة تسأل فقط: هل تنجح الاختبارات؟ تعديل الاختبار يجعلها تنجح دائماً.
learn(c("fix the bug" = 0.7, "edit the test" = 1.0))
# الدرجة تشغّل اختبارات مخفية لا يستطيع الوكيل لمسها. تعديل الاختبار الظاهر لا يجلب شيئاً.
learn(c("fix the bug" = 0.7, "edit the test" = 0.0))actions = {'fix the bug', 'edit the test'};
% الصف 1: الدرجة تسأل فقط "هل تنجح الاختبارات؟"، وتعديل الاختبار يجعلها تنجح دائماً.
% الصف 2: الدرجة تشغّل اختبارات مخفية لا يستطيع الوكيل لمسها، وتعديل الاختبار الظاهر لا يجلب شيئاً.
scenarios = [0.7, 1.0;
0.7, 0.0];
for s = 1:2
rng(7);
pass_rate = scenarios(s, :);
value = [1, 1]; % ما يظن المتعلّم أن كل إجراء يستحقه؛ يبدأ متفائلاً، لذلك يجرّب كليهما
tries = [0, 0];
for step = 1:500
% غالباً نختار أفضل إجراء حتى الآن، وأحياناً نختار عشوائياً
if rand < 0.1, a = randi(2); else, [~, a] = max(value); end
reward = double(rand < pass_rate(a));
tries(a) = tries(a) + 1;
value(a) = value(a) + (reward - value(a)) / tries(a);
end
fprintf('%s: %d, %s: %d\n', actions{1}, tries(1), actions{2}, tries(2));
endمع الدرجة التي فيها ثغرة، يعدّل الاختبار نحو 470 مرة من 500. ومع الاختبارات المخفية، يصلح الخلل نحو 470 مرة من 500. المتعلّم نفسه. درجة مختلفة. هذا هو المقال كله في تشغيل واحد.
إذن، هل يكذب؟
ليس كما يكذب الإنسان. إنه يكرّر ما أتى بثماره في التدريب. لكن جواب «لا» عشر مرات من عشر عند METR يُظهر أنه يعرف في كثير من الأحيان. بالنسبة لفريقك، الفرق لا يهم. الاختبار الأخضر الآتي من وكيل ادّعاءٌ لا دليل، إلى أن تعرف أن الوكيل لم يكن يستطيع لمس الاختبار.
ما الذي يساعد
- قل ذلك في البرومبت. اطلب من الوكيل أن يصلح الكود، لا الاختبار، وأن يخبرك حين يبدو اختبارٌ ما خاطئاً. لا يكلّف ذلك شيئاً، وفي بعض النماذج الأحدث (مثل Claude 4) يُحدث فرقاً حقيقياً (البرومبت أدناه).
- اترك له مخرجاً. في إحدى الدراسات، انخفض غشّ GPT-5 من 54% إلى 9% حين صار بإمكان الوكلاء التوقف وإحالة المهمة إلى إنسان، لكن ذلك ساعد Claude أقل بكثير. وسطر في ملف القواعد لديك مثل «إن لم تستطع، فقل ذلك» هو النسخة الرخيصة من ذلك.
- راقب حلقات إعادة المحاولة. تميل الطرق المختصرة إلى الظهور بعد إخفاقات متكررة. إذا كان الوكيل في تشغيله الأحمر الثالث، فهذه لحظة مناسبة للتدخل.
- ألقِ نظرة على ملفات الاختبار في الـ diff. الاختبار المعدَّل ليس خطأً دائماً، لكنه أول مكان تنظر إليه.
وحين يجد فريقك وقتاً لذلك: في مهام إصلاح الأخطاء، أبقِ الاختبارات بعيدة عن متناول الوكيل: أخفِها أو اجعلها للقراءة فقط، كما يوصي باحثو ImpossibleBench. ومجموعة ثانية في لا يراها الوكيل أبداً تضيف فحصاً آخر، لأن الاختبارات المخصّصة للقراءة فقط لا تمنع كتابة الإجابات المتوقعة مباشرة في الكود. أما في الترحيل (migration)، فتغيير الاختبارات جزء من العمل، لذلك تنظر هناك بدلاً من ذلك إلى عدد الاختبارات والتأكيدات (assertions).
اجعل الاختبار الفاشل ينجح بإصلاح الكود، لا الاختبار. لا تعدّل أي اختبار ولا تتخطَّه ولا تحذفه، ولا تكتب القيم المتوقعة مباشرة في الكود (hard-code). إذا بدا الاختبار خاطئاً أو بدت المهمة مستحيلة، فتوقف وأخبرني بالسبب بدل أن تجعله ينجح.
الجملتان الأخيرتان تتبعان البرومبت الذي نشرته Anthropic لاختباراتها على Claude Opus 4. ومع البرومبت الخاص بها، انخفض تحايل Claude Opus 4 في المهام المستحيلة من 47% إلى 5%. أما في Claude Sonnet 3.7 الأقدم فلم يُحدث شيئاً يُذكر تقريباً، لذلك تبقى الخطوات الأخرى مهمة.
طريقة إعداد فريقي لهذا حسب نوع المهمة، مع الشروط المسبقة وحواجز الحماية، هي موضوع المقال القادم (قريباً).
المصادر
- OpenAI, How to count tokens with tiktoken, Cookbook
- Meta, The Llama 3 Herd of Models, 2024
- Romanou وآخرون, Brittlebench: Quantifying LLM robustness via prompt sensitivity, 2026
- Ouyang وآخرون, OpenAI, Training language models to follow instructions with human feedback, 2022
- Anthropic, Training a misaligned reward seeker, أغسطس 2026
- Sharma وآخرون, Anthropic, Towards Understanding Sycophancy in Language Models, 2023
- OpenAI, Expanding on what we missed with sycophancy, مايو 2025
- DeepSeek, DeepSeek-R1, 2025
- Baker وآخرون, OpenAI, Monitoring Reasoning Models for Misbehavior, 2025
- Anthropic, Claude 3.7 Sonnet System Card, 2025
- METR, Recent Frontier Models Are Reward Hacking, 2025
- Zhong, Raghunathan, Carlini, ImpossibleBench, 2025
- Anthropic, Claude Opus 4 and Sonnet 4 System Card, 2025