IA générative déterministe avec appel de fonction Gemini en Java

1. Introduction

Les modèles d'IA générative sont remarquables pour comprendre le langage naturel et y répondre. Mais que se passe-t-il si vous avez besoin de résultats précis et prévisibles pour des tâches critiques telles que la standardisation des adresses ? Les modèles génératifs traditionnels peuvent parfois fournir des réponses différentes à différents moments pour les mêmes requêtes, ce qui peut entraîner des incohérences. C'est là qu'intervient la fonctionnalité d'appel de fonction de Gemini, qui vous permet de contrôler de manière déterministe des éléments de la réponse de l'IA.

Cet atelier de programmation illustre ce concept à travers le cas d'utilisation de la saisie semi-automatique et de la standardisation d'adresses. Pour cela, nous allons créer une fonction Cloud Java qui effectue les tâches suivantes:

  1. Détecte les coordonnées de latitude et de longitude
  2. Appel à l'API Google Maps Geocoding pour obtenir les adresses correspondantes
  3. Utilise la fonctionnalité d'appel de fonction de Gemini 1.0 Pro pour standardiser et résumer ces adresses de manière déterministe dans le format spécifique dont nous avons besoin

C'est parti !

2. Appel de fonction Gemini

L'appel de fonction Gemini se démarque à l'ère de l'IA générative, car il vous permet de combiner la flexibilité des modèles de langage génératifs à la précision de la programmation traditionnelle.

Voici les tâches que vous devez effectuer pour implémenter l'appel de fonction Gemini:

  1. Définir les fonctions: décrivez clairement les fonctions. Les descriptions doivent inclure les informations suivantes:
  • Nom de la fonction, par exemple getAddress.
  • Paramètres attendus par la fonction, tels que latlng sous forme de chaîne.
  • Type de données renvoyées par la fonction, tel qu'une liste de chaînes d'adresse.
  1. Créer des outils pour Gemini: empaquetez les descriptions de fonctions sous la forme d'une spécification d'API dans des outils. Considérez un outil comme une boîte à outils spécialisée que Gemini peut utiliser pour comprendre les fonctionnalités de l'API.
  2. Orchestrez les API à l'aide de Gemini: lorsque vous envoyez une requête à Gemini, celui-ci peut l'analyser et reconnaître où il peut utiliser les outils que vous avez fournis. Gemini joue alors le rôle d'un orchestrateur intelligent en effectuant les tâches suivantes:
  • Il génère les paramètres d'API nécessaires pour appeler les fonctions que vous avez définies. Gemini n'appelle pas l'API en votre nom. Vous devez appeler l'API en fonction des paramètres et de la signature que l'appel de la fonction Gemini a générés pour vous.
  • Gemini traite les résultats en renvoyant les résultats de vos appels d'API à sa génération et intègre des informations structurées dans sa réponse finale. Vous pouvez traiter ces informations comme vous le souhaitez pour votre demande.

L'image suivante montre le flux de données, les étapes de l'implémentation et le propriétaire de chaque étape (application, LLM ou API, par exemple) :

b9a39f55567072d3.png

Objectifs de l'atelier

Vous allez créer et déployer une fonction Cloud Java qui:

  • Prend les coordonnées de latitude et de longitude.
  • Il appelle l'API Google Maps Geocoding pour obtenir les adresses correspondantes.
  • Utilise la fonctionnalité d'appel de fonction de Gemini 1.0 Pro pour standardiser et résumer ces adresses de manière déterministe dans un format spécifique.

3. Conditions requises

  • Un navigateur (Chrome ou Firefox, par exemple)
  • Un projet Google Cloud avec facturation activée.

4. Avant de commencer

  1. Dans la console Google Cloud, sur la page du sélecteur de projet, sélectionnez ou créez un projet Google Cloud.
  2. Assurez-vous que la facturation est activée pour votre projet Google Cloud. Découvrez comment vérifier si la facturation est activée sur un projet.
  3. Activez Cloud Shell depuis la console Google Cloud. Pour en savoir plus, consultez la page Utiliser Cloud Shell.
  4. Si votre projet n'est pas défini, utilisez la commande suivante pour le définir:
gcloud config set project <YOUR_PROJECT_ID>
  1. Dans Cloud Shell, définissez les variables d'environnement suivantes:
export GCP_PROJECT=<YOUR_PROJECT_ID>
export GCP_REGION=us-central1
  1. Activez les API Google Cloud nécessaires en exécutant les commandes suivantes dans Cloud Shell:
gcloud services enable cloudbuild.googleapis.com cloudfunctions.googleapis.com run.googleapis.com logging.googleapis.com storage-component.googleapis.com cloudaicompanion.googleapis.com aiplatform.googleapis.com
  1. Ouvrez l'éditeur Cloud Shell, cliquez sur Extensions, puis installez l'extension Gemini + Google Cloud Code.

5. Implémenter la fonction Cloud

  1. Lancer l'éditeur de code Cloud Shell
  2. Cliquez sur Cloud Code, puis développez la section Cloud Functions.
  3. Cliquez sur l'icône Créer une fonction (+).
  4. Dans la boîte de dialogue Create New Application (Créer une application), sélectionnez l'option Java: Hello World.
  5. Indiquez un nom pour le projet dans son chemin d'accès, par exemple GeminiFunctionCalling.
  6. Cliquez sur Explorer pour afficher la structure du projet, puis ouvrez le fichier pom.xml. L'image suivante illustre la structure du projet:

bdf07515f413dd9e.png

  1. Ajoutez les dépendances nécessaires dans la balise <dependencies>... </dependencies> du fichier pom.xml. Vous pouvez accéder à l'intégralité de pom.xml à partir du dépôt GitHub de ce projet. Copiez le fichier pom.xml dans le fichier pom.xml de votre projet actuel que vous modifiez.
  2. Copiez la classe HelloWorld.java à partir du lien GeminiFunctionCalling github. Vous devez mettre à jour les champs API_KEY et project_id respectivement avec votre clé API de geocoding et votre ID de projet Google Cloud.

6. Comprendre l'appel de fonction à l'aide de la classe HelloWorld.java

Saisie de la requête

Dans cet exemple, la requête d'entrée suivante est la suivante: Quelle est l'adresse de la valeur latlong 40.714224,-73.961452.

Voici l'extrait de code correspondant à la requête d'entrée dans le fichier:

String promptText = "What's the address for the latlong value '" + latlngString + "'?"; //40.714224,-73.961452

Spécifications de l'API

L'API Reverse Geocoding est utilisée dans cet exemple. Voici la spécification de l'API:

/* Declare the function for the API to invoke (Geo coding API) */ 
FunctionDeclaration functionDeclaration =
    FunctionDeclaration.newBuilder()
        .setName("getAddress")
        .setDescription("Get the address for the given latitude and longitude value.")
        .setParameters(
            Schema.newBuilder()
                .setType(Type.OBJECT)
                .putProperties(
                    "latlng",
                    Schema.newBuilder()
                        .setType(Type.STRING)
                        .setDescription("This must be a string of latitude and longitude coordinates separated by comma")
                        .build())
                .addRequired("latlng")
                .build())
        .build();

Orchestrer la requête avec Gemini

L'entrée de la requête et la spécification de l'API sont envoyées à Gemini:

// Add the function to a "tool"
Tool tool = Tool.newBuilder()
.addFunctionDeclarations(functionDeclaration)
.build();

// Invoke the Gemini model with the use of the tool to generate the API parameters from the prompt input.
GenerativeModel model = GenerativeModel.newBuilder()
.setModelName(modelName)
.setVertexAi(vertexAI)
.setTools(Arrays.asList(tool))
.build();
GenerateContentResponse response = model.generateContent(promptText);
Content responseJSONCnt = response.getCandidates(0).getContent();

La réponse est la syntaxe JSON des paramètres envoyés à l'API. Voici un exemple de résultat:

role: "model"
parts {
 function_call {
   name: "getAddress"
   args {
     fields {
       key: "latlng"
       value {
         string_value: "40.714224,-73.961452"
       }
     }
   }
 }
}

Transmettez le paramètre suivant à l'API Reverse Geocoding: "latlng=40.714224,-73.961452"

Faites correspondre le résultat orchestré au format "latlng=VALUE".

Appeler l'API

Voici la section du code qui appelle l'API:

// Create a request
     String url = API_STRING + "?key=" + API_KEY + params;
     java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
         .uri(URI.create(url))
         .GET()
         .build();
     // Send the request and get the response
     java.net.http.HttpResponse<String> httpresponse = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
     // Save the response
     String jsonResult =  httpresponse.body().toString();

La chaîne jsonResult contient la réponse de l'API reverse Geocoding. Voici une version formatée du résultat:

"...277 Bedford Ave, Brooklyn, NY 11211, USA; 279 Bedford Ave, Brooklyn, NY 11211, USA; 277 Bedford Ave, Brooklyn, NY 11211, USA;..."

Traiter la réponse de l'API et préparer la requête

Le code suivant traite la réponse de l'API et prépare la requête avec des instructions sur la façon de traiter la réponse:

// Provide an answer to the model so that it knows what the result
     // of a "function call" is.
     String promptString =
     "You are an AI address standardizer for assisting with standardizing addresses accurately. Your job is to give the accurate address in the standard format as a JSON object containing the fields DOOR_NUMBER, STREET_ADDRESS, AREA, CITY, TOWN, COUNTY, STATE, COUNTRY, ZIPCODE, LANDMARK by leveraging the address string that follows in the end. Remember the response cannot be empty or null. ";

Content content =
         ContentMaker.fromMultiModalData(
             PartMaker.fromFunctionResponse(
                 "getAddress",
                 Collections.singletonMap("address", formattedAddress)));
     String contentString = content.toString();
     String address = contentString.substring(contentString.indexOf("string_value: \"") + "string_value: \"".length(), contentString.indexOf('"', contentString.indexOf("string_value: \"") + "string_value: \"".length()));

     List<SafetySetting> safetySettings = Arrays.asList(
       SafetySetting.newBuilder()
           .setCategory(HarmCategory.HARM_CATEGORY_HATE_SPEECH)
           .setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_ONLY_HIGH)
           .build(),
       SafetySetting.newBuilder()
           .setCategory(HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT)
           .setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_ONLY_HIGH)
           .build()
   );

Appeler Gemini et renvoyer l'adresse standardisée

Le code suivant transmet à Gemini la sortie traitée de l'étape précédente en tant qu'invite:

GenerativeModel modelForFinalResponse = GenerativeModel.newBuilder()
     .setModelName(modelName)
     .setVertexAi(vertexAI)
     .build();
     GenerateContentResponse finalResponse = modelForFinalResponse.generateContent(promptString + ": " + address, safetySettings);
      System.out.println("promptString + content: " + promptString + ": " + address);
       // See what the model replies now
       System.out.println("Print response: ");
       System.out.println(finalResponse.toString());
       String finalAnswer = ResponseHandler.getText(finalResponse);
       System.out.println(finalAnswer);

La variable finalAnswer possède l'adresse standardisée au format JSON. Voici un exemple de résultat:

{"replies":["{ \"DOOR_NUMBER\": null, \"STREET_ADDRESS\": \"277 Bedford Ave\", \"AREA\": \"Brooklyn\", \"CITY\": \"New York\", \"TOWN\": null, \"COUNTY\": null, \"STATE\": \"NY\", \"COUNTRY\": \"USA\", \"ZIPCODE\": \"11211\", \"LANDMARK\": null} null}"]}

Maintenant que vous savez comment fonctionne l'appel de fonction Gemini dans le cas d'utilisation de la standardisation des adresses, vous pouvez déployer la fonction Cloud.

7. Déployer et tester

  1. Si vous avez déjà créé le projet GeminiFunctionCalling et implémenté la fonction Cloud, passez à l'étape 2. Si vous n'avez pas créé le projet, accédez au terminal Cloud Shell et clonez ce dépôt: git clone https://github.com/AbiramiSukumaran/GeminiFunctionCalling
  2. Accédez au dossier du projet: cd GeminiFunctionCalling.
  3. Exécutez l'instruction suivante pour créer et déployer la fonction Cloud:
gcloud functions deploy gemini-fn-calling --gen2 --region=us-central1 --runtime=java11 --source=. --entry-point=cloudcode.helloworld.HelloWorld --trigger-http

Voici le format d'URL après le déploiement: https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/gemini-fn-calling

  1. Testez la fonction Cloud en exécutant la commande suivante depuis le terminal:
gcloud functions call gemini-fn-calling --region=us-central1 --gen2 --data '{"calls":[["40.714224,-73.961452"]]}'

Voici une réponse à un exemple de requête aléatoire: '{"replies":["{ "DOOR_NUMBER": "277", "STREET_ADDRESS": "Bedford Ave", "AREA": null, "CITY": "Brooklyn", "TOWN": null, "COUNTY": "Kings County", "STATE": "NY", "COUNTRY": "USA", "ZIPCODE": "11211", "LANDMARK": null}}```"]}'

8. Effectuer un nettoyage

Pour éviter que les ressources utilisées dans cet article soient facturées sur votre compte Google Cloud, procédez comme suit:

  1. Dans la console Google Cloud, accédez à la page Gérer les ressources.
  2. Dans la liste des projets, sélectionnez celui que vous souhaitez supprimer, puis cliquez sur "Supprimer".
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur "Arrêter" pour supprimer le projet.
  4. Si vous souhaitez conserver votre projet, ignorez les étapes ci-dessus et supprimez la fonction Cloud Functions en accédant à Cloud Functions. Dans la liste des fonctions, cochez celle que vous souhaitez supprimer, puis cliquez sur SUPPRIMER.

9. Félicitations

Félicitations ! Vous avez utilisé la fonctionnalité d'appel de fonction Gemini dans une application Java et avez transformé une tâche d'IA générative en un processus déterministe et fiable. Pour en savoir plus sur les modèles disponibles, consultez la documentation sur les LLM de Vertex AI.