Java での Gemini 関数呼び出しを使用した確定的な生成 AI

1. はじめに

生成 AI モデルは、自然言語を理解して応答することに優れています。しかし、住所の標準化のような重要なタスクのために、正確で予測可能な出力が必要な場合はどうすればよいでしょうか。従来の生成モデルでは、同じプロンプトに対して異なるタイミングで異なるレスポンスを返すことがあり、それが不整合につながる可能性があります。ここで威力を発揮するのが Gemini の関数呼び出し機能です。この機能を使用すると、AI のレスポンスの要素を確定的に制御できます。

この Codelab では、このコンセプトについて、住所補完と標準化のユースケースを使って説明します。そのために、次のタスクを行う Java の Cloud Functions の関数を作成します。

  1. 緯度と経度の座標を受け取ります
  2. Google Maps Geocoding API を呼び出して、対応する住所を取得する
  3. Gemini 1.0 Pro の関数呼び出し機能を使用して、これらの住所を決定的に標準化し、必要な特定の形式で要約します。

それでは詳しく見ていきましょう。

2. Gemini の関数呼び出し

Gemini の関数呼び出しは、生成言語モデルの柔軟性と従来のプログラミングの精度を融合できるという点で、生成 AI の時代に突出しています。

Gemini の関数呼び出しを実装するために必要なタスクは次のとおりです。

  1. 関数を定義する: 関数をわかりやすく説明します。説明には次の情報を含める必要があります。
  • 関数の名前(getAddress など)。
  • 関数が想定するパラメータ(文字列としての latlng など)。
  • 関数が返すデータのタイプ(住所文字列のリストなど)。
  1. Gemini 用ツールを作成する: 関数の説明を API 仕様の形式でツールにパッケージ化します。ツールは、Gemini が API の機能を理解するために使用できる専用のツールボックスだと考えてください。
  2. Gemini を使用して API をオーケストレートする: Gemini にプロンプトを送信すると、Gemini はリクエストを分析し、提供されたツールをどこで使用できるかを認識できます。Gemini はスマート オーケストレーターとして機能し、次のタスクを実行します。
  • 定義済みの関数を呼び出すために必要な API パラメータを生成します。Gemini がユーザーに代わって API を呼び出すことはありません。Gemini 関数呼び出しによって生成されたパラメータと署名に基づいて API を呼び出す必要があります。
  • Gemini は、API 呼び出しの結果をその生成にフィードすることで結果を処理し、構造化された情報を最終レスポンスに組み込みます。この情報は、アプリケーションに適した方法で処理できます。

次の図は、データのフロー、実装のステップ、各ステップ(アプリケーション、LLM、API など)のオーナーを示しています。

b9a39f55567072d3.png

作成するアプリの概要

次の処理を行う Java Cloud Functions の関数を作成してデプロイします。

  • 緯度と経度の座標を受け取ります。
  • Google Maps Geocoding API を呼び出して、対応する住所を取得します。
  • Gemini 1.0 Pro の関数呼び出し機能を使用して、これらの住所を決定的に標準化し、特定の形式に要約します。

3. 要件

  • ブラウザ(ChromeFirefox など)
  • 課金を有効にした Google Cloud プロジェクト

4. 始める前に

  1. Google Cloud コンソールのプロジェクト選択ページで、Google Cloud プロジェクトを選択または作成します。
  2. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
  3. Google Cloud コンソールから Cloud Shell をアクティブにします。詳細については、Cloud Shell を使用するをご覧ください。
  4. プロジェクトが設定されていない場合は、次のコマンドを使用してプロジェクトを設定します。
gcloud config set project <YOUR_PROJECT_ID>
  1. Cloud Shell で、次の環境変数を設定します。
export GCP_PROJECT=<YOUR_PROJECT_ID>
export GCP_REGION=us-central1
  1. Cloud Shell で次のコマンドを実行して、必要な Google Cloud APIs を有効にします。
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. Cloud Shell エディタを開き、[拡張機能] をクリックして、Gemini + Google Cloud Code 拡張機能をインストールします。

5. Cloud Functions の関数を実装する

  1. Cloud Shell エディタを起動する
  2. [Cloud Code] をクリックし、[Cloud Functions] セクションを開きます。
  3. [Create Function](+)アイコンをクリックします。
  4. [Create New Application] ダイアログで、[Java: Hello World] オプションを選択します。
  5. プロジェクト パスでプロジェクトの名前を入力します(例: GeminiFunctionCalling)。
  6. [Explorer] をクリックしてプロジェクト構造を表示し、pom.xml ファイルを開きます。次の図は、プロジェクトの構造を示しています。

bdf07515f413dd9e.png

  1. pom.xml ファイルの <dependencies>... </dependencies> タグ内に必要な依存関係を追加します。このプロジェクトの GitHub リポジトリから pom.xml 全体にアクセスできます。そこから、編集中の現在のプロジェクトの pom.xml ファイルに pom.xml をコピーします。
  2. GeminiFunctionCalling GitHub リンクから HelloWorld.java クラスをコピーします。API_KEYproject_id をそれぞれ、ジオコーディング API キーと Google Cloud プロジェクト ID に更新する必要があります。

6. HelloWorld.java クラスを使用して関数呼び出しを理解する

プロンプト入力

この例では、入力プロンプトは「What's the address for the latlong value 40.714224,-73.961452」です。

以下に、ファイルの入力プロンプトに対応するコード スニペットを示します。

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

API 仕様

この例では、Reverse Geocoding API を使用しています。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();

Gemini でプロンプトをオーケストレートする

プロンプト入力と API 仕様が 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();

からのレスポンスは、API にオーケストレーションされたパラメータの JSON です。出力例は次のとおりです。

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

Reverse Geocoding API にパラメータ "latlng=40.714224,-73.961452" を渡します。

オーケストレートされた結果を "latlng=VALUE" 形式に一致させます。

API を呼び出す

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

文字列 jsonResult には、リバース Geocoding API からのレスポンスが保持されます。出力のフォーマット バージョンは次のとおりです。

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

API レスポンスを処理してプロンプトを準備する

次のコードは、API からのレスポンスを処理し、レスポンスの処理方法を示すプロンプトを準備します。

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

Gemini を呼び出して標準化された住所を返す

次のコードは、前のステップで処理された出力をプロンプトとして 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);

finalAnswer 変数には、JSON 形式の標準化された住所があります。出力例は次のとおりです。

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

Gemini の関数呼び出しが住所の標準化のユースケースでどのように機能するかを理解したところで、次は Cloud Functions の関数をデプロイします。

7. デプロイとテスト

  1. すでに GeminiFunctionCalling プロジェクトを作成して Cloud Functions の関数を実装している場合は、ステップ 2 に進みます。プロジェクトを作成していない場合は、Cloud Shell ターミナルに移動して、次のリポジトリのクローンを作成します。git clone https://github.com/AbiramiSukumaran/GeminiFunctionCalling
  2. プロジェクト フォルダ cd GeminiFunctionCalling に移動します。
  3. 次のステートメントを実行して、Cloud Functions の関数をビルドしてデプロイします。
gcloud functions deploy gemini-fn-calling --gen2 --region=us-central1 --runtime=java11 --source=. --entry-point=cloudcode.helloworld.HelloWorld --trigger-http

デプロイ後の URL 形式は次のとおりです。https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/gemini-fn-calling

  1. ターミナルから次のコマンドを実行して、Cloud Functions の関数をテストします。
gcloud functions call gemini-fn-calling --region=us-central1 --gen2 --data '{"calls":[["40.714224,-73.961452"]]}'

ランダムなサンプル プロンプトに対するレスポンスは次のとおりです。'{"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. クリーンアップ

この投稿で使用したリソースについて、Google Cloud アカウントに課金されないようにするには、次の操作を行います。

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。
  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。
  4. プロジェクトを残しておく場合は、上記の手順をスキップして、Cloud Functions に移動して Cloud Functions の関数を削除します。関数のリストで、削除する関数をオンにして [削除] をクリックします。

9. 完了

これで、Java アプリケーションで Gemini の関数呼び出し機能を使用し、生成 AI のタスクを決定論的で信頼性の高いプロセスに変換できました。使用可能なモデルの詳細については、Vertex AI LLM プロダクトのドキュメントをご覧ください。