איך משתמשים בממשקי API באמצעות הפעלת פונקציות ב-Gemini

1. סקירה כללית

מה המשמעות של הפעלת פונקציות ב-Gemini?

Vertex AI Gemini API היא קבוצת מודלים של בינה מלאכותית גנרטיבית שפותחה על ידי Google DeepMind, ומיועדת לתרחישים לדוגמה רב-אופניים. שליחת פונקציות היא תכונה במודלים של Gemini שמאפשרת למפתחים לקבל בקלות רבה יותר פלט של נתונים מובְנים ממודלים גנרטיביים.

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

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

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

ממשק API

מה תפַתחו

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

מה תלמדו

  • איך משתמשים במודל Gemini באמצעות ספריית לקוח של Python
  • איך מגדירים הצהרת פונקציה ורושמים אותה ככלי
  • איך קוראים ל-Gemini ומקבלים תשובה לקריאה לפונקציה
  • איך להחזיר את התשובה של הפונקציה ל-Gemini ולהגיב למשתמש

למה תזדקק?

2. הגדרה ודרישות

כדי להתחיל להשתמש בקריאות לפונקציות ב-Gemini, צריך להפעיל את Vertex AI API ולהתקין את הגרסה האחרונה של ספריית הלקוח Vertex AI Python.

הפעלה של Vertex AI API

כדי להפעיל את Vertex AI API, פועלים לפי השלבים הבאים:

  1. בדפדפן, עוברים לדף 'Vertex AI API Service Details'.
  2. לוחצים על הלחצן Enable כדי להפעיל את Vertex AI API בפרויקט ב-Google Cloud.

התקנה של ספריית לקוח Python ל-Vertex AI

כדי להתקין את ספריות הלקוח של Python ל-Vertex AI, מבצעים את השלבים הבאים:

  1. פותחים טרמינל בסביבת הפיתוח.
  2. מוודאים שיש לכם סביבת פיתוח Python תקינה, ומעיינים בהנחיות האלה במקרה הצורך.
  3. כדי להתקין את ספריית הלקוח של Python ל-Vertex AI, מריצים את הפקודה הבאה:
    pip install --upgrade google-cloud-aiplatform
    
  4. אם אתם מריצים בסביבת notebook, יכול להיות שתצטרכו להפעיל מחדש את סביבת זמן הריצה/הליבה כדי להשתמש בחבילות החדשות שהותקנו.

עכשיו אתם מוכנים להשתמש ב-Vertex AI API!

3. הסבר על הבעיה

יצא לכם פעם ליצור אינטראקציה עם מודל שפה גדול (LLM) או עם מודל בינה מלאכותית גנרטיבית, ושאלתם אותו לגבי מידע בזמן אמת או מידע עדכני, רק כדי לקבל תשובה עם מידע מיושן או מידע לא מדויק?

רוצה לנסות? קודם כול, נייבא את חבילות ה-Python הרלוונטיות ונפעיל את המודל של Gemini. אפשר להריץ את הקוד הבא בסביבת פיתוח Python, כמו Colab או Colab Enterprise, ועל ידי התקנת הגרסה האחרונה של Vertex AI SDK ל-Python:

import vertexai
from vertexai.generative_models import GenerativeModel
model = GenerativeModel("gemini-1.5-pro-001")

עכשיו נשאל שאלה לגבי שער החליפין של מטבעות שונים כיום:

response = model.generate_content(
    "What's the exchange rate for euros to dollars today?"
)
print(response.text)

המודל אמור ליצור תשובה מוגבלת או מיושנת בשבילכם, שדומה לזו:

As an AI language model, I don't have access to real-time currency exchange
rates. However, as of my last update in September 2021, the approximate exchange
rate between euros (EUR) and US dollars (USD) was:

1 EUR ≈ 1.18 USD

Please note that currency exchange rates constantly fluctuate and can vary
depending on various factors such as economic conditions, supply and demand,
political events, and more. To obtain the most up-to-date and accurate exchange
rate, I recommend using a reliable currency converter or financial website that
provides live rates.

[...]

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

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

4. פתרונות נפוצים

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

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

נחזור על השאלה ששאלנו בקטע הקודם ונוסיף עוד הוראות למודל. אפשר לנסות לשלוח את הבקשה הבאה למודל של Gemini:

user_prompt = "What's the exchange rate from euros to US dollars today?"

response = model.generate_content("""
Your task is to extract parameters from the user's input and return it as a
structured JSON payload. The user will ask about the exchange rate and which
currency they are converting from and converting to.

User input: {user_prompt}

Please extract the currencies as parameters and put them in a JSON object.
""".format(user_prompt=user_prompt))
print(response.text)

תוצאות אלה הן תגובת הטקסט הבאה, שאינה JSON חוקית ויהיה קשה לנו לעבוד איתה:

```json
{
  "currency_from": "euros",
  "currency_to": "US dollars"
}
```

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

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

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

5. הסבר על שליחת פונקציות

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

סקירה כללית של הפעלת פונקציות ב-Gemini

קלט של משתמשים ל-Gemini API

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

Gemini API מחזיר קריאה לפונקציה

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

שליחת בקשת API

לאחר מכן, תשתמשו בשם הפונקציה ובפרמטרים כדי לשלוח בקשת API לאחזור מידע ממערכת חיצונית או מ-API חיצוני. הבקשה והתגובה הזו מה-API מוטמעים על ידי המפתח בקוד האפליקציה, ולא נכללים בהיקף של Gemini API ו-SDK. לדוגמה, אפשר להשתמש בספרייה requests ב-Python כדי לקרוא ל-API ל-REST ולקבל תשובת JSON. אפשר גם להפעיל את הפונקציה באמצעות הגישה וספריית הלקוח המועדפים עליכם.

החזרת התשובה מה-API ל-Gemini

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

6. בחירת ה-API

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

באפליקציה שלנו להמרת מטבעות, נשתמש ב-API ל-REST בכתובת https://www.frankfurter.app/ כדי לאחזר את המידע העדכני ביותר על שערי חליפין גלובליים.

כדי לקיים אינטראקציה עם ה-API הזה ל-REST, יכול להיות שנבצע קריאה ל-API ל-REST עם requests ב-Python כך:

import requests
url = "https://api.frankfurter.app/latest"
response = requests.get(url)
response.text

או בקשת cURL כמו:

curl https://api.frankfurter.app/latest

שמחזירה תשובה שדומה לזו:

{
  "amount": 1,
  "base": "EUR",
  "date": "2023-12-20",
  "rates": {
    "AUD": 1.6186, "BGN": 1.9558, "BRL": 5.3287,
    "CAD": 1.4609, "CHF": 0.946, "CNY": 7.8121,
    "CZK": 24.538, "DKK": 7.4565, "GBP": 0.86555,
    "HKD": 8.5439, "HUF": 385.23, "IDR": 16994,
    "ILS": 3.9983, "INR": 91.06, "ISK": 150.3,
    "JPY": 157.12, "KRW": 1425.62, "MXN": 18.6867,
    "MYR": 5.0977, "NOK": 11.2895, "NZD": 1.7421,
    "PHP": 60.991, "PLN": 4.3413, "RON": 4.9699,
    "SEK": 11.129, "SGD": 1.4562, "THB": 38.252,
    "TRY": 31.883, "USD": 1.0944, "ZAR": 20.111
  }
}

הפעלת פונקציות ב-Gemini לא מפעילה את הקריאה החיצונית ל-API, ולכן אין הגבלות כאלה על סוג ה-API שבו אתם משתמשים. אפשר להשתמש בשירות Cloud Run, בפונקציה של Cloud Functions, בבקשת API לשירות של Google Cloud או בכל API חיצוני של REST.

7. הגדרת פונקציה וכלי

אחרי שבחרתם API ל-REST לשימוש, עכשיו אפשר להגדיר מפרט API ולרשום את הפונקציה בכלי.

מוודאים שהתקנתם את הגרסה העדכנית של Vertex AI SDK ל-Python.

לאחר מכן, מייבאים את המודולים הנדרשים מ-Python SDK ומפעילים את המודל של Gemini:

from vertexai.generative_models import (
    Content,
    FunctionDeclaration,
    GenerativeModel,
    Part,
    Tool,
)

model = GenerativeModel("gemini-1.5-pro-001")

אם נחזור ל-API ל-REST בכתובת https://api.frankfurter.app/, אפשר לראות שהוא מקבל את הפרמטרים הבאים של הקלט:

פרמטר

סוג

תיאור

from

מחרוזת

המטבע להמרת המטבע

to

מחרוזת

המטבע שאליו רוצים להמיר

date

מחרוזת

התאריך לאחזור שער החליפין עבור

עם הפרמטרים האלה, מפרט OpenAPI חלקי ל-API ל-REST בפורמט YAML נראה כך:

openapi: 3.0.0
info:
  title: Frankfurter Exchange Rate API
  description: This API provides current and historical exchange rates
  version: 1.0.0
servers:
  - url: https://api.frankfurter.app
paths:
  /{date}:
    get:
      summary: Get the latest currency exchange rates.
      parameters:
        - name: date
          in: path
          description: Get currency rates for a specific date or 'latest' if a date is not specified
          required: true
          schema:
            type: string
        - name: from
          in: query
          description: The currency to convert from.
          required: true
          schema:
            type: string
        - name: to
          in: query
          description: The currency to convert to.
          schema:
            type: string

עכשיו נרשום את זה כ-FunctionDeclaration באמצעות Python SDK ל-Gemini:

get_exchange_rate_func = FunctionDeclaration(
    name="get_exchange_rate",
    description="Get the exchange rate for currencies between countries",
    parameters={
    "type": "object",
    "properties": {
        "currency_date": {
            "type": "string",
            "description": "A date that must always be in YYYY-MM-DD format or the value 'latest' if a time period is not specified"
        },
        "currency_from": {
            "type": "string",
            "description": "The currency to convert from in ISO 4217 format"
        },
        "currency_to": {
            "type": "string",
            "description": "The currency to convert to in ISO 4217 format"
        }
    },
         "required": [
            "currency_from",
            "currency_date",
      ]
  },
)

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

בשלב האחרון צריך להגדיר Tool שכולל את הצהרת הפונקציה:

exchange_rate_tool = Tool(
    function_declarations=[get_exchange_rate_func],
)

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

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

8. יצירת בקשה להפעלת פונקציה

עכשיו אתם יכולים להנחות את המודל הגנרטיבי ולכלול את tool שהגדרתם:

prompt = """What is the exchange rate from Australian dollars to Swedish krona?
How much is 500 Australian dollars worth in Swedish krona?"""

response = model.generate_content(
    prompt,
    tools=[exchange_rate_tool],
)

בואו נסתכל על אובייקט התשובה:

print(response.candidates[0].content)

role: "model"
parts {
  function_call {
    name: "get_exchange_rate"
    args {
      fields {
        key: "currency_to"
        value {
          string_value: "SEK"
        }
      }
      fields {
        key: "currency_from"
        value {
          string_value: "AUD"
        }
      }
      fields {
        key: "currency_date"
        value {
          string_value: "latest"
        }
      }
    }
  }
}

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

בקטע הבא משתמשים במידע שבתשובה כדי לשלוח בקשת API.

9. שליחת בקשת API

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

כאן צריך להשתמש בספרייה requests ב-Python כדי לקרוא ל-API לשער חליפין ל-REST.

נפרק את התשובה במילון Python:

params = {}
for key, value in response.candidates[0].content.parts[0].function_call.args.items():
    params[key[9:]] = value
params

עכשיו אנחנו יכולים לקרוא ל-requests או לכל שיטה אחרת:

import requests
url = f"https://api.frankfurter.app/{params['date']}"
api_response = requests.get(url, params=params)
api_response.text

התוצאה תהיה תשובה שדומה לזו:

'{"amount":1.0,"base":"AUD","date":"2024-01-16","rates":{"SEK":6.8682}}'

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

10. יצירת תשובה

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

response = model.generate_content(
    [
    Content(role="user", parts=[
        Part.from_text(prompt + """Give your answer in steps with lots of detail
            and context, including the exchange rate and date."""),
    ]),
    Content(role="function", parts=[
        Part.from_dict({
            "function_call": {
                "name": "get_exchange_rate",
            }
        })
    ]),
    Content(role="function", parts=[
        Part.from_function_response(
            name="get_exchange_rate",
            response={
                "content": api_response.text,
            }
        )
    ]),
    ],
    tools=[exchange_rate_tool],
)


response.candidates[0].content.parts[0].text

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

The exchange rate from Australian dollars to Swedish krona on January 16, 2024,
is 1 Australian dollar is equal to 6.8663 Swedish krona.

So, 500 Australian dollars would be worth 500 * 6.8663 = 3,433.15 Swedish krona.

11. הצגת הקוד המלא לדוגמה

בשלב הזה, תוכלו להציב את קוד Python ב-API לקצה העורפי באמצעות שירות של Cloud Run, פונקציה של Cloud Functions או שירות אחר של Cloud, ולפרוס אפליקציה של הקצה העורפי שמשתמשת ב-API הזה בקצה העורפי כדי לבצע שאילתות מודלים וקריאות ל-API.

הנה דוגמה לקוד המלא של הפתרון הסופי שלנו:

import requests
from vertexai.generative_models import (
    Content,
    FunctionDeclaration,
    GenerativeModel,
    Part,
    Tool,
)

model = GenerativeModel("gemini-1.5-pro-001")

get_exchange_rate_func = FunctionDeclaration(
    name="get_exchange_rate",
    description="Get the exchange rate for currencies between countries",
    parameters={
    "type": "object",
    "properties": {
        "currency_date": {
            "type": "string",
            "description": "A date that must always be in YYYY-MM-DD format or the value 'latest' if a time period is not specified"
        },
        "currency_from": {
            "type": "string",
            "description": "The currency to convert from in ISO 4217 format"
        },
        "currency_to": {
            "type": "string",
            "description": "The currency to convert to in ISO 4217 format"
        }
    },
         "required": [
            "currency_from",
            "currency_date",
      ]
  },
)

exchange_rate_tool = Tool(
    function_declarations=[get_exchange_rate_func],
)

prompt = """What is the exchange rate from Australian dollars to Swedish krona?
How much is 500 Australian dollars worth in Swedish krona?"""

response = model.generate_content(
    prompt,
    tools=[exchange_rate_tool],
)

response.candidates[0].content

params = {}
for key, value in response.candidates[0].content.parts[0].function_call.args.items():
    params[key[9:]] = value
params

import requests
url = f"https://api.frankfurter.app/{params['date']}"
api_response = requests.get(url, params=params)
api_response.text

response = model.generate_content(
    [
    Content(role="user", parts=[
        Part.from_text(prompt + """Give your answer in steps with lots of detail
            and context, including the exchange rate and date."""),
    ]),
    Content(role="function", parts=[
        Part.from_dict({
            "function_call": {
                "name": "get_exchange_rate",
            }
        })
    ]),
    Content(role="function", parts=[
        Part.from_function_response(
            name="get_exchange_rate",
            response={
                "content": api_response.text,
            }
        )
    ]),
    ],
    tools=[exchange_rate_tool],
)


response.candidates[0].content.parts[0].text

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

דוגמאות קוד נוספות מופיעות בnotebook לדוגמה של להפעלת פונקציות ב-Gemini.

12. מזל טוב

באמצעות קריאה לפונקציות ב-Gemini, פיתחתם בהצלחה צינור עיבוד נתונים של בינה מלאכותית גנרטיבית שמשתמש ב-Vertex AI Gemini API וב-Python! המשתמשים יכולים לשאול שאלות לגבי שערי חליפין, והמערכת תאחזר את הנתונים העדכניים מ-API חיצוני ותגיב בתשובה.

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

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

ממשק API

הסרת המשאבים

אפשר לבצע את ניקוי הניקוי הבא כדי להימנע מצבירת חיובים בחשבון Google Cloud עבור המשאבים שבהם נעשה שימוש ב-Codelab הזה:

  • כדי להימנע מחיובים מיותרים ב-Google Cloud, אפשר להשתמש במסוף Google Cloud כדי למחוק את הפרויקט אם אין לכם צורך בו.
  • אם רוצים להשבית את ממשקי ה-API של Vertex AI, נכנסים לדף 'פרטי השירות של Vertex AI API', לוחצים על Disable API ומאשרים.

מידע נוסף

במדריכים ובמקורות המידע האלה תוכלו להמשיך ללמוד על AI בממשק שיחה ו-AI גנרטיבי:

רישיון

היצירה הזו בשימוש ברישיון Creative Commons Attribution 2.0 גנרי.