תוכן עניינים
אמ:לק - שימוש ב-Function Calling מאפשר לנו לשלוט על התשובות של מודל השפה, לרבות הוספת יכולות כמו מתן כלים למודל השפה באמצעות הוספת קריאות API.
שלב 1: תסכול
יודע מתכנת פיקח. וגם הנחיל זאת לכולם. שהומר סיפמסון צודק כי הטכניקה שנקראת Function Calling לא קלה לעיכול.
ולא מספיק זה, אלא עבודה עם מודלים גדולים של שפה בעולם האמיתי היא פשוט סיוט. זה לא באמת משנה עם איזה מודל אנחנו עובדים, בשלב כזה או אחר כאשר נרצה לפתח אפליקציה משלנו ונרצה לשלוח פניות למודל שפה ולקבל תשובות בפורמט מסוים - זה יתיש אותנו עד אובדן שפיות.
כאשר אנו פונים ל-ChatGPT דרך קריאת API ושואלים אותו שאלה כללית כגון: ״מה האוכל הטעים ביותר בשוק מחנה יהודה״? סביר להניח שנקבל תגובה ארוכה מכפי שרצינו.
כך הפנייה נראית בקוד:
user_prompt = "What is the best food in mahane yehuda market?"
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": user_prompt}],
)
print(completion.choices[0].message.content.strip())
וזו התגובה שהתקבלה:
There are many amazing food options at Mahane Yehuda market in Jerusalem, so it's difficult to pinpoint just one as the best. Some popular choices include the freshly baked bread from Marzipan Bakery, the falafel and shawarma from Azura, the Middle Eastern pastries from Marzipan and Jachnun Bar, and the fresh produce from the various stalls. Ultimately, the best food at Mahane Yehuda market will depend on your personal taste preferences.
זה נחמד אם אנחנו סתם מתכתבים. אבל מה אם היינו רוצים לפתח אתר שמציג היכן האוכל הכי טעים בארץ ומאפשר למשתמש לבחור מקום בארץ ולקבל את האוכל הכי טעים שם? במקרה כזה אנחנו לא רוצים לקבל את כל הסיפור הזה שקיבלנו, אלא נרצה לקבל ערך מדויק: ״שם המקום״ ו-״המאכל המומלץ״. משהו שייראה כמו: ״מחנה יהודה״, ״מרציפן \ עזורא״ וכו' (ואגב בתור ירושלמי מלידה באמת טעים מאוד בעיר הקודש שלנו).
שלב 2: מציאת פתרון באמצעות שליטה על התשובה
וכעת נשאלת השאלה: איך אנחנו יכולים לגרום למודל השפה להשיב לנו בצורה שאנו מבקשים? אם ננסה להגדיר זאת בפרומפט עצמו, מהר מאוד נגלה שזה לא עובד והוא משיב לנו רוב הזמן כמו שהוא רוצה ולא כפי שביקשנו. זה מאבק שגוזל שעות ימים ולילות. מניסיון.
בדיוק לשם כך יצרו עבורנו OpenAI אי שם ב-2023 את היכולת להשתמש בקריאות פונקציה, Function Calling, שבעצם עוזרות לנו לקבל ממודל השפה את הערכים שאנו רוצים בפורמט המדויק שאנו רוצים. כך למשל אם נשלח שוב את הפנייה אבל עם שימוש ב-Function Calling, הערך שנקבל חזרה יהיה אחר לגמרי, בואו נמחיש את הפנייה החדשה:
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": user_prompt}],
functions=function_test,
function_call="auto"
)
# Extracting response
answer = completion.choices[0].message.function_call.arguments
json.loads(answer)
print(answer)
שימו לב לשוני בין הבקשה הזו לבקשה הקודמת. בבקשה הזו הוספתי את השורות:
functions=function_test,
function_call="auto"
מה שגרם לתשובה שלנו להיות שונה הפעם:
{"place":"Mahane Yehuda Market","food":"Kuba Hamusta"}
הבנתם מה קרה פה? במקום לקבל את כל הסיפור, קיבלנו ממש שם של מקום ואת שם המאכל. למה זה שימושי? כי כעת אנו יכולים לחלץ מהתשובה הזו את שם המקום ואת שם המאכל ולהשתמש בה בפיתוח שלנו, באתר שלנו, באפליקציה שלנו, במה שרק נרצה. כעת הצלחנו לחלץ ממודל השפה תשובות בפורמט שאנו רוצים. אנו שולטים בו! וזה דרמטי!
ואיך כל הקסם הזה קרה בכלל? מה זה 2 השורות שהוספתי ששינו הכל?
השורה הראשונה בעצם אומרת ל-ChatGPT: שים לב שכשאני פונה אליך אני מעביר לך גם פונקציה שאני רוצה שתשקול להשתמש בה בהתאם לפרומפט שלי. אני רוצה שתדע שיש פונקציה, שבמקרה זה נקראת function_test.
השורה השנייה באה ואומרת: תשתמש בלוגיקה של function_test כפי איך שנראה לך. אם לא צריך - אל תשתמש, אם צריך - תשתמש.
ומה זו הפונקציה הזו? ב-Function Calling מתרחש מעין קסם. אנו לא צריכים בהכרח לכתוב ממש את הלוגיקה. אנו רק מתארים מה הפונקציה אמורה לבצע, מבלי לכתוב את השלבים עצמם. מה שחשוב הוא לציין את סוג הפלט שאנו מצפים לקבל. כמו במקרה הזה שביקשתי משתנים מסוג string.
לא רק זאת, אלא גם הכרחתי את מודל השפה להחזיר ערך במקום ובסוג המאכל באמצעות שימוש בפקודה require. להלן התחביר של הפונקציה:
function_test = [
{
"name": "recommend_on_best_food",
"description": "You get user's input and recommend on the most delicious food ever.",
"parameters": {
"type": "object",
"properties": {
"place": {
"type": "string",
"description": "The place name, e.g. Jerusalem.",
},
"food": {
"type": "string",
"description": "The food's name, e.g. Kuba Hamusta",
},
},
"required": ["place", "food"],
},
}
]
ולהשלמת התמונה, כך נראה הקוד המלא שלי שמשתמש בקריאת פונקציות:
function_test = [
{
"name": "recommend_on_best_food",
"description": "You get user's input and recommend on the most delicious food ever.",
"parameters": {
"type": "object",
"properties": {
"place": {
"type": "string",
"description": "The place name, e.g. Jerusalem.",
},
"food": {
"type": "string",
"description": "The food's name, e.g. Kuba Hamusta",
},
},
"required": ["place", "food"],
},
}
]
user_prompt = "What is the best food in mahane yehuda market?"
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": user_prompt}],
functions=function_test,
function_call="auto"
)
# Extracting response
answer = completion.choices[0].message.function_call.arguments
json.loads(answer)
print(answer)
שלב 3: העמקה
יש אפשרויות מגוונות להשתמש ב-function calling, כמו למשל להגדיר פונקציות מרובות, להוסיף ממש לוגיקות שפונות ב-API לשירותים שונים, להגדיר פניות מרובות במקביל למספר פונקציות ועוד. מידע נוסף נמצא באתר של OpenAI, בדוקומנטציה למפתחים:
https://platform.openai.com/docs/guides/function-calling
שלב 4: סיכום
אז נסכם: שימוש ב-Function Calling מאפשר לנו לשלוט על הפורמט של התשובה של מודל השפה, שזו יכולת עוצמתית מאוד בעולם האמיתי שבו אנו עובדים בעיקר עם סוגים של מידע מסוג JSON, אובייקטים וכדומה. כל מה שצריך הוא להגדיר את הסכמה כראוי, ניתן להרחיב את היכולות עם קריאות API נוספות.
טיפ קטן: ב-Langchain יש אפשרות לשלוט על הפלט שנקבל באמצעות טכניקה שנקראת Output Parser, ממליץ לקרוא עליה.
ולסיום , אני מזכיר לכם שאדבר על API בהרצאה הקרובה שלי ב-17.6 ומציע לכם לשריין כרטיסים בהקדם כי מספר המקומות אוזל.
מקווה שקיבלתם ערך!
יובל
בונוס
ועוד דבר קטן, בגלל שאני יודע שזה נושא לא קל לעיכול, מצרף לכם שני סרטונים שלי אישית מאוד מאוד עזרו. תהנו!