Tutoriel: Appeler une fonction avec l'API Gemini


Voir sur ai.google.dev Exécuter dans Google Colab Afficher la source sur GitHub

Utilisez les appels de fonction pour définir des fonctions personnalisées et les transmettre à Gemini. Le modèle n'appelle pas directement ces fonctions, mais génère une sortie de données structurées qui spécifie le nom de la fonction et les arguments suggérés. Cette sortie permet d'appeler des API externes. Elle peut ensuite être réintégrée dans le modèle, ce qui permet d'obtenir des réponses aux requêtes plus complètes. Les appels de fonctions permettent aux LLM d'interagir avec des informations en temps réel et divers services, tels que des bases de données, des systèmes de gestion de la relation client et des référentiels de documents, ce qui améliore leur capacité à fournir des réponses pertinentes et contextuelles. Vous pouvez fournir aux modèles Gemini des descriptions de fonctions. Le modèle peut vous demander d'appeler une fonction et de renvoyer le résultat pour l'aider à gérer votre requête.

Si vous ne l'avez pas déjà fait, consultez l'article Présentation de l'appel de fonctions pour en savoir plus.

Préparation

Installer le SDK Python

Le SDK Python pour l'API Gemini est contenu dans le package google-generativeai. Installez la dépendance à l'aide de pip:

pip install -U -q google-generativeai

Importer des packages

Importez les packages nécessaires.

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))

Configurer votre clé API

Pour pouvoir utiliser l'API Gemini, vous devez d'abord obtenir une clé API. Si vous ne possédez pas encore de clé, créez-en une en un clic dans Google AI Studio.

Obtenir une clé API

Dans Colab, ajoutez la clé au gestionnaire de secrets sous le bouton "EIDR" du panneau de gauche. Donnez-lui le nom API_KEY.

Une fois la clé API obtenue, transmettez-la au SDK. Pour cela, vous avez le choix entre deux méthodes :

  • Placez la clé dans la variable d'environnement GOOGLE_API_KEY (le SDK la récupérera automatiquement à partir de là).
  • Transmettre la clé à 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)

Principes de base de l'appel de fonctions

Pour utiliser les appels de fonctions, transmettez une liste de fonctions au paramètre tools lors de la création d'un GenerativeModel. Le modèle utilise le nom de la fonction, la docstring, les paramètres et les annotations de type de paramètre pour déterminer s'il a besoin de la fonction pour répondre au mieux à une invite.

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>,
)

Il est recommandé d'utiliser les appels de fonction via l'interface de chat. En effet, les appels de fonction s'intègrent naturellement aux chats multitours, car ils capturent les interactions aller-retour entre l'utilisateur et le modèle. La classe ChatSession du SDK Python est une excellente interface pour les chats, car elle gère l'historique des conversations à votre place, et l'utilisation du paramètre enable_automatic_function_calling simplifie encore plus les appels de fonctions:

chat = model.start_chat(enable_automatic_function_calling=True)

Lorsque l'appel de fonction automatique est activé, chat.send_message appelle automatiquement votre fonction si le modèle le demande.

Il semble simplement renvoyer une réponse textuelle contenant la bonne réponse:

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

Examinez l'historique des discussions pour voir le déroulement de la conversation et la façon dont les appels de fonction y sont intégrés.

La propriété ChatSession.history stocke un enregistrement chronologique de la conversation entre l'utilisateur et le modèle Gemini. Chaque tour de la conversation est représenté par un objet glm.Content, qui contient les informations suivantes:

  • Rôle: indique si le contenu provient de l'utilisateur ou du modèle.
  • Parties: liste d'objets glm.Part représentant les différents composants du message. Dans un modèle basé uniquement sur du texte, les éléments suivants peuvent être :
    • Texte: messages en texte brut.
    • Appel de fonction (glm.FunctionCall): requête du modèle pour exécuter une fonction spécifique avec les arguments fournis.
    • Réponse de la fonction (glm.FunctionResponse): résultat renvoyé par l'utilisateur après l'exécution de la fonction demandée.

Dans l'exemple précédent avec le calcul de moufles, l'historique montre la séquence suivante:

  1. Utilisateur: pose la question sur le nombre total de moufles.
  2. Modèle: détermine que la fonction de multiplication est utile et envoie une requête FunctionCall à l'utilisateur.
  3. Utilisateur: ChatSession exécute automatiquement la fonction (en raison de la définition de enable_automatic_function_calling) et renvoie une FunctionResponse avec le résultat calculé.
  4. Modèle: utilise la sortie de la fonction pour formuler la réponse finale et la présente sous forme de texte.
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.'}
--------------------------------------------------------------------------------

En général, le diagramme d'état est le suivant:

Le modèle peut toujours répondre avec du texte ou un appel de fonction. Si le modèle envoie un appel de fonction (FunctionCall), l&#39;utilisateur doit répondre avec une réponse FunctionResponse.

Le modèle peut répondre avec plusieurs appels de fonction avant de renvoyer une réponse textuelle, et les appels de fonction avant la réponse textuelle.

Bien que tout ait été géré automatiquement, si vous avez besoin de plus de contrôle, vous pouvez:

  • Conservez la valeur par défaut enable_automatic_function_calling=False et traitez vous-même les réponses glm.FunctionCall.
  • Vous pouvez aussi utiliser GenerativeModel.generate_content, où vous devez également gérer l'historique des discussions.

Appels de fonctions parallèles

En plus des appels de fonctions de base décrits ci-dessus, vous pouvez également appeler plusieurs fonctions en un seul tour. Cette section présente un exemple d'utilisation des appels de fonctions parallèles.

Définir les outils

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

Appelez maintenant le modèle avec une instruction pouvant utiliser tous les outils spécifiés.

# 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)

Chacun des résultats affichés reflète un seul appel de fonction demandé par le modèle. Pour renvoyer les résultats, incluez les réponses dans l'ordre dans lequel elles ont été demandées.

# 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! 🕺💃

(Facultatif) Accès de bas niveau

L'extraction automatique du schéma à partir des fonctions Python ne fonctionne pas dans tous les cas. Par exemple, elle ne gère pas les cas où vous décrivez les champs d'un objet de dictionnaire imbriqué, mais l'API le permet. L'API est capable de décrire les types suivants:

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

La bibliothèque cliente google.ai.generativelanguage donne accès aux types de niveau inférieur, ce qui vous offre un contrôle total.

import google.ai.generativelanguage as glm

Tout d'abord, dans l'attribut _tools du modèle, vous pouvez voir comment il décrit la ou les fonctions que vous lui avez transmises:

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"
   }
 }]

Cela renvoie la liste des objets glm.Tool qui seraient envoyés à l'API. Si le format imprimé ne vous est pas familier, il s'agit de classes de tampons de protocole Google. Chaque glm.Tool (1 dans ce cas) contient une liste de glm.FunctionDeclarations, qui décrivent une fonction et ses arguments.

Voici une déclaration pour la même fonction de multiplication écrite à l'aide des classes glm.

Notez que ces classes ne font que décrire la fonction de l'API, elles n'incluent pas d'implémentation. L'utilisation de cette option ne fonctionne donc pas avec l'appel automatique de fonctions, mais les fonctions n'ont pas toujours besoin d'être implémentées.

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']
        )
      )
    ])

De manière équivalente, vous pouvez décrire cela comme un objet compatible 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"
  }
}

Dans tous les cas, vous transmettez une représentation d'un glm.Tool ou d'une liste d'outils à

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

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

Comme avant que le modèle ne renvoie un glm.FunctionCall en appelant la fonction multiply du calculateur:

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
]

Exécutez la fonction vous-même:

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

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

Envoyez le résultat au modèle pour poursuivre la conversation:

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

Résumé

Le SDK prend en charge les appels de fonctions de base. N'oubliez pas qu'il est plus facile à gérer en mode chat, en raison de la structure naturelle des échanges. Vous êtes en charge d'appeler les fonctions et de renvoyer les résultats au modèle afin qu'il puisse générer une réponse textuelle.