Tools

כלי (Tool) כיחידת היסוד — תזכורת קצרה

בפרק על יצירת ה-agent הראשון בנינו כלי בודד, getWeather, עם tool ו-schema של zod. עכשיו נעמיק: מה הופך תיאור כלי לטוב, איך כלי מקבל מידע שהמודל עצמו לא שולח, ואיך מרכיבים כמה כלים יחד ב-agent אחד.

מה הופך תיאור כלי לטוב

המודל לא רואה את קוד הכלי — הוא רואה רק את name, description וה-schema של הפרמטרים, ומחליט על סמך זה בלבד אם ומתי להפעיל את הכלי. תיאור עמום ("מחזיר מידע") משאיר למודל לנחש; תיאור טוב אומר במפורש מה הכלי עושה, מתי להשתמש בו, ומה כל פרמטר אמור להכיל.

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

TypeScript
// תיאור גרוע — עמום מדי, המודל לא יודע מתי להשתמש בו
tool(getData, {
  name: "get_data",
  description: "מחזיר מידע",
  schema: z.object({ id: z.string() }),
});

// תיאור טוב — ברור מה הכלי עושה, מתי להשתמש בו, ומה הפרמטר אומר
tool(getWeather, {
  name: "get_weather",
  description:
    "מחזיר את מזג האוויר הנוכחי (טמפרטורה ותנאים) בעיר נתונה. " +
    "יש להשתמש בו רק כששואלים במפורש על מזג אוויר.",
  schema: z.object({
    city: z.string().describe("שם העיר, למשל 'תל אביב'"),
  }),
});

כלים שצריכים יותר מהפרמטרים המוצהרים: runtime context

לא כל מה שכלי צריך מגיע מהמודל — לעיתים צריך גם מידע שלא בטוח שרוצים שהמודל יבחר בעצמו, כמו מזהה המשתמש המחובר. LangChain מעביר לפונקציית הכלי ארגומנט שני, config, שמכיל את מה שהועבר ב-configurable בזמן agent.invoke (בדיוק כמו thread_id שראינו בפרק הקודם).

כך אפשר להעביר userId פעם אחת ברמת ה-invoke, ולסמוך על כך שכל כלי שזקוק לו יכול לקרוא אותו מתוך config — בלי לחשוף אותו כפרמטר שהמודל צריך למלא בעצמו, ובלי סיכון שהמודל "יבדה" מזהה משתמש שגוי.

TypeScript
const getUserOrders = tool(
  async ({ status }: { status?: string }, config) => {
    const userId = config?.configurable?.userId;
    return await fetchOrders(userId, status);
  },
  {
    name: "get_user_orders",
    description: "מחזיר את ההזמנות של המשתמש המחובר, אפשר לסנן לפי סטטוס",
    schema: z.object({ status: z.string().optional() }),
  }
);

דוגמה מלאה: agent עם שני כלים

agent יכול לקבל כמה כלים במקביל ברשימת tools — בכל סיבוב בלולאה האגנטית המודל בוחר איזה כלי (אם בכלל) להפעיל, בהתאם לשאלה. בדוגמה הבאה יש לו גם getWeather וגם getUserOrders, ובבקשה אחת הוא מפעיל את שניהם.

TypeScript
const agent = createAgent({
  model: "anthropic:claude-sonnet-5",
  tools: [getWeather, getUserOrders],
  systemPrompt: "אתה עוזר אישי שיכול לבדוק מזג אוויר ולשלוף הזמנות של המשתמש.",
});

const result = await agent.invoke(
  {
    messages: [
      { role: "user", content: "מה מזג האוויר בתל אביב, ומה ההזמנות הפתוחות שלי?" },
    ],
  },
  { configurable: { thread_id: "chat-2", userId: "user-42" } }
);

שאלות נפוצות

כמה כלים אפשר לתת ל-agent אחד?

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

האם כלי יכול לקרוא ל-API חיצוני או לבסיס נתונים?

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

איך בודקים כלי בלי להריץ מודל?

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