AG-UI — חיבור בין Backend Agentic לממשק Frontend
מאת צוות מדיה דיל · 12.08.2026 · Generative UI · 5 דק׳
מדריך טכני לפרוטוקול AG-UI: איך מזרימים אירועי מצב, כלים ואישורים בזמן אמת מריצת סוכן Backend אל ממשק Frontend חי, בלי Spinner אחיד וריק.
צוות פיתוח בונה סוכן AI שמבצע מחקר שוק מרובה-שלבים: הוא מריץ שאילתות, מנתח נתונים, כותב טיוטת דוח, ולפעמים עוצר כדי לשאול את המשתמש שאלת הבהרה. הבעיה שמתגלה מהר היא לא הסוכן עצמו — היא הפער בין מה שקורה בפועל בצד השרת לבין מה שהמשתמש רואה במסך. אם ה-Frontend מקבל רק "תשובה סופית" בסוף הריצה, המשתמש יושב מול מסך טעינה ריק לדקה וחצי, בלי לדעת אם הסוכן תקוע, עובד, או ממתין לתשובה ממנו. AG-UI (Agent-to-UI, בגרסתו הממוקדת-Backend) הוא פרוטוקול שנועד לפתור בדיוק את הפער הזה: ערוץ תקשורת חי בין ריצת סוכן ל-Frontend, שמזרים אירועי מצב, ביניים ופעולה תוך כדי שהסוכן עדיין רץ.
מה AG-UI פותר שלא נפתר ב-REST רגיל
קריאת API קלאסית היא בקשה-תשובה: שולחים בקשה, מחכים, מקבלים תשובה אחת. סוכן agentic לא מתאים לדפוס הזה כי הוא לא פעולה אטומית — הוא שרשרת של צעדים, קריאות לכלים, והחלטות ביניים, שיכולה להימשך שניות ספורות או דקות ארוכות. AG-UI מגדיר פרוטוקול streaming מבוסס אירועים (בדרך כלל על גבי Server-Sent Events או WebSocket) שמעביר לכל אורך הריצה הודעות כמו "התחלתי כלי חיפוש", "קיבלתי תוצאה חלקית", "אני זקוק לאישור לפני שאמשיך", ורק בסוף "סיימתי, הנה הפלט הסופי". זה משנה את חוויית המשתמש מהיסוד: במקום Spinner אחיד, המשתמש רואה תהליך חי — בדיוק כמו שהתקדמות אמיתית ב-רינדור concurrent ב-React מאפשרת להציג חלקי ממשק לפני שהכול מוכן.
אנטומיית אירוע AG-UI
הפרוטוקול מגדיר טקסונומיה של סוגי אירועים, שכל אחד מהם מתורגם בצד הלקוח לעדכון state ספציפי:
- run.started / run.finished — תחילת וסוף ריצה שלמה של הסוכן.
- tool.call / tool.result — קריאה לכלי חיצוני והתוצאה שהתקבלה ממנו, מה שמאפשר להציג "הסוכן מחפש טיסות..." בזמן אמת.
- text.delta — קטע טקסט נוסף שהתווסף לתשובה, לרינדור streaming של תוכן טקסטואלי.
- ui.render — בקשת רינדור של רכיב ממשק, לרוב לפי סכימת A2UI — כאן שני הפרוטוקולים משיקים: AG-UI מעביר את האירוע, A2UI מגדיר את מבנה הרכיב עצמו.
- state.snapshot — תמונת מצב מלאה של ה-state הפנימי של הסוכן, לשימוש בסנכרון מחדש אם הלקוח התנתק ונדרש להתעדכן.
ה-Frontend מנהל state machine שמאזינה לזרם הזה ומעדכנת את המסך בהתאם. חשוב להבין: זה לא סתם "לוג" שמוצג למשתמש — כל אירוע יכול לשנות רכיב אמיתי במסך, ולכן שכבת הצריכה (consumer) חייבת להיות עמידה לסדר לא מובטח, כפילויות, ואירועים שמגיעים מאוחר בגלל latency ברשת.
ניהול State כשהמקור הוא זרם אירועים
האתגר הארכיטקטוני המרכזי הוא לגשר בין זרם אירועים רציף (שמגיע מ-Backend) לבין מודל state סינכרוני שרוב מסגרות ה-Frontend בנויות עליו. הפתרון הנפוץ הוא reducer שמצפה על כל אירוע נכנס ומעדכן עץ state מקומי בהתאם — דפוס דומה מאוד לאיך שמנהלים side effects אסינכרוניים בכל ארכיטקטורת ניהול State רצינית. ההבדל המהותי הוא שקצב האירועים כאן לא נשלט על ידי המשתמש (כמו קליק) אלא על ידי הסוכן, ולכן צריך גם מנגנון backpressure — אם אירועים מגיעים מהר מדי לרינדור (למשל text.delta בקצב גבוה), עדיף לצבור אותם ולרנדר ב-batch מתוזמן, ולא לגרום ל-re-render בכל תו בודד שמגיע.
שיקול נוסף הוא שחזור אחרי ניתוק. אם המשתמש סוגר את הטאב ופותח מחדש באמצע ריצת סוכן ארוכה, ה-Frontend צריך לבקש state.snapshot עדכני במקום לנסות "לנחש" את מה שהפסיד מהזרם. תכנון נכון מפריד בין ערוץ ה-streaming השוטף (לחוויית משתמש חלקה) לבין נקודת אמת מרכזית שאפשר לשלוף בכל רגע (ל-resync אמין).
אישורים בזמן אמת (Human-in-the-Loop)
אחת התכונות החשובות ביותר ב-AG-UI היא תמיכה מובנית באירוע "בקשת אישור" — הסוכן עוצר באמצע הריצה ומחכה לתגובת משתמש לפני שהוא ממשיך. זה שונה מהותית מקריאת API רגילה, כי ה-state של הריצה כולה "תלוי" בענן עד שהתשובה מגיעה, לפעמים דקות ארוכות אחר כך. המימוש דורש שה-Backend ישמור את מצב הריצה בצורה שניתן להמשיך אותה (resumable), ושה-Frontend יידע להציג בבירור מה בדיוק מבקשים מהמשתמש לאשר, ולא רק כפתור "אישור" גנרי — עיקרון שנרחיב עליו במדריכים על שקיפות והרשאות בממשקי סוכן.
ברמה הפרקטית, כדאי לתכנן מראש רשימת "נקודות עצירה" חוקיות בכל תהליך אגנטי — לא כל צעד צריך לעצור לאישור, אבל צעדים בלתי הפיכים (שליחת מייל, ביצוע תשלום, מחיקת רשומה) כן. הגדרה מפורשת של הרשימה הזו בקוד, ולא הסתמכות על שיקול הדעת של המודל בזמן ריצה, היא ההבדל בין מערכת שאפשר לסמוך עליה למערכת שמפתיעה לרעה פעם בכמה מאות ריצות.
ריבוי סוכנים וניתוב אירועים
בארכיטקטורות מתקדמות יותר, לא מדובר בסוכן בודד אלא בכמה סוכנים שפועלים במקביל — אחד מחפש מידע, שני מנתח, שלישי כותב סיכום. AG-UI צריך במקרה כזה לתייג כל אירוע במזהה הסוכן שיצר אותו, כדי שה-Frontend יוכל להציג לוח מעקב שמפריד בין הפעילויות ולא מציג אותן כזרם מבולבל אחד. זה דורש גם מנגנון ניתוב (routing) בצד הלקוח שמפצל את זרם האירועים המשולב לתת-זרמים לפי סוכן, לפני שהוא מגיע ללוגיקת הרינדור עצמה — דפוס דומה לרכיבי Islands עצמאיים שמתעדכנים כל אחד בנפרד בלי לגרור רענון של כל העמוד.
נקודה מעשית נוספת היא ניהול שגיאות ברמת הריצה כולה, לא רק ברמת אירוע בודד. אם סוכן נכשל באמצע ריצה — כלי חיצוני מחזיר שגיאה, או המודל עצמו לא זמין רגעית — AG-UI מגדיר אירוע run.error ייעודי שמאפשר ל-Frontend להציג הודעה ברורה ואפשרות ניסיון חוזר, במקום להשאיר את המסך תקוע במצב "בתהליך" לנצח. חוויית משתמש טובה בממשקי סוכן נמדדת לא רק בזמן ריצה מוצלח, אלא באיכות הטיפול בכשל — וזה בדיוק המקום שבו רוב המימושים הראשוניים נופלים, כי קל להתמקד ב-happy path ולשכוח לתכנן את מסלול הכישלון מראש.
ביצועים: קצב אירועים מול קצב רינדור
נקודה שקל לפספס היא שקצב האירועים שהסוכן שולח לא בהכרח תואם לקצב שבו כדאי לרנדר מחדש את המסך. מודל שמזרים תשובה מילה-מילה יכול לשלוח עשרות text.delta בשנייה, וניסיון לעדכן את ה-DOM על כל אחד מהם פוגע בביצועים ויוצר תחושת "רעד" חזותי במקום זרימה חלקה. הפתרון המקובל הוא לצבור דלתא בחוצץ (buffer) ולשחרר רינדור בקצב קבוע — למשל כל 16 עד 50 מילישניות — כך שהמשתמש רואה טקסט זורם באופן טבעי, אבל ה-Frontend לא מבזבז מחזורי רינדור מעבר לנדרש. אותו עיקרון חל גם על אירועי state.snapshot כבדים יותר: אין צורך לעבד כל תמונת מצב שמגיעה אם היא לא השתנתה מהותית מהקודמת, ועדיף להשוות (diff) לפני שמפעילים רינדור מלא מחדש.
תגיות: AG-UI · Agent Protocol · Server-Sent Events · State Management · Human in the Loop · Streaming