הקדמה
מאז הגעת הLLM יכולות של שפה ותקשורת של בני אדם נכנסו לאפשרויות הפיתוח . אבל כיצד ניתן לתת לLLM לתקשר עם תשתית תוכנה? לדוגמא להחליט האם לבצע קריאה לפונקציה או לקרוא לתוכנה חיצונית שתבצע פעולה? מודלי השפה מדברים בשפה טבעית של בני אדם. לכן לאחרונה אומנו LLM-ים להחזיר בפורמט כך שיכולו להחזיר ערכים בדומה לJSON, כדי לתת להחליט מתי לבצע קוד אחר או דברים נוספים הדורשים מידע בפורמט. היכולת החדשה של OpenAI הנקראת FunctionCalling שימושית במיוחד למפתחים לעבוד עם LLM. קורס זה נבנה לעדכן על גבי הקורסים האחרים. לאחרונה פותחה LangChain Expression Language (LCEL) המאפשרת הרכבה של chain-ים בצורה אינטואיטיבית ונוחה יותר. בנוסף נכנסה לLangchain התמיכה בOpenAI function calling שמאפשר לבצע משימות כמו Tagging וExtraction בצורה פשוטה יותר ואמינה יותר.
קריאה לפונקציות - OpenAI Function Calling
בחלק זה נסקור את התכולה החדשה שOpenAI הוסיפו למודל שלהם - Function Calling.
קריאה לפונקציות
החידוש שנעשה הוא שOpenAI ביצעה fine-tunning לחלק מהמודלים שלה כך ש:
- יקבלו ארגומנטים נוספים כך שמשתמשים יוכלו להעביר תיאור של פונקציות.
- אם זה רלוונטי, יוחזר השם של הפונקצייה לשימוש יחד עם אובייקט JSON עם פרמטרי הקלט המתאימים.
דוגמת קוד
נשתמש בדוגמת קוד הרשמית שOpenAI מציגים. מזג האוויר זו דוגמה טובה לדבר שמודלים לא יכולים לדעת ולכן נייצר פונקציה שכאילו מחזיר את מזג האוויר בקריאה אליה.
import json
# Example dummy function hard coded to return the same weather
# In production, this could be your backend API or an external API
def get_current_weather(location, unit="fahrenheit"):
"""Get the current weather in a given location"""
weather_info = {
"location": location,
"temperature": "72",
"unit": unit,
"forecast": ["sunny", "windy"],
}
return json.dumps(weather_info)עכשיו נותר להעביר את הפונקצייה למודל, נעשה זאת כך:
# define a function
functions = [
{
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
}
]נגדיר מערך של פונקציות, אותם נשלח למודל בנוסף להודעות שלנו. כל פונקציה תכיל את השם שלה (name), תיאור מילולי של מה שהיא מבצעת (description) ואת הפרמטרים (parameters) ופירוט והגדרות עליהם.
כעת נקרא למודל:
import openai
messages = [
{
"role": "user",
"content": "What's the weather like in Boston?"
}
]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
functions=functions
)
print(response)ניתן לראות שהמודל החזיר null בתוכן, אבל בfunction_call חזר בname שם הפונקצייה שנדרש לקרוא לה, ובarguments חזרו הפרמטרים שיש לשלוח לה.
נשים לב שסיבת הסיום (finish_reason) נקבעה כfunction_call.
{
"id": "chatcmpl-9gmce2HUzrNM1AxW32Q0qeEilbH7O",
"object": "chat.completion",
"created": 1719983708,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"function_call": {
"name": "get_current_weather",
"arguments": "{\n \"location\": \"Boston, MA\"\n}"
}
},
"logprobs": null,
"finish_reason": "function_call"
}
],
"usage": {
"prompt_tokens": 82,
"completion_tokens": 18,
"total_tokens": 100
},
"system_fingerprint": null
}כעת נקרא לפונקציה:
args = json.loads(response_message["function_call"]["arguments"])
get_current_weather(args)לפני שנמשיך לשלוח למודל את הפלט של הפונקציה, נבדוק כיצד נראה הפלט כאשר לא נדרש להשתמש בפונקציה:
messages = [
{
"role": "user",
"content": "hi!",
}
]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
functions=functions,
function_call="auto",
)
print(response)נקבל שסיבת הסיום היא עצירה, ופלט רגיל של מודל:
{
"id": "chatcmpl-9gmgYR1SsqnFldWl7PIg5UYPAGrwX",
"object": "chat.completion",
"created": 1719983950,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 76,
"completion_tokens": 10,
"total_tokens": 86
},
"system_fingerprint": null
}הפרמטר function_call
ניתן לשלוט בהאם המודל ישתמש בFunction Calling באמצעות הפרמטר function_call שנשלח למודל. במידה והפרמטר none נכריח את המודל לא לבצע קריאה לפונקציה וייתכנו התנהגויות מוזרות אם המודל מודע לפונקציות דוגמת החזרת הקריאה לפונקצייה בתוכן ההודעה. במידה והפרמטר מכיל פונקציה בדומה לפורמט הבא, נכריח את המודל להחזיר קריאה לפונקציה, גם אם זו תשובה לא נכונה:
messages = [
{
"role": "user",
"content": "hi!",
}
]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
functions=functions,
function_call={"name": "get_current_weather"},
)
print(response)ואכן במקרה זה הוא ימציא. המקרה האחרון הוא המקרה שהשתמשנו בו בהתחלה בו המודל בוחר אם להשתמש בקריאה לפונקציות.
השפעת הקריאה לפונקציות על מספר הטוקנים
יש לשים שהגדרת הפונקציות ותיאור הפונקציות מתקבלות כקלט לכל דבר למודל הנספר בטוקנים.
הזנת תוצאת הפונקציות
נעדכן את המשך השיחה עם הפלט שחזר מהקריאה לפונקציה:
messages = [
{
"role": "user",
"content": "What's the weather like in Boston!",
}
]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
functions=functions,
function_call={"name": "get_current_weather"},
)
print(response)
messages.append(response["choices"][0]["message"])
args = json.loads(response["choices"][0]["message"]['function_call']['arguments'])
observation = get_current_weather(args)
messages.append(
{
"role": "function",
"name": "get_current_weather",
"content": observation,
}
)נשים לב שעדכנו את השיחה עם הקריאה לפונקציה שחזרה מהמודל כמו שהיא, מה שאומר שהפורמט כעת התרחב ואנחנו כבר לא מדברים רק על צמדים של תפקיד ותוכן הודעה כבעבר. בקלט כאן אנחנו מעדכנים בשיחה את הבקשה של הassistent לקריאה לפונקציה עם ערך function_calling ואז מבצעים קריאה לפונקצייה ומעדכנים בהודעות הודעה חדשה עם תפקיד חדש function עם שם הפונקציה ומה שחזר ממנה בcontent.