طراحی ابزارهای توسعه: استفاده کارآمد از توکن در دستیار هوش مصنوعی

منتشر شده: ۳۰ ژانویه ۲۰۲۶

هنگام ساخت دستیار هوش مصنوعی برای عملکرد ، چالش مهندسی اصلی این بود که کاری کنیم Gemini به خوبی با ردیابی‌های عملکردی ثبت‌شده در DevTools کار کند.

مدل‌های زبان بزرگ (LLM) در یک «پنجره زمینه» عمل می‌کنند که به محدودیت دقیقی در میزان اطلاعاتی که می‌توانند همزمان پردازش کنند اشاره دارد. این ظرفیت با توکن‌ها اندازه‌گیری می‌شود. برای مدل‌های Gemini، یک توکن تقریباً گروهی از چهار کاراکتر است.

ردیابی‌های عملکرد، فایل‌های JSON بزرگی هستند که اغلب شامل چندین مگابایت می‌شوند. ارسال یک ردیابی خام، فوراً پنجره context مدل را پر می‌کند و جایی برای سوالات شما باقی نمی‌گذارد.

برای اینکه کمک هوش مصنوعی برای عملکرد امکان‌پذیر شود، ما مجبور بودیم سیستمی طراحی کنیم که میزان داده‌های مفید برای یک LLM را با حداقل استفاده از توکن به حداکثر برساند. در این وبلاگ، می‌توانید در مورد تکنیک‌هایی که ما برای انجام این کار استفاده کرده‌ایم، اطلاعات کسب کنید و آنها را برای پروژه‌های خود به کار بگیرید.

زمینه اولیه را متناسب کنید

اشکال‌زدایی عملکرد یک وب‌سایت کار پیچیده‌ای است. یک توسعه‌دهنده می‌تواند برای درک زمینه، کل ردیابی را بررسی کند، یا روی Core Web Vitals و بازه‌های زمانی مرتبط با ردیابی تمرکز کند، یا حتی به جزئیات بپردازد و روی رویدادهای منفرد مانند کلیک‌ها یا اسکرول‌ها و call stack های مرتبط با آنها تمرکز کند.

برای کمک به فرآیند اشکال‌زدایی، دستیار هوش مصنوعی DevTools باید با آن سفرهای توسعه‌دهنده مطابقت داشته باشد و فقط با داده‌های مرتبط کار کند تا توصیه‌هایی متناسب با تمرکز توسعه‌دهنده ارائه دهد. بنابراین به جای اینکه همیشه کل مسیر را ارسال کنیم، دستیار هوش مصنوعی را طوری ساخته‌ایم که داده‌ها را بر اساس وظیفه اشکال‌زدایی شما برش دهد:

وظیفه اشکال‌زدایی داده‌ها در ابتدا برای کمک به هوش مصنوعی ارسال شدند
درباره ردیابی عملکرد گپ بزنید خلاصه ردیابی : گزارشی مبتنی بر متن که شامل اطلاعات سطح بالا از جلسه ردیابی و اشکال‌زدایی است. شامل URL صفحه، شرایط محدودکننده، معیارهای کلیدی عملکرد (LCP، INP، CLS)، لیستی از بینش‌های موجود و در صورت وجود، خلاصه CrUX می‌شود.
درباره بینش عملکرد گپ بزنید خلاصه ردیابی و نام بینش عملکرد انتخاب شده.
درباره یک کار از طریق ردیابی گپ بزنید خلاصه ردیابی و درخت فراخوانی سریالی شده که وظیفه انتخاب شده در آن قرار دارد.
گفتگو درباره درخواست شبکه خلاصه ردیابی، و کلید درخواست انتخاب شده و مهر زمانی
حاشیه‌نویسی‌های ردیابی ایجاد کنید درخت فراخوانی سریالی شده که وظیفه انتخاب شده در آن قرار دارد. درخت سریالی شده مشخص می‌کند که کدام وظیفه، وظیفه انتخاب شده است.

خلاصه ردیابی تقریباً همیشه برای ارائه زمینه اولیه به Gemini، مدل زیربنایی کمک هوش مصنوعی، ارسال می‌شود. برای حاشیه‌نویسی‌های تولید شده توسط هوش مصنوعی، حذف می‌شود.

ارائه ابزار به هوش مصنوعی

دستیار هوش مصنوعی در DevTools به عنوان یک عامل عمل می‌کند. این بدان معناست که می‌تواند به طور خودکار، بر اساس درخواست اولیه توسعه‌دهنده و زمینه اولیه‌ای که با آن به اشتراک گذاشته شده است، برای داده‌های بیشتر پرس‌وجو کند. برای پرس‌وجوی داده‌های بیشتر، ما مجموعه‌ای از توابع از پیش تعریف‌شده را به دستیار هوش مصنوعی دادیم که می‌تواند آنها را فراخوانی کند. الگویی که به عنوان فراخوانی تابع یا استفاده از ابزار شناخته می‌شود.

بر اساس مراحل اشکال‌زدایی که قبلاً شرح داده شد، مجموعه‌ای از توابع جزئی را برای عامل تعریف کردیم. این توابع بر اساس زمینه اولیه، به جزئیاتی که مهم تلقی می‌شوند، می‌پردازند، مشابه نحوه برخورد یک توسعه‌دهنده انسانی با اشکال‌زدایی عملکرد. مجموعه توابع به شرح زیر است:

عملکرد توضیحات
getInsightDetails(name) اطلاعات دقیقی درباره یک بینش عملکردی خاص (برای مثال، جزئیاتی درباره دلیل علامت‌گذاری LCP) را برمی‌گرداند.
getEventByKey(key) ویژگی‌های جزئی مربوط به یک رویداد خاص و واحد را برمی‌گرداند.
getMainThreadTrackSummary(start, end) خلاصه‌ای از فعالیت نخ اصلی را برای محدوده‌های داده شده، شامل خلاصه‌های بالا به پایین، پایین به بالا و خلاصه‌های شخص ثالث، برمی‌گرداند.
getNetworkTrackSummary(start, end) خلاصه‌ای از فعالیت شبکه را برای بازه‌های زمانی مشخص برمی‌گرداند.
getDetailedCallTree(event_key) درخت فراخوانی کامل را برای یک رویداد خاص نخ اصلی در ردیابی عملکرد برمی‌گرداند.
getFunctionCode(url, line, col) کد منبع تابعی را که در یک مکان خاص در یک منبع تعریف شده است، برمی‌گرداند، که با داده‌های عملکرد زمان اجرا از ردیابی عملکرد حاشیه‌نویسی شده است.
getResourceContent(url) محتوای یک منبع متنی که توسط صفحه استفاده می‌شود (مثلاً HTML یا CSS) را برمی‌گرداند.

با محدود کردن دقیق بازیابی داده‌ها به این فراخوانی‌های تابع، تضمین می‌کنیم که فقط اطلاعات مرتبط با فرمتی خوش‌تعریف وارد پنجره‌ی زمینه می‌شوند و استفاده از توکن را بهینه می‌کنند.

مثالی از عملیات عامل

بیایید به یک مثال عملی از نحوه استفاده دستیار هوش مصنوعی از فراخوانی تابع برای بازیابی اطلاعات بیشتر نگاهی بیندازیم. پس از یک درخواست اولیه "چرا این درخواست کند است؟"، دستیار هوش مصنوعی می‌تواند توابع زیر را به صورت تدریجی فراخوانی کند:

  1. getEventByKey : جزئیات زمان‌بندی (TTFB، زمان دانلود) درخواست خاص انتخاب شده توسط کاربر را دریافت می‌کند.
  2. getMainThreadTrackSummary : بررسی می‌کند که آیا نخ اصلی هنگام شروع درخواست، مشغول (مسدود) بوده است یا خیر.
  3. getNetworkTrackSummary : تجزیه و تحلیل می‌کند که آیا منابع دیگری همزمان برای پهنای باند رقابت می‌کردند یا خیر.
  4. getInsightDetails : بررسی کنید که آیا خلاصه ردیابی از قبل به بینشی مربوط به این درخواست به عنوان یک گلوگاه اشاره کرده است یا خیر.

با ترکیب نتایج این فراخوانی‌ها، دستیار هوش مصنوعی می‌تواند تشخیص را ارائه دهد و مراحل عملی مانند پیشنهاد بهبود کد با استفاده از getFunctionCode یا بهینه‌سازی بارگذاری منابع بر اساس getResourceContent را پیشنهاد دهد.

با این حال، بازیابی داده‌های مرتبط تنها نیمی از چالش است. حتی با توابعی که داده‌های جزئی ارائه می‌دهند، داده‌هایی که توسط آن توابع برگردانده می‌شوند می‌توانند حجم زیادی داشته باشند. به عنوان مثال دیگر، getDetailedCallTree می‌تواند درختی با صدها گره را برگرداند. در JSON استاندارد، این تعداد { و } فقط برای تودرتو کردن کافی است!

بنابراین، نیاز به قالبی وجود دارد که به اندازه کافی متراکم باشد تا از نظر توکن کارآمد باشد، اما در عین حال به اندازه کافی ساختار یافته باشد تا یک LLM بتواند آن را درک و به آن ارجاع دهد.

سریال‌سازی داده‌ها

بیایید عمیق‌تر به نحوه‌ی برخورد ما با این چالش بپردازیم و با مثال درخت فراخوانی ادامه دهیم، زیرا درخت‌های فراخوانی اکثر داده‌ها را در ردیابی عملکرد تشکیل می‌دهند. برای مرجع، مثال‌های زیر یک وظیفه‌ی واحد را در یک پشته‌ی فراخوانی در JSON نشان می‌دهند:

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

همانطور که در تصویر زیر نشان داده شده است، یک ردیابی عملکرد می‌تواند شامل هزاران مورد از آنها باشد. هر کادر رنگی کوچک با استفاده از این ساختار شیء نمایش داده می‌شود.

یک پشته فراخوانی در یک ردیابی عملکرد ثبت‌شده در DevTools

این فرمت برای کار برنامه‌نویسی در DevTools خوب است، اما به دلایل زیر برای LLMها بی‌فایده است:

  1. کلیدهای اضافی: رشته‌هایی مانند "duration" ، "selfTime" و "children" برای هر گره در درخت فراخوانی تکرار می‌شوند. بنابراین، درختی با ۵۰۰ گره که به یک مدل ارسال می‌شود، برای هر یک از این کلیدها ۵۰۰ بار توکن مصرف می‌کند.
  2. فهرست‌های مفصل: فهرست کردن هر شناسه فرزند به صورت جداگانه از طریق children ، تعداد زیادی توکن مصرف می‌کند، به خصوص برای کارهایی که رویدادهای پایین‌دستی زیادی را ایجاد می‌کنند.

پیاده‌سازی یک فرمت توکن-کارآمد برای تمام داده‌های مورد استفاده با کمک هوش مصنوعی برای عملکرد، یک فرآیند گام به گام بود.

تکرار اول

وقتی کار روی کمک هوش مصنوعی برای بهبود عملکرد را شروع کردیم، سرعت ارسال را بهینه کردیم. رویکرد ما در بهینه‌سازی توکن‌ها ابتدایی بود و 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 ، ... در مثال قبلی) را حذف کردیم. از آنجایی که دستیار هوش مصنوعی از طریق فراخوانی تابع به همه گره‌ها دسترسی دارد و این اطلاعات از قبل در سر گره ( 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 از قلم افتاده است.

دلیل این امر این است که در اولین تلاش خود از الگوریتم جستجوی عمق-اول (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.

....

اگرچه این توصیف قالب هزینه توکن را متحمل می‌شود، اما این یک هزینه ثابت است که یک بار برای کل مکالمه پرداخت می‌شود. این هزینه با صرفه‌جویی‌های حاصل از بهینه‌سازی‌های قبلی قابل جبران است.

نتیجه‌گیری

بهینه‌سازی استفاده از توکن‌ها هنگام ساخت با هوش مصنوعی یک ملاحظه حیاتی است. با تغییر از JSON خام به یک قالب سفارشی تخصصی، فهرست‌بندی مجدد درخت‌ها با جستجوی Breadth-First و استفاده از فراخوانی‌های ابزار برای واکشی داده‌ها بر اساس تقاضا، ما میزان توکن‌هایی را که هوش مصنوعی در Chrome DevTools مصرف می‌کند، به میزان قابل توجهی کاهش دادیم.

این بهینه‌سازی‌ها پیش‌نیازی برای فعال کردن کمک هوش مصنوعی برای ردیابی عملکرد بودند. به دلیل پنجره زمینه محدود، در غیر این صورت نمی‌توانست حجم عظیم داده‌ها را مدیریت کند. اما فرمت بهینه‌شده، یک عامل عملکرد را قادر می‌سازد که بتواند سابقه مکالمه طولانی‌تری را حفظ کند و پاسخ‌های دقیق‌تر و آگاه از زمینه را بدون غرق شدن در نویز ارائه دهد.

امیدواریم این تکنیک‌ها شما را ترغیب کنند تا هنگام طراحی برای هوش مصنوعی، نگاهی دوباره به ساختارهای داده خود بیندازید. برای شروع کار با هوش مصنوعی در برنامه‌های وب، Learn AI را در web.dev بررسی کنید.