1. מבוא
סדנה בת 90 דקות בנושא מודל מבדל, מודל ההחלטות System One של TypeSafe AI, ואיך להשתמש בו לצד Gemini בתהליך עבודה של Google ADK. בסדנה הזו יש שישה שלבים, שמבוססים על משחק לחימה. תילחמו באוגר קודם בידיים, ואז תעבירו את הרפלקסים למודל הדיסקרימינטיבי, ואז תצפו בתהליך עבודה של ADK מנצח את הקרב כשהמודל הדיסקרימינטיבי מחליט על כל פעולה ו-Gemini קורא כרטיסי לחשים מחוץ למסך כדי לשיר לחשים.

סקירה כללית
המודל המפלה (jev-1.13, שם אחר jev-latest) הוא מודל מתארח מבית TypeSafe AI, שהושק ב-19 בספטמבר 2026. היא לא יוצרת טקסט. אתם שולחים לו מצב (טקסט, JSON או רשימה) ושאלות מוקלדות (Choice, Score, Noul), והוא מחזיר תשובות מוקלדות עם הסתברויות מכוילות, בערך ב-70 עד 500 אלפיות השנייה, בעלות של 0.042 $למיליון טוקנים של קלט, ללא עלות על פלט. התפקיד שלו הוא לקבל החלטות לפני, בין ואחרי מודלים של שפה: ניתוב, סיווג, גישה מוגבלת, ובמקרה הזה, רפלקסים של לוחם.
משחק כדוגמה

יצא לך לשחק במשחק לחימה? אתם מתמודדים מול יריב וצריכים להגיב באופן מיידי למהלכים שלו. ניחוש שגוי אחד וה-HP שלכם יירד. במשחקים, בדרך כלל קשה להטיל לחשים. במשחק שלנו, צריך לבחור את הצבע והצורות של כרטיס הכישוף, לפי הסדר, לפני שהכישוף מופעל. בסדנה הזו נסביר איך לשלב בין שני סוגי המודלים כדי שהדמות שלכם תנצח.
כל אלמנט במשחק ממופה למערכת אמיתית:
- המהלך של היריב הוא אירוע נכנס, כמו בקשה או טרנזקציה.
- התשובה היא החלטה מוגבלת, שהתקבלה על ידי המודל המפלה ונבדקה על ידי קוד.
- כרטיס הכישוף הוא קלט לא מובנה שצריך מודל שפה כדי לקרוא אותו.
- המשחק הוא תהליך העבודה, שבו עבודות מהירות ועבודות איטיות רצות בקצב שלהן.
הדגש הוא על שילוב של ארבעת הרכיבים וחיבורם יחד כדי לבנות מערכת מהירה וחכמה.
מה תלמדו
- להסביר על ההבדלים בין מודלים מפלים (מערכת 1) וגנרטיביים (מערכת 2), ומתי להשתמש בכל אחד מהם.
- הסבר על האופן שבו Jev ו-DiffusionGemma מוגשים, והגדרת אחד מהם לסדנה, כולל DiffusionGemma במכונה וירטואלית של GPU ב-Compute Engine.
- לכתוב שאלות מסוג בחירה, ניקוד ו-Noul, ולפרש הסתברויות ורמות סמך.
- כדאי להשתמש בערכי סף בקוד דטרמיניסטי כדי להפוך הסתברויות לפעולות.
- יוצרים בקשה באמצעות TypeSafe SDK, ואז מאפשרים למודל לבחור כל מהלך במשחק.
- יוצרים ענף איטי שבו Gemini קורא תמונה, וענף מהיר שבו המודל המפלה מחליט בלולאה, ומריצים כל אחד מהם בנפרד.
- הצטרפות לשני הענפים בתהליך עבודה של גרף ADK שמשתף מצב בלולאת אירועים אחת, כך שעבודה איטית אף פעם לא מעכבת החלטות מהירות.
ארכיטקטורה
סביבת העבודה נמצאת ב-Cloud Shell(או במחשב שלכם), היא כותבת למערכת הקבצים המקומית ומבצעת אינטראקציה עם הזירה וגם עם Gemini ומודל קבלת ההחלטות. ② קורא למודל ההחלטה ב'קרבות מודלים דיסקרימינטיביים'; ③ קורא לשניהם ב'קרבות תהליכי עבודה'.

מי קורא למה. הדפדפן מתקשר רק עם ①. שני המודלים נקראים מ-Python במחשב:
מתקשר | מודל דיסקרימינטיבי | Gemini |
② arena, "Discriminative model fights" | כל סימון, | לא |
③ תהליך עבודה | כל סימון, | לא |
③ תהליך עבודה | לא | תמונת כרטיס הכישוף; הסיפור אחרי הקרב |
| כן | לא |
טיק אחד לכל מצב.
- אתם נלחמים. בדף מופיע ② טלגרף, הוא מוצג עם טיימר של 2 שניות, והכפתור שלוחצים עליו (או האיות שמקלידים) מופיע שוב. ② פותר את הבעיה.
- מודל דיסקרימינטיבי נלחם. בדף מבקשים ② לסמן וי; ② מצייר טלגרף, שואל את המודל שלוש שאלות בקריאה אחת, מפעיל את
choose()ומחזיר את התשובות ואת התוצאה. הדף מצייר את העמודות. - התנגשויות בתהליכי עבודה. הפעולה Start גורמת להפעלת ② כ-subprocess (מתבצעת כניסה
runs/arena-workflow.log). ③ מנהל את הקרב: הוא מבקש מ-② כל טלגרף, קורא למודל ומפרסם את ההחלטה. האיות של Gemini מגיע לענף משלו כשהוא מוכן. בדף מוצגות רק תוצאות הסקר השני ②. ההשהיה היא דגל ב-② ש-③ בודק לפני כל טיק.
המיקום שבו מתארח מודל ההחלטות. כל קריאה עוברת דרך אותו typesafe-sdk, רק כתובת ה-URL הבסיסית משתנה. scripts/jevauth.py נותן שם לחלק האחורי של האתר ומגדיר את המפתח ואת הזמן הקצוב לתפוגה:
בק-אנד |
| מפתח | הוגדר על ידי |
TypeSafe, hosted | unset (api.typesafe.ai) |
|
|
DiffusionGemma ב-VM L4 | | אין |
|
DiffusionGemma ב-Cloud Run |
| אסימון זהות של Google, שאוחזר כל שעה |
|
חזרה |
| אין |
|
התשובה של כרטיס הכישוף ②: תהליך העבודה מקבל רק את ה-PNG, והתשובה של כרטיס הכישוף ② היא מה שנשלח בחזרה. כך אפשר לבדוק באמת את יכולת הקריאה של Gemini, ואתם יכולים לבדוק את עצמכם באמצעות הכישוף שאתם יוצרים ב-'אתם נלחמים'.
2. הגדרה
מימוש שוברי הפרסום לסדנאות
אם קיבלתם קרדיט ל-Google Cloud לסשן הזה, עליכם לממש אותו קודם – התהליך ייקח בערך דקה וייצור בשבילכם את החשבון לחיוב.
פתיחת Cloud Shell
Google Cloud Shell היא סביבת לינוקס שאפשר לגשת אליה דרך הדפדפן. היא מוגדרת מראש עם gcloud, Python, Node.js, uv ו-git, והאימות שלה כבר בוצע באמצעות חשבון Google שלכם.
- פותחים את מסוף Google Cloud.
- לוחצים על Activate Cloud Shell (סמל הטרמינל בסרגל הניווט העליון) כדי לפתוח סשן טרמינל בחלק התחתון של הדפדפן.

הפעלת סביבת העבודה
ב-Cloud Shell, או בכל מקום שבו gcloud מחובר לחשבון:
git clone https://github.com/gca-americas/discriminative-models-workshop.git
cd discriminative-models-workshop
./setup_project.sh # a new project with billing, recorded in ~/project_id.txt
./setup_codelab.sh # everything else, then the workbench on port 4900
setup_project.sh יוצר פרויקט (discrim-models-XXXX), מקשר אליו את החיוב, מעדיף חשבון קרדיט על אירועים אם יש לכם כזה, וממתין עד שהפרויקט יוכל לספק שירות. אם מריצים אותו שוב, הפרויקט ישמש שוב בעוד ~/project_id.txt. כדי להשתמש בפרויקט שכבר קיים, צריך להזין את המזהה שלו בקובץ ולדלג על הסקריפט הזה.
setup_codelab.sh לא שואל כלום. הסקריפט מתקין את uv ואת חבילות Python, מפעיל את Vertex AI, Compute Engine ו-IAP, מפנה את Gemini אל Vertex AI בפרויקט ב-.env, מבצע קריאה אמיתית אחת ל-Gemini עם מודל שהפרויקט יכול לקרוא לו, בונה את הדף, מפעיל את Workbench ברקע ומריץ את scripts/check_setup.py. אם מפעילים אותו שוב, קובצי התרגיל נשמרים. אם מפעילים את scripts/starter.sh, הם מתאפסים. מודל ההחלטה נבחר בשלב 2 של סביבת העבודה.
כדי לפתוח את ממשק המשתמש של סביבת העבודה ב-Cloud Shell:
- לוחצים על הקישור לתצוגה המקדימה שמופיע בסוף
./setup_codelab.sh, או על Web Preview (תצוגה מקדימה באינטרנט) בפינה הימנית העליונה של סרגל הכלים של Cloud Shell. - בוחרים באפשרות שינוי היציאה, מזינים 4900 ולוחצים על שינוי ותצוגה מקדימה.
Gemini פועל ב-Vertex AI בפרויקט שלכם, עם פרטי הכניסה שלכם ל-Google: GOOGLE_GENAI_USE_VERTEXAI=1, GOOGLE_CLOUD_PROJECT ו-GOOGLE_CLOUD_LOCATION=global ב-.env.
מודל ההחלטה נבחר באופן עצמאי בשלב 2 של סביבת העבודה, או ממסוף עם scripts/setup_model.sh:
בחירה | צרכים | הגדרה | עלות |
המודל הדיסקרימינטיבי (TypeSafe, hosted) | מפתח API מסוג TypeSafe | אין | לכל טוקן, שבריר סנט |
DiffusionGemma (Google, משקלים פתוחים) | חיוב + מכסה של Compute Engine ל-GPU | כ-15 דקות, אוטומטי | ~$0.71 לשעה בזמן שהמכונה הווירטואלית פועלת |
חזרה (ללא מודל) | nothing | אין | אין |
DiffusionGemma במכונה וירטואלית ב-Compute Engine
scripts/setup_gemma.sh בודק קודם את מכסת ה-GPU, ואז יוצר מכונת VM אחת g2-standard-4 (1 × L4 24 GB, 4 vCPU, 16 GB) מתמונת Deep Learning של Google עם דרייבר NVIDIA 580. בהפעלה הראשונה של המכונה הווירטואלית, המערכת מתקינה את Docker, מורידה את המשקלים מ-Hugging Face (nvidia/diffusiongemma-26B-A4B-it-NVFP4, 17.5GB, ציבורי, ללא טוקן) ומריצה את djev-run: DiffusionGemma מאחורי ה-API המדויק של המודל המפלה. היציאה של המודל לא פתוחה לאינטרנט: סביבת העבודה מגיעה אליה דרך מנהרת IAP ב-localhost:8096, ש-scripts/start.sh פותח.
השהיה או המשך | | |
מנהרה |
| |
הסרה |
| |
תרגול הפקודות |
| |
פריסת מאגר
app/ the arena app, as built so far (see "The app, one stage at a time")
main.py the server, the "You fight" mode, and the plugin loader
engine.py the rules and the ogre's moves, the one copy
sigil.py spell cards: a color and three shapes, judged and drawn (a tiny PNG rasteriser)
static/ the page: HP bars, the telegraph and timer, the spell card; modes/ holds plugins
static/sounds/ bgm.mp3 plus optional effects: fight, ogre-attack, block, strike, hurt, charge,
cast, fizzle, ready, ko, timeup (.mp3). A missing file is silent. Add them in stages/03-you-fight/.
reflex.py step 5: the three questions and choose()
mode_model.py step 5: the server side of "Discriminative model fights"
mode_workflow.py step 6: the server side of "Workflow fights"
branches/ step 6b's exercises: each branch as a workflow of its own, nothing from the arena
slow_branch.py Gemini reads spell_card.png and is checked against spell_card.json
fast_branch.py the Discriminative model decides on a list of moves, in a loop
starter/ Reset restores from here
server/ The workbench
3. סיכום
ניקוי הסביבה
בסיום הסדנה, מבצעים את השלבים הבאים כדי להסיר את כל משאבי ה-GPU של DiffusionGemma, להפסיק את תהליכי סביבת העבודה והחזרה ברקע, להסיר את קובצי הסדנה מ-Cloud Shell, ובאופן אופציונלי, למחוק את פרויקט הסדנה ב-Google Cloud.
- מחיקת מכונת ה-GPU של DiffusionGemma וכלל חומת האש (אם נוצר): אם הקציתם את DiffusionGemma במכונת GPU של Compute Engine בשלב 2, צריך להסיר את מכונת ה-VM, הדיסק וכלל חומת האש של IAP כדי שלא יצטברו חיובים על שימוש ב-Compute או באחסון בדיסק:
cd ~/discriminative-models-workshop ./scripts/teardown_gemma.sh - הפסקת תהליכי סביבת העבודה והחזרה הגנרלית ב-Cloud Shell: בטרמינל של Cloud Shell, מפסיקים את שרת סביבת העבודה שפועל ברקע ואת כל תהליך החזרה הגנרלית:
cd ~/discriminative-models-workshop ./scripts/stop.sh ./scripts/rehearsal.sh stop 2>/dev/null || true - מחיקת תיקיית הסדנה מ-Cloud Shell: חוזרים לספריית הבית ומסירים את התיקייה של המאגר המשוכפל ואת קובץ מזהה הפרויקט:
cd ~ rm -rf ~/discriminative-models-workshop ~/project_id.txt - מחיקת הפרויקט ב-Google Cloud: אם
./setup_project.shיצרתם פרויקט ייעודי לסדנה (לדוגמה,discrim-models-XXXX), השבתת הפרויקט תמחק באופן סופי את כל המשאבים שנוצרו בתוכו, אבל החשבון לחיוב ב-Cloud יישאר ללא שינוי:- פותחים את הדף 'ניהול משאבים' במסוף Google Cloud.
- ברשימת המשאבים, בוחרים את הפרויקט של הסדנה (לדוגמה,
discrim-models-...). - בסרגל הכלים העליון, לוחצים על מחיקה, מקלידים את מזהה הפרויקט כדי לאשר ולוחצים על השבתה.
סיימתם את הסדנה הזו.
סיכום שיעור Lab
- בחרתי מודל דיסקרימינטיבי, Jev או DiffusionGemma במכונת GPU וירטואלית ב-Compute Engine, ווידאתי שהוא עונה.
- שיחקתי בזירה באופן ידני, נגד השעון, כדי ללמוד את הכללים.
- הסברנו איך מודל מפלה עונה על שאלות מסוג בחירה, ניקוד ו-Noul, על הסתברויות ועל רמת הביטחון, ואיך הקוד שלכם מחיל עליהן ספים.
- שלחתם את הבקשה הראשונה, ועכשיו אתם מאפשרים למודל לבחור כל מהלך בזירה, כש-
choose()הופך את התשובות שלו לפעולות. - כל ענף בתהליך העבודה של ADK נבנה בנפרד, כש-Gemini קורא תמונה של כרטיס לחש והמודל מחליט בלולאה.
- הוספנו אותם לרצף עבודה אחד שמשתף את המצב, כך שהקרב אף פעם לא מחכה ל-Gemini והלחש מופעל על פתיחה.
משיחה להחלטות

רוב הצוותים נחשפו ל-AI גנרטיבי דרך צ'אט ויצירת תוכן. השלב הבא הוא AI בתוך מוצרים וצינורות, שבו הפלט של המודל מניע פעולה ישירות: ניתוב של כרטיס תמיכה, סימון של עסקה, השהיה של בקשה מסוכנת לצורך בדיקה, אישור או חסימה של קריאה לכלי של סוכן, בחירת מהלך במשחק.
יש שלוש דרישות משותפות להחלטות האלה שלא חלות על צ'אט:
- זמן טעינה. התשובה נמצאת בדרך כלל בנתיב הבקשה של המשתמש או בלולאה בזמן אמת, ולכן היא צריכה להגיע במילישניות, לא בשניות.
- מבנה. המתקשר הוא קוד, ולכן התשובה צריכה להיות ערך שהקוד יכול לפעול לפיו, ולא פסקה שהקוד צריך לנתח.
- יכולת חיזוי. לכל החלטה צריך להיות ערך מהימנות שהקוד יכול לבדוק, ועלות נמוכה מספיק כדי לשאול על כל אירוע.
מודל שפה יוצר טקסט אסימון אחד בכל פעם. אפשר להנחות אותו להשיב בחיוב או בשלילה, אבל הוא איטי בלולאה בזמן אמת, צריך לנתח את הפלט שלו והוא לא מדווח על רמת הוודאות שלו.
מודלים שנועדו לקבלת החלטות
מודל מפלה עונה על שאלה מוקלדת עם הסתברות לכל אפשרות מותרת, במעבר יחיד. היא לא יוצרת טקסט. יש שתי אפשרויות להשתתפות בסדנה הזו:
מודל | ספק | איפה המודל פועל בסדנה הזו |
Jev | TypeSafe AI | שירות מארח של TypeSafe, שנקרא באמצעות מפתח API |
DiffusionGemma | Google, משקולות פתוחות | אירוח עצמי במכונה וירטואלית עם GPU בפרויקט בענן שלכם ב-Google Cloud |
אפשר להחליף בין המודלים לפי הצורך, ואין צורך לשנות את הקוד שמתחבר אליהם.
שילוב רכיבים
מערכת מוצלחת מורכבת מכמה רכיבים:
רכיב | תפקיד | בסדנה הזו |
תהליך עבודה | מבצע תזמור של שלבים, מריץ ענפים במקביל ומחזיק מצב משותף | תהליך עבודה של גרף ADK |
קוד דטרמיניסטי | כללים, ערכי סף, אימות. מיידי, חינמי וניתן לבדיקה | כללי המשחק, |
מודל דיסקרימינטיבי | החלטות מהירות ומוגבלות עם ציון רמת סמך | בחירת תגובה בכל טיק |
מודל שפה | תפיסה ויצירה: תמונות וטקסט חופשי | Gemini קורא את התמונה של כרטיס הכישוף וכותב את הכישוף |
ארכיטקטורה של פרסום מודל

בשלב 2 בוחרים את המודל, בהתאם להעדפות ולסביבה. אם אתם מתכננים להשתמש ב-DiffusionGemma, ודאו שיש לכם גישה ל-GPU ב-Google Cloud.
Jev | DiffusionGemma | |
ספק | TypeSafe AI, API מתארח | Google, משקולות פתוחות |
פועל ב | התשתית של TypeSafe | מכונה וירטואלית ב-Compute Engine בפרויקט שלכם, עם GPU |
נקודת קצה (endpoint) |
| באמצעות מנהרת IAP |
אימות |
| הזהות שלכם ב-Google Cloud, שנבדקת על ידי IAP |
עלות | לכל טוקן קלט | התמחור של GPU ב-Compute Engine ב-Google Cloud, בזמן שהמכונה הווירטואלית פועלת |
הגדרה | מפתח API | התקנת המודל במכונה וירטואלית או ב-Cloud Run |
זרימת נתונים
- אפליקציית הזירה או תהליך העבודה של ADK יוצרים בקשה: המצב (מה היריב עשה) ושלוש שאלות.
- ה-SDK של TypeSafe שולח אותו כ-
POST /v1/systemoneלכתובת ה-URL הבסיסית שהוגדרה. - ב-Jev, הבקשה עוברת דרך HTTPS אל
api.typesafe.ai, עם מפתח ה-API כאסימון bearer. - במקרה של DiffusionGemma, הבקשה מועברת אל
localhost:8096. תהליךgcloud compute start-iap-tunnelברקע מעביר אותו דרך שרת proxy לאימות זהויות (IAP), שבודק את הזהות שלכם ב-Google, ליציאה 8080 במכונה הווירטואלית. - במכונה הווירטואלית, הפקודה djev-run מקבלת את הבקשה, מריצה את DiffusionGemma דרך vLLM ב-GPU וקוראת את ההסתברות של כל אפשרות מותרת.
- שני ה-backends מחזירים את אותה תשובה: תשובה לכל שאלה, עם הסתברויות וציון רמת סמך. קוד הסדנה מחיל את ערכי הסף שלו ופועל בהתאם.
DiffusionGemma ב-Compute Engine
scripts/setup_gemma.sh יוצר את הקובץ הבא בפרויקט:
- בודקת אם יש מכסה ל-GPU באזור.
- מפעיל את ממשקי ה-API של Compute Engine ו-IAP, ויוצר את כלל חומת האש
allow-iap-djev. הוא מאפשר רק את טווח הכתובות של IAP, ביציאות 22 ו-8080. - המכונה הווירטואלית נוצרת
djev-l4: סוג המכונהg2-standard-4(4 vCPU, 16GB של זיכרון), מעבד GPU עם 24GB, דיסק של 100GB ואימג' של מכונה וירטואלית ללמידה עמוקה עם מנהל התקן NVIDIA 580. אם באזור מסוים אין קיבולת GPU, המערכת מנסה את האזור הבא. - בהפעלה הראשונה, סקריפט לטעינה בזמן ההפעלה של המכונה הווירטואלית מתקין את Docker ואת ערכת הכלים של NVIDIA לקונטיינרים, מושך את קובץ האימג' של הקונטיינר djev-run, מוריד את המשקלים מ-Hugging Face (17.5GB) ומפעיל את הקונטיינר עם גישה ל-GPU ביציאה 8080. התהליך נמשך כ-15 דקות. הפעלה מאוחרת יותר אורכת כ-2.
- המדיניות הזו כותבת את הגדרות החיבור ל-
.envופותחת את המנהרה.
משימה | פקודה |
הפסקת הפעולה של ה-VM (הדיסק נשמר) |
|
התחלה מחדש |
|
בדיקת המנהרה |
|
מחיקת הכול |
|
הגדרת המודל

TypeSafe SDK
ספריית הלקוח היא typesafe-sdk ל-Python. הסדנה הזו כבר כוללת את התוסף: הוא מותקן בסביבה של סביבת העבודה, לצד google-adk לשלב 6.
pip install typesafe-sdk # or: uv add typesafe-sdk
נקודת קצה של Jev
מודל Jev הוא API מתארח, כך שאין עוד מה להוריד. כדי לקבל מפתח, נרשמים במסוף TypeSafe. ערכת ה-SDK מחפשת את המפתח במשתנה הסביבה TYPESAFE_API_KEY, והסקריפטים של הסדנה הזו קוראים גם קובץ .env בספריית השורש, כך ששורה אחת שם מספיקה:
TYPESAFE_API_KEY=ts-...
שימוש ב-DiffusionGemma
djev-run מטמיע מחדש את ה-API של המודל המפלה. הוא מציג את אותה נקודת קצה של POST /v1/systemone, עם אותן שאלות לגבי noul, בחירה וציון, מ-DiffusionGemma, מודל הדיפוזיה הפתוח של Google DeepMind (26 מיליארד פרמטרים בסך הכול, כ-4 מיליארד פעילים, Apache 2.0). מכיוון שפורמט החיבור זהה, ה-SDK של TypeSafe מתקשר איתו ללא שינוי.
אם בוחרים ב-DiffusionGemma בתרגיל, הוא פועל ב-GPU במכונה וירטואלית בפרויקט בענן שלכם ב-Google Cloud, והכיתוב בטבלט בפינה השמאלית העליונה הוא gemma on vm. סביבת העבודה מגיעה אליו דרך מנהרת IAP פרטית, והיציאה של המודל לא פתוחה לאינטרנט. בשלב 1 מתואר המבנה המלא.
למה מודל דיפוזיה יכול לעשות את זה: הוא ממלא בבת אחת בלוק שלם של מיקומים, וכל מיקום רואה את הקלט המלא, כך שאפשר לקרוא את ההסתברות של כל אפשרות מותרת בשלב אחד. מודל שפה רגיל מייצר אסימון אחד בכל פעם, ולכן צריך לבצע דגימה חוזרת ונשנית.
הפעלת המשחק באופן ידני

הזירה היא משחק הלחימה הקטן ביותר, אבל זה לא אומר שהוא קל: אתם צריכים להיות מהירים וחכמים. מולכם עומד עוג. יש לו הרבה סוגי התקפות, ולפני כל אחת מהן הוא מבצע תנועה עדינה (טלגרף): הוא מרים את האלה, הוא מסתער, הוא מתנדנד כשההגנה שלו פתוחה. בתור לוחם, אתם יכולים להגיב למהלך הזה בחמישה מהלכים שונים: חסימה גבוהה, חסימה נמוכה, התחמקות, מכה והמתנה. זה לא סוג המשחק שמחכה לתור שלכם. יש לכם שתי שניות להגיב לפני שהעוג יתקוף. אם הטיימר יסתיים ולא תעשו כלום, תצטערו על כך מאוד.
בפינה הימנית העליונה של הטבעת יש כרטיס לחשים: כרטיס צבעוני עם שלושה צורות. רק כישוף שתואם לו גורם נזק אמיתי. במשחק, אפשר להטיל לחש באמצעות הלחצנים שמתחת לקרב: בוחרים את צבע הכרטיס, ואז את הצורות שלו משמאל לימין, ואז לוחצים על CAST (הטלה). השעון ממשיך לתקתק בזמן שאתם בוחרים, אז אתם צריכים לבנות את הכישוף ולהגיב להתקפות של העוג בו-זמנית. המקשים 1 עד 5 עדיין משמשים למענה על כל מהלך. אם הטלתם לחש לא נכון, הוא ייכשל. בשלב 6, Gemini מקריא לכם את כרטיס הכישוף.
מסקנה חשובה: קרב הוא רצף של החלטות קטנות עם מועד אחרון לכל אחת מהן. ככה נראית רוב האוטומציה של התוכנה, רק בלי המועדון.
מושגים שקשורים למודל דיסקרימינטיבי

החלטות בתוכנה
מודלים של שפה כבר שנים מצטיינים בשיחות. ברוב התוכנות עדיין לא נעשה שימוש בנתונים האלה לביצוע פעולות אוטומטיות, והסיבה לכך היא לא חוסר אינטליגנציה. היא מהירות.
אם שואלים מודל שפה אם העוג שנמצא מולכם עומד לתקוף, הוא כותב את התשובה שלו טוקן אחד בכל פעם. עד שהפסקה מגיעה, המועדון כבר נחת. בשלב 3 הרגשתם את הגרסה של שתי השניות. גם במקרה כזה, התשובה 'כן' מוטמנת בפסקה שהקוד צריך למצוא ולסמוך עליה, בלי לדעת עד כמה המודל היה בטוח בתשובה שלו.
המודל המפלה לוקח את המצב ואת השאלות והתשובות שהקלדתם במעבר אחד, במילישניות. לכל תשובה מצורפת הסתברות מדויקת: 0.9 אומר שהתשובה נכונה תשע פעמים מתוך עשר. אין טקסט לניתוח ואין JSON שאפשר להפיק ממנו.
מודלים של מערכת 1 ומערכת 2
השם מגיע מהספר לחשוב מהר ולחשוב לאט של דניאל כהנמן. מערכת 2 היא חשיבה רציונלית איטית ומכוונת, שלב אחר שלב. מערכת 1 היא מהירה ומזהה דפוסים.
מודל שפה הוא מכונה של מערכת 2. הוא מנמק באסימונים, אחד בכל פעם. המודל המפלה הוא מודל של מערכת 1: הוא לא מנמק בקול רם, לא יוצר שום דבר ועונה על כל שאלה במעבר אחד. לכן הוא מהיר (בערך 70 עד 500 אלפיות השנייה) וזול (חלקיקי סנט לכל אלף החלטות).
נקודה מרכזית: מודל שפה כותב. מודל החלטה מחליט. רוב מה שתוכנה צריכה מ-AI הוא החלטה.
מגבלות
המודל המבדל לא ייצור טקסט, לא יכתוב קוד, לא ינהל שיחה, לא יבצע פעולות חשבון, לא יקרא תמונה ולא יבצע רצף של פעולות.
בסדנה נבחר אחד מהמודלים המפלים:
- אחד מהמודלים הדיסקרימינטיביים הוא Jev. זהו API מתארח של TypeSafe AI, שהושק בספטמבר 2026. המודל הראשון הוא
jev-1.13, שאליו אפשר לגשת באמצעות הכינויjev-latest. אין משקלים שפורסמו, ולכן מתבצעת קריאה ולא הורדה. - Jev היא לא הדרך היחידה לקבל מודל System One. DiffusionGemma של Google הוא מודל עם משקלים פתוחים שכותב בלוק שלם של טוקנים במקביל במקום אחד בכל פעם, ואותו מעבר מקביל יכול לקרוא הסתברויות על פני קבוצה קבועה של אפשרויות. שרתים בקוד פתוח כמו djev-run מציבים את ה-API המדויק של Jev לפניו, כך שכל מה שמוצג בסדנה הזו פועל ללא שינוי.
מצב ושאלות: Choice, Score וNoul
בכל שיחה נשלחים מצב ושאלות. המצב הוא הטקסט שרוצים לשפוט. הוא יכול להיות מחרוזת, אובייקט JSON או רשימה. השאלות הן לגבי מה שרוצים לדעת על הטקסט. לכל שאלה יש סוג: בחירה, ניקוד או Noul. השאלות מעובדות במקביל, ולכן התשובות מתקבלות במהירות. אפשר להוסיף כמה שאלות שרוצים.
- בחירה בוחרת אפשרות אחת מתוך קבוצה שאתם מגדירים, עד 255 אפשרויות. התשובה היא האפשרות, הסבירות לכל אפשרות ורמת הביטחון. משתמשים בה כשאין סדר בין האפשרויות: חסימה גבוהה, חסימה נמוכה, התחמקות, מכה, המתנה.
- הניקוד מסווג את המצב לפי רמות מסודרות שאתם מתארים, בין שתיים לעשר רמות. התשובה היא מיקום בסולם (מספר עשרוני, כך ש-1.4 פירושו 'בין אחד לשניים, קרוב יותר לאחד'), ההסתברות של כל רמה ורמת סמך. כדאי להשתמש בו כשרוצים לדעת עד כמה המכה הנכנסת תהיה חזקה.
- הפונקציות Choice ו-Score מחזירות הסתברות לכל אפשרות ורמת ודאות. ההבדל הוא בתשובה העיקרית. הפונקציה Choice מחזירה את האפשרות הסבירה ביותר. הציון מתייחס לאפשרויות כאל רמות מסודרות ומחזיר את הממוצע המשוקלל שלהן, שיכול להיות בין שתי רמות. אם לא בוחרים אף אחת מהאפשרויות, המשקל הוא 0.05. אם בוחרים באפשרות 'קל', המשקל הוא 0.55. אם בוחרים באפשרות 'כבד', המשקל הוא 0.40. אם בוחרים באפשרות 'קל', התשובה היא 1.35, בין קל לכבד. הזירה משתמשת בערך הזה:
choose()מתייחסת לציון סכנה של 1.5 ומעלה כפגיעה חזקה.
- הפונקציות Choice ו-Score מחזירות הסתברות לכל אפשרות ורמת ודאות. ההבדל הוא בתשובה העיקרית. הפונקציה Choice מחזירה את האפשרות הסבירה ביותר. הציון מתייחס לאפשרויות כאל רמות מסודרות ומחזיר את הממוצע המשוקלל שלהן, שיכול להיות בין שתי רמות. אם לא בוחרים אף אחת מהאפשרויות, המשקל הוא 0.05. אם בוחרים באפשרות 'קל', המשקל הוא 0.55. אם בוחרים באפשרות 'כבד', המשקל הוא 0.40. אם בוחרים באפשרות 'קל', התשובה היא 1.35, בין קל לכבד. הזירה משתמשת בערך הזה:
- Noul שואל שאלה עם תשובות של כן או לא ומחזיר את ההסתברות שהתשובה היא כן. ערך קרוב ל-1 מציין הסתברות גבוהה, ערך קרוב ל-0 מציין הסתברות נמוכה, וערך קרוב ל-0.5 מציין הסתברות בינונית. אין רמת סמך נפרדת, כי ההסתברות היא רמת הסמך.
כתיבת שאלות ממוקדות
המודל הדיסקרימינטיבי פועל בצורה הטובה ביותר כששואלים שאלה על דבר ספציפי אחד, עם היקף מוגדר היטב. התשובה לשאלה 'מה המצב?' היא תשובה סבירה עם רמת מהימנות נמוכה. "What is the right response?" התשובות לשאלות 'האם היריב חשוף?' ו'כמה חזקה תהיה המכה הזו?' הן שלוש תשובות ממוקדות שהקוד משלב.
תיאורים של אפשרויות ורמות הם זולים וחשובים. הכללים שקראתם בשלב 3 הופכים לתיאורי האפשרויות: block_high: "Raise the shield. Right against an overhead or a high swing." כך המודל הדיסקרימינטיבי לומד את כללי הקרב, בזמן הבקשה, בשורה אחת כל פעם. האפשרויות יכולות להשתנות בהתאם למצב: הזירה מציעה רק cast כשכישוף מוכן.
הסתברויות ומהימנות
תשובה מסוג בחירה היא לא תווית. זהו פיזור על פני התוויות, והתווית היא רק העמודה הכי גבוהה.
איך המודל מקבל את המספר. הוא משתמש באותו שלב שבו מודל שפה בוחר את המילה הבאה. מודל טרנספורמר קורא את הטקסט, ובמיקום מסוים נותן לכל טוקן באוצר המילים שלו ציון גולמי שנקרא לוגיט. ערך לוגיט גבוה יותר מציין שהטוקן מתאים יותר למיקום הזה. פונקציית softmax הופכת את ערכי ה-logit להסתברויות שסכומן הוא 1. מודל שפה בוחר טוקן, מוסיף אותו לטקסט וחוזר על הפעולה. מודל דיסקרימינטיבי מפסיק אחרי ההסתברויות.
התשובה הריקה היא פער בטופס תשובות. השרת כותב את הטופס עצמו, כמו response: ▢, ומשאיר רווח אחד לכל שאלה. התפקיד היחיד של המודל הוא לתת ניקוד למה שצריך להיות בכל רווח.
- ההנחיה מכילה את המצב ואת כל שאלה, עם כל תשובה אפשרית כתווית קצרה:
aל-block_high,bל-block_low וכן הלאה. - השרת מוסיף את טופס התשובה, עם שורה ריקה לכל שאלה.
- המודל קורא את ההנחיה ואת הטופס במעבר אחד, ונותן לכל טוקן לוגיט בכל מקום ריק. מודל הדיפוזיה רואה את הטופס כולו בבת אחת ומקצה ניקוד לכל השדות הריקים יחד.
- השרת שומר רק את הלוגיטים של התוויות המותרות ומחיל עליהן softmax, כך שהתשובות המותרות מסתכמות ב-1.
- אם הקריאה נראית לא בטוחה, השרת קורא שוב מנקודת התחלה אקראית אחרת ומחשב את הממוצע של הקריאות.
רמת הסמך היא מספר אחד שמציין עד כמה התשובה ודאית. TypeSafe מחשב אותה על סמך ההסתברות של כל אחת מהאפשרויות. אם כל התקציב מוקצה לאפשרות אחת, הציון הוא 1. אם התקציב מחולק באופן שווה בין האפשרויות, הציון הוא 0. אם יש שלוש אפשרויות, הנוסחה היא (3 × הגודל הגדול ביותר − 1) / 2.
TypeSafe trains Jev for calibrated probabilities. הסבירות תואמת לתדירות שבה התשובה נכונה. במודל מכויל, תשובות שניתנות ברמת ודאות של 0.7 נכונות בערך ב-70% מהמקרים. לכן, סף הוודאות הוא סף שמגדיר את התדירות שבה אתם מקבלים תשובה שגויה. השרת של DiffusionGemma בסדנה הזו מדווח על ההסתברות הכי גבוהה כרמת סמך, כממוצע של הקריאות שלו. אם יש הבדלים בין הקריאות, הממוצע מתפזר ורמת הוודאות יורדת.
מסקנה עיקרית: התשובה מסבירה מה. המדד הזה מצביע על מידת הוודאות שהתוצאות לא מקריות, ושהשיפור נבע מהמודעות.
סכומי סף
הסף הוא האופן שבו אתם מגדירים את הפעולה בקוד. המודל מחזיר רמת ודאות או הסתברות. הקוד משווה את המספר למספר שבחרתם, והתוצאה קובעת מה יקרה.
סף לכל פעולה. TypeSafe מציעה לפצל את רמת הביטחון לטווחים. פעולה ברמת סמך גבוהה מתבצעת באופן אוטומטי. פעולות ברמת מהימנות בינונית, כמו בקשת אישור או סימון המקרה לבדיקה. רמת סמך נמוכה לא פועלת, וחוזרת למשהו בטוח או לאדם.
TRUST = 0.40 # below this, the answer is a guess
AUTO = 0.80 # at or above this, act without a check
def route(answer):
if answer.confidence >= AUTO:
return act(answer.choice) # high: act on its own
if answer.confidence >= TRUST:
return confirm(answer.choice) # medium: act with a check
return fall_back() # low: do something safe
הכללים של הזירה. הסף של הזירה נמצא ב-choose(), שמפעילים בשלב 5.
TRUST_CONFIDENCE = 0.40 # below this, the model is guessing between responses
HEAVY_DANGER = 1.5 # a danger score at or above this is a heavy hit
SPEND_ON_OPENING = 0.60 # exposed at or above this, with a spell ready, cast
def choose(answers, spell_ready):
response = answers["response"]
exposed = answers["exposed"].noul
danger = answers["danger"].score
action = response.choice
if response.confidence < TRUST_CONFIDENCE and danger >= HEAVY_DANGER:
action = "dodge" # shaky answer, heavy hit coming
if spell_ready and action == "strike" and exposed >= SPEND_ON_OPENING:
action = "cast" # a clear opening is worth the spell
return action
אזהרה: תשובה תקפה היא לא תמיד תשובה נכונה. המודל המפלה לא יכול להחזיר אפשרות שלא הצעתם, ולכן הוא אף פעם לא ממציא מהלך, אבל הוא יכול לבחור את המהלך הלא נכון, לפעמים ברמת ודאות גבוהה. לפני שסומכים על סף מסוים, כדאי לבדוק את השאלות ביחס למצבים שכבר נשפטו.
קבלת החלטות אוטומטית באמצעות המודל

בקשה ותשובה
בקשה. ערכת TypeSafe Python SDK מאפשרת לכם ליצור את השאלות ולשלוח אותן למודל.
from typesafe_sdk import Choice, Noul, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state={"opponent": OPPONENT, "telegraph": telegraph},
questions={
"response": Choice(instructions="What is the right response?", criteria=RESPONSES),
"exposed": Noul(instructions="Is the opponent exposed to a counter-attack right now?"),
},
)
response.choices["response"].choice # "strike"
response.nouls["exposed"].noul # 0.97
בקשה אחת לכל סימון
בכל תקתוק, האפליקציה שולחת את הטלגרף כמצב ושואלת שלוש שאלות בשיחה אחת:
- איזו תשובה נכונה מתוך חמש (או שש, כשמוכנה הטלת לחש). בחירה.
- אם העוגר חשוף לדלפק כרגע. נוול.
- עד כמה קשה הפגיעה הנכנסת, לפי סולם של שלוש רמות. ציון.
def reflex_questions(spell_ready):
options = dict(RESPONSES)
if spell_ready:
options["cast"] = CAST # only offered when there is a spell
return {
"response": Choice(instructions="The opponent has just done this. What is the right response?",
criteria=options),
"exposed": Noul(instructions="Is the opponent exposed to a counter-attack right now?"),
"danger": Score(instructions="How much damage is about to land if the fighter does nothing?",
criteria=["None: this is not an attack.", "A light hit.", "A heavy hit."]),
}
הפונקציה choose()
זוכרים את ערכי הסף משלב 4? choose() משווה את התשובות של המודל למספרים קבועים, והמספרים הקבועים האלה הם הספים.
TRUST_CONFIDENCE = 0.40
HEAVY_DANGER = 1.5
SPEND_ON_OPENING = 0.60
def choose(answers, spell_ready):
action = answers["response"].choice
if answers["response"].confidence < TRUST_CONFIDENCE and answers["danger"].score >= HEAVY_DANGER:
action = "dodge" # shaky call, heavy hit coming: play it safe
if spell_ready and action == "strike" and answers["exposed"].noul >= SPEND_ON_OPENING:
action = "cast" # the Discriminative model saw the opening; the code spends the spell
...
choose() הוא קריאה רגילה של Python של ערכים מוקלדים, עם שני כללים. המודל הדיסקרימינטיבי מספק את ההסתברות והניתוח שלו, והקוד משתמש בספי ערכים לכללים. הפעולה שנבחרה נשלחת למנוע, שבו היא תשמש כדי להילחם בעוג.
הנקודה העיקרית: כדאי לשמור את השאלות ואת ערכי הסף במקום אחד. הם החלק בשילוב של System One שתצטרכו לבצע בו את רוב ההתאמות.
זמן תגובה, תמחור לפי קלט ולוגיקת החלטות
- זמן התגובה לכל החלטה. כל טיק של הקרב בחלק ב' חזר תוך כ-100 מילישניות, חלקם תוך שתיים או שלוש. זה מספיק מהר ללולאת משחק, לנתיב בקשה או לבדיקה של כל הודעה לפני שאדם או מודל שפה רואים אותה.
- תמחור מבוסס-קלט. עלות של קרב שלם, שכולל שישים החלטות עם שלוש שאלות בכל אחת, היא הרבה פחות מעשירית הסנט. מספר הטוקנים של הפלט הוא אפס כי לא נוצר שום דבר. המשמעות היא שאתם יכולים לבקש יותר ממה שאתם צריכים. הזירה שואלת אם העוג חשוף בכל טיק, למרות שרק
strikeו-castמתעניינים בכך, כי השאלה כמעט לא עולה כלום והתשובה שימושית בלוח הבקרה. ב-TypeSafe זה נקרא speculative fan-out. - לשלב בין ביטחון לסכנה. אם רמת הביטחון של המודל הדיסקרימינטיבי בתשובה שלו נמוכה מ-0.40 וציון הסכנה מצביע על פגיעה חמורה,
choose()מבטל את התשובה ומחליף אותה בתשובה מתחמקת. התחמקות היא בדרך כלל לא התשובה הכי טובה, אבל גם לא הכי גרועה. בוחרים ערכי סף מתוך העלות של כל טעות, לא מתוך מספר עגול, ובודקים אותם מול טלגרפים שכבר שפטתם ידנית. ההמלצה של TypeSafe: אם החלטה מסוימת ממשיכה להניב תוצאות שגויות, כדאי להגדיר את השאלה בצורה מדויקת יותר לפני שמזיזים את הסף.
שילוב מודלים בתהליך עבודה ב-ADK

מגבלות של החלטות בכל טיק ולמה תשובות נכונות לא מספיקות
בשורה האחרונה של הקרב בשלב 5 כתוב: העוג מתרחק, בקושי שרוט. המודל הדיסקרימינטיבי לא ספג נזק וגרם נזק קטן בכל טיק, ו-300 נקודות פגיעה זה יותר מנזק קטן כפול שישים. כרטיס הכישוף בפינת הזירה היה שם כל הזמן. כדי לקרוא את התמונה, צריך מודל שיכול לראות אותה.
לעוג יש 300 נקודות פגיעה. שיחה נכונה נספרת כ-3. מכה לתוך הפתח תגרום לנזק של 8, כי העור עבה. גם אם תצליחו להילחם באוגר במשך 60 טיקים מושלמים, הוא יישאר עומד עם חבורות, והמשחק יקבע שהקרב הסתיים בתיקו. כאן הסתיים שלב 5: המודל התגונן היטב, אבל עדיין לא הצליח לנצח.
רק לחש גורם נזק אמיתי: 45 אם ההטלה מושלמת, 67 אם הוא פוגע בפתח.
הקצאת כל משימה למודל המתאים
קלף הכישוף בפינת הזירה הוא הדרך לנצח, והקריאה שלו לא קשורה לטקסט: זהו ציור עם צבע ושלוש צורות בשורה, וצריך לשיר את הכישוף בהתאם. התהליך הזה מתבצע על ידי מודל שיכול לבחון תמונה במשך כמה שניות. בקרב, כמה שניות הן עשרה טיקים.
לכן, תהליך העבודה משתמש בשניהם, כל אחד במהירות שלו:
- המודל הדיסקרימינטיבי נלחם. כל טיק, קריאה אחת, החלטה אחת, מאית שנייה. הלולאה אף פעם לא מחכה למשהו איטי יותר ממנה.
- Gemini קורא ושר. בענף משלו, שמתחיל בצלצול, הוא מצלם תמונה של כרטיס הקסם ממסך הזירה, נותן שם לצבע ולצורות ושר לחש. הזירה שופטת את השיר לפי התשובה של קלף הכישוף, שלא יוצאת מהשרת.
- אחרי כל החלפה, הלוחם בודק את המשבצת. צומת
check_spellבודק את המצב. לא מוכן: כתוב שהוא לא מוכן, כמה זמן Gemini שר, והוא חוזר ישר לסימן הבא. היא אף פעם לא מחכה. מוכן:castמצטרף לאפשרויות שהמודל הדיסקרימינטיבי מציע, וchoose()משתמש בלחש ברגע שהמודל הדיסקרימינטיבי מדווח על פתיחה. כשהלחש נגמר, מופיע על המסך כרטיס לחש חדש והחוט האיטי מתחיל שוב. אם השיר לא נקרא נכון, כרטיס הכישוף נשרף והחוט האיטי קורא את הכרטיס החדש. - Gemini כותב סיפור קצר פעם אחת, בסוף.

ענפים מקבילים עם השהיות שונות ולולאת אירועים אחת
זהו Workflow של ADK: תרשים של צמתים שמחוברים באמצעות קצוות. צומת הוא פונקציית Python רגילה או סוכן LLM. קשת מצומת אחד לטופל של צמתים היא fan-out: שניהם מתחילים בו-זמנית. צומת שמחזיר Event עם route בוחר איזו קשת תהיה הבאה, וצומת שמנתב את עצמו הוא לולאה.
אפשר לחשוב על זה כמו על שני שרשורים. הפעולה הראשונה איטית: קוראים את כרטיס הכישוף, שרים, מאחסנים את הכישוף. ההליך בשרשור 2 מהיר: תקתוק, בדיקת המשבצת, תקתוק נוסף. השרשור 1 מסתיים בפונקציה שכותבת את האיות שנשפט למשתנה state של הסשן ולא מחזירה פלט. השרשור השני check_spell קורא את המצב הזה אחרי כל חילופי נתונים. השרשורים לא קוראים אחד לשני ולא מחכים אחד לשני, הם רק משתפים מצב.
המסר העיקרי: כדאי להגדיר את ההחלטות בקוד ולתת לכל מודל משימה ספציפית בקצב שלו.
ערכת ה-ADK מריצה את שני הענפים כמשימות בלולאת אירועים אחת, בשרשור יחיד. אפשר להריץ רק משימה אחת בכל פעם. כשמשימה מגיעה ל-await, היא מחכה לתשובה, והלולאה מפעילה את הענף השני בינתיים. הענף המהיר מחכה למודל בערך עשירית השנייה, והענף האיטי מחכה ל-Gemini כמה שניות, כך שאף אחד מהם לא מעכב את השני.
הסתעפות איטית
הפונקציה read_rune() מוציאה את כרטיס הכישוף מהמסך כתמונה.
def read_rune(ctx: Context, node_input) -> Event:
png = _arena(ctx).rune_png() # exactly what the screen shows
return Event(output=types.Content(role="user", parts=[
types.Part(text="This spell card is on the arena's screen right now. Sing the spell that matches it."),
types.Part.from_bytes(data=png, mime_type="image/png"),
]))
spellwright הוא Gemini. הוא קורא את התמונה ועונה בצורה קבועה.
class Sung(BaseModel):
element: str # fire, frost, earth, storm
glyphs: list[str] # three of: circle, ring, square, diamond, triangle, cross, crescent, bar
incantation: str
spellwright = LlmAgent(name="spellwright", model="gemini-flash-latest",
instruction="You are the spellwright ... read the three shapes left to right ...",
output_schema=Sung)
הפונקציה spell_ready() גורמת לשופט בזירה לשפוט את הכישוף, ואז לשמור אותו או לנסות שוב.
def spell_ready(ctx: Context, node_input: dict) -> Event:
spell = _arena(ctx).sung(dict(node_input)) # the arena judges it against the spell card
return Event(state={"spell": spell if spell["damage"] > 0 else None},
route="retry" if spell["damage"] <= 0 else "stored")
צומת פונקציה יכול להחזיר Content עם חלק של תמונה, וצומת ה-LLM מקבל אותו כפנייה של המשתמש. spell_ready מחזירה Event עם שינוי במצב וללא output. בטיק הבא, הכישוף נקרא מהמצב, וענף ללא פלט הוא לא סיום שני לגרף: ADK דורש פלט סופי אחד, והוא הקרב.
הערה: השיפוט הוא קוד, בזירה, מול התשובה המוסתרת של כרטיס הכישוף. קריאה מושלמת היא 45, יותר לתוך פתיחה. שתי צורות ימינה נותנות 25. אם יש טעות בקריאה, הכישוף נכשל והקלף נשרף. לא שואלים את Gemini אם הוא צדק.
הסתעפות מהירה
tick() מפעיל החלפה אחת ואז בוחר את הקצה הבא.
async def tick(ctx: Context, node_input) -> Event:
arena = _arena(ctx)
spell = ctx.state.get("spell") # did the slow branch deliver?
move = await asyncio.to_thread(arena.telegraph)
async with AsyncTypeSafeClient() as jev:
answers = await jev.system_one(
state={"opponent": engine.OPPONENT["description"], "telegraph": move["telegraph"]},
questions=reflex.reflex_questions(spell_ready=spell is not None),
)
decision = reflex.choose(answers.answers, spell_ready=spell is not None)
entry = await asyncio.to_thread(arena.respond, decision["action"], decision, ...)
over = entry["you"] <= 0 or entry["foe"] <= 0 or entry["tick"] >= engine.MAX_TICKS
routes = [] # which arrows in the graph to follow next
if entry["spell_used"] and not over:
routes.append("recast") # a new spell card is on the screen: read it
routes.append("done" if over else "next")
return Event(output="fight", route=routes, state={"tick": ..., "spell": None, ...})
check_spell() בודקת את משבצת הכישוף אחרי כל חילופי מידע.
def check_spell(ctx: Context, node_input) -> Event:
spell = ctx.state.get("spell") # thread 1 writes it; this only reads
if spell:
report = {"ready": True}
else:
report = {"ready": False, "waited": now - ctx.state["forging_since"]}
return Event(output="fight", route="again", state={"spell_check": report})
השיטה check_spell בודקת את המיקום אחרי כל החלפה. הוא אף פעם לא חוסם: אם הכישוף לא מוכן, הוא מדווח על כך וממשיך הלאה.
יש שלושה דברים שקובעים את העיצוב. הקריאה למודל הדיסקרימינטיבי awaitתתבצע באמצעות הלקוח האסינכרוני, כך שהלולאה תניב בזמן ההמתנה והסתעפות Gemini תמשיך לפעול. השאלות נוצרות מחדש בכל עדכון, ולכן הסמל cast מופיע רק כשיש תוכן להפעלה ב-Chromecast. route יכול להיות רשימה: ["recast", "next"] לוקח את שני הקצוות בבת אחת.
הזירה עצמה נמצאת מאחורי לקוח קטן: האפליקציה שפועלת באמצעות HTTP אם יש כזו, כך שהדף מציג את הקרב; המנוע בתהליך אם אין כזו.
הגדרת הגרף
root_agent = Workflow(
name="arena",
edges=[
("START", enter),
(enter, (read_rune, tick)), # fan-out: slow branch + fast loop
(read_rune, spellwright, spell_ready),
(spell_ready, {"retry": read_rune, "stored": rest}), # misread: read the new spell card; else rest
(tick, {"next": check_spell, "recast": read_rune, "done": summarise}),
(check_spell, {"again": tick}), # not ready? keep fighting
(summarise, bard, finish),
],
)
טופל כיעד הוא פיצול. טופל כקצה הוא שרשרת. מילון ממפה שמות של נתיבים לצמתים. tick → check_spell → tick היא הלולאה המהירה. "recast": read_rune מתחיל מחדש את השרשור האיטי אחרי שמשתמשים בלחש, "retry" עושה את אותו הדבר אחרי שהלחש נכשל, ו-"stored": rest מאפשר לשרשור האיטי להסתיים בשקט, ללא פלט, אחרי שהלחש נמצא במשבצת. ה-ADK דורש לפחות קצה אחד עם ניתוב במחזור, ולכן לולאה ללא תנאי נדחית לפני שהיא יכולה לפעול לנצח.
הערה: הכלי root_agent הוא מה שכלי ה-ADK מחפשים. adk web agents מהשורש של הסדנה פותח את ממשק המשתמש למפתחים עם הזירה, אם רוצים לראות את הגרף ואת האירועים בדפדפן ולא במסוף.