ארכיטקטורת מערכות LLM – על Agentic Workflows

בפוסט הקודם, ארכיטקטורת מערכות LLM – אבני היסוד, ראינו כיצד אפשר בעזרת RAG ו Prompt Engineering לבנות על גבי LLM גנרי מגוון פונקציות מעבר לפונקציית ה Chat. לעתים, אלו פונקציות שיהיה מאתגר מאוד לממש באופן אחר. ראינו כיצד Tools use (או Tools Architecture) מצליח למתן (mitigate) אזורים בהם LLM אינו מוצלח.

בפוסט הזה נראה כיצד ניתן להשתמש במודלי הדור הבא (GPT 5.0 או Claude 4.0, בזמן כתיבת הפוסט) כבר היום, בעזרת Patterns הנקראים Agentic Workflows.

המודלים של הדור הבא לא באמת זמינים היום (ואין לבלוג קומבינות עם OpenAI או Anthropic), אלא ש Agentic Workflows מאפשרים למודלים הקיימים להגיע לביצועים משופרים – שהם בסדר גודל של הביצועים של דור אחד קדימה של מודלי LLM. היתרון הזה מגיע גם בעלויות גבוהות יותר, הדומות לעלויות של דור אחד קדימה – ומכאן נגזר התיאור ״להשתמש במודלי הדור-הבא, היום״.

ישנן שמועות, למשל, שמודל GPT-o1 של OpenAI הוא בעיקר מימוש פנימי של Agentic Workflows על גבי מודל GPT-4, ולא מודל חדש שאומן מאפס. אני לא יודע אם זה נכון, אבל זה אפשרי.

מקור: Andrew Ng
Zero-Shot = הרצה של המודל ללא Agentic Workflows.
כל השאר = Agentic Workflows שונים, שהופיעו במכתב של AndrewNg ממרץ 2024.

ניתן לראות בתרשים כיצד GPT-3.5 מצליח להגיע לביצועים טובים יותר מ GPT-4 בעזרת Agentic Workflows, וכיצד GPT-4 מצליח גם הוא לשפר ביצועים משופרים בעזרתם.

את הדפוס של Tools Use כיסינו כבר בפוסט הקודם.
בפוסט הזה נכסה את כל שאר ה Agentic Worfklows, ואת Conversational Memory.

רגע של אנגלית: Agentic איננה מילה תקנית באנגלית. ביטוי מקובל למערכת AI כיום הוא ״AI Agent״ (״סוכן חכם״), ומכאן המילה Agentic כמשהו שקשור ל Agent (AI).

Reflection

מי שעובד הרבה עם מודלי LLM אולי מכיר את ההתנהגות הבאה:

משתמש :שואל שאלה כלשהי.
ChatGPT: <תשובה>
התשובה מרגישה למשתמש קצת משונה..
משתמש: ״אתה בטוח ש…״
ChatGPT: אני מתנצל על הבלבול. בעצם <תשובה נכונה יותר>…

נתקלתם בזה? סיכוי טוב שכן. הדפוס הזה גרם לחוקרים לנסות ולבדוק האם כשמבקשים מ LLM לשפר את תשובותיו, הוא יצליח לעשות זאת? התשובה היא שהרבה פעמים כן, ומשם נולד דפוס ה Reflection. הרעיון הוא לקחת Prompt של משתמש, ולהריץ אותו בכמה איטרציות (להלן Nested Chat) מול ה LLM כדי להגיע לתשובה מדויקת יותר. בדוגמה הבאה לקוח נטפל בשאלה של משתמש מתי הביטוח שלו מכסה תאונות שנגרמו משריפה.

User (external) Prompt: “When am I covered for fire incidents?”

Nested Chat:
  Internal Prompt 1: “When am I covered for fire incidents?”
  Internal Response 1: "Generally, coverage for fire incidents 
    is included in standard homeowners or renters insurance
    policies from the start date of the policy. However, 
    specific details case like … cannot be covered"

  Internal Prompt 2: "Check carefully { Internal Response1 } for correctness. 
    and give constructive criticism for how to improve it".
  Internal Response 2: "<feedback>"

  Internal Prompt 3: "improve your answer based on <feedback>"
  Internal Response 3: "<improved answer>"

Response to user (external): { Internal Response 3 }

כלומר: סדר פעולות של:

  1. שאלה
  2. בקשה לאתגור
  3. בקשה להתייחס לאתגור

יכול להוביל מודלי LLM לספק תשובות מדויקות יותר.
המחיר: קראנו ל LLM שלוש פעמים במקום פעם אחת – מה שמעלה לנו את זמן ביצוע ועלות חישוב.

אם ה LLM מגיב היטב לבקשה לבדוק את עצמו, מדוע לעשות זאת בשלוש איטרציות? מדוע לא לבקש ב Prompt כבר מראש ״חשוב מה עשוי להיות לא מדויק בתשובה – ותקן בהתאם״, או נוסח דומה?

בפועל, תהליך ה generation של ה LLM לא מודע לתוצאה המתהווה בשלמותה, או לפחות הוא לא יכול להתייחס אליה ככזו באופן שהיינו רוצים. לפחות כיום לסדרת קריאות נפרדות ל LLM – יש פוטנציאל להגיע לתוצאות טובות יותר מ Prompt מתוחכם בקריאה יחידה.

באלו מקרים מתאים דפוס ה Reflection? – כאשר אנחנו רוצים לשפר את רמת הדיוק של התשובה.
דוגמה: GPT-4 becomes 30% more accurate when asked to critique itself
האם מובטחת הצלחה? כמו כל דבר בעולם ה LLM – בוודאי שלא. בוודאי ישנם מקרים בהם Reflection יפחית את דיוק התוצאה. זהו כלי בעל פוטנציאל, אבל עליכם לנסות ולבדוק מתי הוא עובד – וזה באחריותכם.
סה״כ כלי ה Reflection נחשב לדפוס יציב שקל להשיג בו תוצאות טובות. Reflection ו Tools Use הם הדפוסים בעלי שיעורי ההצלחה הגבוהים יותר והעקביים יותר.

Planning

Planning הוא דפוס העוזר ל LLM לשפר ביצועים במשימות מורכבות, לעתים מאפיינים את המשימות הללו כמשימות הדורשות "Reasoning״. גם אופן הפעולה שלו, בדומה לדפוס של ה Reflection – הוא דיי אינטואיטיבי.
גם כאן, נפעיל סדרה של קריאות פנימיות למודל (Nested Chat) כדי לקבל תוצאה טובה יותר.

User (external) Prompt: "Please suggest how thick an iron bridge should be in order to safely support <certain traffic>. 
The bridge properties are..."

<we recognize this request may be complex, and decide to activate the planning workflow>

Nested Chat:
  Internal Prompt 1: Given the user prompt, identify the key steps required to solve the problem. Break these into small, 
    logical steps, each achievable independently. Then, determine the optimal order of execution for these steps.
  Internal Response 1: Here is the <list of steps> 
    // e.g. 1: Define a structural formula for bridge strength, considering the material (iron) and design. 
    // 2. Estimate the total load based on the specified traffic properties. 
    // 3. Combine the results of Steps 1 and 2 to calculate the required material thickness to safely support the load.

  Internal Prompt 2: Execute step 1 suggested above
  Internal Response 2: <...>

  Internal Prompt 3: Execute step 2 suggested above, use response of step 1
  Internal Response 3: <...>

  Internal Prompt 4: Execute step 3 suggested above, use response of steps 1 and 2.
  Internal Response 4: <...>

  Internal Prompt 5: Combine the results from all steps to produce a final, coherent response to the original user query.
  Internal Response 5: <combined answer>

User response: <combined answer>

הרעיון מאחורי דפוס ה Planning הוא שמודלי מתקשים במענה נכון על בעיות מורכבות. בעזרת עקרון הדומה ל divide and conquer אנחנו מנחים את המודל לפתור על פעם בעיה קטנה יותר, שבה יש סיכוי קטן יותר שהוא יגיע לתוצאה נכונה.

דפוס ה Planning משתלב יפה עם דפוס ה Tools Use בו למודל אין מספיק נתונים בכדי לסיים את המשימה, או שיש שלבים ש LLM באופן טבעי פחות מוצלח בהם. השימוש בכלים חיצוניים (גישה למאגי נתונים, שימוש במנוע חישוב ״דטרמיניסטי״) יכולים לעזור להגעה לתשובה נכונה ומדויקת.

כפי שציינתי קודם לכן, הדפוס הזה הוא יותר רעיון מ״דפוס ליישום מיידי״. סיכוי ההצלחה ביישום הדפוס הזה הם נמוכים יותר מדפוסי ה Reflection וה Tool Use, ויש הרבה יותר מקום לניסוי-וטעיה עד שמגיעים ליישום מוצלח, בהקשר נתון.

הערה חשובה: הדוגמה למעלה הייתה לצורך המחשה. איני ממליץ, בשלב זה, לבנות גשר על בסיס חישוב של LLM, וללא ביקורת מעמיקה של מהנדס אנושי.

גיוון מודלים

לפני שנעבור לדפוסים הבאים, אני רוצה לעצור לרגע ולדבר על גיוון מודלים. לכאורה, אם אני משיג תוצאות טובות בעזרת GPT-4, למשל – למה שלא אשתמש בו תמיד, לכל הצרכים? להלן, ״לא מחליפים סוס מנצח״.

בפועל יש שלוש סיבות עיקריות, מדוע יש סיכוי טוב שנגיע לביצועים טובים יותר תוך כדי שימוש במגוון מודלים – על פני שימוש במודל יחיד:

  1. עלויות – קריאה למודל LLM היא יקרה, ככל שהמודל גדול יותר. העלות היא גם כספית וגם ב Latency. בהפעלת Agentic Workflows אנו מבצעים מספר קריאות ל LLM עבור כל בקשה של המשתמש – מה שגורם לעלויות (הגבוהות גם כך) לעלות באופן ניכר. אם נוכל לבצע חלק מהקריאות למודל קטן וזול יותר (פרקטי במיוחד ב Tool Use) – נוכל לחסוך עלויות ולהיות יעילים יותר.
    • בחודשים האחרונים יש שיח רב מסביב ל״מודלים קטנים״, בעיקר בשל ההבנה שהמשך התקדמות על בסיס מודלים גדולים יותר ויותר – אינה sustainable לרוב היישומים.
  2. התמחות – מודלים שונים טובים יותר במשימות שונות. למשל, אומרים ש PaLM2 מוצלח יותר בהסבר של קטע קוד, בעוד GPT-4 מוצלח יותר בזיהוי באגים בקוד. שימוש במודל המתאים יותר למשימה – היא דרך לשפר ביצועים.
    • כיום ההבדלים בהתמחות בין המודלים הנפוצים אינם הבדלים דרמטים. יש סברה שעם הזמן והצורך להתבדל – זה ישתנה. כמו כן, ההנחה היא שעם הזמן נראה מודלים שנבנו למטרות ספציפיות ולא General Purpose כמו היום.
  3. השלמה הדדית – יש טענה ששילוב בין מודלים שונים למשימות שונות, מביאות לתוצאות טובות יותר משימוש במודל יחיד.
    ב Reflection, למשל, מדווחים על שיפורי ביצועים כאשר משתמשים במודל שונה בשלב הביקורת (שלב 2) מאשר המודל לשאר השלבים. למשל: שילוב בין GPT ל Claude.

סה״כ, כדאי להיות פתוחים לרעיון של גיוון מודלים – בעיקר בשל עניין העלויות. שווה לעצב את הארכיטקטורה שלכם כך שתוכל לתמוך במגוון מודלים שונים בו-זמנית. לשימוש במודלים שונים יש גם מחירים, למשל:

  • ייתכן יידרשו אופטימיזציה שונה ב Prompt Engineering ו RAG על מנת להשיג תוצאות מיטביות ממודלים שונים.
  • מודלים שונים לרוב דורשים Encoding שונה – מכאן שלאותם נתונים ייתכן ותצטרכו לעשות Encoding מספר פעמים למגוון המודלים שאתם עובדים איתם (עבור Knoledge bases, למשל).

Multi-Agent Collaboration

דפוס ה Multi-Agent מדובר מאוד לאחרונה, במידה שאני ממליץ באופן אישי לקחת אותו עם קרטוב של מלח בכדי לאזן את ההתלהבות הכללית.

ניתן להסביר את הדפוס מכמה כיוונים שונים, ובחרתי להתחיל ולהסביר אותו מהכיוון של גיוון מודלים.

לאחר שראינו שגיוון מודלים עובד. נניח ב Reflection יש לנו תוצאות טובות יותר כאשר מודל B מאתגר ומבקר את מודל A – ננסה לקחת את העקרון הזה קדימה.

כאשר נותנים לבני אדם מטרות שונות, זה משפיע על אופן הראייה שלהם של הדברים, ואופן הביצוע שלהם. למשל אדם א׳ הוא מהנדס שמתכנן גשרים, ואדם ב׳ הוא מבקר איכות המחפש טעויות תכנוניות. עצם הגדרת המשימה ״עליך להיות מבקר ולחפש בעיות״ – מסייעת, כך אנחנו מניחים, לאדם ב׳ לבצע את המשימה שלו בצורה טובה יותר.

באופן דומה, ננסה להשתמש ב Agents שונים עם הגדרות שונות (context) על מנת לכוון אותם טוב יותר למשימות שונות. ההכוונה היא בבסיסה הוראות שונות ב Prompt (למשל: ״אתה מבקר קפדן המתמחה בזיהוי תקלות תכנון…״). האם זה תמיד עובד? בוודאי שלא, אבל יש מספיק נסיונות שמצביעים שזה לפעמים עובד.

כמה דוגמאות בהן Multi-agent Collaboration (שכלל גם reflection) הצליח לשפר ביוצעים מול עבודה של Agent יחיד. מקור

ניקח את ההכוונה עוד צעד אחד קדימה, ונספק למודל B גם גישה לנתונים מתאימים יותר (נניח, כ RAG או Tool Use). ייתכן ומודל B יצליח להשתמש ולשלב את הנתונים הללו בצורה יותר טובה בשלב הביקורת, מאשר יכול היה מודל A להשתמש בו בשלב התכנון.

מכאן אנחנו מגיעים למודל בו אנחנו מחלקים את המערכת שלנו ל Agents שכל אחד בעל תחום התמחות מוגדר. הוא מקבל את ה Prompt/Context המתאים, והזרקה של נתונים (RAG) או כלים (Tools Use) המתאימים לתפקיד שלו. לבסוף – ייתכן ונתאים ל Agents שונים מודלי LLM שונים המתאימים יותר לצרכים. מכיוון שכולם מדברים בשפה משותפת (טקסט אנושי) – ניתן לבקש מהם להיעזר אחד בשני.

לב העניין אם כן הוא יצירת Agents או תצורות שונות של הפעלת LLM למשימות שונות. הדפוס של Multi-Agent Collaboration יכול להיות מיושם מצוין גם על גבי מודל יחיד.

יש משהו רומנטי בדפוס הזה, כי הוא מאפשר לנו לתכנן מערכות LLM באופן דומה לזה שבו אנחנו מתכננים ארגונים אנושיים: חלוקת מומחיויות, אחריויות, וכלים – לבעלי תפקידים בארגון. יש לנו הרבה ניסיון וידע בתכנון ארגונים, ולכן מאוד קוסמת המחשבה שנוכל לנצל את הידע והניסיון בתכנון ארגונים אנושיים בכדי לתכנון בהצלחה מערכות LLM מורכבות.

בצד המעשי, מניסיון קצר למדי, אני יכול לומר שתכנון מערכת Multi-Agent מוסיפה הרבה סיבוכיות למערכת, ולא הייתי רץ לממש אותה, אלא כדי לנסות לשפר מערכת פשוטה יותר שלא עובדת טוב מספיק.

כמו בדפוס ה Planning, דפוס ה Multi-Agent הוא יותר רעיון לחקור ולהתנסות בו, מאשר ״דפוס ליישום מיידי״.

Conversational Memory

הדפוס הזה לא כלול ברשימה ״המקורית״ של ה Agentic Workflows, אבל הוא עדיין דפוס פופולרי.

דפוס ה Conversational Memory נועד ליצור חווית שיחה עם LLM שהיא דומה יותר לחוויה אנושית. למשל להימנע מהתסריט הבא:

משתמש: היי דוקטור

דוקטור (Agent): יום נפלא! מה שלומך? מה שלום הילדים או האשה?

משתמש: עדיין כואב הגב. כואב מאוד.

דוקטור (Agent): אוי, אני מצטער לשמוע. האם הרמת חפץ כבד או עשית תנועה חדה לאחרונה?

משתמש: עברתי תאונת דרכים. דיברנו על זה אתמול כשעה, ובאתי להמשך התייעצות.

דוקטור (Agent): צר לי לשמוע. האם אני יכול להמליץ לך על זריקה להפחתת כאב?

משתמש: דיברנו אתמול שאני לא מוכן לקבל זריקות. אני רוצה טיפול אחר.

דוקטור (Agent): אהה. אז אולי….

אמנם כיום, כחברה גדולה ומורכבת לא כל נותני השירותים יזכרו אותנו ויתנו לנו שירות אישי מבוסס על זיכרון, אבל מספיק נותני שירותים עושים זאת, מה שמשאיר את ה AI Agents קצת מאחור. השאיפה של ה Conversational Memory הוא לצמצם את הפערים הללו, ולאפשר ל AI Agent לספק שירות קרוב יותר למענה אנושי.

גם הדפוס הזה הוא יותר בגדר רעיון, אבל עם צורך עסקי ברור, וכבר כמה יישומים שמראים תוצאות.

הרעיון הוא שנשמור memory של שיחות שהיו למשתמש עם ה LLM, בכדי להשתמש בזיכרונות הללו כ context לשיחות עתידיות על מנת לאפשר ל LLM להגיב טוב יותר למשתמש. הנה כמה עקרונות / רעיונות:

  • נחזיק Storage (להלן ״Memory״) ששומר פרטים משיחות קודמות עם המשתמש.
  • לשמור את כל השיחות יגרור context גדול מדי (= איבוד Attention) – ולכן:
    • נזהה מקטעים משמעותיים במיוחד בשיחה – ונשמור רק אותם (בד״כ: בעזרת מודל קטן ומותאם).
    • נבצע סיכום של המקטעים הללו – על מנת לצמצם את ה context ולהיות יעילים וממוקדים יותר (בד״כ: בעזרת מודל קטן).
  • בעת שיחה חדשה, נזהה נושאים שדיברנו עליהם בעבר, ונטען באופן דינמי ל Context סיכומים של מקטעים משמעותיים רלוונטיים משיחות עבר. זה ממש סוג של RAG כאשר ההבדל העיקרי הוא שהמאגר ממנו שולפים נתונים הוא ספציפי למשתמש, ולא משותף.
  • על מנת שהזיכרונות יישארו יעילים ורלוונטיים לשימוש, יש לרוב מנגנון ״שכחה״ שמסיר זיכרונות ישנים לאחר פרק זמן, או אם יש עדכון חדש יותר שסותר את הנאמר בזיכרון הישן.

עד כאן הרעיון. מכאן יש הרבה שאלות והחלטות של מימוש שיכולות עוד לשנות. למשל: כיצד מזהים מקטעים משמעותיים? הנה כמה תשובות נפוצות:

  • כאשר המשתמש מדבר על עצמו או מה שקרוב אליו (משפחה, עסק, וכו׳) – נחשיב את זה כמידע חשוב שכדאי לזכור (העקרון: דמיון לאופן שבו סוכן אנושי פועל).
  • כאשר המשתמש מעלה נושא פעמים רבות. כאן צריך לזהות נושאים חוזרים (דמיון סמנטי) ולספור כמות מופעים בזמן נתון. החישוביות הנדרשת לגישה הזו אינה קטנה.
  • כאשר המשתמש מתקן את המודל – נרצה לצמצם מקרים בהם המשתמש נדרש לתקן את המודל באותו עניין יותר מפעם אחת (שוב, בדומה להתנהגות אנושית מקובלת).

אני מקווה שעוד שנה כבר יהיו יותר Best Practices מוכחים, המצמצמים את מרחב החיפוש אחר הדרך היעילה ביותר ליישם מנגנון memory.

סיכום

אחד הנושאים החשובים והשימושיים ביישום LLM הוא שיפור התוצאות בעזרת Agentic Workflows. ניתן לראות שתוך כדי שהמודלים משתפרים, משתפרות גם הטכניקות ההנדסיות כיצד לנצל אותם בצורה יעילה יותר. יש טענה אפילו שהמודלים עוברים תהליך של Commoditization, ודווקא השימוש בהם ילך ויהפוך יותר ויותר ליתרון התחרותי של חברות. שבסוף מי שיעשה את האימפקט היא האפליקציה המשתמשת ב LLM בצורה טובה – ולא המודל עצמו.

אני מקווה שהפוסט הזה הצליח לחשוף אתכם בצורה רלוונטית ופוקחת עיניים ל Workflows הללו – גם אם לא סיפקתי תשובות (שאין לי) לשאלות רבות שצצות.

צצים לאחרונה גם לא מעט Frameworks המסייעים ליישם את ה Workflows הללו. אפשר לציין את Dify, את AutoGen של מייקרוסופט, Flowise.AI או Swarm של OpenAI, יש ויהיו כמובן עוד. נזכור שמה שחשוב הוא לא כל-כך ה Framework אלא ההבנה את ה Workflow ומה ההיגיון מאחוריו.

נ.ב. באופן לא ממש מפתיע, פייטון היא השפה הדומיננטית בתחום הזה – וזה נקודה לקחת בחשבון.

שיהיה בהצלחה!

ארכיטקטורת מערכות LLM – אבני היסוד

LLMs הם הכתום החדש, ומאפשרים למפתחים בודדים לפתח בקלות יכולות שעד לפני שנה-שנתיים היו נחלתם של צוותי ML גדולים, במקרה הטוב.

בסדרת הפוסטים הבאה ננסה לאפיין, מצד הנדסת התוכנה, מה המשמעות של ארכיטקטורת של מערכות LLM, כלומר: כאשר אנו בונים מערכות המשתמשות הרבה ב LLM – מה המבנים הנפוצים וההגיוניים של התוכנה מסביבם.

נתחיל בדוגמה בסיסית

ברמה הבסיסית ביותר, מערכת המשתמשת ב LLM נראית כך:

  • המערכת שלנו שולחת prompt (טקסט) למודל LLM. המודל יכול להיות SaaS (כמו OpenAI) או מנוהל ב self-hosting כמו מודלי open source שונים.
  • ה LLM יְעַבֵּד את ה prompt – ויחזיר תשובה טקסטואלית.

כמובן, שאם לא עשינו שום דבר מיוחד עם ה prompt, בעצם יצרנו עוד אפליקציית צ׳ט נוסח chatGPT או Gemini. נוכל להוסיף UI נחמד יותר, או צלצולים ברגעים הנכונים – אבל בבסיס יש פה עוד אפליקציית Chat. לא משהו חדש.

כפי שכתבתי בפוסט קודם בנושא, יש ארבע דרכים עקרוניות להשפיע על מודל LLM – כדי לאפשר יכולות חדשות (מסומנות בתרשים בגווני כחול):

אימון מודל חדש, ו fine-tuning למודל קיים הם מאמצים הדורשים משאבים ומומחיות רבה. עשרות אלפי דולרים ל fine tune של מודל קטן עד מיליונים רבים מאוד לאימון מודל גדול חדש.

נראה שכ 99% מהפתרונות מבוססי-ה LLM מתבססים על שתי הפרקטיקות הפשוטות והזמינות: Prompt Engineering ו RAG – בהם נתמקד בפוסט. אלו טכניקות זולות מאוד, שניתן להפעיל בהשקעה קטנה וללא התעמקות בקרביים של מודלי LLM.

Agentic workflows הם שורה של Patterns של שימוש ב Prompt Engineering ו RAG על מנת להשיג תוצאות טובות יותר. נדון בהם בהמשך.

נתחיל בתיאור פתרון מינימלי מבוסס-LLM, כזה שמפתח בודד יכול לפתח, ובזמן קצר. נעשה זאת בעזרת הכלי הבסיסי ביותר- Prompt Engineering. שימו לב ל Prompt template הבא:

prompt_template = """You are a teacher grading a question. 
You are given a question, the student's answer, and the true answer, 
and are asked to score the student answer as either Correct or Incorrect.

Grade the student answers based ONLY on their factual accuracy. 
Ignore differences in punctuation and phrasing between the student answer and true answer. 
It is OK if the student answer contains more information than the true answer, 
as long as it does not contain any conflicting statements. 
If the student answers that there is no specific information provided in the context, 
then the answer is Incorrect. Begin! 

QUESTION: {question}
STUDENT ANSWER: {studentAnswer}
TRUE ANSWER: {trueAnswer}
GRADE:

Your response should be as follows:

GRADE: (Correct or Incorrect)
(line break)
JUSTIFICATION: (Without mentioning the student/teacher framing of this prompt, 
explain why the STUDENT ANSWER is Correct or Incorrect. Use up to five sentences maximum. 
Keep the answer as concise as possible.)
"""

מקור
אם נציב במקומות הנכונים ערכים לפרמטרים question, studentAnswer ו trueAnser ונשלח ל LLM לעבד את התשובה – הוא יחזיר לנו ניתוח האם התשובה נכונה או לא, והסבר למה.

בפועל: יצרנו פונקציה ממוקדת ושימושית, שלא הייתה קיימת קודם לכן – בדיקת נכונות של שאלה.

עבור שאלה בהיסטוריה, בנוסח ״מנה את הגורמים העיקריים לפריצת מלחמת העולם הראשונה״ – נוכל לקבל במאמץ קטן מערכת שתפתור בעיה הנדסית שבתכנות אימפרטיבי – לא סביר לפתור. ניתוח הטקסט, הבנת משמעות והתאמה שלו לטקסט לדוגמה (התשובה הנכונה) הוא דבר קשה מאוד ליישם בעזרת שפת תכנות כמו ג׳אווה, וללא מומחיות עמוקה בטכניקות של NLP (קרי: Natural Language Procesing).

בעזרת LLM ומעט prompt engineering – זו משימה פשוטה.

שימו לב שיש אומנות בבחירת ההוראות המדויקות ב prompt וההשפעה שלהן על התוצאה. האומנות הזו נקראת prompt engineering, וכדי להגיע לתוצאה טובה, צריך להכיר מה לרוב עובד, וגם לנסות מספר איטרציות של prompts תוך כדי ניסוי וטעיה.

שימו לב שאנחנו לא מסתמכים פה על הידע של LLM בהיסטוריה (ידע שדווקא יש לו) אלא את היכולת להשוות בין תשובת תלמיד לתשובה שהגדרנו כנכונה, בצורה סמנטית ומעמיקה. זה גם מאפשר לנו לקבע מהי התשובה הנכונה מבחינתנו וגם מאפשר לנו לעסוק בנושאים בהם אין ל LLM ידע מספיק (למשל: חוקי העזר העירוניים של עיריית ת״א). ה LLM משמש פה בעיקר כמומחה לשפה ופחות כמקור הידע. את הידע הזה אנחנו מזריקים לו כחלק מה prompt בדמות התשובה לדוגמה.

נקודה חשובה: אם אנו רוצים להשתמש ב prmopt הנ״ל על לבדוק את התשובה על לשאלה ״באיזו שנה הוקמה מדינת ישראל?״ – אנחנו כנראה עושים כאן עוול. אין צורך ב LLM לבדוק כזו שאלה. LLM הוא יקר לשימוש (כמות ה compute, זמן חישוב ארוך), אינו דטרמניסטי, ודורש תחזוקה על מנת לשמר אותו מ drifts בתוצאות לאורך זמן. עדיף לעשות pattern matching פשוט בקוד כדי לבדוק תשובה לכזו שאלה.

לסיכום

השתמשנו ב LLM לייצר פונקציה שימושית שיכולה להשתלב במערכת קיימת. פונקציה שאם היינו מנסים לפתח בלי LLM – ספק אם היינו מצליחים באותה איכות ובזמן פיתוח דומה. עד כאן זו איננה מערכת LLM אלא מערכת שיש בה פונקציה של LLM, למשל: מערכת לבדיקת וניהול ציונים.

LLM Agents

היכולת לשלב פונקציות מועצמות-LLM במערכות קיימות ולספק ערך חדש ונוסף – היא מלהיבה בפני עצמה, וראוי שנעשה בה שימוש רחב בכל מקום בה היא מועילה.

LLM פותח בפנינו הזדמנויות גדולות יותר מאשר כתיבת פונקציות חכמות יותר, כמו לתת ל LLM לנהל תהליכים שלמים ומורכבים. העולם מדבר עכשיו על Agents – סוכנים חכמים שיידעו לבצע מגוון פעולות הדומות לבעל מקצוע המתמחה בתחום מסוים. למשל סוכן שהוא עו״ד בתחום הנדל״ן (A real estate attorney agent).

להזכיר: מודל שפה מאומנים על כמות גדולה של מידע ציבורי מהאינטרנט (כמו וויקיפדיה), ועוד מאגרי צד-שלישי או מאגרים פרטיים שזמינים למאמני המודל. הם מכילים ידע רחב מאוד, אך לרוב לא מתמחים לעומק בנושאים ספציפיים. ChatGPT יוכל לענות על תשובות כלליות בנושאי חוק ונדל״ן – אך הוא רחוק להיות שקול לעו״ד מנוסה בתחום. את הפערים האלו אפשר לצמצם בעזרת כלים מסוימים, כמו RAG.

RAG מאפשר לי ״להזריק״ למודל עוד מידע רלוונטי שלא קיים במודל (קרי: המודל לא אומן עליו) בצורה דינמית ומותאמת לשימוש הספציפי. RAG יכול להפוך צ׳ט גנרי מבוסס LLM – לצ׳ט שמצליח לענות בצורה טובה על שאלות מעמיקות בתחום מסוים (נניח: נדל״ן).

מענה על שאלות הוא עדיין שימוש דיי מוגבל, ומה שהיינו רוצים מהסוכנים שלנו (באידאל) הוא לטפל בכל משימה שבעל המקצוע האנושי היה מטפל בה. למשל:

  • תן לי חוות דעת על תיק מסוים והצע דרכי פעולה.
  • הגב לחוות הדעת של עו״ד השני – על מנת לשכנע בעמדה מסוימת.
  • פנה אל הלקוח ואסוף ממנו פרטים נוספים על מנת להכין את חוות הדעת.
  • הצע מה הצעד הבא בניהול התיק.
  • נהל את התיק קצה-לקצה עד להגעה להצלחה.

היכולות האלו הם עליית מדרגה פונקצינלית מ ״chat חכם שיודע המון״, אבל בעזרת כלים נוספים שעליהם נדבר – אפשר לראות כיצד העולם מתקדם לכיוון הזה.

החזון ממשיך עוד הלאה לארכיטקטורה של Multi-Agents בה נבנית מערכת של סוכנים חכמים המתקשרים זה עם זה ומבצעים משימות מורכבות באופן דומה לאופן שבו ארגון אנושי פועל.

מקור

בפועל, אלו רעיונות שעדיין לא הוכיחו את עצמם ב scale. ייתכן שיצוץ משהו דומה בעתיד אולי בארכיטקטורה של Multi-Agents ואולי בארכיטקטורה אחרת – עוד מוקדם מדי לומר.

Retrieval Augmented Generation (RAG)

על הנושא הזה כבר כתבתי, אבל הוא חשוב מספיק כדי שאסכם אותו שוב. נניח אני בונה צ׳ט לאתר שעוסק במסעדות – ואני רוצה לענות ללקוחות על שאלות מסוימות. למשל:

ל LLM, כמודל, אין ידע מספיק כדי לענות על השאלה:

  • הוא לא יודע מי המשתמש, והיכן הוא גר. באיזה סניף מדובר.
  • הוא לא מכיר את רשת המסעדות ״בורגר סאלון״ – זה לא היה כלול במידע האימון שלו. בכל זאת, הוא הצליח להבין מהשם את ההקשר, ולענות תשובה הגיונית – שזה מרשים!
  • הוא לא יודע מה שעות הפתיחה של המסעדה הספציפית – מידע שיכול להתעדכן כל הזמן.
  • הוא לא יודע מה התאריך הנוכחי, קרי אפילו אם יידע מה שעות הפתיחה של המסעדה – הוא לא יוכל לספק תשובה טבעית כמו ״היום הם פתוחים עד 20:00, אבל מחר מתשע בבוקר עד חצות״ – מה שכן הייתי רוצה לקבל מצ׳ט אנושי.

יכולנו בעזרת prompt engineering להוסיף context ל prompt שיוסיף עוד מידע. למשל: שעות הפעילות של כל המסעדות שאנחנו מכירים. זה היה עוזר אבל:

  • ה prompt היה גדול ומכאן יקר לעיבוד (זמן, כסף)
  • ככל שה prompt גדול יותר ה attention של ה LLM נפגע – וייתכן שהוא יתבלבל – ויספק פרטים של מסעדה אחרת.

מכאן השלב הבלתי נמנע הוא לספק prompt דינאמי – עם מידע ממוקד לשאלה הספציפית. זהו תהליך ה RAG:

  1. אנחנו מקבלים את ה prompt מהמשתמש
  2. אנחנו מעבירים אותו לתהליך ה RAG בכדי לנסות ולשפר אותו (להעשיר את ה context)
  3. אנחנו מחפשים מילות מפתח ב prompt, למשל את השם ״בורגר סאלון״.
  4. אנחנו שולפים מבסיס הנתונים (נקרא בעולם הזה: Knowledge Base) שהכנו מראש, מידע ואת שעות הפעילות העדכניים של בורגר סאלון.
  5. אנחנו מעשירים את ה prompt ב context רלוונטי:
    • תאריך של היום
    • מיקום של המשתמש (שלפנו geoLocation מהדפדפן)
    • מידע כללי על מסעדת בורגר סאלון ושעות פתיחה עדכניות
  6. נשלח את ה prompt המשופר ל LLM – על מנת לקבל תשובה איכותית, שלא היינו יכולים לקבל ללא תהליך ה RAG

ה prompt המשופר יכול להראות כך:

INFORMATION:
current time is {datetime}
the user current location is {address}
Information about the restaurant:
{restaurant_general_info}
{restaurant_opening_hours}

QUERY:
{user_query}

INSTRUCTIONS:
You are a helpful agent providing information about restaurants.
Answer the users QUERY using the INFORMATION text above. 
Keep your answer ground in the facts of the INFORMATION. 
If the INFORMATION doesn’t contain the facts to answer 
the QUERY answer you don't know.

התוצאה, כמובן, תהיה בסדר גודל מוצלח יותר, מ prompt שלא כולל מידע דינאמי ספציפי.

RAG הוא עולם בפני עצמו, הכולל טכניקות שונות כיצד לבחור איזה מידע לשלוף, כיצד לבחור כמה מידע לשלוף, כיצד למיין אותו ב prompt, כיצד לאנדקס את המידע, וכיצד לנטר ולשפר את תהליך ה Retrieval. קצרה היריעה מלדון בכל הנושאים הללו.

Tools Use

בואו נחזור למערכת בדיקת המבחנים שיצרנו, ונראה מצב שבו LLM הוא לא רק יקר יותר – אלא הרבה פחות מוצלח בתוצאה. למשל: בדיקת פתרון בעיה מתמטית.

מודלי LLM טובים מאוד במענה על שאלות כלליות ומופשטות, אבל הרבה פחות מוצלחים כאשר נדרש דיוק. מודלי ה LLM לא באמת מבינים מתמטיקה, הם רק תוכים סטוכסטיים המתמצתים וחוזרים על מיליארדי שיחות שנחשפו אליהן – עם כשרון נדיר להבין הקשר ולתכלל תשובה הגיונית (לכאורה).

ניתן לאמן מודלי LLM על יותר דוגמאות מתמטיות כדי שישתפרו, אבל גם אז – יש להם תקרת זכוכית, בעיקר בפעולות במספרים גדולים – אשר יש להם פחות ופחות דוגמאות ספציפיות לגבי כל מספר נתון.

מקור: LLMs Bad at Math – מאמר המראה את השיפור הגדול במודל שאומן על מידע מתמטי – אך גם את הדעיכה בתוצאות ככל שהמספרים גדולים יותר (מספר ספרות)

היום, אם תלכו ל chatGPT ותתנו לו לפתור בעיה מתמטית עם מספר רב ספרות – סיכוי טוב שהוא יענה תשובה מדויקת:

השיפור שאתם רואים אינו שיפור במודל GPT – אלא שיפור באפליקציית ה chatGPT. האפליקציה סיפקה למודל כלי של חישוב מתמטי שהוא יכול להשתמש בו.
ברגע שהמודל מזהה שעליו לפתור בעיה מתטמטית, הוא מפעיל את הכלי החיצוני – ואז משלב את התוצאה של הכלי בתשובה.

הכלי עצמו, לא משתמש ב LLM. זהו כלי שפותח בעזרת קוד אימפרטיבי – כזה שקל לו לבצע חישובים מתמטים מדויקים, ובמהירות. הוא פיצוי למודל – בנקודות שבוא המודל הוא חלש. לעתים קרובות, הפיצוי הזה מאוד מוצלח.

אנחנו רואים כיצד LLM הוא אינו ״קליע כסף״ לכל בעיה, אלא השילוב שלו עם כלים קלאסיים (קוד אימפרטיבי, קרי פונקציה בג׳אווה / פייטון / וכו׳) מקדמים אותנו לעבר החזון של סוכן LLM חכם ורב-יכולות.

מערכת יכולה להשתמש במגוון כלים, לא רק כלי אחד. חלק מהכלים יכולים להיות מבוססי קוד אימפרטיבי, חלקם מבוססי מערכות צד שלישי (מערכת לחיזוי מזג האוויר) וחלקם מבוססי מודלי ML המתאימים יותר למשימה ספציפית (למשל: רגרסיה).

הבחירה באיזה כלי להשתמש, שאלה מופשטת בהנתן prompt טקטואלי ממנו אמורים להסיק את הבחירה, יכולה גם היא להעזר ביכולות LLM. הנה prompt לדוגמה:

system_prompt = """You are an assistant that has access to 
the following set of tools. 
Here are the names and descriptions for each tool:

{rendered_tools}

None - if the query require none of the tools above.

Given the user input, return the name of the most appropaite 
tool to respond to the query effectively."""

במקום של {rendered_tools} נשתול מפה המתארת את שמות ה Tools ואת התיאורים המילוליים של מה כל Tool עושה ומתי נכון להשתמש בו. יש פה עניין של prompt engineering, בחירת מילים המובילה לתוצאות טובות.

בשלב הבא, נבדוק אלו arguments ה tool שנבחר זקוק – ונפרסר את ה query של המשתמש כדי לשלוף את הארגומנט הנכון מהשיחה עם המשתמש. למשל: המספר לו אנו רוצים לחשב את השורש. גם הפעולה הזו יכולה להתבצע בעזרת LLM.

מכאן, אנחנו יכולים להפעיל את ה tool (פונקציית חישוב ביטוי מתמטי) – ולשלב את התוצאה בתשובה למשתמש. שילבנו את הבנת הטקסט וההקשר של ה LLM עם חישוב מדויק ש LLM אינו מוצלח בו.

כלים יכולים לספק מגוון השלמות דינאמיות למגבלות של ה LLM. למשל: RAG. במקום להבין מראש איזה מידע חסר – ולהזין אותו כ context על ה prompt, לפעמים יעיל יותר לתת ל LLM להסיק בעצמו איזה מידע חסר לו – ושהוא יבקש אותו, בעזרת פנייה ל Tool המתאים. למשל: Tool המספק שעות פעולה של מסעדה.

כלים אחרים הם כאלו שמבצעים פעולות. למשל: LLM הוא מודל סגור ולא יכול לבצע הזמנה למסעדה. מצד שני הוא יכול להסיק שהלקוח מבקש לבצע הזמנה, ואז להפעיל Tool שפונה ל API של המסעדה, עם פרטי ההזמנה הרצויים שה LLM שלף מהשיחה – ולבצע את ההזמנה בפועל.

השימוש בכלים מרחיב ומגוון את יכולות ה LLM ומקרב אותנו לחזון ה LLM Agent. עדיין חסרים לנו כמה כלים, עליהם נדבר בפוסט הבא.

לסיכום

LLM הוא ״סוג של קסם״, המהווה פריצת דרך ביכולות שלנו לפתור בעיות בעזרת תוכנה. למרות שהמודלים הולכים ומשתפרים, LLM בצורה גנרית יפתור מעט מאוד בעיות. על מנת לפתור בעיות מגוונות אנחנו צריכים לבצע עבודה הנדסית (prompt engineering, RAG, tools) מה שמאפשר לנו באמת לבנות פתרונות מגוונים מבוססי LLM.

החזון המדובר היום הוא LLM Agents שיחליפו מומחים בתחומם, לפחות חלקית. הדרך לשם – היא עקב לצד אגודל – בניית עוד יכולת ועוד יכולת, ושיפור היכולות. ההתקדמות בתחום הזה היא לא פחות ממדהימה, הן בהיבט המודלים שהולכים ומשתפרים (דיוק, עלויות), והן ברמת הטכניקות ההנדסיות המאפשרות לרתום את המודלים והפיכתם לפתרונות לבעיות עסקיות.

לאחר שעסקתי קצת בנושא בחודשים האחרונים, אני ממליץ פחות לתכנן מערכות Multi-Agents ויותר לבנות יכולות עסקיות אחת-אחת ולראות להיכן צומחים משם. כמו כן, ניתן לפתור המון בעיות בעזרת LLM – וזה דיי מגניב, אבל לא תמיד משתלם. פונקציות המבוססות על LLM, במיוחד המורכבות שבהן, דורשות ניטור ותחזוק שוטפים לא מובטלים. המודל הוא סטוכסטי ולא תמיד צפוי או הגיוני. את זה צריך לנטר, ו to mitigate כל הזמן. גם בעקבות שינויים שלא היינו מצפים שישפיעו (עדכנו מעט את ה prompt, יש נתונים נוספים ב knowledge base, עברנו להשתמש במודל מתקדם יותר).

כרגיל, חשוב לבחור בכלי הנכון למשימה, ולא להסתנוור. לא פעם עדיף להשתמש במודל ML קלאסי או אלגוריתם קוד פשוט אבל מספיק טוב – ולא לתחזק פונקציית LLM שהיה קל לכתוב – אך דורשת תחזוקה מתמשכת.

שיהיה בהצלחה!

חיסכון או אסון? מתי וכיצד ליישם YAGNI בסטארט-אפ?

עקרון ה YAGNI (קרי: ״You Ain’t Gonna Need It״) נזכר לראשונה בסדרת הספרים של Extreme Programming (בקיצור: XP) מהמחברים רון ג’פריס וקנט בק.

באחד הספרים הראשונים, רון מתאר דילמה שבו הוא צריך לכתוב אפליקציה שצריכה לשמור (persist) נתון מסוים. הוא מתלבט בין לכתוב את הנתונים לקובץ (הכי מהיר / פשוט) או להשתמש בבסיס נתונים (הגישה המקובלת). תחת עיקרון ה YAGNI הוא מחליט להשתמש במערכת קבצים – כי לא צריך יותר מזה בשלב הזה. אם יהיה צורך מורכב יותר – הוא יעשה Refactoring ויעבור לעבוד עם בסיס נתונים.

אני זוכר שהדוגמה הזו נראתה לי מוגזמת מדי. הרבה רעיונות בספרים של XP היו מוגזמים – באופן מוצהר ומכוון. בסיס נתונים הוא דבר דיי פשוט וקל לקנפוג – למה לעזאזל שאעבוד עם מערכת קבצים כשברור לי שהצורך בבסיס נתונים הוא ״מעבר לפינה?״ הרי ״ברור״ שאגיע לשם.

YAGNI הוא עיקרון נהדר, כי בתעשיית התוכנה אנחנו מגיעים לעשות הרבה Over-Engineering (הנדסת-יתר). החיסרון שלו, כמובן, שהוא עלול להוביל אותנו ל Under-Engineering (הנדסת-חסר), לפתרונות תוכנה פשטניים, שלא יעמדו במבחן הזמן ויאלצו אותנו להשקיע הרבה עבודה על מנת להביא את המערכת בחזרה לאיזון טוב בין הנדסת-יתר וחסר. מה שפעם נקרא ״code and fix״, קדד מהר – תקן בהמשך.

ברור לנו שגם הנדסת-חסר וגם הנדסת-יתר – אינם טובים.

התכונה המעניינת שמחזקת את עקרון ה YAGNI, היא שבעוד הנדסת-יתר ניתן לעשות עד אינסוף (כלומר: הסטייה האפשרית היא בטווח עצום / בלתי-נגמר), הסטייה האפשרית בהנדסת-חסר היא קטנה יותר: אם נחסיר יותר מדי – המערכת תפסיק לעבוד. יש לנו boundary ברור שיעצור אותנו מלהמשיך.

מכאן עולה מסקנה: גודל הטעות האפשרית בהנדסת-יתר – היא גדולה יותר מזו של הנדסת-חסר. מכאן אפשר להסיק שמשתלם תמיד להעדיף הנדסת-חסר על הנדסת-יתר, וכך לצמצם את פוטנציאל הטעות.

סטארט-אפים ישראלים מפנימים את המסר הזה דיי מהר: אין להם זמן לאבד, הסיכוי להסתבכויות של הנדסת-יתר עשויה להיות מכת מוות לסטארט-אפ שלא עומד ב milestones חשובים – ולכן כדאי להעדיף את הנדסת-החסר (בדמות קבלה שיטתית של עקרון ה YAGNI) על פני ״האלטרנטיבה השנייה״.

אַלְיָה – וקוץ בה

רגע של עברית: אַלְיָה היא רקמת שומן בזנבם של חלק ממיני הכבשים. ה"קוץ" הוא חלק פגום בנתח הבשר או לפי פירוש אחר: קוצים שדבקו לפרוות הזנב ומקשים על מלאכת הגֵּז.

הנה סיפור קצר: עבדתי בחברת הזנק שהייתה צריכה לבנות ממשק משתמש לאיש התמיכה הראשון. רבות מהפעולות היו חיפושים בבסיס הנתונים, ולכן החליטו שאין צורך לבנות כרגע UI. איש התמיכה ילמד SQL ויבצע את השאליתות שלו ישירות מעל בסיס הנתונים בפרודקשיין. So far – so Good.

עבר זמן קצר, ואיש התמיכה התקשה לעבוד עם זמנים שונים שהופיעו בבסיס הנתונים: בסיס הנתונים הציג זמנים ב UTC, בעוד החברה עבדה במדינת קליפורניה. כל פעם, היה צריך איש התמיכה לחשב בראש את ההפרש. הפתרון הפשוט היה לשנות את הגדרות בסיס-הנתונים לעבוד בזמן קליפורניה, מה שפתר את הבעיה – בזמן קצר ביותר. הפתרון יושם בשעה ואיש התמיכה היה מרוצה, ועבד יעיל יותר.

כמה שנים מאוחר יותר, בסיס הנתונים עדיין עבד בחברה הזו בזמן קליפורניה. הסיכונים האפשריים משינוי ההגדרות בשלב הזה – עלולים להיות אסוניים, ובכל נקודת זמן בה נעשתה הערכה טכנית הוחלט: מוטב להמשיך לשלם את חוסר הנוחות בעבודה בזמן קליפורניה, מלסכן את המערכת לכניסה לסחרור של טעויות בזמנים.

כלומר, להנדסת-חסר יש חסרון מובנה: היא עלולה לקבע מצב לא-טוב שבשום נקודת זמן לא משתלם לצאת ממנו.

אם היינו בוחרים בהנדסת-יתר היינו עשויים לשבת עם קוד מיותר / חסר-שימוש, שהשארתו איננו נזק מתמשך למערכת. הנדסת-חסר נוטה לכאוב לנו יותר בשוטף.

חשוב לציין: גם בהנדסת-יתר, יש מצבים בהם השארות הקוד היא כאב מתמשך. זה קיים, אך פחות נפוץ.

מציאת איזונים נכונים

השתכנעו אולי בשלב הזה שכדאי לנו להיזהר מהנדסת-יתר, אבל גם הנדסת-החסר ו״הצמדות״ לעקרון ה YAGNI, כלשונו, לא תוביל אותנו למצב בר-קיימא.
למען ההגינות והדיוק: דיונים על גבולות ה YAGNI היו ויהיו בעתיד. למשל, מרטין פאוולר כתב בבליקי עוד ב 2015:

Yagni only applies to capabilities built into the software to support a presumptive feature, it does not apply to effort to make the software easier to modify.

אני שם את הדיונים האלו בצד. בסוף, ביומיום של חברת סטארטאפ, יש לחץ אמיתי להתמקד בתוצאות מיידיות המשפיע על כל רוחב הארגון. בד״כ לא קוראים לזה בשם ״YAGNI״ – פשוט רוצים לעמוד ביעדים העסקיים. גם את הניתוח המדוקדק: ״האם אני מצמצם יכולת למשתמש או קלות לשינוי-התוכנה בעתיד״ – לא ממש עושים. אנו רואים דרך לחסוך עבודה שעדיין משיגה את התוצאה העסקית – ועושים אותה.

נפתח בכמה הנחות-יסוד:

  • גדיעת הנדסת-יתר, היא פעולה חשובה. באמת יש אינספור דרכים לסבך את התוכנה שלנו מתוך רצון טוב – אך בצורה איומה.
  • העתיד בתוכנה איננו ברור, במיוחד בחברות סטארט-אפ. לא פעם יבואו אנשי המוצר וילהיבו בחזון לשנה הבאה, המהנדסים יתכוננו לקראתו בדזיינים וקוד – רק כדי להיווכח שהעתיד הזה מבושש להגיע – או בכלל השתנה, ואיננו רלוונטי עוד.
  • הנדסת-חסר יכולה להיות הרסנית: חסכון השבועיים עכשיו – עלול לגרום לחודשי עבודה נוספים בעוד שנה.
  • אסתכן, בלי ניתוח מעמיק, להעריך שבסטארט-אפים ישראלים מדובר בנטייה ברורה להנדסת-חסר, וסה״כ: טוב שכך. תראו את ההייטק היפני, שבו יש נטייה תרבותית להנדסת-יתר (מתוך: תרבות של מצוינות ודיוק). כמה חברות תוכנה יפניות מצליחות אתם מכירים?

מה באמת אפשר לעשות?

אנסה להציע כמה קווים מנחים, שיעזרו לנו לאזן בצורה טובה יותר בין הסיכון להנדסת-יתר והסיכון להנדסת-חסר. נקודות ההחלטה העלו יעלו מתוך Design או מתוך דיון / דילמה מקצועית.

  • קודם כל, עלינו להציג מספר חלופות אפשריות. אם אנו דנים בחלופה יחידה – בהכרח אנו פועלים עם ״שטח מת״ משמעותי. זה עקרון בסיסי של תכנון תוכנה.
    • למשל: הדילמה האם להשתמש באפליקציה בבסיס-נתונים, או במערכת קבצים.
    • כאשר יהיו לנו מספר חלופות, לא פעם הן יפלו לקטלוג אוטומטי של ״פתרון זול/פשוט ופחות טוב״ מול ״פתרון טוב אך יקר״. זה השלב שנרצה להפעיל את השאלה ״Do we Gonna need it״? (להלן DWGNI)
    • התרבות הכווינה אותנו לחשוב ש "you get what your pay for״, קרי: יקר יותר = טוב יותר. חשוב שנשתחרר מההטיה הפסיכולוגית הזו ונבחן את האלטרנטיבות מבחנים אחרים.
  • אילו מבחנים ניתן להפעיל:
    • מבחן הדחייה, מה יקרה אם נדחה את ההחלטה? כמה יקר יותר יהיה לבצע את ההחלטה בעתיד (נניח: עוד שנה) ולא עכשיו? בארכיטקטורת תוכנה – נהיה חכמים יותר ככל שהזמן עובר, ולכן כדאי לדחות החלטות כאשר אפשר, ואפילו שווה לשלם על זה קצת.
    • מבחן מחיר ההחלטה השגויה לאורך זמן, מה צפוי להיות המשמעות של בחירה באופציה א׳ או באופציה ב׳ לאורך זמן? נניח: שלוש שנים. קל לומר: ״גישה ב׳ תיצור קוד קל יותר לתחזוקה״ ולרוץ לבחור באופציה ״הטובה יותר״. כמה זה טוב יותר? אולי זה לא שווה את ההשקעה? בואו ננסה לכמת את הערך. לא פעם, בעקבות בחינה עמוקה, אנחנו מגלים שהערך הוא שולי ולא שווה את ההשקעה.

דוגמה

בואו ננסה להדגים את העקרון, על מקרה מוחשי. נניח יש לנו פיצ׳ר חדש במערכת.

דילמה: האם לחלק את הפיצ׳ר למיקרו-שירות אחד או שניים.

אנו מזהים בשירות שתי אחריויות. נניח: A. להריץ workflow ו B. לאסוף נתונים ממקורות שונים. ע״פ גישת המיקרו-שירותים טבעי שנחלק את הפונקציה לשני שירותים מובחנים. זה ייתן לנו יותר הפרדה בין השירותים ויקשה על הקוד שלנו להסתבך.

מצד שני, אפשר באמת ליצור שירות אחד ולחסוך זמן. כרגע לא ידוע על מקרה שבו אחריות B תהיה בשימוש שלא בצמוד לאחריות A. לא צריך יותר משירות אחד, וזה יותר פשוט למימוש. כל קריאה בין אחריות A לאחריות B תהיה קריאה של פונקציה (קל) ולא קריאה של API ברשת (עוד עבודה).

מה עדיף? בואו נבחן ע״פ הקריטריונים.

מה המחיר לדחות את ההחלטה בשנה? נתחיל עם האופציה הזולה למימוש (שירות אחד) ונתקדם משם.

  • כמה מהר השירותים הללו עומדים להתפתח? מפתח יעבוד עליהם חודש, או כמה מפתחים יעבדו עליהם רבעון? פיצול של שירות לשניים עשויה להיות עבודה לא קטנה – וגודלה בקשר ישיר לכמות הקוד שכבר נכתב.
  • יש הבדל בין להעביר קוד בלבד, מול להעביר קוד + נתונים. אם אחריות B (השירות שאולי נפצל) כוללת בתוכה נתונים, במיוחד כאלו שמתעדכנים כל הזמן – המחיר לפיצול שירותים הופך להיות יקר הרבה יותר.

בקיצור: התשובה עשויה שונה ממקרה למקרה. אם אנחנו מרגישים שמחיר ההחלטה בעוד שנה הוא קטן (נניח: שבוע-שבועיים עבודה) – ייתכן ונרצה לדחות אותה ולהחליט בעתיד, כאשר נהיה חכמים יותר. אם המחיר יקר – כנראה שנרצה להתאמץ עוד ולקבל החלטה עכשיו.

מה מחיר ההחלטה השגויה לאורך זמן?

טעות קלאסית כאן, היא לבחון רק את האופציה הזולה, מתוך הנחה סמויה שהיא הטעות האפשרית. אנחנו נבחן את שתי האופציות.

נניח שבחרנו בשירות אחד, זו הייתה טעות, והמשכנו כך לאורך שלוש שנים. מה המחיר?

  • הקוד של אחריות A ואחריות B עלולים להתערבב (כי אין הפרדה טבעית ביניהם), ויהיה קשה יותר לתחזק ולהפריד ביניהם.
  • השירות המשותף עלול לנהל תלויות של שתי אחריויות ולא אחת – יותר תלויות אם שירותים שאולי לא רצינו בהם. יהיה קשה להבחין אם תלות נוצרה בגלל אחריות A או אחריות B.
  • אם כמות הקוד גדלה והסתבכה – כנראה ההפרדה בשלב הזה בין אחריות A ואחריות B תהיה מאמץ משמעותי וגדול. יהיה עלינו לפנות הרבה זמן כדי להפוך את ההחלטה. האם זה ישתלם לנו, או שנמשיך ״לסבול״ כי ״מחיר השנוי הוא גבוה מדי בכדי להצדיקו בשלב הזה״?

כמובן שבראייה הזו, ניתן לעשות צמצום-נזקים (mitigation) לחלק מהסעיפים. נניח לנהל את אחריות A ו B כמודולים נפרדים בשירות שבניהם יש הפרדה נוקשה – אם יש לכם כלים כאלו בארגון.

נניח שבחרנו בשני שירותים, זו הייתה טעות, והמשכנו כך לאורך שלוש שנים. מה המחיר?

נניח שהשירותים דיי פשוטים, או הקשר בין אחריות A ו B הוא פשוט ומאוד ברור וטבעי. הפרדנו את השירותים על מנת למנוע ערבוב, אך לאורך הזמן אנו מבינים שאין בכך באמת בעיה. יצרנו הגנה שאינה באמת נדרשת.

  • כל קריאה חדשה בין אחריות A לאחריות B כוללת יצירה של API חדש – תקורה ברורה בעבודה.
  • גילינו שבעצם יש קשר הדוק בין A ל B ולא היה נכון לראות בהן אחריויות שונות – ואנו עובדים קשה על התיאום בין שירות A לשירות B.
  • הביצועים של המערכת נפגעו קשות מההפרדה בין A ל B, או אולי היכולת לעשות troubleshooting מורכבת יותר.

בתסריט שבו התברר שההפרדה היא הנדסת-יתר כי הקוד פשוט למדי, הנזק גם אינו גדול. יש לנו תקורה, אך היא במכפלה של בסיס-קוד קטן.

התסריט של קשר הדוק בין A ל B שלא היה נכון להפריד ביניהם יותר קשה. אם רק שילוב של אחרית A ו B באותו שירות יפתור את התקורה, ייתכן ונרצה לשלב ביניהם בחזרה. לרוב קשיים כאלו הם קשיים שניתן למתן (mitigate) במאמץ נקודתי: שיפורי ביצועים או שיפור ה API.

ניתוח שכזה עוזר גם לנהל סיכונים בלי קשר. אולי למשל, אם עשויות להיות הרבה קריאות בין אחריות A ו B אולי עדיף שנגדיר API גנרי בין שני השירותים, שיכול לטפל במגוון המקרים, בלי להוסיף APIs נוספים כל פעם. למשל:

fun collectData (type: DataType): List<DataItem>

סיכום

בחזרה לשאלתנו: האם סטארט-אפים זקוקים לעקרון ה YAGNI?

כן, הם זקוקים לו, כי הסיכון מהנדסת-יתר הוא ממשי ומשמעותי. יש מהנדסים שאם תתנו להם ״לשפר תשתיות״, הם לעולם לא יסיימו. אנשים טובים ואיכותיים.

נוח מאוד לקבוע לקבוצת ההנדסה כללים ברורים, כמו ״אנחנו סטארט-אפ ואנחנו ולכן עושים YAGNI״ – אבל אז הסכנה היא שהמערכת תקלע למקום לא טוב, ממנו לא תצליח לתמוך בביזנס – מה שלא טוב לבריאות הארגונית.

שווה וכדאי לעשות את הדרך הקשה, ליצור תרבות של ספקנות וניסיון תדיר לקצץ ב scope של פיתוחים – תוך כדי בחינה מתמדת של ההשפעות העתידיות. כפי שקצת ראינו, עצם הבחינה הזו יכולה לאפשר לנו למצוא וריאציה מוצלחת יותר של הגישה המקורית.

כמו כן, נכון ״לכייל״ את המערכת. אם הגענו למסקנה שיש לנו נטייה מוגזמת לאחד הכיוונים (הנדסת-יתר או הנדסת-חסר) – להעלות את זה למודעות ולדרוש מאנשים ״להטות לתיקון״. כמובן שיותר קל לומר את זה מאשר לעשות 🙂

שיהיה בהצלחה!

כמה נקודות על Large Language Models

LLM מעסיק רבים מאתנו בחודשים האחרונים. חשבתי לשתף בכמה נקודות / תובנות בסיסיות. נתחיל:

נקודה ראשונה:
LLMs אינם נבונים בצורה הדומה לבני אדם. ChatGPT סה״כ יודע להשלים ולנחש טקסט בצורה קוהרנטית, אבל הוא עושה זאת בהבנה עמוקה של השפה האנושית, הסמנטיקה שבה, דפוסים שחוזרים בה, ויודע לבצע הכללות – כך שהוא מסוגל בעצם לייצר טקסטים שמרגישים מאוד ״נבונים״. הוא כמובן מתבסס על ידע שזמין לו / הוא אומן עליו – ומוגבל אליו. אם הטקסטים שאומן עליו מתארים את תאוריית ״המפץ הגדול״ – הוא יענה לפיה, ולא יסיק שזו שטות מוחלטת, אפילו אם יש בידיו נתונים שיכולים להפריך אותה. כמו כן, הוא לא יפעיל חשיבה ביקורתית, ותמיד יענה על מה ששאלנו – אפילו אם היה נכון יותר להראות לנו שהשאלה שלנו בעצם לא נכונה.

בכל מקרה – התוצאות מעוררות התפעלות, אפילו באינטראקציות קצרות ופשוטות:

מרשים: הוא ״מרחיב את השיחה״, לנושא הירחים של ירחים, מתוך ״תובנה״ שזה כיוון שיחה ״מעניין״.

(במקרה הספציפי הזה, Gemini ולא ChatGPT)

נקודה שנייה:
LLMs ״יורשים״ הטיות תרבותיות של הטקסטים / נתונים שהם מאומנים עליהם. אם לדוגמה היו מעט נשים בתפקידי מפתח בטקסטים שעליהם אומן GPT – הוא יציע פחות נשים בתשובות שלו (אלא אם תהיה התערבות לנסות ולשנות את זה, כמו Gemini שלא הצליח לצייר אפיפיור לבן או צייר נאצים גם כשחורים). ישנן התערבויות שונות במודל כדי לשמור אותו ״מוסרי״ ו Politically correct.

נקודה שלישית:
LLM הוא הקיצור, כמובן, ל Large Language Model – ו״הסיפור״ מאחורי המודלים האלו, ופריצת הדרך הגדולה שלהם לאחרונה, הוא סיפור של Brute Force. בניגוד לאידאל של ״תחכום מוביל לתוצאות טובות יותר״, ה LLMs הם דווקא מודלים פשוטים יחסית (יחסית למודלים אחרים כמו: LSTM או XGBoost), שייחודם ב:

  1. רשת נוירונים גדולה יותר, עצומה, יחסית לעבר הקרוב.
  2. אומנו על כמויות אדירות של נתונים (כאן האיכות דווקא חשובה, איכות הנתונים).

כלומר, במידת מה, אפשר לצפות לשיפור משמעותי במודלים הבאים (GPT-6, GPT-5) רק מתוך התחזקות כח המחשוב, וגם אם לא יהיו התקדמויות משמעותיות באלגוריתמיקה של המודלים או אימונם.

אין נתונים ברורים, אבל יש הערכות שכדי רק להריץ את המודל של GPT-4 זקוקים לכ 1TB של זיכרון, מפוזר על פני מספיק GPUs (עשרה לפחות). על מחשב ביתי חזק, גם לו היה לנו הזיכרון, ייתכן והיינו מחכים יממה בכדי לקבל תשובה בודדת.

עלויות האימון של מודל, בזמנים, ובכח המחשוב – ומכאן לכסף. לא סתם מסתובבות שמועות על Stargate – ה DataCenter בעלות של 100 מיליארד $ ש OpenAI ומייקרוסופט מתכננות להקים, שרק צריכת החשמל שלו, תהיה גדולה יותר מההספק של הכור הגרעיני הגדול ביותר בארה״ב.

נקודה רביעית:
בבסיס ה LLM ישנה רשת נוירונים (אני מניח שאתם יודעים מה זה) גדולה במיוחד, מה שנקרא גם Deep Learning. מודדים את גודל הרשתות באלו ב״כמות פרמטרים״ (ג׳מיני 1.5 – 1.5 טריליון פרמטרים, GPT-4 עם 1.76 טריליון פרמטרים) – במה בדיוק מדובר?

גרף לוגריתמי של כמות הפרמטרים במודלים לאורך השנים

בגדול, כמות הפרמטרים היא סכום ה biases, היושבים על nodes (מלבד השכבה הראשונה, ה input layer, שבה אין biases) + ה weight, היושבים על הקשרים ביניהם. ב GPT-4 יש רשת נוירונים בעומק 120 שכבות עם סה״כ 1,760,000,000,000~ node + קשרים. מטורף!

הנה קצת סדר / קשרים בין מונחים רלוונטיים:

נקודה חמישית:
מהם אותם tokens בהם מודדים עבודה של LLM? שלושה סנט לעיבוד של 1000 tokens ב GPT-4, למשל.

Tokens הם בקירוב מילים או תתי-מילים. הם השפה הפנימית של ה LLM, המתמקדת בסמנטיקה (משמעות) של המלים.
המילה Home היא token, ו GPT-4 מכיר כ 50,000 tokens שונים בשפה שלו.
המילה schoolhouse מתחלקת לtokens הבאים: school ו house. פחות מלים ל LLM להכיר + דיוק במשמעות של המלים. ה LLM משתמש בקשרים סמנטיים של כל אחד מה tokens והפירוק ל school ו house מאפשרים לו להשתמש בהקשרים של ה tokens האלו.

בעברית, המילה ״הביתה״ הייתה כנראה מתחלקת ל-3 tokens: ״ה-הידיעה״, ״בית״, ו״ה-המגמה״ (צפונה, מצריימה). מצד שני, ישנם מצבים בהם Token יכול להיות יותר ממילה אחת. למשל: "New York״ עשוי להיות token אחד, כי יש משמעות גדולה יותר לשם New York מאשר למלים המרכיבות אותו.

אני באמת ממליץ לכם לקחת כמה דקות ולשחק עם ה OpenAI Tokenizer ולראות כיצד הוא עובד. חלק מהדוגמאות הנ״ל לא עובדות ב GPT כמו התאוריה שתיארתי. בעברית, למשל, כל אות היא token – בכדי להכביד כמה שפחות על המודל של GPT, ובאמת ChatGPT לא מוצלח עם עברית. אם תבדקו מודלים שאפשרו יותר tokens בעברית, כמו Claude או Dikta (מודל עברית!) – תראו שהתוצאות טובות בהרבה.

תהליך ה tokenization הוא בעצם פעולת קלאסית של NLP, קרי Natural Language Processing.

בכדי להיות יעיל (או לפחות: יעיל-יותר), הרשת של ה LLM לא מייצגת token כ string (בזבזני בזיכרון), אלא לכל token בשפה יש id מספרי. בכניסה לרשת עושים מיפוי בין ה tokens ל ids (״השפה של ה LLM״) – וביציאה מהרשת – בחזרה ל strings.

אם נצלול יותר לפרטים, נראה שהייצוג הפנימי של token הוא לא מספר שלם, אלא מספר עשרוני שמבטא סמנטיקה, ולא מספר יחיד, אלא בעצם וקטור מספרים עשרוניים המייצג הקשרים סמנטיים של אותו ה token (לאילו מלים אחרות בקלט הוא ״קרוב סמנטית״ + היכן הופיע בטקסט) – אבל זו כבר צלילה מסוג אחר…

נקודה שישית:
רציתי לסיים את הפוסט, ולשחרר אותו לאוויר, אבל הרגשתי חובה פנימית לקנח בעוד נושא אחר: ה RAG.

בגדול, אם אנחנו רוצים לעשות שימוש ב LLM מעבר ל vanilla offering (קרי ChatGPT, Claude, Gemini) יש כמה אסטרטגיות עיקריות לעשות זאת:

למען האמת, התרשים שלי הוא מעט פשטני, כי יש יותר ממימד אחד להחלטה / שוני בין האסטרטגיות.

בכל מקרה, אם אין לכם תקציב של כמה מיליונים לאמן מודל LLM משלכם, ואם אתם לא רוצים לשנות את אופי המודל (שפה רשמית יותר, העדפת נושאים מסוימים על פני אחרים) מה שאומר שלא מדבר ב Fine Tuning של המודל (תהליך שעשוי גם קשה-לשליטה בתוצאות שלו) – נשארו לכם שתי פרקטיקות עיקריות: Prompot Engineering / Enhancment (שיכול להופיע כאימון למשתמשים, או כתוכנה ״שמהנדסת״ את ה prompt בזמן ריצה) או RAG. בהנחה שכולם מבינים מהו Prompt Engineering, אסביר מהו RAG – אסטרטגיה סופר-חשובה בתחום.

הבעיות ש RAG בא לפתור הן מגבלות ה scope, העדכניות, וההיכרות של ה LLM עם מודל ספציפי. אם אשאל מתי פתוחה מסעדה הקרובה לביתי, אקבל תשובה גנרית ומשכנעת – שלא עוזרת לי:

  • המודל לא יודע איפה אני גר
  • המודל לא יודע מה הזמן עכשיו
  • המודל לא מכיר את המסעדה הספציפית, אם כי הצליח להבין מההקשר (״בורגר״) שזו כנראה מסעדה.

אם הייתי רוצה ליצור chatBot לבורגר סאלון זו הייתה בעיה ממשית. מכיוון שמדובר ברשת קטנה (יחסית), ואימון של מודל LLM הוא כנראה מחוץ לתקציב השירות של הרשת.

הפתרון הוא לשלוף מידע נוסף (Retrieval) כדי להעשיר את ה prompot/query (להלן Augmented) של ה GenAI (להלן: Generation). בקיצור: RAG.

במקום לשלוח את השאלה ״מתי בורגר סאלון פתוחים?״ ל LLM כפי שהיא, אנחנו:

  1. נוסיף הקדמה בראש ה prompt: ״בבקשה התייחס למידע הבא בשאלה:״
  2. נוסיף את הזמן הנוכחי, ומיקום של המשתמש (ששלפנו מהדפדפן)
  3. נבדוק ב DB של הרשת מה שעות הפתיחה (אפשר את כל השעות והסניפים, או לפלטר את הסניף הספציפי והיום הספציפי) ונוסיף את המידע ל prompt.

ורק אז נשלח אותו ל LLM, ומשם ככל הנראה תהיה לנו תשובה נכונה ועדכנית. הידד!
כמובן שאסטרטגיית ה RAG חשובה במיוחד כאשר יש מידע ארגוני שאינו זמין למודל / פתוח לעולם.

יש כאן כמה שאלות פתוחות של מימוש:

  1. כיצד אנחנו מנתחים את ה raw prompt ויודעים באיזה מידע בדיוק להעשיר את ה prompt שלנו?
  2. כיצד לנהל את מאגר הנתונים שלנו (להלן: knowledge base)? טכנולוגיות, אינדקסים, וכו׳
  3. כיצד להזין מידע עדכני למאגר הנתונים? מתוך המערכת האופרטיבית או מקורות שונים באינטרנט? איזה מידע לסנן / לנקות / לשפר?
  4. כיצד ״להרכיב״ prompt אופטימלי מהנתונים שהבאנו? למשל: יותר נתונים לא תמיד מובילים לתוצאה טובה יותר (קשה למודל להחליט במה להתמקד, קרי בחירת ה attention)? יש פה פרקטיקות של Prompt Engineering
  5. איך מאבטחים מידע פרטי, במאגר הנתונים וב prompt – שלא ידלוף?

בקיצור: יש פה עבודה הנדסית לא מעטה.

שווה אולי לציין, ש BingChat (בניגוד ל Gemini או ChatGPT) משתמש ב RAG באופן מובנה, ומבצע חיפושים ב Bing Search, על מנת לשפר את התשובה שניתנת לכם. לכל תשובה הוא יצרף את הקישורים. ישנן גם ביקורת, היכן הוא עושה את זה בצורה פחות טובה.

סיכום

טוב, זה לא היה פוסט מובנה במיוחד, בעיקר מעבר על נקודות שונות עם קשר אסוציאטיבי.

רוב כתיבת הפוסט הזה, היא בעצם שליפת נתונים, הכללה, בחירת attention, ועיבוד לטקסט קוהרנטי (עם קצת גרפיקה). אני מצפה ש GPT-7, או לכל היותר GPT-9 – יוכלו לעשות את זה במקומי, ואז אני אתפנה לדברים ש LLM לא מסוגל לעשות – כמו קיפול כביסה.

שיהיה בהצלחה!

קישורים רלוונטיים
מה LLM מתקשה לעשות? (באנגלית)

על צורת העבודה של צוות ארכיטקטים בארגון תוכנה

אם תקראו ספרים של ארכיטקטורת תוכנה (יש לא מעט כאלו), תמצאו עקרונות טכניים שונים, מחזור-חיים של תוכנה, תהליכי תכנון של מערכות מורכבות, Patterns למכביר, ומגוון נושאים של הנדסת-תוכנה – אך כמעט וכלום על התנהלות של ארכיטקטים בארגון התוכנה.

יוצא דופן אחד שאני מצליח לחשוב עליו הוא The software architect elevator של גרגור הופ (המחבר של Integration Patterns), וגם הוא דיי ״טהור״ / ״תאורטי״ ופחות ממחיש את היומיום בשטח.

בפוסט הזה, אני רוצה לשתף בקצרה איך עובד צוות הארכיטקטים בחברה הנוכחית שלי, Next Insurance (להלן ״נקסט״): מה מתודולוגיות העבודה שלו מול שאר ארגון התוכנה. אני רוצה להאמין שתיאור מהשטח – יכול להיות מעניין ומחדש.

שווה לציין שאני נותן Snapshot בזמן נתון. לפני כשנתיים שלוש ההתנהלות הייתה אחרת, וגם החברה נראתה אחרת.
הנה כמה נתונים על נקסט, שיהיה לכם קל יותר לדמיין את הסביבה המדוברת (context):

  • כ 150 מהנדסי תוכנה. בישראל, וקצת בארה״ב.
  • צוות הארכיטקטים פועל רוחבית על כל הארגון.
  • חברת-מוצר (ביטוח לעסקים קטנים)
  • מוצר SaaS (מיקרו-שירותים, לצורך העניין)
  • קוטלין בצד השרת, TypeScript ואנגולר או ראקט בצד לקוח (לא ממש חשוב).
  • חברה בת כשמונה שנים, כאשר האתגר הטכני העיקרי הוא מורכבות עסקית גדולה מאוד (המון business logic שצריך להכיל)

תהליך העבודה של צוות הארכיטקטים

מטרת-העל של צוות הארכיטקטים בנקסט הוא למנוע מהמערכת לשקוע בסיבוכיות שתאט את הארגון ולא תאפשר לו להמשיך ולעמוד ביעדיו העסקיים.

אני מניח שאתם יודעים, שכאשר חלקים במערכת מסתבכים יתר על המידה, מגיע הרגע שבו שינויים שאמורים להיות קטנים וקלים – מתבררים כדומינו של שינויים במערכת, שמצטברים להרבה עבודה וסיכון – הרבה מעבר למה שהגיוני לצפות משינוי בסדר הגודל שהתבקש.

איך יכולים כמה אנשים, נבונים ככל שיהיו למנוע ממערכת מורכבת עליה עובדים עוד 150 מהנדסים -מלהסתבך?!

הנה סיכום גרפי קצר של תהליך העבודה של צוות הארכיטקטים שלנו, ב high level:

Identify & Study system flaws

  • אנחנו פועלים כל הזמן ללמוד ולהבין את המערכת.
  • הלמידה מתרחשת מתוך עבודה במערכת, נבירה בקוד, ודיונים טכנולוגיים.
  • חלק מהדיונים הללו קורים תוך כדי עבודה על פרויקטים, וחלק מהם הם תוצאה של פעולה יזומה.
    • למשל: check-in פעם בחצי שנה עם הצוותים להבין את ההתפתחויות והקשיים בשירותים שהם מתחזקים.

Insight

  • התובנות לא מגיעות מייד, הן מגיעות מהצטברות של אינפוטים וחוויות.
  • תובנה לדוגמה היא: ״המודל הזה לא מתאר נכון את המציאות, ומודל נכון יותר היה מפשט הרבה קוד במערכת״ או ״החלק הזה של המערכת עוסק בשני concerns שונים, ופשוט יותר היה אם היו שני חלקים – שכל אחד עוסק ב concern יחיד״.
  • כשיש עיוותים במערכת, לא תמיד קל לזהות אותם ולהעריך את משמעותם המלאה. ״כן, אולי יכולנו לעשות את זה אחרת – אבל נראה לי שזה סה״כ סבבה״.
  • יש אינספור בעיות קטנות יותר ש״גונבות״ תשומת-לב.

Design Solutions

  • תובנה היא עדיין לא יכולת לפעול. צריך לחשוב ולמצוא את תוכנית הפעולה הטובה והנכונה (בהתאם לנסיבות).
  • הארכיטקטים יושבים ועושים תכנון בקנה מידה גדול (Architectural Concept או High Level Design) לפתרון טוב יותר לבעיה.
    • לא פעם, הם היחידים שישקיעו זמן ומאמץ בשלב מוקדם – על בעיה ״כ׳׳כ רחוקה״.
    • בדרך כלל, לא יהיה להם מספיק ידע לתכנון מוצלח. הם יהיו חייבים לעבוד עם הצוותים שמכירים את הפרטים.
    • חשוב מאוד לצלול לקוד, ולחלץ תובנות משמעותיות מה domain experts הרלוונטיים. להתבסס על עובדות והבנה מספיק טובה של המצב.
      • בתכנון high level וארוך טווח שכזה – קל לקחת הנחות אופטימיות (או פסימיות), וליצור פתרון עם פערים מהמציאות בשטח. ראייה מפוכחת, ניסיון, ספקנות, ותקשורת טובה – הם כלים הכרחיים להצלחה בכזו משימה. הצלחה היא לא תוכנית שנראית מבטיחה. הצלחה היא תוכנית שיש לה סיכוי טוב להצליח באמת.
  • השלב הזה מתבטא בעדכון שני תוצרים עיקריים:
    • ה Target architecture (בקיצור: TA) הוא סט מסמכים / Architectural Concepts / HLDs שנוצרו ומתארים את השינויים העיקריים שצוות הארכיטקטים מאמין שיש להחיל על המערכת בעתיד הנראה לעין. למשל: להוסיף אחריות חדשה במערכת, לשנות מידול, להחליף טכנולוגיה, להעביר אחריויות ממקום למקום במערכת, או לשנות גישת עבודה בחלקים שונים של המערכת.
      • טכנית, ה TA הוא תיקיה משותפת, עם המסמכים הנ״ל.
      • כמובן שצריך לרענן ולעדכן את התכנים כל הזמן. לא מעט רעיונות יהפכו ללא רלוונטיים עם הזמן.
      • כמובן שמסמכים ארוכים ומפורטים לא משרתים את ה TA היטב. מסמכים תמציתיים, שקל לקרוא, קל לעדכן, לא כואב לארכב – מתאימים יותר. היכולת לתמצת רעיונות למינימום הכרחי – חשובה מאוד.
      • סקירה תקופתית של הרעיונות עם מנהלים בכירים ומהנדסים רלוונטיים – יכולה לעזור מאוד, גם לתקף את הרעיונות, וגם לעזור ולרתום אליהם עוד אנשים.
      • רעיונות ב TA שלא הגיעו מצוות הארכיטקטים, ואפילו לא זכו לתמיכת הארכיטקטים בשלב מוקדם – הם חשובים ל TA. ה TA צריך להיות סיכום / קונצנזוס של הארגון (שהארכיטקטים אחראיים עליו) – ולא דעה אחת, או דעת מיעוט.
    • ישנן תובנות או לקחים שלא מתמפים לשינוי רכיב במערכת, מודל, או Flow במערכת. למשל: איך משדרגים ספריות, משנים סכמה בבסיס הנתונים, או עקרון שאומר שלכל מידע Immutable במערכת נדרש Audit Trail כזה וכזה – לצורכי troubleshooting, למשל.
    • תבנית מתאימה יותר היא ה Best Practices & Guidelines – שורה של כללים ותהליכים הנדסיים שיעזרו למערכת להיות ברת-קיימא (Sustainable) ויעילה יותר. דברים שאם לא נעשה אותם, יש סיכון גובר שנשקע ב״ביצת הסיבוכיות״.
    • גם כאן, הארכיטקטים הם ה Facilitators ואולי האחראים – אבל לא ״המוח״ היחידי להגדיר את אותם כללים.
    • גם כאן, נדרשת עבודה משותפת עם המהנדסים: Guidelines לא טובים, או Guidelines שרק יושבים במגירה – יכולים בהחלט להיות מזיקים.
    • גם כאן, נדרשת התעמקות ומקצועיות על מנת להוביל להחלטות טובות. דיון ״דמוקרטי״ שמנהל ארכיטקט שבו מהנדסים שונים מציעים Guidelines שונים – עתיד להוביל לבינוניות. הארכיטקט צריך להיכנס לעובי הקורה, לאתגר ולחפש שיאתגרו אותו – ולהציע Guideline שלם, הרמוני, ו ״battle tested״ בביקורת עמיתים. עליו יש אחריות שהתוצר הוא נכון ויעיל – ולתקן אותו לאורך הדרך, אם הסיכום הראשוני אינו טוב מספיק.

Driving TA and applying Guidelines through projects

אחרי שיש לנו תוכנית פעולה, נשאר רק ליישם אותה. בהגדרה, ״שינויים ארכיטקטוניים״ הם שינויים שקשה לעשות אותם. להחיל שינויים קשים במערכת, במיוחד בסטארט-אפ צומח, זה לא דבר מובן מאליו. בגדול, יש שני מנועים עיקריים לשינויים במערכת:

  • פרויקטים אסטרטגיים – פרויקטים גדולים ומורכבים, המזיזים ומחדשים, בכל מקרה, חלקים במערכת. פרויקטים אלו הם הזדמנות נכונה להתקדם לכיוון ה Target Architecture.
    • ייתכן ששינוי שהיה לוקח 4 חודשי עבודה בעצמו, פתאום משתלב לתוספת של חודש או חודשיים עבודה כחלק מפרויקט אסטרטגי – ולכן זו הזדמנות לבצע אותו.
    • לא פעם, לא להתקדם לכיוון ה Target Architecture משמע להקשות יותר על ההגעה לשם בעתיד (עיבוי החוב הטכני). הפרויקט יהפוך עבודה עצמאית של 4 חודשים – להיות 5 חודשים.
    • פרויקטים אסטרטגיים הם גם הזדמנות להחיל וליישם Best Practices במקומות חשובים במערכת – כחלק מהעבודה.
  • ״פרויקטים הנדסיים״ – הוא מונח פנימי בנקסט לפרויקט שלא משרתים ישירות צורך עסקי – אלא שינוי ושיפור ארכיטקטורה. לפעמים נדרש שינוי גדול שלא נכון לקשור או לחכות לפרויקט עסקי אסטרטגי.
  • ״פרויקט הנדסי״ הוא כלי משמעותי לקדם את ה TA מהר יותר – היכן שחשוב. לרוב הם יתרחשו רק כאשר נוצר קונצנזוס רחב על הצורך בשינוי המדובר. קונצנזוס שנכון מאוד שארכיטקטים יפעלו ללא ליאות כדי לבנות – אבל נחמד לפעמים לראות שדווקא אנשים אחרים בארגון (ראשי קבוצות, אחרים) הם אלו שמקדמים וגורמים להם לקרות. בסופו של דבר, המערכת היא של כולנו – ומערכת טובה יותר היא אינטרס משותף.

תוצר חשוב נוסף: ה Service Registry

במובן מסוים, ארכיטקטורה היא סדר:

  • הסדר הזה עוזר לארגון לפעול בתיאום, מבלי למשוך את המערכת לכיוונים מנוגדים.
  • הסדר הזה עוזר להבין ולהסכים – היכן פונקציונליות צריכה לשבת במערכת (Orientation, DRY, SRP).
  • הסדר הזה עוזר לארגון להיות יעיל יותר מצד אחד, ולהיות מסוגל לרכז מאמץ (באזור נבחר) – מצד שני.

לרוב השפה לתאר ארכיטקטורה היא תרשימים מורכבים ומרשימים. אם הסדר הזה (״הארכיטקטורה״) קיימת בראש של כמה אנשים בודדים – ההשפעה שלה תהיה קטנה. מהנדסים לא יכולים לשמור או לסייע להגביר סדר – שהם לא מבינים. קשה מאוד להעביר ״ארכיטקטורה״ מורכבת לקהל מגוון של אנשים – ולשמור אותם מעודכנים.

כלי נוסף ששכללתי לאורך השנים – הוא ה ״Service Registry״: תיאור הארכיטקטורה הנוכחית, על צדדיה הטובים יותר והטובים פחות – במסמך וורד פשוט, ללא תרשימים. הפשטות הזו – היא הכוח של הכלי.

ה Service Registry מתאר בכמה bullets כל מיקרו-שירות במערכת. תיאור שקל לקרוא ולהבין – שצריך להיות מאוד תמציתי ומדויק.

דיסקליימר: בעשור האחרון עבדתי רק עם מידי-שירותים, שירותים בגודל בינוני, ולא המון שירותים קטנים. אני לא יודע לומר כיצד הכלי הזה היה עובד במערכת עם מאות מיקרו שירותים קטנים. בנקסט, יש לנו בעת כתיבת הפוסט כ 50 שירותים (כ 10-15 מהם הם באמת ״מיקרו״-שירותים) על 150 מהנדסים.

לכל שירות במערכת יש קטע קצר (שורות מספר, עד עמוד לשירותים המורכבים ביותר) הכולל 3 חלקים:

  • (אופציונלי) רקע – לתאר קצת את ה domain של המיקרו-שירות, אם הוא לא מובן-מאליו.
    למשל: שירות ניהול-מיקום יכול קצת להסביר מהו geolocation ומה מידת הטעות האפשרית באיסוף שלו.
  • תחומי-אחריות – תיאור (לרוב בנקודות) של סוגי הפונקציונליות שצריכות לשבת בשירות.
    תחת עקרון ה SRP (single responsibility) – לא אמורים להיות שני שירותים בעלי אותה אחריות.
    • אם יש חריגה משמעותית מהרצוי (היסטוריה) – אנחנו מציינים אותה במפורש.
  • תחומי-אי-אחריות – תיאור (לרוב בנקודות) של סוגי הפונקציונליות שלא צריכות לשבת בשירות. זו לא רשימה כוללת (ואינסופית), אלא רשימה קצרה של המועמדים הטבעיים לטעות + הסבר קצר היכן במערכת הם כן צריכים לשבת.

בניגוד לתרשימים מורכבים של Flows דינאמיים, את האחריויות קל בד״כ לתאר ולהבין – והם לא משתנים הרבה. פעם בחצי שנה אנחנו עושים מעבר יזום (עם ראשי הצוותים הרלוונטיים) לעדכן את המסמך – ויש מעט שינויים שלא עלו לאורך השנה מהצוותים.

הכלי הזה עוזר לצמצם ולפתור ויכוחים טכניים בין צוותים. הוא עוזר למהנדסים לקבל החלטות שיתיישבו עם הארכיטקטורה, והוא עוזר להציף אי-התאמה של הארכיטקטורה ע״י מהנדסים – שאחרת אולי והיינו מבינים רק מאוחר הרבה יותר. כמו שנאמר: Good fences make good neighbours.

יצירה ראשונה של מסמך כזה במערכת קיימת היא מאמץ ניכר – לפעמים של חודשים רבים. כשמתחילים לעבוד עליו צצים פערי התפיסה הרבים בין הצוותים, ולפעמים זקוקים לדיונים וחקירות רבות – על מנת לסגור ולהגיע להסכמה על כל הפערים הללו. ההתחלה היא לא קלה.

סיכום

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

יש משהו קצת ״לא טבעי״ בצוות ארכיטקטים בארגון תוכנה. מהנדסים או מנהלים – הם צרכים מיידיים שחייבים להתמלא במהרה. צוות ארכיטקטים ממלא תפקיד שרק מתוך תכנון והחלטה אנושית – יתקיים. זהו צורך שיכול להתקיים כ״צוות ארכיטקטים״, אך גם בדרכים אחרות.

נ.ב. – אני מגייס ארכיטקט נוסף לצוות. אם את/ה ארכיטקט/ית שרוצה לנסות לעבוד בגישה הנ״ל, בחברה צומחת בעולם המוצר – baronlior[at]gmail.com היא הכתובת.