תכנון כלים טובים
הכלי הוא ממשק — והמשתמש שלו הוא מודל
המודל לא רואה את הקוד של הכלי. כל מה שהוא יודע עליו מגיע משלושה דברים: השם, התיאור וסכמת הפרמטרים. מהם בלבד הוא צריך להבין מתי להשתמש בכלי, איך למלא את הפרמטרים, ומה לעשות עם התוצאה.
לכן כלי הוא ממשק משתמש לכל דבר, רק שהמשתמש הוא מודל. כלי שמתוכנן גרוע לא "נשבר" — המודל פשוט בוחר בו בזמן הלא נכון, ממלא פרמטרים שגויים, או מבזבז סיבובים על ניסוי וטעייה. ההשקעה בתכנון כלים משתלמת לעיתים קרובות יותר מההשקעה בפרומפט עצמו.
שם ותיאור: לכתוב למודל, לא למפתח
שם טוב חד-משמעי גם כשיש עשרות כלים: search_orders ולא search, ובכלים משירותים שונים כדאי קידומת לפי השירות (github_create_issue, jira_create_issue) כדי שהמודל לא יתבלבל ביניהם.
התיאור צריך לענות על השאלות שעמית חדש היה שואל: מה הכלי עושה, מתי להשתמש בו — ומתי לא (ובמה להשתמש במקום), מה המשמעות של כל פרמטר ובאיזה פורמט (תאריכים, מזהים), ומה חוזר. תיאור של מילה אחת מכריח את המודל לנחש.
// לפני: המודל צריך לנחש
const badTool = {
name: "search",
description: "Search",
input_schema: {
type: "object",
properties: { q: { type: "string" }, t: { type: "string" } },
},
};
// אחרי: ברור מתי להשתמש ומה כל פרמטר אומר
const goodTool = {
name: "search_orders",
description:
"מחפש הזמנות של לקוח. השתמש כשהמשתמש שואל על הזמנה, משלוח או החזר. " +
"לא לחיפוש מוצרים בקטלוג - לשם כך יש search_products.",
input_schema: {
type: "object",
properties: {
customer_id: { type: "string", description: "מזהה לקוח בפורמט CUST-12345" },
status: { type: "string", enum: ["pending", "shipped", "delivered", "refunded"] },
since: { type: "string", description: "תאריך בפורמט YYYY-MM-DD" },
},
required: ["customer_id"],
},
};סכמה שמונעת טעויות
כל מקום שבו הסכמה משאירה חופש הוא מקום שבו המודל יכול לטעות. כשיש מספר קטן של ערכים חוקיים — enum במקום מחרוזת חופשית (כמו status בדוגמה). פרמטרים שבלעדיהם הכלי לא יכול לעבוד — required. ומספרים עם טווח — להגדיר את הטווח.
עדיף גם לתכנן את הפרמטרים כך שקשה להשתמש בהם לא נכון: לבקש נתיב מלא במקום נתיב יחסי שתלוי בתיקייה הנוכחית, מזהה ייחודי במקום שם שעלול להיות כפול, ופרמטרים נפרדים במקום מחרוזת אחת שהמודל צריך להרכיב בפורמט מסוים.
הודעות שגיאה שהמודל יכול לתקן לפיהן
כשכלי נכשל, הודעת השגיאה חוזרת למודל כתוצאת הכלי — והיא כל מה שיש לו כדי להחליט מה לעשות עכשיו. "Error 404" לא אומר לו כלום; הוא ינסה שוב את אותה קריאה, או יוותר.
הודעת שגיאה טובה אומרת מה השתבש ומה אפשר לעשות: איזה פרמטר לא תקין ומה הפורמט הנכון, או באיזה כלי אחר להשתמש כדי להשיג את המידע החסר. כך השגיאה הופכת מנקודת כישלון להוראה לצעד הבא.
async function getCustomer({ customerId }: { customerId: string }): Promise<string> {
const customer = await db.customers.find(customerId);
if (!customer) {
// לא "Error 404" - אלא מה קרה ומה לעשות עכשיו
return (
`לא נמצא לקוח עם המזהה "${customerId}". מזהי לקוח הם בפורמט CUST-12345. ` +
"אם יש לך רק שם או אימייל, השתמש ב-search_customers כדי למצוא את המזהה."
);
}
return formatCustomer(customer);
}פלט חסכוני בטוקנים
כל מה שכלי מחזיר נכנס לקונטקסט ונשאר שם (פרק 9). כלי שמחזיר רשומה מלאה עם 40 שדות, כשהמודל צריך שלושה מהם, מבזבז מקום ומקשה עליו למצוא את מה שחשוב.
כמה טכניקות: להחזיר רק את השדות הרלוונטיים; להגביל את מספר התוצאות ולומר במפורש שיש עוד ואיך לצמצם; להציע פרמטר לרמת פירוט (תקציר כברירת מחדל, פירוט מלא לפי בקשה); ולהחזיר מידע קריא — שם של משתמש ולא רק UUID — כשהמודל צריך להבין אותו ולא רק להעביר אותו הלאה.
const MAX_RESULTS = 20;
function formatSearchResults(matches: Match[]): string {
const lines = matches
.slice(0, MAX_RESULTS)
.map((m) => `${m.file}:${m.line} ${m.preview}`);
if (matches.length > MAX_RESULTS) {
// אומרים למודל שיש עוד, ואיך לצמצם את החיפוש
lines.push(`... ועוד ${matches.length - MAX_RESULTS} תוצאות. צמצם עם הפרמטר path.`);
}
return lines.join("\n");
}פחות כלים, ברמה גבוהה יותר
טעות נפוצה היא לעטוף כל endpoint של API בכלי נפרד. אם כדי לקבוע פגישה המודל צריך לקרוא ל-list_users, אחר כך ל-list_events ואז ל-create_event, כל שלב הוא סיבוב נוסף והזדמנות נוספת לטעות. כלי אחד בשם schedule_meeting, שמבצע את כל השלבים בקוד, פשוט יותר ואמין יותר.
גם מספר הכלים עצמו עולה ביוקר: כל תיאור תופס מקום בקונטקסט, וכלים עם תפקידים חופפים מבלבלים את המודל בבחירה ביניהם. עדיף סט קטן של כלים מובחנים, שכל אחד מהם מתאים למשימה שהסוכןLLM שמופעל בלולאה, עם מטרה וסט כלים, ובכל סיבוב מחליט אם להפעיל כלי נוסף או לסיים ולהחזיר תשובה. מתוך AI Agents, Agents והלולאה האגנטית. באמת מבצע.
ואיך יודעים אם כלי מתוכנן טוב? מריצים את הסוכן על משימות אמיתיות וקוראים את ה-transcripts: איפה הוא בחר בכלי הלא נכון, איפה מילא פרמטר שגוי, איפה נתקע אחרי שגיאה. בפרק 12 נראה איך הופכים את זה לתהליך מסודר.
שאלות נפוצות
עדיף שכלי יחזיר JSON או טקסט חופשי?
שתי הדרכים עובדות, כי המודל קורא את שתיהן. JSON מתאים כשהמודל צריך להעביר ערכים הלאה לכלי אחר (מזהים, תאריכים), וטקסט קריא מתאים כשהוא צריך להבין ולסכם. חשוב יותר מהפורמט הוא התוכן: רק השדות הרלוונטיים, שמות ברורים ובלי רעש.
איך בודקים תיאור של כלי לפני שמשחררים אותו?
מריצים את הסוכן על כמה משימות שבהן הכלי אמור לשמש, וגם על משימות שבהן הוא לא אמור, ובודקים ב-transcripts אם הוא נבחר בזמן הנכון ועם פרמטרים תקינים. טריק פשוט נוסף: לבקש מהמודל לקרוא את התיאור ולהסביר מתי היה משתמש בכלי. אי-הבנה שעולה שם תעלה גם בשימוש אמיתי.
מה עושים עם פעולה שלוקחת הרבה זמן, כמו ייצוא גדול?
לא משאירים את הלולאה תקועה בהמתנה. דפוס נפוץ הוא לפצל לשני כלים: אחד שמתחיל את העבודה ומחזיר מזהה, ואחד שבודק סטטוס ומחזיר את התוצאה כשהיא מוכנה. כך הסוכן יכול להמשיך בעבודה אחרת בינתיים, ו-timeout של קריאת כלי בודדת לא מפיל את כל המשימה.