IA generativa determinista con llamadas a funciones de Gemini en Java

1. Introducción

Los modelos de IA generativa son notables por comprender el lenguaje natural y responder a él. Pero ¿qué sucede si necesitas resultados precisos y predecibles para tareas críticas, como la estandarización de direcciones? Los modelos generativos tradicionales a veces pueden proporcionar respuestas diferentes en diferentes momentos para las mismas instrucciones, lo que podría generar inconsistencias. Ahí es donde se destaca la capacidad de llamada a función de Gemini, que te permite controlar de forma determinística los elementos de la respuesta de la IA.

En este codelab, se ilustra este concepto con el caso de uso de estandarización y finalización de direcciones. Para ello, compilaremos una Cloud Function de Java que realice las siguientes tareas:

  1. Toma las coordenadas de latitud y longitud.
  2. Llama a la API de Geocoding de Google Maps para obtener las direcciones correspondientes.
  3. Usa la función de llamada a función de Gemini 1.0 Pro para estandarizar y resumir de forma determinística esas direcciones en un formato específico que necesitamos.

Comencemos.

2. Llamada a función de Gemini

La llamada a función de Gemini se destaca en la era de la IA generativa porque te permite combinar la flexibilidad de los modelos de lenguaje generativos con la precisión de la programación tradicional.

Estas son las tareas que debes completar para implementar la llamada a función de Gemini:

  1. Define funciones: Describe las funciones con claridad. Las descripciones deben incluir la siguiente información:
  • El nombre de la función, como getAddress.
  • Los parámetros que espera la función, como latlng como una cadena.
  • El tipo de datos que muestra la función, como una lista de cadenas de direcciones.
  1. Crea herramientas para Gemini: Empaqueta las descripciones de funciones en forma de especificación de API en herramientas. Piensa en una herramienta como una caja de herramientas especializada que Gemini puede usar para comprender la funcionalidad de la API.
  2. Orquesta APIs con Gemini: Cuando envías una instrucción a Gemini, puede analizar tu solicitud y reconocer dónde puede usar las herramientas que proporcionaste. Luego, Gemini actúa como un orquestador inteligente realizando las siguientes tareas:
  • Genera los parámetros de API necesarios para llamar a las funciones definidas. Gemini no llama a la API en tu nombre. Debes llamar a la API según los parámetros y la firma que la llamada a función de Gemini generó para ti.
  • Gemini procesa los resultados enviando los resultados de tus llamadas a la API a su generación y, luego, incorpora información estructurada en su respuesta final. Puedes procesar esta información de la manera que desees para tu aplicación.

En la siguiente imagen, se muestra el flujo de datos, los pasos involucrados en la implementación y el propietario de cada paso, como la aplicación, el LLM o la API:

b9a39f55567072d3.png

Qué compilarás

Crearás e implementarás una Cloud Function de Java que haga lo siguiente:

  • Toma las coordenadas de latitud y longitud.
  • Llama a la API de Geocoding de Google Maps para obtener las direcciones correspondientes.
  • Usa la función de llamada a función de Gemini 1.0 Pro para estandarizar y resumir de forma determinística esas direcciones en un formato específico.

3. Requisitos

  • Un navegador, como Chrome o Firefox.
  • Un proyecto de Google Cloud con facturación habilitada.

4. Antes de comenzar

  1. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.
  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud. Obtén información sobre cómo verificar si la facturación está habilitada en un proyecto.
  3. Activa Cloud Shell desde la consola de Google Cloud. Para obtener más información, consulta Usa Cloud Shell.
  4. Si tu proyecto no está configurado, usa el siguiente comando para hacerlo:
gcloud config set project <YOUR_PROJECT_ID>
  1. En Cloud Shell, establece las siguientes variables de entorno:
export GCP_PROJECT=<YOUR_PROJECT_ID>
export GCP_REGION=us-central1
  1. Habilita las APIs de Google Cloud necesarias ejecutando los siguientes comandos en 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. Abre el editor de Cloud Shell, haz clic en Extensiones y, luego, instala la extensión Gemini + Google Cloud Code.

5. Implementa la Cloud Function

  1. Inicia el editor de Cloud Shell.
  2. Haz clic en Cloud Code y, luego, expande la sección Cloud Functions.
  3. Haz clic en el ícono Crear función (+).
  4. En el diálogo Crear aplicación nueva, selecciona la opción Java: Hello World.
  5. Proporciona un nombre para el proyecto en la ruta de acceso del proyecto, como GeminiFunctionCalling.
  6. Haz clic en Explorador para ver la estructura del proyecto y, luego, abre el archivo pom.xml. En la siguiente imagen, se muestra la estructura del proyecto:

bdf07515f413dd9e.png

  1. Agrega las dependencias necesarias dentro de la etiqueta <dependencies>... </dependencies> en el archivo pom.xml. Puedes acceder a todo el pom.xml desde el repositorio de GitHub de este proyecto. Copia el pom.xml de allí en el archivo pom.xml actual de tu proyecto que estás editando.
  2. Copia la clase HelloWorld.java del vínculo de GitHub de GeminiFunctionCalling. Debes actualizar API_KEY y project_id con tu clave de API de geocodificación y el ID del proyecto de Google Cloud, respectivamente.

6. Comprende la llamada a función con la clase HelloWorld.java

Instrucción de entrada

En este ejemplo, la siguiente es la instrucción de entrada: What's the address for the latlong value 40.714224,-73.961452.

El siguiente es el fragmento de código correspondiente a la instrucción de entrada en el archivo:

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

Especificación de la API

En este ejemplo, se usa la API de Reverse Geocoding. La siguiente es la especificación de la 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();

Orquesta la instrucción con Gemini

La instrucción de entrada y la especificación de la API se envían a 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 respuesta es el JSON de parámetros orquestados a la API. A continuación, se muestra un ejemplo de resultado:

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

Pasa el siguiente parámetro a la API de Reverse Geocoding: "latlng=40.714224,-73.961452"

Haz coincidir el resultado orquestado con el formato "latlng=VALUE".

Invoca la API

La siguiente es la sección del código que invoca la 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 cadena jsonResult contiene la respuesta de la API de Reverse Geocoding. La siguiente es una versión con formato del resultado:

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

Procesa la respuesta de la API y prepara la instrucción

El siguiente código procesa la respuesta de la API y prepara la instrucción con instrucciones sobre cómo procesar la respuesta:

// 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()
   );

Invoca a Gemini y muestra la dirección estandarizada

El siguiente código pasa el resultado procesado del paso anterior como instrucción a Gemini:

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 tiene la dirección estandarizada en formato JSON. A continuación, se muestra un ejemplo de resultado:

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

Ahora que comprendes cómo funciona la llamada a función de Gemini con el caso de uso de estandarización de direcciones, puedes implementar la Cloud Function.

7. Implementación y prueba

  1. Si ya creaste el proyecto GeminiFunctionCalling y implementaste la Cloud Function, continúa con el paso 2. Si no creaste el proyecto, ve a la terminal de Cloud Shell y clona este repositorio: git clone https://github.com/AbiramiSukumaran/GeminiFunctionCalling
  2. Navega a la carpeta del proyecto: cd GeminiFunctionCalling
  3. Ejecuta la siguiente instrucción para compilar e implementar la Cloud Function:
gcloud functions deploy gemini-fn-calling --gen2 --region=us-central1 --runtime=java11 --source=. --entry-point=cloudcode.helloworld.HelloWorld --trigger-http

El siguiente es el formato de la URL después de la implementación: https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/gemini-fn-calling

  1. Para probar la Cloud Function, ejecuta el siguiente comando desde la terminal:
gcloud functions call gemini-fn-calling --region=us-central1 --gen2 --data '{"calls":[["40.714224,-73.961452"]]}'

La siguiente es una respuesta para una instrucción de muestra aleatoria: '{"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. Limpia

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos que usaste en esta publicación:

  1. En la consola de Google Cloud, ve a la página Administrar recursos.
  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrarlo.
  4. Si quieres conservar tu proyecto, omite los pasos anteriores y borra la Cloud Function. Para ello, navega a Cloud Functions y, en la lista de funciones, marca la que quieres borrar y haz clic en BORRAR.

9. Felicitaciones

¡Felicitaciones! Usaste correctamente la función de llamada a función de Gemini en una aplicación de Java y transformaste una tarea de IA generativa en un proceso determinístico y confiable. Para obtener más información sobre los modelos disponibles, consulta la documentación del producto de LLM de Vertex AI.