לדלג לתוכן

Function calling

הפיצ'ר של Function calling נועד לשתי מטרות:

  • אחזור מידע ל-LLM כדי שיוכל לענות על ה-prompt (מה שנקרא RAG)
  • הפעלת פעולות

למה צריך לאחזר מידע❓
מודל LLM מאומן על מאגר נתונים שבאמצעותו הוא בונה את הידע והמוח שלו. כל אימון מייצר גירסה של המודל כשמכאן והלאה המודל קופא על שמריו ולא לומד דברים חדשים.
לכן, כאשר המודל נשאל שאלה שדורשת ידע שלא קיים אצלו הוא לא יכול לענות עליה.
ולא, המודל לא מסוגל ללכת לחפש בעצמו את התשובה באינטרנט. מודל הוא יישום שיודע לעשות דבר אחד - completion, כלומר להשלים את ה-token הבא בהקשר הנוכחי.

מה קורה כשה-LLM לא יודע מה לענות❓
במקרה הטוב הוא יודה בכנות שאין לו את המידע הדרוש, ובמקרה הפחות טוב נקבל את תופעת ההזיות הידועה. המודל פשוט ימציא תשובה וידבר שטויות במיץ 🤡
מתי זה קורה? בעיקר כשהמידע הנדרש הוא דינמי ומשתנה מדי פעם.

דוגמה

תחזית מזג האויר להיום בירושלים

אין סיכוי בעולם שהמודל יידע מה התחזית להיום.
גם אם הוא יצא היום לשוק, תהליך האימון לוקח הרבה זמן, כך שבכל מקרה המידע שיש לו הוא כבר היסטוריה.

אומנם למעשה, כששואלים את ChatGPT מה התחזית להיום הוא כן יודע לענות על כך, הנה דוגמא:

img.png

אבל אם נשאל אותו באמצעות ה-API, נקבל תשובה כזו:

img_1.png

מה ההבדל❓

זו המחשה מעולה מה זה Agent 🤖

GPT הוא מודל LLM שיודע לג'נרט completion לפי קונטקסט (הקשר). כאשר אנחנו שולחים קריאה ל-API של OpenAI אנחנו פונים ישירות ל-LLM ולכן הוא לא יודע לספק לנו תשובה. (ויפה מצידו שלא ניסה להמציא משהו על סמך מידע היסטורי...).

לעומת זאת, ChatGPT הוא Agent שהליבה שלו אומנם היא ה-LLM, אבל הוא מוסיף על זה כלים נוספים כמו חיפוש באינטרנט ועוד.

במקרה הזה מתברר ש-OpenAI תכנתו את ChatGPT כך שעבור שאלות על תחזית מזג האויר יבצע חיפוש באינטרנט כדי להביא את המידע העדכני הרלוונטי.

הנה מה שנקבל בלחיצה על כפתור ה-Sources, נוכל לצפות במקורות המידע שמהם ChatGPT שאב את המידע שעליו מתבססת התשובה שקיבלנו.

img.png

עכשיו נשתמש בפיצ'ר של Function calling כדי לעשות בדיוק את מה ש-OpenAI עשו ב-ChatGPT.

נפתח Agent שצורך את GPT באמצעות ה-API ויודע לספק תחזית מזג אויר עדכנית.

Function calling

Function calling זהו פיצ'ר שמאפשר לנו לתת ל-LLM תיאור של פונקציה (או פונקציות) שיש לנו בארסנל ולבקש ממנו שבמידת הצורך יתן לנו הוראה להפעיל את הפונקציה.

התהליך עובד כך:

1️⃣ המשתמש מזין שאילתה, לדוגמא: מה התחזית להיום בירושלים.

2️⃣ ה-Agent שולח ל-LLM את השאלה ומוסיף תיאור של פונקצית get_weather שיודעת להחזיר תחזית לפי קואורדינטות של מיקום מסוים על פני כדור הארץ.

3️⃣ ה-LLM מנתח את השאלה באופן כזה:
מה התחזית להיום אין לו דרך לדעת, בשביל זה הוא צריך כלי עזר חיצוני. ה-LLM מסיק לפי תיאור הפונקציה שלנו שהיא מתאימה לבעיה ויש להפעיל אותה כדי לקבל את התחזית. הוא לומד מתיאור הפונקציה שהיא דורשת נקודות ציון של אורך ורוחב. את המידע הזה אין לו בעיה לדעת, שהרי המיקום על פני כדור הארץ הוא קבוע ולא השתנה מאז שאומן המודל.

4️⃣ ה-LLM מחזיר ל-Agent תשובה עם הוראה להפעיל את הפונקציה get_weather עם הערכים עבור הפרמטרים אורך ורוחב.

5️⃣ ה-Agent מפעיל את הפונקציה get_weather ומקבל תוצאה של מספר שמייצג את התחזית במעלות צלסיוס.

6️⃣ ה-Agent שולח ל-LLM שוב את השאלה של המשתמש, מוסיף את התשובה שקיבל מה-LLM קודם עם ההוראה להפעלת הפונקציה, ולסיום מוסיף את התשובה של הפונקציה - התחזית.

7️⃣ ה-LLM מג'נרט תשובה מנוסחת בשפת בני אדם בסגנון: "תחזית מזג האויר ליום זה וזה בירושלים היא כך וכך מעלות צלסיוס"

🎉 👏

לסיכום,
יש פה סוג של שילוב ידים 🤝
ה-LLM מביא את יכולת ההבנה של שפה טבעית וממיר אותה למידע שהוא structured - מובנה (במקרה הזה json שמתאר איזו פונקציה להפעיל עם איזה פרמטרים).
ה-Agent, שהוא תוכנית קוד קלאסית, מביא את היכולת להפעיל פונקציות ומנהל את התהליך השלם של קריאה ל-LLM, הפעלת הפונקציות וניצוח על כל המקהלה.

התוצאה, ללא ספק, מרשימה ביותר ✨ 🥳

איך זה נראה בפועל❓

🔗 קישור לדמו עם קריאות מוכנות ב-Postman

להלן מבנה ה-body של הבקשות שנשלחות ל-API של OpenAI של התגובות המתקבלות ממנו.

זו הבקשה הראשונה.
יש לנו כאן מערך של messages עם השאלה של המשתמש "מה התחזית להיום בירושלים"
בנוסף יש לנו מערך של tools עם אוביקט מסוג function שמתאר פונקציה בשם get_weather שמחזירה טמפרטורת צלסיוס לפי קואורדינטות.
מוגדרים גם הפרמטרים שהפונקציה דורשת: אורך ורוחב כמספרים.
המאפיין tool_choice מגדיר האם מחיבים את ה-LLM להחזיר הוראת הפעלה לפונקציה או שזה לגיטימי אם יחליט שלא נדרשת הפעלה של פונקציה (לפי שאילתת המשתמש כמובן, ובהתאם לצורך העסקי של האפליקציה)
במקרה הזה מוגדר כ-required כדי להקשיח אותו שיחזיר תמיד הוראת הפעלה לפונקציה.

OpenAI API Request body
{
    "model": "gpt-4o-mini",
    "messages": [
        {
            "role": "user",
            "content": "מה התחזית להיום בירושלים"
        }
    ],
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Get current temperature for provided coordinates in celsius.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "latitude": {
                            "type": "number"
                        },
                        "longitude": {
                            "type": "number"
                        }
                    },
                    "required": [
                        "latitude",
                        "longitude"
                    ],
                    "additionalProperties": false
                },
                "strict": true
            }
        }
    ],
    "tool_choice": "required"
}

זו התגובה של ה-LLM.
נשים לב שהוא החזיר לנו message עם content ריק, מכיון שאין לו תשובה לשאלה אלא הוראה להפעלת פונקציה.
tool_calls מגדיר לנו בצורה מובנית איזו פונקציה להפעיל ומה יהיו ערכי הפרמטרים.

OpenAI API Response body
{
    "id": "chatcmpl-ArwhyboyFpBwbKfVQ1N0TsEa6zXZt",
    "object": "chat.completion",
    "created": 1737420422,
    "model": "gpt-4o-mini-2024-07-18",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": null,
                "tool_calls": [
                    {
                        "id": "call_hH7LRv5VEFHOjCCeVOS2TPIs",
                        "type": "function",
                        "function": {
                            "name": "get_weather",
                            "arguments": "{\"latitude\":31.7683,\"longitude\":35.2137}"
                        }
                    }
                ],
                "refusal": null
            },
            "logprobs": null,
            "finish_reason": "tool_calls"
        }
    ],
    "usage": {
        "prompt_tokens": 59,
        "completion_tokens": 25,
        "total_tokens": 84,
        "prompt_tokens_details": {
            "cached_tokens": 0,
            "audio_tokens": 0
        },
        "completion_tokens_details": {
            "reasoning_tokens": 0,
            "audio_tokens": 0,
            "accepted_prediction_tokens": 0,
            "rejected_prediction_tokens": 0
        }
    },
    "service_tier": "default",
    "system_fingerprint": "fp_72ed7ab54c"
}

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

נניח שהפעלנו את הפונקציה get_weather וקיבלנו את המספר 10.
זו הבקשה השניה שנשלחת ל-LLM.
נשים לב לשרשור במערך של ה-messages. נוסף אוביקט עם התגובה של ה-LLM שאומרת להפעיל את הפונקציה וכן אוביקט עם תוצאת הפונקציה (שהופעלה או שלא, לא משנה...)
בנוסף, חשוב מאוד! tool_choice הושמט מהבקשה, אחרת תגובת ה-LLM תהיה שוב הוראת הפעלה לפונקציה...

OpenAI API Request body
{
  "model": "gpt-4o-mini",
  "messages": [
    {
      "role": "user",
      "content": "מה התחזית להיום בירושלים"
    },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_hH7LRv5VEFHOjCCeVOS2TPIs",
          "type": "function",
          "function": {
            "name": "get_weather",
            "arguments": "{\"latitude\":31.7683,\"longitude\":35.2137}"
          }
        }
      ],
      "refusal": null
    },
    {
      "role": "tool",
      "tool_call_id": "call_hH7LRv5VEFHOjCCeVOS2TPIs",
      "content": "10"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get current temperature for provided coordinates in celsius.",
        "parameters": {
          "type": "object",
          "properties": {
            "latitude": {
              "type": "number"
            },
            "longitude": {
              "type": "number"
            }
          },
          "required": [
            "latitude",
            "longitude"
          ],
          "additionalProperties": false
        },
        "strict": true
      }
    }
  ]
}

וכעת, לתוצאה הסופית!
זו תשובת ה-LLM עם המסקנה העולה מכל התהליך.

OpenAI API Response body
{
    "id": "chatcmpl-ArwvINYR7gzz635qcaCSrzbGCH2UE",
    "object": "chat.completion",
    "created": 1737421248,
    "model": "gpt-4o-mini-2024-07-18",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "הטמפרטורה בירושלים להיום היא 10 מעלות צלזיוס.",
                "refusal": null
            },
            "logprobs": null,
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 92,
        "completion_tokens": 23,
        "total_tokens": 115,
        "prompt_tokens_details": {
            "cached_tokens": 0,
            "audio_tokens": 0
        },
        "completion_tokens_details": {
            "reasoning_tokens": 0,
            "audio_tokens": 0,
            "accepted_prediction_tokens": 0,
            "rejected_prediction_tokens": 0
        }
    },
    "service_tier": "default",
    "system_fingerprint": "fp_72ed7ab54c"
}

👏👏👏 (מחיאות כפיים סוערות!)

דוגמת קוד בפייתון

openai_service.py
import requests
import json

# OpenAI API key configuration
api_key = "sk-xxx"  # Put your API Key here
url = "https://api.openai.com/v1/chat/completions"

headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}


async def openai_chat_completion(message: str):
    messages = [{"role": "user", "content": message}]

    try:
        response = requests.post(url, headers=headers, json={
            "model": "gpt-4o-mini",
            "messages": messages,
            "tools": tools_weather,
            "tool_choice": "required"
        })

        response.raise_for_status()
    except requests.exceptions.HTTPError as http_err:
        return None

    completion = response.json()

    tool_call = completion['choices'][0]['message']['tool_calls'][0]
    args = json.loads(tool_call['function']['arguments'])

    result = get_weather(args["latitude"], args["longitude"])

    messages.append(completion['choices'][0]['message'])  # append model's function call message
    messages.append({  # append result message
        "role": "tool",
        "tool_call_id": tool_call['id'],
        "content": result
    })

    try:
        response2 = requests.post(url, headers=headers, json={
            "model": "gpt-4o-mini",
            "messages": messages,
            "tools": tools_weather
        })
        response2.raise_for_status()
    except requests.exceptions.HTTPError as http_err:
        return None

    return response2.json()


tools_weather = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current temperature for provided coordinates in celsius.",
        "parameters": {
            "type": "object",
            "properties": {
                "latitude": {"type": "number"},
                "longitude": {"type": "number"}
            },
            "required": ["latitude", "longitude"],
            "additionalProperties": False
        },
        "strict": True
    }
}]

לסיום

ראינו דוגמה בסיסית לשימוש ב-Function calling כדי לבצע RAG - העשרה של ה-LLM במידע מבחוץ על מנת לאפשר לו לצאת מעצמו ולתת מענה גם לשאלות שאין לו מידע לגביהן.

מכאן צפונה - האפשרויות הן בלי סוף 🙂‍↕️

דוגמית? נסי להוסיף ל-Agent פיצ'ר המלצה מה ללבוש היום בהתאם לתחזית 🧥 🌂

בהנאה ובהצלחה 🤗