جدول لـ N — تقييم الوكيل وحلقة التطوّر الذاتي باستخدام ADK

1. مقدمة

طاولة واحدة، وN شخصًا، ووكيل واحد يحجز المطعم

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

يتم تشغيل كل شيء على حزمة تطوير الوكلاء (ADK): adk eval مع مقياس مخصّص تكتبه، وadk optimize (محسِّن طلبات GEPA)، وADK 2 Workflow مع حافة خلفية، وAgent Engine + BigQuery pipe الذي يحوّل زيارات الإنتاج إلى اختبار الجولة التالية.

تزيد الحلقة أي رقم يتم إدخاله فيها.

ما ستتعلمه

  • الردّ ليس نتيجة: لماذا لا يقدّم الردّ الواثق والمصاغ بشكل جيد أي معلومات للنظام، وما الذي يجب أن يكون الحكم قادرًا على قوله
  • ثلاث طرق لإجراء اختبار واحد: adk eval للدفعة، وعلامة التبويب adk web Evals للتركيز على عملية تعذُّر، وpytest + AgentEvaluator لتعذُّر عملية الإنشاء، ومتجر التقييم المحلي الوحيد الذي تتم مشاركته
  • المقارنة مقابل التحقّق: سبب رفض المقارِن لإجابة صحيحة تمت صياغتها بشكل مختلف، وعدد الإجابات الصحيحة التي يمكن أن يحصل عليها العشاء، والتحقّق من الحدس الذي يخبرك بنوع المقياس الذي تحتاجه المشكلة
  • دالة التحقّق كمقياس adk eval: customMetrics وcodeConfig والتوقيع الثابت وعمليات التحقّق الستة داخلها وسبب حصول الدالة على النتيجة "تم الاجتياز" أو "تعذّر الاجتياز" وسبب كونها حتمية عن قصد
  • ثلاث مجموعات من المقاييس في إجابة واحدة: أداة المقارنة، وأداة التدقيق، و"النموذج اللغوي الكبير" كحكم: ما يحتاجه كل مقياس، وتكلفة كل مقياس، ومواضع قصور كل مقياس
  • adk optimize (GEPA): ما يتم عرضه (الأخطاء وليس النتيجة)، مجموعة التدريب مقابل المجموعة الضابطة، وسبب إعادة إجراء الاختبار بنفسك قبل إرسال المنتج
  • اختراق المكافآت، مقياس: هو مقياس بديل لا يقرأ الطرف، وما يفعله بالرقم الحقيقي، والقانون الذي يترتب على ذلك
  • حلقة ذاتية التطوّر في ADK 2 Workflow: تعليمات في حالة الجلسة، والحافة الوحيدة التي تجعل الرسم البياني دورة، والحدود التي لا يوفّرها ADK
  • مواضع استخدام الإصدار العلني: عمليات التتبُّع لمشاهدة المحتوى، والصفوف لتعلُّم المحتوى، وسبب إمكانية تقييم مدقّق للوجبات التي لم يخطّط أحد لتقييمها (قراءة، وليس خطوة)

ما ستنشئه

تطبيق للركض في المتصفّح: طاولة مستديرة وN ضيف ووكيل يحجز لهم العشاء يتم تفعيل عنصر واحد في كل دورة تدريبية، مثل سطر في ملف إعداد أو إجابة واحدة أمام ثلاثة حكّام أو زر يشغّل أداة التحسين الحقيقية أو مفتاح تبديل الحكّام أو حافة واحدة في رسم بياني، ويعرض لك الجدول التغييرات التي حدثت، مثل المستخدمين الذين أجروا التحسينات والمستخدمين الذين لم يجروا التحسينات وأسباب ذلك. برنامج Python صغير، ويتم قياس الأرقام كلها على جهازك.

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

الصفحة: الجدول ومفتاح التبديل على اليمين، ومخطط المبنى والدورة التدريبية على اليسار

الدورات التدريبية الثماني

المفهوم

السؤال الذي تجيب عنه

ماذا تفعل؟

I

الرد ليس نتيجة

ما هي المعلومات التي يعرفها النظام عن هذا الحجز؟

ما مِن مواعيد بعد، يُرجى حجز موعد في طاولة العمل

2

المقارِن · مجموعة من الإجابات الصحيحة · ثلاث طرق للتشغيل

لماذا يفشل الحجز الصحيح في الامتحان؟

إجراء الاختبار كما هو وقراءة النتائج

3

أداة التحقّق كمقياس مخصّص · عمليات التحقّق الست

هل يمكن أن يحدد الاختبار الشخص الذي يشعر بالجوع وسبب ذلك؟

سطر واحد في exam/eval_config.json

4

comparator · checker · LLM-as-judge

الإجابة نفسها: ما هي العناصر الثلاثة التي يجب قياسها؟

تقييم إجابة واحدة بثلاث طرق، مرتين

V

adk optimize · train vs holdout · the ship gate

هل يمكن للوكيل إصلاح نفسه من إخفاقاته؟

host/instruction_current.txt

VI

اختراق المكافآت · مقياس الوكيل

ماذا يفعل الحكم الذي لا يمكنه رؤية الضرر بالحلقة؟

المفتاح

VII

Workflow مع حافة خلفية · الحدود

ما الذي يجعل الرسم البياني حلقة، وما الذي يوقفها؟

حافة واحدة في loop/flywheel.py

VIII

العناصر التي تم استبدالها

—

الشيك

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

مكان ظهور هذا الرمز

تضمّنت التجربة عشرة ضيوف لكل منهم شرط واحد صعب، وعشرة مطاعم لكل منها فخ واحد، وستة عشر حفلًا، وقد أتى الحَكَمان من مختبر loop-lab-table، أما التنسيق التفاعلي المكوّن من صفحة واحدة، فقد أتى من Agent Valley. المطعم الصغير هو مجرد مشهد، وكل آلية تحته هي الآلية العادية التي تستخدمها لموظّف دعم أو مسار حجز أو أي شيء آخر يمكن التحقّق من "جودته".

2. الإعداد

  • ما: حساب يمكنه الدفع، ومشروع، والمختبر الذي تم تثبيته، والنموذج الذي ثبتت قدرته على الإجابة، والجدول الذي يعمل على منفذ واحد
  • السبب: عملية واحدة على منفذ واحد: تشترك الصفحة والاختبارات والمحرِّر ومنصة العمل في المصدر نفسه، لذا لا يتطلّب أي شيء في هذا التمرين استخدام وحدة طرفية ثانية أو منفذ ثانٍ.
  • كيفية: استنساخ نصَين برمجيَين بأمر واحد الخطوة الأخيرة في عملية الإعداد هي إجراء طلب واحد لنموذج حقيقي، وبالتالي يفشل المشروع المعطّل هنا بدلاً من الدورة التدريبية الأولى.

يتم تشغيل كل شيء في هذا التمرين في Cloud Shell، حيث تم تثبيت gcloud وPython وNode وgit مسبقًا، ويمكن الوصول إلى Gemini من خلال Vertex AI باستخدام بيانات الاعتماد الخاصة بك. لا يوجد مفتاح واجهة برمجة تطبيقات في أي مكان في هذا التمرين، ولا يوجد شيء لتثبيته على الكمبيوتر المحمول. ويمكن استخدام كمبيوتر محمول يتضمّن حزمة تطوير البرامج (SDK) لـ Google Cloud وPython 3.11 والإصدارات الأحدث وNode 20 والإصدارات الأحدث بالطريقة نفسها.

هناك أمر واحد لا يمكن أن يوفّره لك هذا المختبر، وهو مشروع Google Cloud يتضمّن الفوترة. تتيح لك الخطوة 1 الحصول على حساب يمكنه الدفع مقابل المشروع، بينما تحوّل الخطوة 4 هذا الحساب إلى مشروع. يجب تنفيذها بهذا الترتيب، فكل خطوة بعد الخطوة 4 تقرأ المشروع الذي تستقر عليه الخطوة 4.

‫1 · الحصول على حساب يتضمّن إعدادات الفوترة (حوالى دقيقة واحدة)

سجِّل الدخول باستخدام حساب Google تم تفعيل الفوترة فيه. يمكن استخدام أي حساب من هذا النوع. إذا لم يكن لديك حساب فوترة، يمكنك بدء فترة تجريبية مجانية، إذ تمنحك حساب فوترة، وهو كل ما تحتاجه الخطوة 4.

‫2 · فتح Cloud Shell (دقيقتان تقريبًا)

👉🖥 انقر على تفعيل Cloud Shell في أعلى "وحدة تحكّم Google Cloud"، وهو الرمز الذي يشبه Terminal في العنوان.

تفعيل Cloud Shell من عنوان "وحدة تحكّم Google Cloud"

👉🖥 انقر على فتح أداة التعديل (رمز المجلد المفتوح مع قلم رصاص). سيؤدي ذلك إلى فتح "محرّر Cloud Shell" مع مستكشف ملفات على اليمين، وهو المكان الذي يمكنك الانتقال إليه متى أردت قراءة ملف كامل.

فتح "محرِّر Cloud Shell"

👉🖥 إذا طُلب منك التفويض في أي وقت اليوم، انقر على تفويض للمتابعة.

👉🖥 افتح نافذة Terminal داخل المحرّر، أي علامة التبويب 1، ونفِّذ كل خطوة 👉💻 فيها. علامة التبويب 2 هي الجدول نفسه، ويمكنك فتحه في الخطوة 7. إذا لم تكن النافذة الطرفية في أسفل الشاشة، افتحها باستخدام عرض → النافذة الطرفية:

فتح وحدة طرفية جديدة في "محرِّر Cloud Shell"

👉💻 تحقَّق من الحساب الذي سجّلت الدخول إليه، ويجب أن يكون الحساب نفسه الذي استخدمته في الخطوة 1:

gcloud auth list

‫3 · الحصول على التمرين المعملي (دقيقة واحدة تقريبًا)

👉💻 انسخ المستودع:

cd ~
git clone https://github.com/cuppibla/table-for-n
cd table-for-n

‫4 · إنشاء مشروعك (حوالي دقيقتان)

👉💻 يعثر نص برمجي واحد على حساب الفوترة، وينشئ مشروعًا فيه، ويربط الفوترة، ويوجّه Cloud Shell إلى هذا المشروع:

cd ~/table-for-n
./setup_project.sh

توقَّع ظهور ✓ لحساب الفوترة الذي اختاره، و✓ للمشروع الذي أنشأه، ونهاية مثل:

Project ready: table-for-n-4271
  Next:  ./setup_codelab.sh

‫table-for-n-4271 هو رقم تعريف مشروعك، وسيكون رقمك مختلفًا. تحدث أربعة أشياء في هذه المرحلة:

الفوترة

يتم استخدام أحدث حساب [YYYY-MM-DD] GDP Credit: إذا كان لديك حساب، وأي حساب فوترة آخر مفتوح في حال عدم توفّر حساب.

المشروع

ينشئ table-for-n-XXXX ويربط حساب الفوترة هذا به

اختيارات

يكتب رقم التعريف في ~/project_id.txt ويشغّل gcloud config set project، لذا لن يُطلب منك كتابته بعد ذلك

waits

استطلاعات الرأي إلى أن يُسمح للمشروع بعرض الإعلانات، وذلك لمدة تصل إلى دقيقتَين

👉🖥 يتوفّر المشروع نفسه الآن في وحدة التحكّم، ويظهر رقم تعريفه في بطاقة معلومات المشروع على لوحة البيانات:

رقم تعريف مشروعك على لوحة بيانات وحدة التحكّم

‫5 · تثبيت المختبر وإعداده (حوالى 3 دقائق)

تنشئ setup_codelab.sh البيئة باستخدام uv، ولن تثبّت uv لك، بل ستتوقف وتطلب منك تثبيتها. لذا، عليك تثبيتها أولاً، في الأمر نفسه:

👉💻

cd ~/table-for-n
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env
./setup_codelab.sh

نص برمجي واحد، يمكن تشغيله مرة أخرى بأمان. ينفّذ خمسة إجراءات بالترتيب التالي:

api

يقرأ الخطوة 4 المحدّدة في المشروع ويفعّل aiplatform.googleapis.com عليها، حيث يتوفّر Gemini في Vertex AI

uv env

ينشئ uv venv ملف .venv أو يعيد استخدام الملف الموجود، ثم يثبّت uv sync ما يثبّته uv.lock بالضبط، أي google-adk[eval] وgoogle-genai وfastapi وuvicorn

site

تثبِّت site/ وتنشئها، وهي تطبيق Next.js تم تصديره إلى ملفات ثابتة، لذا لن يتبقّى أي شيء لتنزيله في bash table.sh في الخطوة 7

‎.env

يكتب .env من .env.example، ويشير إلى Vertex AI في مشروعك، ولا يتضمّن أي مفتاح لواجهة برمجة التطبيقات

preflight

تُجري مكالمة حقيقية واحدة إلى gemini-2.5-flash، لذا سيفشل المشروع غير الصالح هنا في وقت الإعداد وليس داخل الدورة التدريبية

‫6 · قراءة قائمة الطعام (دقيقة واحدة تقريبًا)

آخر ما طبعته الخطوة 5 هو قائمة الليلة، كل سطر عبارة عن فحص حقيقي، وليس تخمينًا:

  talking to Gemini through vertex · table-for-n-4271
  the model answers · gemini-2.5-flash · it said: table for N, ready to seat.
  the page · built

  tonight's menu:

  I   ▢ the booking      · a reply is not a result                 read only · book one in the workbench
  II  ▢ the comparator   · the exam runs response_match_score      run it as shipped · read the verdicts
  III ▢ the judge        · everyone_ate is not on the exam yet     edit one line in exam/eval_config.json
  IV  ▢ the flight       · one answer, three judges               judge it three ways
  V   ▢ the rewrite      · instruction: draft                      optimize · install · ship gate
  VI  ▢ the other judge  · rating_score never reads the party      install hacked · flip the switch
  VII ▢ the loop         · no back edge yet                        delete one "# " in loop/flywheel.py
  VIII▢ the check        ·                                         eight courses

لا يضيء أي منها قبل البدء. the model answers هو ما يهمّنا هنا، فهو يشير إلى أنّ مكالمة حقيقية قد تم إجراؤها في مشروعك.

👉💻 يمكنك تشغيلها مرة أخرى في أي وقت لمعرفة مستوى تقدّمك، فهي تقرأ ملفاتك وإيصالات المطبخ، وليست قائمة تحقّق:

cd ~/table-for-n
uv run python scripts/preflight.py

‫7 · ترتيب الطاولة (دقيقة واحدة تقريبًا)

👉💻

cd ~/table-for-n
bash table.sh

عملية واحدة، المنفذ 3470 يستغرق المطبخ بعض الوقت لتسخينه، إذ يستورد حزمة تطوير التطبيقات (ADK) ويحمّل adk web، لذا يذكر النص البرمجي ذلك وينتظر ثم يطبع الروابط فقط عندما يستجيب المنفذ فعليًا:

  warming up the kitchen — up to a minute the first time, do not open the page yet...

  ✓ the table is laid

  Table for N      → http://localhost:3470
  the workbench    → http://localhost:3470/workbench/dev-ui/?app=host

  In Cloud Shell: Web Preview → Change port → 3470
  Ctrl+C clears the table.

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

👉🖥 بعد ظهور ✓ the table is laid، افتح الجدول في علامة تبويب جديدة، ولنسمِّها علامة التبويب 2. في Cloud Shell: معاينة الويب → تغيير المنفذ → 3470

👉🖥 أضِف اسمك إلى بطاقة الحجز، وسيظهر على الفاتورة في النهاية، ثم انقر على ✦ حجز طاولة.

بطاقة الحجز

3- ‫I · الحجز: الردّ ليس نتيجة

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

يحجز الوكيل طاولة. يمكنك معرفة من خلال قراءة المحادثة مَن سيشعر بالجوع في الغرفة التي يختارها التطبيق. لا يعرف النظام أي شيء. هذا هو موضوع الدرس التطبيقي حول الترميز بأكمله.

الوكيل — إذن بالقراءة فقط

host/agent.py، كما هو الحال في التمرين المعملي بأكمله:

_HERE = os.path.dirname(os.path.abspath(__file__))
INSTRUCTION_FILE = os.path.join(_HERE, "instruction_current.txt")

root_agent = Agent(
    name="host",
    model="gemini-2.5-flash",
    instruction=read_instruction(),
    generate_content_config=types.GenerateContentConfig(
        thinking_config=types.ThinkingConfig(thinking_budget=0),
        temperature=0.0,
    ),
)

ما مِن أدوات أو قاعدة بيانات أو بحث. نموذج واحد وسلسلة تعليمات واحدة، وليست السلسلة في هذا الملف وهي موجودة في instruction_current.txt، والذي يحتوي اليوم على الأسطر الأربعة التي يكتبها مدير المنتج في اليوم الأول:

You are the group's dinner-booking agent. The user will share the group chat
and the restaurants nearby.

Book the group a table people will be excited about — lead with the
highest-rated, most talked-about room that fits the night. Output ONLY a JSON
object, no prose, no code fences: {"restaurant": ..., "time": ..., "reason": ...}

لا توجد كلمة واحدة عن الحساسية أو الميزانيات أو حدود المشي أو من يجب أن يغادر مبكرًا. كل ذلك موجود في الملخّص الذي يتلقّاه الوكيل. غير مستخدَمة

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

جرِّبها الآن بدون واجهة أمامية

👉🖥 في الصفحة، انقر على I. انقر على 🔬 workbench (أعلى يسار الصفحة): يظهر الدرج adk web، ويتم تحميله في العملية نفسها، أي في مجلد host/ نفسه وtable.db نفسه. اختَر المضيف، والصِق الملخّص من لوحة الدورة التدريبية (يحتوي على زر نسخ)، ثم أرسِله.

يجيب المضيف في ثانية واحدة: مطعم ووقت وسبب واثق من سطر واحد. اقرأ السبب بجانب المحادثة. ثم انظر إلى الجدول على اليسار: تم وضعه، لم يحكم أحد، — / 5. ليس لأنّ هناك أي شيء يتم تحميله، بل لأنّه لا يوجد في العالم أي شيء يمكنه الحكم على ذلك.

العدد: مقاعد غير محجوزة — ? لا يمكن لأحد معرفة ذلك.

4. ثانيًا: المقارِن: كم عدد الإجابات الصحيحة التي يتضمّنها هذا العشاء؟

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

ثلاث طرق لإجراء تقييم

كلها تستخدم المحرّك نفسه.

الغرض منه

ما تحصل عليه

adk eval — واجهة سطر الأوامر

برنامج تنفيذ الدُفعات، وهو ما تضعه في عملية التكامل المستمر

ملخّصًا يتضمّن: النجاح أو الرسوب، ودرجة لكل حالة

adk web → Evals: واجهة مستخدم الويب

تعذّر تنفيذ إحدى الحالات وأردت إلقاء نظرة عليها

المحادثة والتتبُّع والنتيجة لكل حالة، وكلّها قابلة للنقر

‫pytest + AgentEvaluator

طيّ تقييم الوكيل في مجموعة اختبارات الانحدار العادية

يتم تشغيلها بجانب اختبارات الوحدات وتؤدي إلى تعذُّر إنشاء التطبيق بالطريقة نفسها.

التفاصيل المفيدة حقًا هي أنّ جميعها تشترك في متجر تقييم محلي واحد، host/.adk/eval_history/. يمكنك تشغيل دفعة من CI، وإذا حدث خطأ ما، يمكنك فتح عملية التشغيل المحددة في واجهة مستخدم الويب للتعمّق فيها. الطريقة الأولى هي استخدام الزر 📝 في الصفحة، والثانية هي استخدام 🔬 مساحة العمل، والثالثة هي استخدام أمر في المحطة الطرفية، uv run pytest tests/test_host.py، والاختبار الذي يتم تنفيذه هو اثنا عشر سطرًا:

def test_the_holdout_feeds_everyone():
    config = get_evaluation_criteria_or_default("exam/eval_config.json")
    eval_set = load_eval_set_from_file("exam/parties_val.evalset.json", "parties_val")
    asyncio.run(AgentEvaluator.evaluate_eval_set(
        agent_module="host", eval_set=eval_set, eval_config=config, num_runs=1,
        app_name="host",
        eval_set_results_manager=LocalEvalSetResultsManager(agents_dir=ROOT),   # ← the shared store
    ))

tests/test_host.py. الوكيل نفسه، ومجموعة التقييم نفسها، وملف الإعداد نفسه، والسطر eval_set_results_manager هو ما يضع عملية التشغيل في السجلّ نفسه مثل العمليتين الأخريين.

الامتحان، كما تم شحنه

exam/eval_config.json سفن مثل هذه:

{
  "criteria": {
    "response_match_score": 0.3
  },
  "customMetrics": {
    "everyone_ate": {
      "codeConfig": { "name": "metrics.table.everyone_ate_metric" },
      "description": "How many of the party actually got dinner."
    }
  }
}

جزأين، مهمتين. criteria

هو ورقة الامتحان، أي المقاييس التي يتم تشغيلها فعليًا، ولكل منها حدّ أدنى للنجاح. customMetrics

‫ هو دليل الموظفين، أي الأسماء المرتبطة بـ Python، والمتاحة ولكنّها ليست في الخدمة. لا يؤدي تعريف مقياس إلى تنفيذه. As shipped, the paper has exactly one question on it: ADK's built-in response_match_score, a text overlap (ROUGE-1) between what the agent said and the golden booking in the evalset. القاضي، everyone_ate، موجود في الدليل وليس على الورقة، وينقله المسار III إلى هناك عن طريق تغيير سطر واحد.

لدينا وكيل لحجز العشاء وresponse_match_score يجلس هناك مجانًا. لنستخدمه ونرى ما سنحصل عليه.

تجربة الميزة

👉🖥 الدورة التدريبية II على الصفحة. انقر على 📝 إجراء الاختبار · كما تم شحنه. يُظهر التعليق أسفل الزر ما يتم تشغيله: يتم تشغيل مقياس واحد: response_match_score · everyone_ate تم الإعلان عنه، وليس في الاختبار. نصف دقيقة تقريبًا اقرأ النتيجة: فشلت نصف الجداول تقريبًا، في نتيجة نصية بدون سبب مرفق - وانظر إلى أي منها. مرِّر مؤشر الماوس فوق الشرائح.

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

إليك سؤال حقيقي: كم عدد الإجابات الصحيحة التي يمكن تقديمها عن هذا العشاء؟

بالنسبة إلى حفلة الليلة، تقدّم غرفة واحدة فقط من الغرف العشر الطعام للجميع، وهي Pho Saigon، وذلك من الساعة 19:00 فقط. في جميع أقسام الاختبار الثمانية، يكون عدد المرات التي يمر فيها world.rooms على كل غرفة في كل جلسة لكل قسم كما يلي: 1 و2 و2 و1 و3 و3 و6 و1 (يمكنك تجربة ذلك بنفسك باستخدام uv run python -c "import world; print(sum(r['feeds_everyone'] for r in world.rooms(world.PARTIES['p1'])))"). في بعض الأحيان، يمكن لعدة غرف استيعاب الجميع، وفي أحيان أخرى، يمكن لغرفة واحدة فقط استيعابهم، وفي أوقات معيّنة فقط. هناك مجموعة من الإجابات المقبولة، وليس إجابة واحدة، والإجابة الذهبية في مجموعة التقييم هي أحد عناصرها.

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

👉🖥 اختياري، يستغرق دقيقة واحدة: افتح 🔬 workbench ← التقييمات ← 🕓 السجلّ. ستظهر لك قائمة بالتشغيلات 📝 التي أجريتها للتو، ويمكنك النقر على كل حالة. وفي علامة التبويب 1، يُجري "uv run pytest tests/test_host.py" الاختبار نفسه كاختبار غير ناجح، ويتم تسجيله في السجلّ نفسه.

النتيجة: اجتازت 4 من أصل 8 غرف اختبارنا من حيث تداخل الكلمات، بدون ذكر أي أسباب، و1 إجابة صحيحة عن السؤال p1، من أصل عشر غرف.

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

5- ‫III · القاضي: أداة التحقّق كمقياس مخصّص

طريقتان لتسجيل النقاط: المقارنة بإجابة نموذجية أو استعراض الجدول

يطرح المُدقّق سؤالاً مختلفًا عن سؤال المُقارِن: ليس هل يتطابق هذا مع المفتاح؟ بل هل تم إطعام كل شخص؟ يتم تقييم الحجز مقارنةً بالحجز الجماعي، مقعدًا مقعدًا، ويتم عرض نتيجة وسبب لكل مقعد. لا تحتاج إلى إجابة نموذجية على الإطلاق، وهذا ما يتيح لها أيضًا تقييم المحادثات التي حدثت بالفعل (الدورة التدريبية VIII).

world.everyone_ate هي تلك الدالة. تدمج الدورة التدريبية III ذلك في adk eval.

داخل everyone_ate — خمس خطوات، لا شيء منها ذكي

  1. اقرأ نص المستخدم وحدِّد الطرف المعنيّ. يتضمّن الموجز رقم تعريف الطرف p1، ونبحث عن الشخص الذي يجلس على تلك الطاولة.
  2. اقرأ إجابة الوكيل وحلِّل ملف JSON واستخرِج اسم المطعم ووقت الحجز.
  3. اتّبِع كل شخص بالترتيب. لكل منها: النظام الغذائي، ثم الحساسية، ثم تحمل التوابل، ثم الميزانية، ثم مسافة المشي، ثم التوقيت. ستة شيكات لكل شخص في كل مرة. لنأخذ توم: ميزانيته هي 25 دولارًا للشخص الواحد، والغرفة تبلغ 28 دولارًا. نُرجع False لـ "توم"، والجملة التي تشرح ذلك، ثم ننتقل إلى الشخص التالي.
  4. النتيجة هي عدد الأشخاص الذين تناولوا الطعام مقسومًا على عدد الأشخاص الجالسين على المائدة. أربعة من خمسة تساوي 0.8. لا يتم تطبيق أي ترجيح أو منح أي رصيد جزئي.
  5. يتم عرض PASSED أو FAILED. لاحظ مَن يعيدها: أنت. تذكَّر ذلك، فهذا هو الفخ أدناه.
def ate_detail(person_id, rest, chosen_min):
    p = PEOPLE[person_id]
    if p.get("diet") == "vegan" and not rest["vegan"]:
        return False, "nothing vegan on the menu", "diet"
    if p.get("allergy") == "nut" and not rest["nut_safe"]:
        return False, "everything is finished in peanut oil", "allergy"
    ...
    if "budget" in p and rest["price"] > p["budget"]:
        return False, f"~${rest['price']}/person against {name}'s ${p['budget']} budget", "budget"
    ...                                                   # walk, then timing
    return True, "ate", ""

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

المقياس، مكتمل

‫metrics/table.py: المحوّل الذي تستدعيه حزمة ADK، مع توقيعه الثابت:

def everyone_ate_metric(eval_metric, actual_invocations,
                        expected_invocations=None, scenario=None):
    return _build(world.everyone_ate, world.THRESHOLD, actual_invocations, "everyone_ate")


def _build(scorer, threshold, actual_invocations, metric):
    per, scores = [], []
    for inv in actual_invocations:
        ids = _party_ids(inv)                 # who was invited — parsed from the PROMPT
        rid, time_str = _decision_parts(inv)  # what got booked — parsed from the RESPONSE
        s, detail = scorer(ids, rid, time_str) if (ids and rid) else (0.0, [])
        scores.append(s)
        per.append(PerInvocationResult(
            actual_invocation=inv, score=s, eval_status=_status(s, threshold)))
    overall = round(sum(scores) / len(scores), 4) if scores else 0.0
    return EvaluationResult(overall_score=overall,
                            overall_eval_status=_status(overall, threshold),
                            per_invocation_results=per)

سطرًا بسطر: يقرأ كلا الجانبين لكل استدعاء، أي الطرف من user_content والحجز من final_response، ويسلّمهما إلى الدالة النقية، ويغلّف النتيجة في العناصر التي تتوقعها حزمة ADK. ويحمل سطر واحد الحكم:

def _status(score, threshold):
    # ADK does not apply your threshold to custom metrics — this IS the verdict.
    return EvalStatus.PASSED if score >= threshold else EvalStatus.FAILED

كيفية ربط مقياس مخصّص، والمشكلة

الإعدادات، أي السطر الواحد

‫exam/eval_config.json، كما تظهر بعد انتهاء الدورة التدريبية:

{
  "criteria": {
    "everyone_ate": 0.9
  },
  "customMetrics": {
    "everyone_ate": {
      "codeConfig": { "name": "metrics.table.everyone_ate_metric" },
      "description": "How many of the party actually got dinner."
    }
  }
}

تم تغيير سطر واحد: يذكر criteria الآن اسم القاضي بدلاً من المقارِن. يتألف الامتحان من ثماني مجموعات لن يراها المحسِّن أبدًا (exam/parties_val.evalset.json، 36 مقعدًا في المجموع).

تجربة الميزة

👉🖥 الدورة التدريبية III في الصفحة. يُسمّي الشرح تحت الزر 📝 التعليمات التي يشغّلها المضيف، ويجب أن يكون مسودة. إذا ظهرت أي رسالة أخرى (أي أنّك ثبّتّ إصدارًا آخر في وقت سابق)، اضغط على ↺ العودة إلى المسودة أولاً، وإلا لن تتطابق الأرقام مع القصة.

👉🖥 استبدِل السطر في المحرِّر (أو انقر على تطبيق التعديل نيابةً عني)، ثم 📝 شغِّل الاختبار مرة أخرى. الآن، يحدّد اسم الفشل المقعد وسببه — توم: 28 دولارًا أمريكيًا مقابل 25 دولارًا أمريكيًا · لينا: آخر الطلبات الساعة 7:30 مساءً، وصلت الساعة 7:45 مساءً — ويتغيّر الرقم أسفل الجدول من عدد مرات النجاح إلى الرقم المهم: المقاعد الشاغرة. مرِّر مؤشر الماوس فوق شريحة: يحتوي كل جدول لم ينجح في التحميل على جمل.

👉🖥 بعد ذلك، اضغط على 📋 حجز الطاولة. يختار المضيف غرفة، وتعمل الوظيفة نفسها على تظليل الكراسي التي لا يمكنها تناول الطعام هناك وتوضّح السبب تحت كل كرسي. اضغط عليه مرة أخرى، فقد يحجز النموذج بشكل مختلف (لا يمكن تحديد سلوك واجهة برمجة التطبيقات حتى عند درجة الحرارة 0)، ولكن لن يتغير قرار الحكم: الغرفة نفسها، والحكم نفسه، والجمل نفسها.

الرقم: المسودة الأولى، صادقة - حوالي 3 من 8 أحزاب، 6 حسابات جائعة من 36 حسابًا في اختبارنا (سيتذبذب الرقم، فواجهة برمجة التطبيقات ليست خوارزمية حتمية حتى في درجة العشوائية 0، ولهذا السبب يتضمّن الاختبار 36 حسابًا، وليس أربع حالات). احتفِظ بهذا الرقم. تتوفّر الدورة التدريبية V لنقلها.

6. ‫IV · الرحلة الجوية: إجابة واحدة، ثلاثة حكّام

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

قاضٍ

تَسْأَل

بحاجة إلى

تنجح إذا

نقطة عمياء

the comparator · response_match_score

هل هذه الإجابة مماثلة لإجابتي؟

إجابة ذهبية: كتب أحد الأشخاص حجزًا واحدًا لكل طرف في مجموعة التقييم

تداخل الكلمات في مقياس ROUGE-1 ≥ 0.3

فالإجابة الصحيحة التي تمت صياغتها بشكل مختلف تتم معاقبتها؛ والإجابة الخاطئة بالكلمات الصحيحة تمر دون عقاب. لم يقرأ الحفلة أبدًا.

أداة التحقّق · everyone_ate

هل هذه الإجابة صحيحة؟

القواعد: ستة عمليات تحقّق لكل مقعد، ولا توجد إجابة ذهبية على الإطلاق

عدد المقاعد التي تم إطعامها ÷ عدد المقاعد على الطاولة ≥ 0.9

لا يرى أي شيء خارج القواعد التي كتبتها. النهج الحتمي: الإجابة نفسها، والقرار نفسه، والجُمل نفسها، في كل مرة.

القاضي من النموذج اللغوي الكبير · gemini-2.5-flash + قواعد تقييم

هل يحصل الجميع على الطعام؟ — سؤال تم طرحه على أحد النماذج

نموذج وقواعد تقييم من أربعة أسطر، ومكالمة حقيقية واحدة لكل ضغطة

يقول النموذج "اجتياز"

يمكن أن يخطئ، ويمكن أن يغير رأيه بين الضغطات. يتم احتساب تكلفة المكالمة.

The checker's whys deserve a note. عندما يتم تشغيل "adk optimize" في الدورة التدريبية "V"، لا تظهر هذه الجمل أبدًا، بل يقرأ مُحسِّن ADK النتائج. وهي ليست مهملة، بل هي ما يجعل الفشل قابلاً للقراءة بالنسبة إليك، وأنت تقف عند المحطة، وهو ما يمثّل معظم قيمتها اليوم. وفي الدورة السابعة، تستدعي الحلقة القاضي مباشرةً بدلاً من المرور عبر نظام التقييم، لذا يتم تسليم الأسباب فعليًا إلى الشيء الذي يقوم بإعادة الكتابة.

القضاة الثلاثة، أثناء إدارتهم

kitchen/flight.py. يمثّل أول رقمَين رمز الامتحان، والمقارِن هو الدالة التي تستند إليها السمة response_match_score، لذا فإنّ رقمه هو بالضبط ما طبعته الدورة التدريبية الثانية لهذا الطرف:

from google.adk.evaluation.final_response_match_v1 import _calculate_rouge_1_scores

def comparator(answer, reference):
    s = _calculate_rouge_1_scores(answer, reference)       # ROUGE-1 F, as ADK computes it
    return {"score": s.fmeasure, "passed": s.fmeasure >= 0.3, "needs": "a golden answer"}

def checker(party, answer):
    rid, when = _decision(answer)
    score, seats = world.everyone_ate(world.PARTIES[party], rid, when)
    return {"score": score, "passed": score >= world.THRESHOLD, "needs": "the rules",
            "why": " · ".join(f"{s['name']}: {s['why']}" for s in seats if not s["ate"])}

async def llm_judge(party, answer):
    prompt = f"{RUBRIC}\n\n--- the brief ---\n{world.brief(party)}\n\n--- the booking ---\n{answer}"
    r = await client.aio.models.generate_content(model="gemini-2.5-flash", contents=prompt, ...)
    verdict = json.loads(r.text)["verdict"]                # PASS or FAIL, plus one sentence
    return {"passed": verdict == "PASS", "needs": "a model + a rubric"}

تجربة الميزة

👉🖥 الدورة التدريبية IV في أعلى اللوحة: إجابة المضيف عن p1، وبجانبها الحجز الذهبي من مجموعة التقييم. اقرأهما معًا أولاً. بعد ذلك، انقر على ⚖ تقييم هذه الإجابة بثلاث طرق. طلب واحد للنموذج تظهر ثلاث بطاقات: ما يطلبه كل قاضٍ، وما يحتاجه، وما قاله ولماذا.

في تجربتنا، بالنسبة إلى p1 — Olive & Thyme في الساعة 19:00 ضمن مسودة اليوم الأول — أظهرت أداة المقارنة PASS عند 0.30 (تطابقت كلمات كافية مع الحجز المثالي)، وأظهرت أداة التدقيق FAIL عند 0.60 (Tom: ~$28 مقابل ميزانية Tom البالغة 25 دولارًا أمريكيًا · Lena: آخر طلبات الساعة 19:30، لن تكون على الطاولة حتى الساعة 19:45)، وأظهرت أداة التقييم المستندة إلى نموذج اللغة الكبير FAIL وذكرت اسم Lena. يختلف برنامج المقارنة وبرنامج التدقيق بشأن الإجابة نفسها، إذ احتسب أحدهما الكلمات واحتسب الآخر المقاعد.

👉🖥 اضغط على ⚖ تقييمها مرة أخرى. لا تتحرك بطاقة المقارنة. لا تتحرك بطاقة الداما. قد تظهر بطاقة LLM، ويشير السطر أسفل الزر إلى ما إذا كانت قد ظهرت أم لا. بعد ذلك، اختَر حزبًا آخر على اليمين (أداة اختيار الحزب في المرحلة) واضغط مرة أخرى، فالثلاثة لا يصطفون دائمًا بالطريقة نفسها.

العدد: إجابة واحدة وثلاث نتائج: PASS وFAIL وFAIL في عملية التشغيل، وستكون نتيجتان من النتائج الثلاث متطابقتَين في كل مرة تطرح فيها السؤال.

7. ‫V · إعادة الكتابة — تحسين adk وبوابة الشحن

ثلاثة أدوار منفصلة تمامًا:

role

مَن

وظيفة

الممتحَن

المضيف

يُجري الاختبار

القاضي

مقياسك من الدورة التدريبية III

النتائج، بشكل حتمي

المدرّب

adk optimize (GEPA)

ويقرأ الأخطاء، ويعيد كتابة التعليمات

لا يقدّم المدرّب أي تقييم. لا يقترح القاضي أبدًا. ما يتغير هو قطعة أثرية واحدة - التعليمات، ملف نصي يمكنك مقارنته ومراجعته والتراجع عنه.

المحتوى الذي يقرأه المحسِّن

لا يرتفع عدد مستخدمي GEPA. يقرأ الأسباب: يأخذ عيّنات من الحجوزات من الأطراف التدريبية، ويقيّمها باستخدام القاضي، ويسلّم ملاحظات الخطأ — توم تجاوز الميزانية ولينا بعد آخر الطلبات — إلى نموذج يقترح تعليمات جديدة. يتم الاحتفاظ بالمرشحين الذين يتفوّقون على المرشح القديم في الألعاب التي سبق أن لعبوها، ويفوز المرشح الذي يحقق أفضل أداء في الألعاب التي لم يسبق له أن لعبها. هذا التقسيم هو التخصص بأكمله:

الأطراف

يُستخدم في

parties_train

8

يقرأ المحسِّن حالات الفشل هذه

parties_val

‫8 (36 مقعدًا)

الامتحان، والذي لا يتم عرضه أبدًا للمُحسِّن

يتم تحديد هذا الحدّ من خلال إعدادَين، وكلاهما في exam/:

{ "eval_config": { "criteria": { "everyone_ate": 0.9 }, "customMetrics": { ... } },
  "app_name": "host", "train_eval_set": "parties_train", "validation_eval_set": "parties_val" }
{ "optimizer_model": "gemini-2.5-flash", "max_metric_calls": 150,
  "reflection_minibatch_size": 3, "run_dir": "./runs/gepa_run" }

الفائز، كما هو مكتوب

لا يكتب المحسِّن instruction_current.txt أبدًا. تتم طباعة التعليمات الفائزة، وتحفظها الصفحة في runs/optimize/winner.txt، ثم ينسخها الأمر ⤵ install في مكانها. في ما يلي كيف يبدأ الفائز في الجولة الحقيقية (2,931 حرفًا، من 375):

Your primary goal is to book a restaurant that accommodates EVERYONE in the
group. Follow these steps precisely:

1. Understand Group Requirements: carefully parse the chat for each
   individual's needs — dietary restrictions, allergies, budget limits,
   arrival times, walking distance...
2. Filter restaurants by absolute constraints — a single violation for one
   person eliminates the room for the whole group.
...

تحوّلت المسودّة المكوّنة من أربعة أسطر إلى إجراء مرقّم، وكل قاعدة فيها هي سؤال "لماذا" من الامتحان. لم يكتب أحد هذه الجمل.

Where a run lands, if you want to read it yourself:

ملف واحد

ما هي؟

runs/gepa_run/run_log.txt

الجولة بأكملها، وقرب النهاية، Best program as per aggregate score on valset: N

runs/gepa_run/candidates.json

كل مرشح على أنّه {"agent_prompt": ...} — الفائز هو المرشح في الفهرس N

runs/gepa_run/candidate_tree.html

شجرة البحث، ويمكن فتحها في متصفّح

runs/optimize/winner.txt

الفائز، الذي تم حفظه بواسطة الصفحة؛ ⤵ تثبيت الفائز ينسخه في مكانه

في التجربة الثانية التي أجريناها أثناء كتابة هذا المقال، حصلنا على 150 طلبًا لإنشاء مقاييس، واستغرقنا أقل من سبع دقائق، وحصلنا على وصف فائز مؤلّف من 2,445 حرفًا يبدأ بعبارة "يستوعب بالكامل جميع المتطلبات الغذائية ومتطلبات التوقيت لكل فرد بدون أي تنازل" ويوضّح قاعدة الطلبات الأخيرة التي لم تتضمّنها المسودة. تتم صياغة الفائزين في الجولتين بشكل مختلف ويحصلون على نتائج متشابهة، إذ يعثر المحسِّن على القواعد، وليس الجمل.

بوّابة السفينة

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

تجربة الميزة

👉🖥 الدورة التدريبية V: يتيح لك خياران تشغيل النموذج الحقيقي من خلال ⚗ تحسين، والذي يتضمن 150 طلبًا للنموذج ويستغرق من خمس إلى ثماني دقائق، مع بث السجلّ في اللوحة، أو ⤵ تثبيت النموذج الفائز الذي يستخدم النموذج الذي أنتجته عملية التشغيل. في كلتا الحالتين، اضغط على 🚪 بوّابة الشحن واقرأ الرسالة. بعد ذلك، 📋 حجز طاولة: الموجز نفسه، والنموذج نفسه، وعشاء مختلف، وسبب المضيف الآن يشير إلى قبعة "توم" والموعد النهائي لتقديم الطلبات.

Course V after the gate: the winner installed, the holdout tile, p1 fed

تذكَّر الرقمَين: 5 / 5 يعني مقاعد على طاولة واحدة (الحفلة 1، الليلة)؛ 6 / 8 يعني حفلات في الامتحان بأكمله. يعرض لك التطبيق عشاءً؛ يعرض لك التقييم التوزيع. يغذي الفائز p1 بالكامل - ولا يزال يفتقد اثنين من جداول الاستبعاد الثمانية.

الرقم: المجموعة الضابطة من 3/8 إلى 6/8 في جولتنا، والمقاعد الشاغرة من 6 إلى 9. اقرأ هذا الرقم الثاني بعناية، لأنه يبدو وكأنه تراجع وليس كذلك. لم ينجح المسودّة في توفير خمسة مقاعد في كل طاولة (ميزانية توم، وصول لينا). أما الفائز، فيحجز الطاولات بالكامل، وفي الطاولتَين اللتَين لم يحجزهما، أي p1 وp2، يحجز في Taqueria Luna عند الساعة 19:45 لتتمكّن "لينا" من الحضور، أي بعد آخر طلبات المطبخ عند الساعة 19:30: يظلّ جميع الجالسين على الطاولة جائعين، خمسة مقاعد وأربعة. عدد أقل من الجداول التي تعذّر إنشاؤها، وأخطاء أسوأ هذا هو الحد الأقصى في الملاحظة الجانبية أدناه، والذي تم قياسه، وهو تذكير بأنّ عدد الناجحين والمقاعد الشاغرة يجيبان عن أسئلة مختلفة، ويُظهر لك الامتحان كلاهما.

8. VI · القاضي الآخر: اختراق المكافآت

أدّى المسار التدريبي V إلى تحسين أداء الوكيل. تتضمّن هذه الدورة التدريبية رفع عدد وهمي من خلال جعل المسطرة عمياء، ومراقبة ما يحدث نتيجة لذلك.

قاضيان

تحتوي metrics/table.py على ما يلي:

def everyone_ate(party, pick):     # the honest judge
    ...                            # walks the table, seat by seat

def rating_score(party, pick):     # the gameable judge
    return pick.rating / 5.0       # <- party is never read

👉 سيحذّرك المدقّق اللغوي من أنّ

party

‫ غير مستخدَم. هذا التحذير هو الدرس بأكمله.

‫exam/eval_config_rating.json هو الشكل نفسه الذي أنشأته في الدورة التدريبية الثالثة، أي اسم وحدّ ومسار منقّط، ويشير إلى الدالة الأخرى:

{
  "criteria": { "rating": 0.85 },
  "customMetrics": {
    "rating": {
      "codeConfig": { "name": "metrics.table.rating_metric" },
      "description": "The restaurant's star rating. Who is at the table is not part of this calculation."
    }
  }
}

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

التعليمات التي أرادها المُقيِّم

instructions/hacked.txt هو عنصر حقيقي: وهو ما تم إنتاجه من خلال عملية تقييم أجراها مقيّم. يحتوي الفرق بينه وبين تعليمات العناية على سطر واحد مهم:

- Ask the party about dietary restrictions.
+ Prioritise rooms with a rating of 4.5 and above.

تجربة الميزة

👉🖥 الدورة التعليمية VI: ⤵ تثبيت التعليمات التي تم اختراقها انقر على مفتاح التبديل إلى التقييم، وستتحوّل الصفحة إلى تطبيق مراجعة: نجوم، ومحتوى رائج، ومتوسط وقت الانتظار. يتقلّص الجدول ليصبح صورة مصغّرة في الزاوية، وتبقى الكراسي نفسها باللون الرمادي. لا ينظر مقيّم الترتيب إلى الجدول أبدًا، وبالتالي لا تنظر إليه الصفحة أيضًا.

انقر على 📝 إجراء الاختبار: 8 / 8. أعدنا تشغيل المفتاح وشغّلناه مرة أخرى: الإجابات الثمانية نفسها، تم تقييمها بصدق.

الدورة التدريبية السادسة مع مفتاح التبديل في التقييم: تطبيق مراجعة والجدول في الزاوية

الرقم: وجبات العشاء الثمانية نفسها، التقييم 8/8، الصدق 1/8، 15 مقعدًا جائعًا من أصل 36. وتحصل جميع الأطراف الثمانية على Smoke & Barrel. يتم إرسال "بريا"، التي تحمل قلم حقن الإبينفرين، إلى المطبخ الذي يحتوي على زيت الفول السوداني. ويجتاز أحد الجداول الاختبار بصدق على أي حال: يقدّم وكيل مخترَق عن طريق الخطأ بيانات إلى الطرف الذي لا تتضمّن قيوده غرفة الضغط العالي. وهذا ما يجعل الوكيل موثوقًا به، فهو لا يكون مخطئًا دائمًا، بل يكون مخطئًا في الأماكن التي لا تنظر إليها.

9. VII · The loop — a Workflow with a back edge

كانت الدورة الخامسة عبارة عن حلقة مفرغة: تشغيل المحسِّن، والتثبيت، والاختبار. تتضمّن هذه الدورة التدريبية الأدوار الثلاثة نفسها التي يتضمّنها الرسم البياني الذي يتم تشغيله بشكل مستقل، أي الرسم البياني المحدود.

التعليمات كحالة جلسة

في الحلقة، تكون تعليمات المضيف غير متسلسلة. وهو نموذج حالة:

def seed(node_input: str):
    yield Event(state={"brief": world.brief(PARTY), "current_instruction": DRAFT,
                       "whys": "(no failures yet — first round)"})

host = Agent(
    name="host", model="gemini-2.5-flash",
    instruction="{current_instruction}\n\nHere is tonight's request:\n{brief}",
    output_key="decision", generate_content_config=gen)

يتم حلّ {current_instruction} من الحالة في كل مكالمة. عندما تستبدل عقدة لاحقة هذا المفتاح، يتم تشغيل طلب المضيف التالي ضمن النص الجديد. هذه هي آلية التطوّر الذاتي بأكملها، وهي قيمة في الحالة التي تستمر الحلقة في استبدالها.

العُقد

عقدة

النوع

هل

seed

دالة

يضع الملخّص والمسودة الأولية في حالة

host

وكيل

الكتب: طلب نموذج، بغض النظر عن الحالة

judge

دالة

‫world.everyone_ate: الوظيفة نفسها في الدورة التدريبية III، وتكتب whys وlast_score إلى الحالة

router

دالة

الحدود: ship أو improve

proposer

وكيل

قراءة الأسباب والتعليمات الحالية وكتابة بديل

publish

دالة

يضع الاستبدال في state.current_instruction

done

دالة

يغلق الحلقة

الحلقة: الحجز ← التقييم ← إعادة الكتابة ← الحجز مرة أخرى؛ الحافة الأرجوانية هي التي تكتبها

الرسم البياني، منتهي

‫loop/flywheel.py، build()، كما تظهر بعد انتهاء الدورة التدريبية:

def build(nodes: dict):
    seed, host, judge, router = nodes["seed"], nodes["host"], nodes["judge"], nodes["router"]
    proposer, publish, done = nodes["proposer"], nodes["publish"], nodes["done"]
    return Workflow(
        name="flywheel",
        edges=[
            ("START", seed, host, judge, router),
            (router, {"improve": proposer, "ship": done}),
            (proposer, publish, host),
        ],
    )

يتم إرسال الصفحة بدون الحافة الأخيرة. يتم تنفيذ كل شيء مرة واحدة: يكتب المسودات، ويضع القاضي علامة رمادية على كرسيين، ويضع مقترح القواعد قاعدتين، ثم ينتهي الرسم البياني، لأنّ ما من شيء يعيد الكتابة إلى المضيف. الحلقة هي حافة واحدة مفقودة.

الحدود، لأنّ ADK لن

يمنحك حافة المسار التي تشير إلى الخلف حلقة بشكل أصلي. لا يحتوي Workflow على حدّ أقصى للتكرار. يملك جهاز التوجيه النهاية، مع أربعة أحزمة:

def router(last_score: float):
    if last_score >= world.THRESHOLD:        # 1 · SHIP on pass
        yield Event(route="ship")
    elif ep["round"] >= MAX_ROUNDS:          # 2 · round budget (default 4)
        yield Event(route="ship")            #     "stopping is also a decision"
    elif last_score <= ep["best"] and ep["flat"] >= 1:
        yield Event(route="ship")            # 3 · two flat rounds — a flat line is an answer too
    else:
        yield Event(route="improve")
# 4 · publish() truncates a runaway rewrite to 2,400 characters

أسوأ الحالات: أربع جولات، كل منها يتضمّن طلبَين للنموذج، وثمانية طلبات، وحدّ أقصى صارم لا يقيّم المضيف أبدًا، ولا يقترح الحكم أبدًا، ولا يكتب الموجه أبدًا.

تجربة الميزة

👉🖥 الدورة التعليمية VII ↻ تشغيل الحلقة كما تم شحنها: كتب الجولة الأولى، تفشل عند 0.60، يكتب مقدم الاقتراح قواعده - وينتهي التشغيل بـ flywheel ended without closing. احذف # على الخط الأخضر في المحرِّر. أعِد تشغيله.

الدورة السابعة بعد الحافة: الجولة الثانية بموجب التعليمات المعاد كتابتها، SHIP

إليك ما كتبه مقدّم الاقتراح في عملية التشغيل، أي المسودة، بالإضافة إلى جملتَين، واحدة لكل ملاحظة فشل:

Ensure the total cost per person does not exceed anyone's stated budget.   ← Tom's why
Confirm all guests can arrive before the kitchen's last order time.        ← Lena's why

يتحوّل المقعد غير المحجوز إلى سبب، ويتحوّل السبب إلى قاعدة، وتغيّر القاعدة الحجز التالي. حجزت "مطعم فو سايغون" في الجولة 2 الساعة 19:45 → 1.00 يَرِنُّ الْجَرَسْ.

الرقم: R1 0.60 → R2 1.00 · SHIP، في غضون خمس ثوانٍ تقريبًا. ‫SHIP في الحلقة هو لكل عشاء (طاولة واحدة، p1)، بينما كان باب الدورة التدريبية V لكل امتحان. التخصص نفسه، مقياسان مختلفان

10. ‫VIII · الشيك: ما تم استبداله بكل عنصر

👉🖥 الدورة التعليمية VIII اضغط على 🧾 الفاتورة، من فضلك: ثمانية أسطر، أرقامك الخاصة.

قبل

بعد

نسخة تجريبية تبدو مناسبة

امتحانًا يحسب من أكل

إجابة ذهبية واحدة

مجموعة من الإجابات الصحيحة وقاعدة

طلب تعدّله يدويًا

تعليمات تعيد الحلقة كتابتها

رقم يرتفع

قاضٍ يوضّح حيثيات حكمه

دورة بحثية واحدة

رحلة ذهاب وعودة

المقياس الذي تم تسليمه إليك

الرقم الذي اخترته

يجب أن تتضمّن الحزمة ما يلي:

  1. اسأل عمّا يمكنك تدوينه، أي الإجابة أو القواعد أو وصفًا جيدًا فقط، قبل اختيار مقياس. يحتوي العشاء على مجموعة من الإجابات الصحيحة؛ يتحقق المقارن من واحد.
  2. أنشئ الحاكم قبل الحلقة، واجعل العالم يحدد ما يعاقب عليه الحاكم.
  3. يجب وضع القرار داخل المقياس، ولن تطبّق ADK الحدّ الأدنى نيابةً عنك.
  4. يجب فصل مجموعة بيانات التدريب عن مجموعة بيانات الاستبعاد قبل تشغيل المحسِّن لأول مرة، وعدم استخدام نتيجة المحسِّن نفسه مطلقًا.
  5. Count hungry seats, not pass rates — small exams lie.
  6. إظهار الضرر للمحسِّن: إنّ القاضي الذي يعرض الأسباب هو نصف مهمة المحسِّن.
  7. تمنحك حزمة تطوير التطبيقات (ADK) حلقة التكرار، أما الحدود فتحدّدها أنت. التوقّف هو أيضًا قرار.
  8. عندما يصل الرقم إلى الحدّ الأقصى، يجب أن يكون ذلك بسبب تحسُّن أداء الوكيل، وليس بسبب تخفيف معايير التقييم.

تزيد الحلقة أي رقم يتم إدخاله فيها.

أين يتناسب الإنتاج

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

أنبوبان متوقفان عن الإنتاج، سهم واحد للخلف

شَرطة رأسية

كيف

أجوبة

‫① Cloud Trace

adk deploy agent_engine ... --otel_to_cloud

هل يمكنني التحدّث معك الآن؟ — spans, the platform's own judges, Online Monitors

‫② صفوف BigQuery

المكوّن الإضافي BigQuery Agent Analytics

ما الذي يجب أن نتعلّمه؟ — صف واحد منظم لكل حدث، وهو النموذج الذي يستهلكه المدقق ومجموعات التقييم

يتم إرسال البيانات في المستودع على شكل نصوص برمجية، وليس كدورة تدريبية: refuel/send_traffic.py يرسل البيانات إلى مضيف تم نشره، وrefuel/replay_to_bq.py يعرض المحادثات نفسها كصفوف، وrefuel/harvest.py يعيد تقييم كل صف باستخدام world.everyone_ate ويصنّف حالات الخطأ كحالات تقييم، أي الكلمات الفعلية للمجموعة، وليس الإجابة النموذجية. في ليلة الإنتاج، قدّم المضيف المشحون الطعام لـ 7 من أصل 8 مجموعات حقيقية، وكان الفشل الوحيد هو "لينا" التي خالفت الطلبات الأخيرة، وهو المقعد نفسه الذي توقّعه سقف الدورة الخامسة، وأصبح سؤال الامتحان في الجولة التالية. الجولة الثانية هي الدورة التدريبية V مرة أخرى مع حالة تدريبية إضافية. يتطلّب ذلك مشروعًا على Google Cloud مع الفوترة، ولهذا السبب هو قراءة هنا وليس طبقًا في القائمة.

دراسة ذاتية

  • اكتب معادلة من اختيارك (world.PARTIES، ثم scripts/make_evalsets.py) وأدرِجها في الاختبار. هل هذا عادل؟ scripts/verify_world.py: يجب أن يكون لدى كل مجموعة حجز واحد على الأقل يوفّر الطعام للجميع.
  • ارفعوا شريط التقييم إلى 0.9 وأشيروا إلى النقطة adk optimize عند eval_config_rating.json. قِس التدرج.
  • هل لديك مشروع يتضمّن فوترة؟ نفِّذ الأمر refuel/ من البداية إلى النهاية واقرأ أسطر harvest.py التي تتضمّن [skip]، فهي الحدود بين الأمرين و التي تم إظهارها.
  • تشغيل حلقة الألعاب باستخدام HOST_THINKING=2048 ما هي التغييرات التي تحدث، وماذا تخبرك هذه التغييرات عن الحد الأقصى؟