תכנון כלי פיתוח: שימוש יעיל באסימונים בעזרת AI

תאריך פרסום: 30 בינואר 2026

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

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

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

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

התאמה אישית של ההקשר הראשוני

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

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

ניפוי באגים במשימה הנתונים שנשלחים בהתחלה לעזרה מ-AI
צ'אט על מעקב אחר ביצועים סיכום מעקב: דוח מבוסס-טקסט שכולל מידע כללי ממעקב ומסשן ניפוי הבאגים. הדוח כולל את כתובת ה-URL של הדף, תנאי ההגבלה, מדדי ביצועים מרכזיים (LCP,‏ INP,‏ CLS), רשימה של תובנות זמינות וסיכום של CrUX, אם הוא זמין.
לפתוח צ'אט על תובנה לגבי הביצועים סיכום המעקב ושם התובנה לגבי הביצועים שנבחרה.
צ'אט על משימה ממעקב סיכום המעקב ועץ השיחות הסדרתי שבו נמצאת המשימה שנבחרה.
צ'אט בנושא בקשת רשת סיכום של ה-Trace, ומפתח הבקשה וחותמת הזמן שנבחרו
יצירת הערות למעקב עץ השיחות שעבר סריאליזציה, שבו נמצאת המשימה שנבחרה. העץ שעבר סריאליזציה מזהה איזו משימה נבחרה.

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

מתן כלים ל-AI

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

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

פונקציה תיאור
getInsightDetails(name) הפונקציה מחזירה מידע מפורט על תובנה ספציפית לגבי ביצועים (לדוגמה,פרטים על הסיבה לסימון של LCP).
getEventByKey(key) הפונקציה מחזירה מאפיינים מפורטים של אירוע ספציפי יחיד.
getMainThreadTrackSummary(start, end) הפונקציה מחזירה סיכום של הפעילות בשרשור הראשי עבור הגבולות שצוינו, כולל סיכומים מלמעלה למטה, מלמטה למעלה ושל צד שלישי.
getNetworkTrackSummary(start, end) הפונקציה מחזירה סיכום של פעילות ברשת עבור גבולות הזמן שצוינו.
getDetailedCallTree(event_key) מחזירה את עץ השיחות המלא לאירוע ספציפי בשרשור הראשי בנתוני המעקב אחר הביצועים
getFunctionCode(url, line, col) מחזירה את קוד המקור של פונקציה שהוגדרה במיקום ספציפי במשאב, עם הערות של נתוני ביצועים בזמן הריצה ממעקב הביצועים
getResourceContent(url) הפונקציה מחזירה את התוכן של משאב טקסט שנעשה בו שימוש בדף (לדוגמה, HTML או CSS).

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

דוגמה לפעולה של סוכן

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

  1. getEventByKey: אחזור של פירוט התזמון (TTFB, זמן ההורדה) של הבקשה הספציפית שנבחרה על ידי המשתמש.
  2. getMainThreadTrackSummary: בודקים אם השרשור הראשי היה עמוס (חסום) כשהבקשה הייתה אמורה להתחיל.
  3. getNetworkTrackSummary: ניתוח כדי לבדוק אם משאבים אחרים התחרו על רוחב הפס באותו הזמן.
  4. getInsightDetails: בודקים אם בסיכום המעקב כבר מוזכר תובנה שקשורה לבקשה הזו כצוואר בקבוק.

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

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

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

סריאליזציה של הנתונים

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

{
  "id": 2,
  "name": "animate",
  "selected": true,
  "duration": 150,
  "selfTime": 20,
  "children": [3, 5, 6, 7, 10, 11, 12]
}

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

מחסנית קריאות בעקבות ביצועים מוקלטות בכלי הפיתוח

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

  1. מפתחות מיותרים: מחרוזות כמו "duration",‏ "selfTime" ו-"children" חוזרות על עצמן בכל צומת בעץ השיחות. לכן, אם שולחים למודל עץ עם 500 צמתים, המודל יצרוך טוקנים עבור כל אחד מהמפתחות האלה 500 פעמים.
  2. רשימות מפורטות: רישום כל מזהה צאצא בנפרד באמצעות children צורכת מספר עצום של טוקנים, במיוחד במשימות שמפעילות הרבה אירועים במורד הזרם.

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

איטרציה ראשונה

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

allUrls = [...]

Node: 1 - update
Selected: false
Duration: 200
Self Time: 50
Children:
   2 - animate

Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children:
   3 - calculatePosition
   5 - applyStyles
   6 - applyStyles
   7 - calculateLayout
   10 - applyStyles
   11 - applyStyles
   12 - applyStyles

Node: 3 - calculatePosition
Selected: false
Duration: 15
Self Time: 2
URL: 0
Children:
   4 - getBoundingClientRect

...

אבל הגרסה הראשונה הזו הייתה שיפור קל בלבד לעומת JSON גולמי. הוא עדיין מפרט במפורש את צאצאי הצומת עם מזהים ושמות, ומוסיף בתחילת כל שורה מפתחות חוזרים ותיאוריים (Node:,‏ Selected:,‏ Duration: וכו').

אופטימיזציה של רשימות צמתי צאצא

כשלב הבא באופטימיזציה, הסרנו את השמות של צאצאי הצומת (calculatePosition, applyStyles, … בדוגמה הקודמת). מאחר שלעזרה מ-AI יש גישה לכל הצמתים באמצעות קריאה לפונקציות, והמידע הזה כבר נמצא בראש הצומת (Node: 3 - calculatePosition), אין צורך לחזור על המידע הזה. כך יכולנו לצמצם את Children לרשימה פשוטה של מספרים שלמים:

Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3, 5, 6, 7, 10, 11, 12

..

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

הסיבה לכך היא שבניסיון הראשון שלנו השתמשנו באלגוריתם Depth-First Search (DFS) כדי לבצע סריאליזציה של נתוני העץ ממעקב הביצועים. התוצאה הייתה מזהים לא רציפים לצמתי אחים, ולכן נדרשנו לציין כל מזהה בנפרד.

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

הסימון הסופי של הצומת, עם רשימת Children שעברה אופטימיזציה, נראה כך:

allUrls = [...]

Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3-9

צמצום מספר המפתחות

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

allUrls = [...]

2;animate;150;20;0;3-10

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

...
Each call frame is presented in the following format:

'id;name;duration;selfTime;urlIndex;childRange;[S]'

Key definitions:

*   id: A unique numerical identifier for the call frame.
*   name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
*   duration: The total execution time of the call frame, including its children.
*   selfTime: The time spent directly within the call frame, excluding its children's execution.
*   urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
*   childRange: Specifies the direct children of this node using their IDs. If empty ('' or 'S' at the end), the node has no children. If a single number (e.g., '4'), the node has one child with that ID. If in the format 'firstId-lastId' (e.g., '4-5'), it indicates a consecutive range of child IDs from 'firstId' to 'lastId', inclusive.
*   S: **Optional marker.** The letter 'S' appears at the end of the line **only** for the single call frame selected by the user.

....

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

סיכום

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

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

אנחנו מקווים שהטכניקות האלה יעוררו בכם השראה לבחון מחדש את מבני הנתונים שלכם כשאתם מתכננים פתרונות מבוססי-AI. כדי להתחיל להשתמש ב-AI באפליקציות אינטרנט, אפשר לעיין במאמר Learn AI on web.dev.