7.2 שימוש בכלים הרצאה
בהרצאה הקודמת ראינו איך לשלוח בקשה למודל ולקבל טקסט בחזרה. אבל מודל שיודע רק לדבר מוגבל: הוא לא יודע מה השעה עכשיו, מה מזג האוויר בתל אביב, כמה שורות יש בקובץ מסוים, או מה יש בבסיס הנתונים שלנו. כל אלה הם נתונים שהמודל לא הודרך עליהם ולא יכול לגשת אליהם. הפתרון נקרא שימוש בtools - נותנים למודל רשימת פונקציות שהוא רשאי "לבקש" מאיתנו להריץ. המודל לא מריץ שום דבר בעצמו; הוא רק מבקש, אנחנו מריצים בקוד שלנו, ומחזירים לו את התוצאה. זה הרעיון שהופך צ'אטבוט פשוט לagent שיכול לפעול בעולם.
בהרצאה הזו נבין איך מגדירים tool, נכיר את לולאת השימוש בtools - הריקוד שבו המודל מבקש קריאה, אנחנו מבצעים, ומחזירים תוצאה - ונבנה דוגמה קטנה ורצה מקצה לקצה.
מה זה שימוש בtools - tool use¶
שימוש בtools (נקרא גם function calling) עובד כך: בבקשה אנחנו מוסיפים שדה tools עם רשימת הtools שהמודל רשאי להשתמש בהם. כל tool הוא תיאור: שם, מה הוא עושה, ואילו פרמטרים הוא מקבל. המודל לא מקבל את הקוד של הtool - רק את התיאור. הוא מחליט לבד, לפי הבקשה של המשתמש, אם ומתי לקרוא לtool.
התהליך המדויק:
1. We send a request with a list of tools + the user's message
2. The model decides it needs a tool, and returns stop_reason = "tool_use"
with a tool_use block containing the tool name and the input
3. We run the actual function in our code
4. We send a new request, attaching a tool_result with the result
5. The model reads the result and formulates a final answer for the user
הנקודה הקריטית: המודל אף פעם לא מריץ קוד. הוא רק מבקש קריאה. הביצוע בפועל - זה באחריותנו, בקוד שלנו. זה מה שנותן לנו שליטה מלאה על מה מותר ומה אסור.
הגדרת tool - defining a tool¶
כל tool מוגדר עם שלושה שדות:
- name - שם ברור ותיאורי, למשל
get_weather. - description - הסבר מפורט מתי ולמה להשתמש בtool. המודל נשען על זה מאוד.
- input_schema - סכמת JSON Schema שמתארת את הפרמטרים.
הנה tool מזג אוויר לדוגמה:
weather_tool = {
"name": "get_weather",
"description": "Returns the current weather in a given city. Use this tool when the user asks about the weather.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, for example 'Tel Aviv'",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit",
},
},
"required": ["city"],
},
}
עצות חשובות: התיאור צריך להיות מנחה - לא רק "מה הtool עושה" אלא "מתי לקרוא לו". שימוש ב-enum עבור ערכים מוגבלים מונע טעויות. ומסמנים ב-required רק את הפרמטרים שבאמת חובה.
הבקשה הראשונה עם tool - the first request¶
עכשיו נשלח בקשה שמצרפת את הtool:
import anthropic
client = anthropic.Anthropic()
weather_tool = { ... } # as we defined above
messages = [
{"role": "user", "content": "מה מזג האוויר בתל אביב?"}
]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
print(response.stop_reason) # probably: "tool_use"
אם המודל החליט להשתמש בtool, stop_reason יהיה tool_use, וה-content יכיל בלוק מסוג tool_use:
for block in response.content:
if block.type == "tool_use":
print(block.name) # "get_weather"
print(block.input) # {"city": "Tel Aviv", "unit": "celsius"}
print(block.id) # unique id - we'll need it for the response
ה-block.input הוא כבר מילון (dict) מפוענח - תמיד ניגשים אליו כאובייקט, אף פעם לא מנתחים מחרוזת JSON ידנית.
החזרת התוצאה - the tool result¶
עכשיו אנחנו מריצים את הפונקציה האמיתית ומחזירים את התוצאה. יש שלושה שלבים:
- מוסיפים את תשובת המודל (עם בלוק ה-
tool_use) להיסטוריה. - מריצים את הtool בקוד שלנו.
- מוסיפים הודעת
userחדשה עם בלוקtool_resultשמכיל את התוצאה - וחייבים לכלול אתtool_use_idשתואם לבלוק ה-tool_use.
def run_get_weather(city, unit="celsius"):
# here we'd actually call a weather API. We'll fake a result:
return f"בעיר {city} עכשיו 26 מעלות {unit} ובהיר."
# find the tool_use block
tool_block = next(b for b in response.content if b.type == "tool_use")
# run the actual tool
result_text = run_get_weather(**tool_block.input)
# add the model's turn to the history
messages.append({"role": "assistant", "content": response.content})
# add the tool's result as a user message
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_block.id, # must match!
"content": result_text,
}],
})
# send again - now the model will formulate a final answer
final = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
for block in final.content:
if block.type == "text":
print(block.text) # "The weather in Tel Aviv is 26 degrees and clear."
אם הtool נכשל, מחזירים tool_result עם "is_error": True וטקסט שגיאה מסביר - המודל יבין ויתאושש.
לולאת הagent - the agentic loop¶
בדוגמה למעלה טיפלנו בקריאה אחת. אבל המודל עשוי לרצות לקרוא לכמה tools ברצף: קודם לבדוק מזג אוויר, ואז לפי התוצאה לחפש מסעדות. לכן בונים לולאה שממשיכה עד שהמודל מסיים (כלומר stop_reason הוא end_turn ולא tool_use):
messages = [{"role": "user", "content": "מה מזג האוויר בתל אביב, וכדאי לצאת לטיול?"}]
while True:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
# if the model is done - exit the loop
if response.stop_reason != "tool_use":
break
# add the model's turn
messages.append({"role": "assistant", "content": response.content})
# run all the tools the model requested (there can be several!)
tool_results = []
for block in response.content:
if block.type == "tool_use":
output = run_get_weather(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
# return all the results in a single user message
messages.append({"role": "user", "content": tool_results})
# the final answer
final_text = next(b.text for b in response.content if b.type == "text")
print(final_text)
שימו לב שכל תוצאות הtools מאותו תור נשלחות בהודעת user אחת. פיצול שלהן לכמה הודעות "מלמד" את המודל להפסיק לקרוא לtools במקביל - טעות עדינה שכדאי להימנע ממנה.
הרצת הtools אוטומטית - the tool runner¶
לכתוב את הלולאה ידנית זה חינוכי, אבל ה-SDK מציע דרך נוחה יותר: מריץ הtools (tool runner). בגרסת ה-beta של ה-SDK מגדירים tool כפונקציה עם דקורטור, וה-SDK מנהל את כל הלולאה - קורא ל-API, מריץ את הפונקציות, מחזיר תוצאות, וחוזר עד שהמודל מסיים:
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def get_weather(city: str, unit: str = "celsius") -> str:
"""Returns the current weather in a city.
Args:
city: The city name, e.g. Tel Aviv.
unit: The temperature unit, celsius or fahrenheit.
"""
return f"בעיר {city} עכשיו 26 מעלות {unit} ובהיר."
runner = client.beta.messages.tool_runner(
model="claude-opus-4-8",
max_tokens=1024,
tools=[get_weather],
messages=[{"role": "user", "content": "מה מזג האוויר בתל אביב?"}],
)
for message in runner:
print(message) # the loop is managed automatically until completion
היתרון: אין לולאה ידנית, סכמת הtool נגזרת אוטומטית מחתימת הפונקציה ומה-docstring, והכל טיפוסי (typed). זו הדרך המומלצת לagent עם tools מותאמים אישית. הלולאה הידנית נשארת שימושית כשצריכים שליטה מלאה שהמריץ לא חושף.
אבטחה ואישור - security¶
הtools נותנים למודל כוח - וכוח דורש זהירות. אם לtool יש תופעות לוואי (שליחת מייל, מחיקת נתונים, חיוב כספי), חייבים לשקול "שער אישור" (approval gate) לפני הביצוע. שתי גישות:
- אימות קלט: לפני שמריצים, בודקים שהקלט חוקי ובטוח.
- אישור אנושי: לפעולות הרסניות, עוצרים ומבקשים אישור מהמשתמש לפני שמריצים בפועל.
הכלל הזהב: המודל מבקש, אנחנו מחליטים אם להריץ. אף פעם לא מריצים באופן עיוור קלט שהגיע מהמודל, במיוחד לא פקודות מעטפת או שאילתות SQL שנבנו ממחרוזת.
שליטה בבחירת tool - tool choice¶
לפעמים אנחנו רוצים לשלוט מתי ואם המודל משתמש בtools. השדה tool_choice נותן בדיוק את זה:
| ערך | התנהגות |
|---|---|
{"type": "auto"} |
המודל מחליט לבד אם להשתמש בtool (ברירת מחדל) |
{"type": "any"} |
המודל חייב להשתמש בtool כלשהו |
{"type": "tool", "name": "..."} |
המודל חייב להשתמש בtool הספציפי |
{"type": "none"} |
המודל לא יכול להשתמש בtools |
לדוגמה, אם אנחנו בונים מערכת שתמיד צריכה לחלץ נתונים דרך tool מסוים, נכפה אותו:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
tool_choice={"type": "tool", "name": "get_weather"}, # must call this tool
messages=[{"role": "user", "content": "מה שלומך?"}],
)
כמה tools במקביל - parallel tool use¶
כברירת מחדל, המודל יכול לבקש כמה tools בבת אחת בתוך תשובה אחת. למשל, אם משתמש שואל על מזג האוויר בשלוש ערים, המודל עשוי להחזיר שלושה בלוקי tool_use בבת אחת. הלולאה שכתבנו כבר מטפלת בזה - היא עוברת על כל הבלוקים ואוספת את כל התוצאות להודעת user אחת.
זה חשוב לביצועים: אפשר להריץ את הtools במקביל (concurrently) במקום בזה אחר זה. אבל הכלל שראינו נשאר קדוש - כל התוצאות חוזרות בהודעה אחת. אם רוצים לאסור על מקביליות (למשל כשהtools משנים מצב משותף), אפשר להוסיף "disable_parallel_tool_use": True בתוך tool_choice.
הtools בצד השרת - server-side tools¶
עד כה דיברנו על tools שאנחנו מריצים. אבל ל-Anthropic יש גם tools שרצים בצד השרת שלה - אנחנו רק מכריזים עליהם, והמודל מריץ אותם לבד, בלי לולאת ביצוע אצלנו. הבולטים:
- חיפוש באינטרנט - המודל מחפש מידע עדכני מעבר לתאריך האימון.
- הרצת קוד - המודל מריץ קוד פייתון בארגז חול מבודד בצד השרת.
מכריזים עליהם בדיוק כמו tool רגיל, אבל אין פונקציה לממש:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[{"type": "web_search_20260209", "name": "web_search"}],
messages=[{"role": "user", "content": "מה החדשות האחרונות על Claude?"}],
)
התוצאות חוזרות כבלוקי תוכן באותה תשובה. זה נוח כשצריכים יכולת סטנדרטית (חיפוש, הרצת קוד) בלי לבנות אותה בעצמנו.
הגבלת הלולאה - bounding the loop¶
הלולאה שכתבנו ממשיכה עד שהמודל מסיים. אבל מה אם המודל "נתקע" ומבקש tools שוב ושוב בלי להגיע לתשובה? כדי להימנע מלולאה אינסופית, מגבילים את מספר האיטרציות:
max_turns = 10
turns = 0
while turns < max_turns:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
if response.stop_reason != "tool_use":
break
turns += 1
# ... run the tools and add the results ...
if turns >= max_turns:
print("The agent exceeded the maximum number of iterations")
זו הגנה בסיסית וחשובה בכל מערכת ייצור. מגבלה של 10 עד 20 איטרציות מתאימה לרוב המשימות; אם מגיעים אליה, כנראה יש בעיה בהגדרת הtools או בprompt. בtools בצד השרת, אגב, קיים מצב מיוחד pause_turn - המודל עצר באמצע ריצה ארוכה ואפשר לחדש אותה על ידי שליחת הבקשה שוב.
סיכום¶
בהרצאה הזו למדנו לתת למודל לא רק לדבר, אלא לפעול, דרך שימוש בtools:
- שימוש בtools מאפשר למודל לבקש שנריץ פונקציות שאנחנו מגדירים. המודל לא מריץ קוד - הוא רק מבקש, ואנחנו מבצעים.
- הגדרת tool דורשת
name,description(מנחה - מתי לקרוא לtool) ו-input_schemaבפורמט JSON Schema. - לולאת השימוש בtools: המודל מחזיר
stop_reason = "tool_use"עם בלוקtool_use; אנחנו מריצים; מחזיריםtool_resultעם ה-tool_use_idהתואם; המודל מנסח תשובה סופית. - לולאת הagent ממשיכה עד
stop_reason = "end_turn". כל תוצאות הtools מתור אחד נשלחות בהודעתuserאחת. - מריץ הtools (
tool_runner) של ה-SDK מנהל את הלולאה אוטומטית ומייצר סכמות מחתימות פונקציות - הדרך המומלצת. - ה-
inputשל הtool הוא אובייקט מפוענח - ניגשים אליו כאובייקט, לא מנתחים JSON ידנית. - אבטחה: מאמתים קלט, מגנים על פעולות הרסניות בשער אישור, ואף פעם לא מריצים עיוור קלט מהמודל.
עכשיו שאנחנו יודעים לבנות לולאת tools בעצמנו, נעבור להרצאה הבאה - שם נכיר את ה-Agent SDK, ספרייה שנותנת לנו agent שלם עם tools מובנים, בלי לכתוב את הלולאה בכלל.