מדריך: קריאה לפונקציה באמצעות Gemini API


לצפייה ב-ai.google.dev הפעלה ב-Google Colab הצגת המקור ב-GitHub

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

למידע נוסף, מומלץ לקרוא את המאמר מבוא לקריאה לפונקציות.

הגדרה

התקנת Python SDK.

ה-Python SDK ל-Gemini API נכלל בחבילה google-generativeai. מתקינים את התלות באמצעות PIP:

pip install -U -q google-generativeai

ייבוא חבילות

מייבאים את החבילות הנדרשות.

import pathlib
import textwrap
import time

import google.generativeai as genai

from IPython import display
from IPython.display import Markdown

def to_markdown(text):
  text = text.replace('•', '  *')
  return Markdown(textwrap.indent(text, '> ', predicate=lambda _: True))

הגדרת מפתח ה-API

כדי להשתמש ב-Gemini API, עליך לקבל מפתח API. אם עדיין אין לך מפתח, אפשר ליצור מפתח בלחיצה אחת ב-Google AI Studio.

קבלת מפתח API

ב-Colab, מוסיפים את המפתח למנהל הסודות בקטע '🔑' בחלונית שמשמאל. נותנים לו את השם API_KEY.

אחרי שמוצאים את מפתח ה-API, מעבירים אותו ל-SDK. תוכל לעשות זאת בשתי דרכים:

  • מזינים את המפתח במשתנה הסביבה GOOGLE_API_KEY (ה-SDK יאסוף אותו משם באופן אוטומטי).
  • יש להעביר את המפתח אל genai.configure(api_key=...)
try:
    # Used to securely store your API key
    from google.colab import userdata

    # Or use `os.getenv('API_KEY')` to fetch an environment variable.
    GOOGLE_API_KEY=userdata.get('GOOGLE_API_KEY')
except ImportError:
    import os
    GOOGLE_API_KEY = os.environ['GOOGLE_API_KEY']

genai.configure(api_key=GOOGLE_API_KEY)

העקרונות הבסיסיים של קריאה לפונקציה

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

def multiply(a:float, b:float):
    """returns a * b."""
    return a*b

model = genai.GenerativeModel(model_name='gemini-1.0-pro',
                              tools=[multiply])

model
genai.GenerativeModel(
    model_name='models/gemini-1.0-pro',
    generation_config={},
    safety_settings={},
    tools=<google.generativeai.types.content_types.FunctionLibrary object at 0x10e73fe90>,
)

מומלץ להשתמש בקריאות לפונקציה דרך ממשק הצ'אט. הסיבה לכך היא שקריאות לפונקציה משתלבות באופן טבעי בצ'אטים עם כמה פניות שמתעדים את האינטראקציה בין המשתמש למודל. ה-ChatSession של Python SDK הוא ממשק מצוין לצ'אטים, כי הוא מטפל בהיסטוריית השיחות בשבילכם, והשימוש בפרמטר enable_automatic_function_calling מפשט עוד יותר את הקריאות לפונקציות:

chat = model.start_chat(enable_automatic_function_calling=True)

כשמפעילים את התכונה 'קריאה אוטומטית של פונקציה', הפונקציה chat.send_message מפעילה באופן אוטומטי את הפונקציה אם המודל מבקש זאת.

נראה שהוא פשוט מחזיר תגובת טקסט שמכילה את התשובה הנכונה:

response = chat.send_message('I have 57 cats, each owns 44 mittens, how many mittens is that in total?')
response.text
'The total number of mittens is 2508.'
57*44
2508

לבחון את היסטוריית הצ'אט כדי לראות את רצף השיחה ואת האופן שבו קריאות עם פונקציות משתלבות בה.

בנכס ChatSession.history נשמר תיעוד כרונולוגי של השיחה בין המשתמש למודל Gemini. כל תור בשיחה מיוצג על ידי אובייקט glm.Content, שמכיל את הפרטים הבאים:

  • תפקיד: מציין אם התוכן הגיע מה'משתמש' או מה'מודל'.
  • חלקים: רשימה של glm.Part אובייקטים שמייצגים רכיבים ספציפיים של ההודעה. במודל של טקסט בלבד, החלקים הבאים יכולים להיות:
    • טקסט: הודעות טקסט פשוטות.
    • קריאה לפונקציה (glm.FunctionCall): בקשה מהמודל להפעיל פונקציה ספציפית עם ארגומנטים נתונים.
    • תגובה לפונקציה (glm.FunctionResponse): התוצאה שהוחזרה על ידי המשתמש אחרי ביצוע הפונקציה המבוקשת.

בדוגמה הקודמת עם חישוב הכפפות, ההיסטוריה מציגה את הרצף הבא:

  1. משתמש: שואל את השאלה לגבי המספר הכולל של כפפות.
  2. Model: קובע שפונקציית ההכפלה מועילה ושולחת למשתמש בקשת FunctionCall.
  3. משתמש: ChatSession מפעיל את הפונקציה באופן אוטומטי (אם enable_automatic_function_calling מוגדרת) ושולח בחזרה FunctionResponse עם התוצאה המחושבת.
  4. Model: משתמש בפלט של הפונקציה כדי לנסח את התשובה הסופית ומציג אותה כתגובה בטקסט.
for content in chat.history:
    part = content.parts[0]
    print(content.role, "->", type(part).to_dict(part))
    print('-'*80)
user -> {'text': 'I have 57 cats, each owns 44 mittens, how many mittens is that in total?'}
--------------------------------------------------------------------------------
model -> {'function_call': {'name': 'multiply', 'args': {'a': 57.0, 'b': 44.0} } }
--------------------------------------------------------------------------------
user -> {'function_response': {'name': 'multiply', 'response': {'result': 2508.0} } }
--------------------------------------------------------------------------------
model -> {'text': 'The total number of mittens is 2508.'}
--------------------------------------------------------------------------------

באופן כללי, תרשים המצב הוא:

המודל תמיד יכול להשיב עם טקסט או באמצעות FunctionCall. אם המודל שולח FunctionCall, המשתמש חייב להשיב באמצעות FunctionResponse

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

כל זה טופל באופן אוטומטי, אבל אם נדרשת לך שליטה רבה יותר, ניתן:

  • אפשר להשאיר את ברירת המחדל enable_automatic_function_calling=False ולעבד את glm.FunctionCall התשובות בעצמך.
  • לחלופין, אפשר להשתמש בGenerativeModel.generate_content, שם צריך לנהל גם את היסטוריית הצ'אט.

קריאה לפונקציה במקביל

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

להגדיר את הכלים.

def power_disco_ball(power: bool) -> bool:
    """Powers the spinning disco ball."""
    print(f"Disco ball is {'spinning!' if power else 'stopped.'}")
    return True


def start_music(energetic: bool, loud: bool, bpm: int) -> str:
    """Play some music matching the specified parameters.

    Args:
      energetic: Whether the music is energetic or not.
      loud: Whether the music is loud or not.
      bpm: The beats per minute of the music.

    Returns: The name of the song being played.
    """
    print(f"Starting music! {energetic=} {loud=}, {bpm=}")
    return "Never gonna give you up."


def dim_lights(brightness: float) -> bool:
    """Dim the lights.

    Args:
      brightness: The brightness of the lights, 0.0 is off, 1.0 is full.
    """
    print(f"Lights are now set to {brightness:.0%}")
    return True

עכשיו קוראים למודל עם הוראה שיכולה להשתמש בכל הכלים שצוינו.

# Set the model up with tools.
house_fns = [power_disco_ball, start_music, dim_lights]

model = genai.GenerativeModel(model_name="gemini-1.5-pro-latest", tools=house_fns)

# Call the API.
chat = model.start_chat()
response = chat.send_message("Turn this place into a party!")

# Print out each of the function calls requested from this single call.
for part in response.parts:
    if fn := part.function_call:
        args = ", ".join(f"{key}={val}" for key, val in fn.args.items())
        print(f"{fn.name}({args})")
power_disco_ball(power=True)
start_music(energetic=True, loud=True, bpm=120.0)
dim_lights(brightness=0.3)

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

# Simulate the responses from the specified tools.
responses = {
    "power_disco_ball": True,
    "start_music": "Never gonna give you up.",
    "dim_lights": True,
}

# Build the response parts.
response_parts = [
    glm.Part(function_response=glm.FunctionResponse(name=fn, response={"result": val}))
    for fn, val in responses.items()
]

response = chat.send_message(response_parts)
print(response.text)
Let's get this party started! I've turned on the disco ball, started playing some upbeat music, and dimmed the lights. 🎶✨  Get ready to dance! 🕺💃

(אופציונלי) גישה ברמה נמוכה

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

AllowedType = (int | float | bool | str | list['AllowedType'] | dict[str, AllowedType]

ספריית הלקוח google.ai.generativelanguage מספקת גישה לסוגים ברמה נמוכה, כך שיש לכם שליטה מלאה.

import google.ai.generativelanguage as glm

כדי לראות איך הוא מתאר את הפונקציות שהעברתם למודל, אפשר לראות קודם את המאפיין _tools של המודל:

def multiply(a:float, b:float):
    """returns a * b."""
    return a*b

model = genai.GenerativeModel(model_name='gemini-1.0-pro',
                             tools=[multiply])

model._tools.to_proto()
[function_declarations {
   name: "multiply"
   description: "returns a * b."
   parameters {
     type_: OBJECT
     properties {
       key: "b"
       value {
         type_: NUMBER
       }
     }
     properties {
       key: "a"
       value {
         type_: NUMBER
       }
     }
     required: "a"
     required: "b"
   }
 }]

הפעולה הזו מחזירה רשימה של glm.Tool אובייקטים שיישלחו ל-API. אם הפורמט המודפס לא מוכר, הסיבה לכך היא שמדובר במחלקות של Google Protobuf. כל glm.Tool (1 במקרה הזה) מכיל רשימה של glm.FunctionDeclarations, שמתארת פונקציה ואת הארגומנטים שלה.

זוהי הצהרה לגבי אותה פונקציית כפל שנכתבה באמצעות המחלקות glm.

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

calculator = glm.Tool(
    function_declarations=[
      glm.FunctionDeclaration(
        name='multiply',
        description="Returns the product of two numbers.",
        parameters=glm.Schema(
            type=glm.Type.OBJECT,
            properties={
                'a':glm.Schema(type=glm.Type.NUMBER),
                'b':glm.Schema(type=glm.Type.NUMBER)
            },
            required=['a','b']
        )
      )
    ])

באותו אופן, ניתן לתאר את זה כאובייקט תואם ל-JSON:

calculator = {'function_declarations': [
      {'name': 'multiply',
       'description': 'Returns the product of two numbers.',
       'parameters': {'type_': 'OBJECT',
       'properties': {
         'a': {'type_': 'NUMBER'},
         'b': {'type_': 'NUMBER'} },
       'required': ['a', 'b']} }]}
glm.Tool(calculator)
function_declarations {
  name: "multiply"
  description: "Returns the product of two numbers."
  parameters {
    type_: OBJECT
    properties {
      key: "b"
      value {
        type_: NUMBER
      }
    }
    properties {
      key: "a"
      value {
        type_: NUMBER
      }
    }
    required: "a"
    required: "b"
  }
}

בשני המקרים, נעביר ייצוג של glm.Tool או רשימת כלים כדי

model = genai.GenerativeModel('gemini-pro', tools=calculator)
chat = model.start_chat()

response = chat.send_message(
    f"What's 234551 X 325552 ?",
)

למשל, לפני שהמודל מחזיר glm.FunctionCall, עם הפונקציה multiply של המחשבון:

response.candidates
[index: 0
content {
  parts {
    function_call {
      name: "multiply"
      args {
        fields {
          key: "b"
          value {
            number_value: 325552
          }
        }
        fields {
          key: "a"
          value {
            number_value: 234551
          }
        }
      }
    }
  }
  role: "model"
}
finish_reason: STOP
]

בצעו את הפונקציה בעצמכם:

fc = response.candidates[0].content.parts[0].function_call
assert fc.name == 'multiply'

result = fc.args['a'] * fc.args['b']
result
76358547152.0

שולחים את התוצאה למודל כדי להמשיך את השיחה:

response = chat.send_message(
    glm.Content(
    parts=[glm.Part(
        function_response = glm.FunctionResponse(
          name='multiply',
          response={'result': result}))]))

סיכום

ב-SDK יש תמיכה בהפעלת פונקציות בסיסיות. חשוב לזכור שקל יותר לנהל אותו במצב צ'אט כי המבנה שלו הוא טבעי וטבעי. אתם אחראים על הקריאה של הפונקציות בפועל ועל שליחת התוצאות חזרה למודל כדי שהוא יוכל להניב תשובה טקסט.