1. はじめに
概要
この Codelab では、関数呼び出しという新機能を使用して、Gemini にリアルタイム データへのアクセス権を付与する方法を学びます。リアルタイム データをシミュレートするために、2 か所の現在の天気情報を返す気象サービスのエンドポイントを作成します。次に、関数呼び出しを使用して現在の天気情報を取得する、Gemini を利用したチャットアプリを作成します。
簡単な図で関数呼び出しを理解しましょう。
- 特定の場所の現在の天気情報をリクエストするプロンプト
- このプロンプトと getWeather() の関数コントラクトが Gemini に送信されます
- Gemini が chat bot アプリが「getWeather(Seattle)」を呼び出すよう求めるを代表して
- アプリが結果を返送します(華氏 40 度、雨)。
- Gemini が呼び出し元に結果を返す
要約すると、Gemini が関数を呼び出すことはありません。デベロッパーは、この関数を呼び出して Gemini に結果を返す必要があります。
学習内容
- Gemini の関数呼び出しの仕組み
- Gemini 搭載の chatbot アプリを Cloud Run サービスとしてデプロイする方法
2. 設定と要件
前提条件
- Cloud コンソールにログインしています。
- 以前に第 2 世代の関数をデプロイしました。たとえば、Cloud Functions 第 2 世代のクイックスタートのデプロイの手順に沿って開始できます。
Cloud Shell をアクティブにする
- Cloud Console で、[Cloud Shell をアクティブにする] をクリックします。
Cloud Shell を初めて起動する場合は、内容を説明する中間画面が表示されます。中間画面が表示されたら、[続行] をクリックします。
Cloud Shell のプロビジョニングと接続に少し時間がかかる程度です。
この仮想マシンには、必要なすべての開発ツールが読み込まれます。5 GB の永続的なホーム ディレクトリが用意されており、Google Cloud で稼働するため、ネットワークのパフォーマンスと認証が大幅に向上しています。この Codelab での作業のほとんどはブラウザを使って行うことができます。
Cloud Shell に接続すると、認証が完了し、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。
- Cloud Shell で次のコマンドを実行して、認証されたことを確認します。
gcloud auth list
コマンド出力
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project
コマンド出力
[core] project = <PROJECT_ID>
上記のようになっていない場合は、次のコマンドで設定できます。
gcloud config set project <PROJECT_ID>
コマンド出力
Updated property [core/project].
3. 環境変数を設定して API を有効にする
環境変数を設定する
この Codelab 全体で使用する環境変数を設定できます。
PROJECT_ID=<YOUR_PROJECT_ID> REGION=<YOUR_REGION, e.g. us-central1> WEATHER_SERVICE=weatherservice FRONTEND=frontend SERVICE_ACCOUNT="vertex-ai-caller" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
API を有効にする
この Codelab を使用する前に、いくつかの API を有効にする必要があります。この Codelab では、次の API を使用する必要があります。これらの API を有効にするには、次のコマンドを実行します。
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com
4. Vertex AI を呼び出すためのサービス アカウントを作成する
このサービス アカウントは、Cloud Run が Vertex AI Gemini API を呼び出すために使用します。
まず、次のコマンドを実行してサービス アカウントを作成します。
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
次に、サービス アカウントに Vertex AI ユーザーロールを付与します。
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
5. バックエンドの Cloud Run サービスを作成する
まず、ソースコード用のディレクトリを作成し、そのディレクトリに移動します。
mkdir -p gemini-function-calling/weatherservice gemini-function-calling/frontend && cd gemini-function-calling/weatherservice
次に、次の内容の package.json
ファイルを作成します。
{ "name": "weatherservice", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.18.3" } }
次に、以下の内容で app.js
ソースファイルを作成します。このファイルには、サービスのエントリ ポイントと、アプリのメインロジックが含まれています。
const express = require("express"); const app = express(); app.get("/getweather", (req, res) => { const location = req.query.location; let temp, conditions; if (location == "New Orleans") { temp = 99; conditions = "hot and humid"; } else if (location == "Seattle") { temp = 40; conditions = "rainy and overcast"; } else { res.status(400).send("there is no data for the requested location"); } res.json({ weather: temp, location: location, conditions: conditions }); }); const port = parseInt(process.env.PORT) || 8080; app.listen(port, () => { console.log(`weather service: listening on port ${port}`); }); app.get("/", (req, res) => { res.send("welcome to hard-coded weather!"); });
Weather Service をデプロイする
このコマンドを使用して、天気サービスをデプロイできます。
gcloud run deploy $WEATHER_SERVICE \ --source . \ --region $REGION \ --allow-unauthenticated
Weather Service をテストする
curl を使用して、2 つの場所の天気を確認できます。
WEATHER_SERVICE_URL=$(gcloud run services describe $WEATHER_SERVICE \ --platform managed \ --region=$REGION \ --format='value(status.url)') curl $WEATHER_SERVICE_URL/getweather?location=Seattle curl $WEATHER_SERVICE_URL/getweather?location\=New%20Orleans
シアトルは華氏 40 度で雨が降り、ニューオーリンズは華氏 99 度でいつも湿度があります。
6. フロントエンド サービスを作成する
まず、cd でフロントエンド ディレクトリに移動します。
cd gemini-function-calling/frontend
次に、次の内容の package.json
ファイルを作成します。
{ "name": "demo1", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node app.js", "nodemon": "nodemon app.js", "cssdev": "npx tailwindcss -i ./input.css -o ./public/output.css --watch", "tailwind": "npx tailwindcss -i ./input.css -o ./public/output.css", "dev": "npm run tailwind && npm run nodemon" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@google-cloud/vertexai": "^0.4.0", "axios": "^1.6.7", "express": "^4.18.2", "express-ws": "^5.0.2", "htmx.org": "^1.9.10" }, "devDependencies": { "nodemon": "^3.1.0", "tailwindcss": "^3.4.1" } }
次に、以下の内容で app.js
ソースファイルを作成します。このファイルには、サービスのエントリ ポイントと、アプリのメインロジックが含まれています。
const express = require("express"); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); const path = require("path"); const fs = require("fs"); const util = require("util"); const { spinnerSvg } = require("./spinnerSvg.js"); const expressWs = require("express-ws")(app); app.use(express.static("public")); const { VertexAI, FunctionDeclarationSchemaType } = require("@google-cloud/vertexai"); // get project and location from metadata service const metadataService = require("./metadataService.js"); // instance of Gemini model let generativeModel; // 1: define the function const functionDeclarations = [ { function_declarations: [ { name: "getweather", description: "get weather for a given location", parameters: { type: FunctionDeclarationSchemaType.OBJECT, properties: { location: { type: FunctionDeclarationSchemaType.STRING }, degrees: { type: FunctionDeclarationSchemaType.NUMBER, "description": "current temperature in fahrenheit" }, conditions: { type: FunctionDeclarationSchemaType.STRING, "description": "how the weather feels subjectively" } }, required: ["location"] } } ] } ]; // on instance startup const port = parseInt(process.env.PORT) || 8080; app.listen(port, async () => { console.log(`demo1: listening on port ${port}`); const project = await metadataService.getProjectId(); const location = await metadataService.getRegion(); // Vertex client library instance const vertex_ai = new VertexAI({ project: project, location: location }); // Instantiate models generativeModel = vertex_ai.getGenerativeModel({ model: "gemini-1.0-pro-001" }); }); const axios = require("axios"); const baseUrl = "https://weatherservice-k6msmyp47q-uc.a.run.app"; app.ws("/sendMessage", async function (ws, req) { // this chat history will be pinned to the current // Cloud Run instance. Consider using Firestore & // Firebase anonymous auth instead. // start ephemeral chat session with Gemini const chatWithModel = generativeModel.startChat({ tools: functionDeclarations }); ws.on("message", async function (message) { let questionToAsk = JSON.parse(message).message; console.log("WebSocket message: " + questionToAsk); ws.send(`<div hx-swap-oob="beforeend:#toupdate"><div id="questionToAsk" class="text-black m-2 text-right border p-2 rounded-lg ml-24"> ${questionToAsk} </div></div>`); // to simulate a natural pause in conversation await sleep(500); // get timestamp for div to replace const now = "fromGemini" + Date.now(); ws.send(`<div hx-swap-oob="beforeend:#toupdate"><div id=${now} class=" text-blue-400 m-2 text-left border p-2 rounded-lg mr-24"> ${spinnerSvg} </div></div>`); const results = await chatWithModel.sendMessage(questionToAsk); // Function calling demo let response1 = await results.response; let data = response1.candidates[0].content.parts[0]; let methodToCall = data.functionCall; if (methodToCall === undefined) { console.log("Gemini says: ", data.text); ws.send(`<div id=${now} hx-swap-oob="true" hx-swap="outerHTML" class="text-blue-400 m-2 text-left border p-2 rounded-lg mr-24"> ${data.text} </div>`); // bail out - Gemini doesn't want to return a function return; } // otherwise Gemini wants to call a function console.log( "Gemini wants to call: " + methodToCall.name + " with args: " + util.inspect(methodToCall.args, { showHidden: false, depth: null, colors: true }) ); // make the external call let jsonReturned; try { const responseFunctionCalling = await axios.get( baseUrl + "/" + methodToCall.name, { params: { location: methodToCall.args.location } } ); jsonReturned = responseFunctionCalling.data; } catch (ex) { // in case an invalid location was provided jsonReturned = ex.response.data; } console.log("jsonReturned: ", jsonReturned); // tell the model what function we just called const functionResponseParts = [ { functionResponse: { name: methodToCall.name, response: { name: methodToCall.name, content: { jsonReturned } } } } ]; // // Send a follow up message with a FunctionResponse const result2 = await chatWithModel.sendMessage( functionResponseParts ); // This should include a text response from the model using the response content // provided above const response2 = await result2.response; let answer = response2.candidates[0].content.parts[0].text; console.log("answer: ", answer); ws.send(`<div id=${now} hx-swap-oob="true" hx-swap="outerHTML" class="text-blue-400 m-2 text-left border p-2 rounded-lg mr-24"> ${answer} </div>`); }); ws.on("close", () => { console.log("WebSocket was closed"); }); }); function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }
tailwindCSS の input.css
ファイルを作成します。
@tailwind base; @tailwind components; @tailwind utilities;
tailwindCSS の tailwind.config.js
ファイルを作成します。
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
デプロイされた Cloud Run サービスのプロジェクト ID とリージョンを取得するための metadataService.js
ファイルを作成します。これらの値は、Vertex AI クライアント ライブラリのインスタンスをインスタンス化するために使用されます。
const your_project_id = "YOUR_PROJECT_ID"; const your_region = "YOUR_REGION"; const axios = require("axios"); module.exports = { getProjectId: async () => { let project = ""; try { // Fetch the token to make a GCF to GCF call const response = await axios.get( "http://metadata.google.internal/computeMetadata/v1/project/project-id", { headers: { "Metadata-Flavor": "Google" } } ); if (response.data == "") { // running locally on Cloud Shell project = your_project_id; } else { // use project id from metadata service project = response.data; } } catch (ex) { // running locally on local terminal project = your_project_id; } return project; }, getRegion: async () => { let region = ""; try { // Fetch the token to make a GCF to GCF call const response = await axios.get( "http://metadata.google.internal/computeMetadata/v1/instance/region", { headers: { "Metadata-Flavor": "Google" } } ); if (response.data == "") { // running locally on Cloud Shell region = your_region; } else { // use region from metadata service let regionFull = response.data; const index = regionFull.lastIndexOf("/"); region = regionFull.substring(index + 1); } } catch (ex) { // running locally on local terminal region = your_region; } return region; } };
spinnerSvg.js
というファイルを作成します。
module.exports.spinnerSvg = `<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path></svg>`;
新しい public
ディレクトリを作成します。
mkdir public cd public
次に、htmx を使用するフロントエンドの index.html
ファイルを作成します。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous" ></script> <link href="./output.css" rel="stylesheet" /> <script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script> <title>Demo 2</title> </head> <body> <div id="herewego" text-center> <!-- <div id="replaceme2" hx-swap-oob="true">Hello world</div> --> <div class="container mx-auto mt-8 text-center max-w-screen-lg" > <div class="overflow-y-scroll bg-white p-2 border h-[500px] space-y-4 rounded-lg m-auto" > <div id="toupdate"></div> </div> <form hx-trigger="submit, keyup[keyCode==13] from:body" hx-ext="ws" ws-connect="/sendMessage" ws-send="" hx-on="htmx:wsAfterSend: document.getElementById('message').value = ''" > <div class="mb-6 mt-6 flex gap-4"> <textarea rows="2" type="text" id="message" name="message" class="block grow rounded-lg border p-6 resize-none" required > What's is the current weather in Seattle?</textarea > <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded-lg text-center text-sm font-medium" > Send </button> </div> </form> </div> </div> </body> </html>
7. フロントエンド サービスをローカルで実行する
まず、Codelab のディレクトリ frontend
にいることを確認します。
cd .. && pwd
次に、次のコマンドを実行して依存関係をインストールします。
npm install
ローカル実行時の ADC の使用
Cloud Shell で実行している場合は、すでに Google Compute Engine 仮想マシンで実行されています。この仮想マシンに関連付けられている認証情報(gcloud auth list
を実行することで表示)は、アプリケーションのデフォルト認証情報で自動的に使用されるため、gcloud auth application-default login
コマンドを使用する必要はありません。アプリをローカルで実行するセクションまでスキップできます。
ただし、Cloud Shell ではなく、ローカル ターミナルで実行している場合は、アプリケーションのデフォルト認証情報を使用して Google API に対する認証を行う必要があります。1)認証情報を使用してログインするか(Vertex AI ユーザーと Datastore ユーザーのロールの両方がある場合)、2)この Codelab で使用するサービス アカウントの権限を借用してログインできます。
オプション 1)ADC にお客様の認証情報を使用する
認証情報を使用する場合は、まず gcloud auth list
を実行して、gcloud での認証方法を確認します。次に、ID に Vertex AI ユーザーロールを付与する必要があります。ID にオーナーのロールがある場合、この Vertex AI のユーザーロールはすでに付与されています。権限がない場合は、次のコマンドを実行して、Vertex AI ユーザーロールと Datastore ユーザーロールを自分の ID に付与できます。
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/aiplatform.user gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
次のコマンドを実行します。
gcloud auth application-default login
オプション 2)ADC 用のサービス アカウントの権限借用
この Codelab で作成したサービス アカウントを使用するには、ユーザー アカウントにサービス アカウント トークン作成者のロールが必要です。このロールを取得するには、次のコマンドを実行します。
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
次に、以下のコマンドを実行して、サービス アカウントで ADC を使用します。
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
アプリをローカルで実行する
最後に、次のスクリプトを実行してアプリを起動できます。この dev スクリプトは、tailwindCSS から output.css ファイルを生成します。
npm run dev
ウェブサイトをプレビューするには、[ウェブでプレビュー] ボタンを開き、[ポート 8080 をプレビュー] を選択します
8. フロントエンド サービスをデプロイしてテストする
まず、次のコマンドを実行してデプロイを開始し、使用するサービス アカウントを指定します。サービス アカウントが指定されていない場合は、デフォルトのコンピューティング サービス アカウントが使用されます。
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
ブラウザでフロントエンドのサービス URL を開きます。「シアトルの現在の天気は?」と尋ねるGemini は「現在 40 度で、雨が降っています」と応答するはずです。「ボストンの現在の天気は?」と聞くと、Gemini は「このリクエストには対応できません。利用可能な天気情報 API にボストンのデータがありません。"
9. 完了
これでこの Codelab は完了です。
Cloud Run、Vertex AI Gemini API、関数呼び出しのドキュメントを確認することをおすすめします。
学習した内容
- Gemini の関数呼び出しの仕組み
- Gemini 搭載の chatbot アプリを Cloud Run サービスとしてデプロイする方法
10. クリーンアップ
不注意による料金の発生(たとえば、この Cloud Run サービスが誤って無料枠の毎月の Cloud Run 呼び出し割り当てよりも多く呼び出された場合)を回避するには、Cloud Run サービスを削除するか、手順 2 で作成したプロジェクトを削除します。
Cloud Run サービスを削除するには、Cloud Run Cloud コンソール(https://console.cloud.google.com/functions/)に移動し、この Codelab で作成した $WEATHER_SERVICE サービスと $FRONTEND サービスを削除します。
また、誤って Gemini を呼び出さないように、vertex-ai-caller
サービス アカウントを削除するか、Vertex AI ユーザーロールを取り消すこともできます。
プロジェクト全体を削除する場合は、https://console.cloud.google.com/cloud-resource-manager に移動し、手順 2 で作成したプロジェクトを選択して [削除] を選択します。プロジェクトを削除する場合は、Cloud SDK でプロジェクトを変更する必要があります。使用可能なすべてのプロジェクトのリストを表示するには、gcloud projects list
を実行します。