Streaming Architecture ל-AI — Streaming Tokens ו-Events בזמן אמת
מאת צוות מדיה דיל · 09.08.2026 · AI · 9 דק׳
מדריך טכני ל-Streaming Architecture במערכות AI: Server-Sent Events מול WebSockets, טיפול בניתוק חיבור באמצע stream, streaming של tool calls, ובניית UI שמרגיש חי בזמן אמת.
משתמש שולח שאלה למערכת AI, ומקבל מסך ריק למשך שלוש שניות, ואז לפתע - כל התשובה המלאה מופיעה בבת אחת. אותו משתמש, באותה מערכת בדיוק אבל עם Streaming מוטמע, רואה את התשובה "נכתבת" מולו מילה אחר מילה, כמעט מיד. שתי החוויות שונות דרמטית, גם כשהזמן הכולל עד לתשובה השלמה זהה. Streaming - העברת התשובה למשתמש בהדרגה, טוקן אחרי טוקן, ככל שהמודל מייצר אותה - הוא לא רק שיפור קוסמטי; הוא דורש ארכיטקטורה שונה לגמרי מצד השרת, ולא רק שינוי ב-UI בצד הלקוח.
Server-Sent Events מול WebSockets - איזה פרוטוקול ומתי
שתי הטכנולוגיות המרכזיות למימוש streaming הן Server-Sent Events (SSE) ו-WebSockets, וההבדל ביניהן חשוב להבין לפני בחירה. SSE הוא פרוטוקול חד-כיווני - השרת שולח נתונים ללקוח בזרם רציף, אבל הלקוח לא יכול לשלוח נתונים חזרה דרך אותו חיבור. זה מתאים בול לרוב מקרי השימוש של streaming תשובות LLM: הלקוח שולח שאלה אחת דרך בקשת HTTP רגילה, והשרת מזרים בחזרה את התשובה טוקן אחרי טוקן. SSE פשוט יותר למימוש, עובד מעל HTTP רגיל בלי צורך בפרוטוקול נפרד, ותומך אוטומטית בהתחברות מחדש (reconnection) במקרה של ניתוק זמני.
WebSockets, לעומת זאת, הוא פרוטוקול דו-כיווני מלא - שני הצדדים יכולים לשלוח נתונים בכל רגע דרך אותו חיבור פתוח. זה נחוץ במקרים שבהם צריך אינטראקציה דו-כיוונית אמיתית תוך כדי streaming - למשל משתמש שיכול "להפריע" למודל באמצע ייצור תשובה, או מערכת שדורשת עדכונים בזמן אמת בשני הכיוונים בו-זמנית (כמו שיחת קול חיה). עבור רוב מערכות ה-chat הטקסטואליות הסטנדרטיות, SSE מספיק ופשוט יותר לתחזוקה; WebSockets מוצדק בעיקר כשיש צורך אמיתי בערוץ דו-כיווני מתמשך.
מבנה אירועי Streaming - יותר מסתם טוקנים
Streaming אמיתי במערכת AI מודרנית לא מעביר רק טוקני טקסט - הוא מעביר זרם אירועים (events) מובנה שכולל סוגי מידע שונים: תחילת תשובה, טוקן טקסט, קריאה לכלי (tool call) שמתחילה, תוצאת כלי שהתקבלה, סיום תשובה, ולעיתים גם מטא-דאטה כמו מספר טוקנים שנצרכו. תכנון נכון של פרוטוקול האירועים הזה מראש חשוב מאוד - הוא קובע כמה גמיש ה-UI בצד הלקוח יכול להיות בהצגת מה שקורה בפועל.
// דוגמה למבנה אירוע SSE טיפוסי
event: token
data: {"text": "שלום"}
event: tool_call_start
data: {"name": "search_database", "id": "call_1"}
event: tool_call_result
data: {"id": "call_1", "result": "..."}
event: done
data: {"finish_reason": "stop", "tokens_used": 342}
מבנה כזה מאפשר ל-UI להציג לא רק את הטקסט המתגלגל, אלא גם אינדיקציות ויזואליות משמעותיות - "מחפש במסד הנתונים..." כשמתחילה קריאת כלי, ולא רק שקט מסתורי עד שהתשובה הסופית מגיעה. זה קריטי במיוחד במערכות אג'נטיות שמבצעות כמה שלבים לפני מתן תשובה סופית - בלי streaming של אירועי ביניים, המשתמש לא יודע אם המערכת בכלל "עובדת" או תקועה.
Streaming של Tool Calls - האתגר הטכני המורכב יותר
streaming של טקסט רגיל פשוט יחסית - כל טוקן חדש פשוט מתווסף לתצוגה. Streaming של קריאות כלים (Function Calling) מורכב יותר, כי הפרמטרים של קריאת הכלי עצמם מיוצרים באופן הדרגתי, טוקן אחרי טוקן, בדיוק כמו טקסט - אבל הם צריכים להיות תקינים כ-JSON שלם רק בסוף, לא בכל שלב ביניים. מודלים ו-SDK מודרניים תומכים ב"streaming חלקי" של JSON - שידור הפרמטרים ככל שהם מיוצרים, עם parser שמתמודד עם JSON לא שלם, ומאפשר ל-UI להתחיל להציג מידע (למשל שם הכלי שנקרא) עוד לפני שכל הפרמטרים הושלמו.
האתגר המעשי כאן הוא בצד הלקוח: קוד שמנתח את זרם ה-JSON החלקי צריך להיות עמיד לכשלים - מה קורה אם החיבור מתנתק באמצע ייצור פרמטר, והלקוח נשאר עם JSON חלקי ולא תקין? מערכות בשלות בונות מנגנון validation שמזהה מצב כזה ומטפל בו בנפרד מהמקרה הרגיל של תשובה שהושלמה בהצלחה.
טיפול בניתוק חיבור באמצע Stream
אחד ההיבטים שקל להזניח בתכנון ראשוני, אבל קריטי לחוסן המערכת בפרודקשן: מה קורה כשהחיבור נופל באמצע stream - בעיית רשת אצל המשתמש, טאב שנסגר, שרת שמופעל מחדש? כמה שאלות תכנון צריכות תשובה מפורשת:
- האם המודל ממשיך לרוץ בצד השרת גם אחרי שהלקוח התנתק? אם כן, יש לוודא שהתשובה שנוצרה נשמרת (למשל להיסטוריית שיחה) גם בלי לקוח שמקבל אותה בזמן אמת.
- האם הלקוח יכול "להתחבר מחדש" ולקבל את המשך התשובה מהמקום שבו הפסיק, או שהוא חייב להתחיל שאלה חדשה מאפס? SSE תומך במנגנון resume מובנה עם מזהי אירוע (event ID), אבל זה דורש שהשרת ישמור מצב ביניים בפועל.
- מה קורה אם הניתוק קורה בדיוק באמצע tool call - האם הכלי כבר בוצע בצד השרת (ואולי גרם לתופעת לוואי כמו שליחת אימייל) גם אם הלקוח לא קיבל אישור על כך?
הטיפול הנכון בשאלות האלה הוא מה שמבדיל בין streaming שעובד "כשהכל טוב" לבין streaming שעמיד בפועל בתנאי רשת אמיתיים ולא אידיאליים.
Backpressure - כשהלקוח לא מדביק את קצב השרת
בעיה טכנית פחות מוכרת אך משמעותית: מה קורה כשהשרת מייצר טוקנים מהר יותר מאשר הלקוח מסוגל לעבד ולהציג אותם - למשל בגלל רשת איטית, או עיבוד כבד בצד הלקוח (כמו רינדור Markdown מורכב בכל טוקן חדש)? בלי טיפול נכון, זה יוצר Backpressure - תור הולך וגדל של נתונים שממתינים לעיבוד, שיכול לצרוך זיכרון בצורה בלתי מבוקרת בצד הלקוח או אפילו בצד השרת אם הוא ממתין לאישור קבלה. מנגנוני streaming בשלים כוללים buffering מבוקר, ולעיתים אפילו האטה מכוונת של קצב השידור מהשרת כשמזוהה שהלקוח לא מדביק את הקצב - עדיף להאט מעט את הצגת הטקסט מאשר לגרום לדפדפן לקרוס תחת עומס זיכרון.
Streaming בצד הלקוח - רינדור הדרגתי בלי לפגוע בביצועים
מעבר לתשתית השרת, גם צד הלקוח דורש תכנון מפורש כדי ש-streaming ירגיש חלק ולא ימרוד בביצועי הדפדפן. אתגר נפוץ: תשובות שמכילות Markdown מובנה - כותרות, רשימות, בלוקי קוד - קשות לרינדור הדרגתי, כי טוקן בודד יכול "לשבור" תחביר Markdown שעדיין לא הושלם (למשל שלוש נקודות של בלוק קוד שנפתח אבל טרם נסגר). פתרון נפוץ הוא רינדור "מיטבי" (best-effort) של הטקסט הגולמי בזמן אמת, עם רינדור Markdown מלא ומדויק רק כשמזוהה שבלוק שלם הושלם - כך המשתמש רואה תוכן מתעדכן ברציפות בלי הבהובים ויזואליים מכוערים כשתחביר חלקי מנסה להתפרש כאילו הוא שלם.
שיקול ביצועים נוסף: עדכון ה-DOM (או ה-state בפריימוורק כמו React) בכל טוקן בודד שמגיע יכול להיות יקר אם קצב הטוקנים גבוה - כדאי לשקול batching של כמה טוקנים יחד לפני רינדור, כדי לאזן בין תחושת רציפות לבין עומס רינדור מיותר על הדפדפן. הבחירה בגודל ה-batch היא בעצמה טרייד-אוף בין חלקות ויזואלית לביצועים, ושווה לבדוק אמפירית מה מרגיש הכי טוב במכשירים איטיים יותר, לא רק בסביבת פיתוח על מחשב חזק.
אבטחה ב-Streaming - מה זולג בזמן אמת
נקודה שקל לשכוח בהתלהבות מהתשתית הטכנית: כל אירוע ב-stream, כולל אירועי ביניים כמו tool calls, נחשף ללקוח בזמן אמת - מה שאומר שצריך לחשוב מראש מה בדיוק מותר לחשוף. פרמטרים של קריאת כלי פנימית שמכילים מידע רגיש (מפתחות זיהוי פנימיים, נתוני משתמשים אחרים) לא צריכים להישלח בזרם הגלוי ללקוח רק כי הם נוחים לניפוי שגיאות בסביבת פיתוח. סינון מפורש של מה בדיוק כל סוג אירוע חושף - ולא רק הזרמה "שקופה" של כל מה שקורה בצד השרת - הוא חלק בלתי נפרד מתכנון הפרוטוקול, ונדון בהרחבה רבה יותר בהקשר הכללי של אבטחת Function Calling.
טעויות נפוצות בבניית Streaming Architecture
- בחירת WebSockets כברירת מחדל - הוספת מורכבות תשתית מיותרת (ניהול חיבורים דו-כיווניים, reconnection ידני) כשמקרה השימוש בעצם דו-כיווני חלקית בלבד ו-SSE היה מספיק.
- הזנחת טיפול בניתוק חיבור - אי-תכנון מראש למה קורה כשחיבור נופל באמצע stream, מה שמוביל לתופעות לוואי כפולות או מצבים לא עקביים.
- Streaming טקסט בלבד בלי אירועי ביניים - הזרמת רק טוקני תשובה סופית בלי אינדיקציה על שלבי ביניים כמו קריאות כלים, מה שמשאיר משתמשים במערכות אג'נטיות בלי הבנה מה קורה.
- הזנחת Backpressure - הנחה שהלקוח תמיד מדביק את קצב השרת, בלי מנגנון הגנה כשזה לא המצב בפועל.
דוגמה מהשטח - כשניתוק אמצעי גרם לפעולה כפולה
בפרויקט של סוכן AI שמבצע פעולות בשם המשתמש (כמו יצירת רשומה במערכת CRM), הצוות בנה streaming עם SSE, אבל לא טיפל במפורש במקרה של ניתוק חיבור באמצע ביצוע tool call. משתמשים עם חיבור רשת לא יציב חוו לעיתים מצב שבו הם לא ראו אישור שהפעולה הושלמה (בגלל הניתוק), וניסו שוב את אותה בקשה - מה שגרם ליצירת רשומות כפולות במערכת ה-CRM, כי הפעולה בפועל כבר בוצעה בצד השרת לפני שהניתוק קרה, רק שהאישור לא הגיע ללקוח. הפתרון כלל שני שינויים: הראשון, הוספת מזהה ייחודי (idempotency key) לכל בקשת פעולה, כך שניסיון חוזר עם אותו מזהה לא יוצר פעולה כפולה גם אם הלקוח לא ידע שהפעולה הראשונה כבר הצליחה. השני, שמירת מצב הסשן בצד השרת כך שהלקוח יכול "להתחבר מחדש" ולקבל עדכון על מה שקרה בפועל, במקום להניח שהניתוק פירושו שדבר לא קרה. הלקח המרכזי: streaming לא יכול להניח רשת אידיאלית - תכנון idempotency ומצב שרת עמיד הוא חלק בלתי נפרד מארכיטקטורת streaming רצינית, לא תוספת אופציונלית.
שאלות נפוצות
מתי לבחור SSE ומתי WebSockets?
SSE מתאים לרוב מערכות ה-chat הטקסטואליות - פשוט יותר ותומך בהתחברות מחדש אוטומטית. WebSockets נחוץ כשיש צורך אמיתי בערוץ דו-כיווני מתמשך, כמו הפרעה למודל באמצע תשובה.
איך מטפלים בניתוק חיבור באמצע Streaming?
באמצעות שמירת מצב בצד השרת שמאפשר ללקוח להתחבר מחדש ולקבל עדכון, יחד עם idempotency keys שמונעים פעולות כפולות אם המשתמש מנסה שוב.
איך מזרימים Tool Calls בלי לשבור את מבנה ה-JSON?
באמצעות streaming חלקי (partial JSON) עם parser שמתמודד עם מבנה לא שלם, ותקינות מלאה נבדקת רק כשהאובייקט הושלם.
מה זה Backpressure ולמה זה משנה ב-streaming?
מצב שבו השרת מייצר נתונים מהר יותר מאשר הלקוח מעבד, שדורש buffering מבוקר או האטת קצב שידור כדי למנוע צריכת זיכרון בלתי מבוקרת.
האם Streaming משפר את זמן התגובה הכולל?
לא בהכרח את הזמן הכולל, אבל הוא משפר דרמטית את התפיסה הסובייקטיבית של מהירות - מדד שמתואר בהרחבה במדריך Latency Optimization.
בניית תשתית Streaming עמידה דורשת תכנון קפדני של פרוטוקול, טיפול בשגיאות, ומצבי קצה - לא רק שינוי קוסמטי ב-UI. מדיה דיל בונה מערכות AI ותשתיות פרודקשן בזמן אמת - דברו איתנו בוואטסאפ.
תגיות: Streaming · Server-Sent Events · WebSockets · LLM · Latency · Real-time · AI Architecture