Deterministyczna generatywna AI z wywołaniem funkcji Gemini w języku Java

1. Wprowadzenie

Modele generatywnej AI wyróżniają się rozumieniem języka naturalnego i reagowaniem na niego. A co, jeśli potrzebujesz precyzyjnych i przewidywalnych danych wyjściowych do realizacji kluczowych zadań, takich jak standaryzacja? Tradycyjne modele generatywne mogą czasami dostarczać różne odpowiedzi w różnym czasie na te same prompty, co może prowadzić do niespójności. To właśnie wyróżnia funkcje Gemini, które pozwalają w sposób deterministyczny kontrolować elementy odpowiedzi AI.

To ćwiczenie w Codelabs ilustruje tę koncepcję wraz z uzupełnieniem adresu i przypadkiem standaryzacji. Chcemy utworzyć funkcję w Cloud Functions w języku Java, która wykona te zadania:

  1. Przyjmuje współrzędne szerokości i długości geograficznej
  2. Wywołuje interfejs Geocoding API Map Google, aby uzyskać odpowiednie adresy
  3. Wykorzystuje funkcję połączeń funkcji Gemini 1.0 Pro w celu deterministycznego standaryzacji i podsumowywania tych adresów w odpowiednim formacie, którego potrzebujemy.

Zaczynamy!

2. Wywoływanie funkcji Gemini

Wywołanie funkcji w Gemini wyróżnia się w erze generatywnej AI, ponieważ umożliwia połączenie elastyczności generatywnych modeli językowych z precyzją tradycyjnego programowania.

Oto zadania, które musisz wykonać, aby wdrożyć funkcję wywoływania funkcji Gemini:

  1. Zdefiniuj funkcje: jasno opisz funkcje. Opisy muszą zawierać następujące informacje:
  • Nazwa funkcji, na przykład getAddress.
  • Parametry, których funkcja oczekuje, na przykład latlng jako ciąg znaków.
  • Typ danych zwracanych przez funkcję, na przykład lista ciągów adresów.
  1. Utwórz narzędzia dla Gemini: przekształcaj opisy funkcji w narzędzia w formie specyfikacji API. Potraktuj narzędzie jako specjalistyczny zestaw narzędzi, z którego Gemini może korzystać do analizowania funkcji interfejsu API.
  2. Orchestrate API za pomocą Gemini: gdy wyślesz prompt do Gemini, może on przeanalizować Twoje żądanie i określić, do czego może użyć dostarczonych przez Ciebie narzędzi. Gemini będzie pełnił funkcję inteligentnego administracyjnego zespołu, wykonując następujące zadania:
  • Generuje parametry interfejsu API niezbędne do wywołania zdefiniowanych funkcji. Gemini nie wywołuje interfejsu API w Twoim imieniu. Musisz wywołać interfejs API na podstawie parametrów i podpisu wygenerowanego dla Ciebie przez wywołanie funkcji Gemini.
  • Gemini przetwarza wyniki, przesyłając wyniki z wywołań interfejsu API z powrotem do ich generowania oraz uwzględnia uporządkowane informacje w ostatecznej odpowiedzi. Możesz przetwarzać te informacje w wybrany przez siebie sposób.

Ten obraz przedstawia przepływ danych, kroki związane z implementacją i właściciela każdego z nich, np. aplikacji, LLM lub API:

b9a39f55567072d3.png

Co utworzysz

Utworzysz i wdrożysz funkcję w Cloud Functions w języku Java, która:

  • Przyjmuje współrzędne szerokości i długości geograficznej.
  • Wywołuje Google Maps Geocoding API, aby uzyskać odpowiednie adresy.
  • Wykorzystuje funkcję wywoływania funkcji Gemini 1.0 Pro, aby deterministycznie standaryzować i podsumowywać te adresy w określonym formacie.

3. Wymagania

  • Przeglądarka, na przykład Chrome lub Firefox.
  • Projekt Google Cloud z włączonymi płatnościami.

4. Zanim zaczniesz

  1. W konsoli Google Cloud na stronie selektora projektów wybierz lub utwórz projekt Google Cloud.
  2. Sprawdź, czy w projekcie Google Cloud są włączone płatności. Dowiedz się, jak sprawdzić, czy w projekcie są włączone płatności.
  3. Aktywuj Cloud Shell z konsoli Google Cloud. Więcej informacji znajdziesz w artykule Korzystanie z Cloud Shell.
  4. Jeśli Twój projekt nie jest skonfigurowany, ustaw go za pomocą tego polecenia:
gcloud config set project <YOUR_PROJECT_ID>
  1. W Cloud Shell ustaw te zmienne środowiskowe:
export GCP_PROJECT=<YOUR_PROJECT_ID>
export GCP_REGION=us-central1
  1. Włącz niezbędne interfejsy API Google Cloud, wykonując w Cloud Shell te polecenia:
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. Otwórz edytor Cloud Shell, kliknij Rozszerzenia, a następnie zainstaluj rozszerzenie Gemini + Google Cloud Code.

5. Wdrażanie funkcji w Cloud Functions

  1. Uruchom edytor Cloud Shell.
  2. Kliknij Cloud Code, a następnie rozwiń sekcję Cloud Functions.
  3. Kliknij ikonę Utwórz funkcję (+).
  4. W oknie Tworzenie nowej aplikacji wybierz opcję Java: Hello World.
  5. Podaj nazwę projektu w ścieżce projektu, na przykład GeminiFunctionCalling.
  6. Kliknij Explorer, aby wyświetlić strukturę projektu, a następnie otwórz plik pom.xml. Poniższa grafika przedstawia strukturę projektu:

bdf07515f413dd9e.png

  1. Dodaj niezbędne zależności w tagu <dependencies>... </dependencies> w pliku pom.xml. Dostęp do całego zasobu pom.xml możesz uzyskać z repozytorium GitHub tego projektu. Skopiuj stamtąd plik pom.xml do edytowanego przez Ciebie pliku pom.xml bieżącego projektu.
  2. Skopiuj klasę HelloWorld.java z linku GeminiFunctionCalling na github. Musisz zaktualizować API_KEY i project_id odpowiednio kluczem interfejsu API geokodowania i identyfikatorem projektu Google Cloud.

6. Omówienie wywoływania funkcji za pomocą klasy HelloWorld.java

Wprowadzanie promptów

W tym przykładzie ciąg znaków wejściowy to: Jaki jest adres dla wartości szerokości geograficznej 40.714224,-73.961452.

Poniżej znajduje się fragment kodu odpowiadający promptowi wejściowemu w pliku:

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

Specyfikacja interfejsu API

W tym przykładzie użyto interfejsu Reverse Geocoding API. Oto specyfikacja interfejsu 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();

Tworzenie promptu za pomocą Gemini

Dane wejściowe promptu i specyfikacja interfejsu API są wysyłane do 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();

Odpowiedź z tego komunikatu to administrowane parametry w formacie JSON dla interfejsu API. Oto przykładowe dane wyjściowe:

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

Przekaż do interfejsu API Reverse Geocoding ten parametr: "latlng=40.714224,-73.961452"

Dopasuj wynik administracyjny do formatu "latlng=VALUE".

Wywoływanie interfejsu API

Poniżej znajduje się sekcja kodu wywołującego interfejs 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();

Ciąg jsonResult zawiera odpowiedź z back Geocoding API. Oto sformatowana wersja danych wyjściowych:

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

Przetworzenie odpowiedzi interfejsu API i przygotowanie promptu

Ten kod przetwarza odpowiedź z interfejsu API i przygotowuje prompt wraz z instrukcjami:

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

Wywołaj Gemini i zwróć standardowy adres.

Ten kod przekazuje do Gemini przetworzone dane wyjściowe z poprzedniego kroku jako prompt:

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

Zmienna finalAnswer ma standardowy adres w formacie JSON. Oto przykładowe dane wyjściowe:

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

Wiesz już, jak wywoływanie funkcji w Gemini sprawdza się w przypadku standaryzacji adresów, możesz więc przejść do wdrożenia funkcji w Cloud Functions.

7. Wdrażanie i testowanie

  1. Jeśli masz już projekt GeminiFunctionCalling i wdrożoną funkcję w Cloud Functions, przejdź do kroku 2. Jeśli projekt nie został jeszcze utworzony, otwórz terminal Cloud Shell i skopiuj to repozytorium: git clone https://github.com/AbiramiSukumaran/GeminiFunctionCalling
  2. Przejdź do folderu projektu: cd GeminiFunctionCalling
  3. Uruchom tę instrukcję, aby skompilować i wdrożyć funkcję w Cloud Functions:
gcloud functions deploy gemini-fn-calling --gen2 --region=us-central1 --runtime=java11 --source=. --entry-point=cloudcode.helloworld.HelloWorld --trigger-http

Po wdrożeniu ten format adresu URL wygląda tak: https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/gemini-fn-calling

  1. Przetestuj funkcję w Cloud Functions, uruchamiając w terminalu to polecenie:
gcloud functions call gemini-fn-calling --region=us-central1 --gen2 --data '{"calls":[["40.714224,-73.961452"]]}'

Oto odpowiedź na losowy przykładowy prompt: '{"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. Czyszczenie danych

Aby uniknąć obciążenia konta Google Cloud opłatami za zasoby zużyte w tym poście, wykonaj te czynności:

  1. W konsoli Google Cloud otwórz stronę Zarządzanie zasobami.
  2. Na liście projektów wybierz projekt do usunięcia, a potem kliknij Usuń.
  3. W oknie wpisz identyfikator projektu i kliknij Wyłącz, aby usunąć projekt.
  4. Jeśli chcesz zachować projekt, pomiń powyższe kroki i usuń funkcję w Cloud Functions, przechodząc do Cloud Functions i z listy funkcji zaznacz tę, którą chcesz usunąć, i kliknij USUŃ.

9. Gratulacje

Gratulacje! Udało Ci się użyć funkcji wywoływania funkcji Gemini w aplikacji Java i przekształcić zadanie generatywnej AI w deterministyczny, niezawodny proces. Więcej informacji o dostępnych modelach znajdziesz w dokumentacji usługi Vertex AI LLM.