1. Giới thiệu
Tổng quan
Trong lớp học lập trình này, bạn sẽ tìm hiểu cách cấp cho Gemini quyền truy cập vào dữ liệu theo thời gian thực thông qua một tính năng mới có tên là Gọi hàm. Để mô phỏng dữ liệu theo thời gian thực, bạn sẽ tạo một điểm cuối của dịch vụ thời tiết trả về thông tin thời tiết hiện tại cho 2 địa điểm. Sau đó, bạn sẽ tạo một ứng dụng nhắn tin sử dụng tính năng gọi hàm để lấy thông tin thời tiết hiện tại, dựa trên công nghệ của Gemini.
Hãy xem nhanh hình ảnh để tìm hiểu về tính năng Gọi hàm.
- Các yêu cầu nhắc về những vị trí dự báo thời tiết hiện tại ở một vị trí cụ thể
- Câu lệnh này + hợp đồng hàm cho getWeather() sẽ được gửi đến Gemini
- Gemini yêu cầu ứng dụng bot trò chuyện gọi "getWeather(Seattle)" thay mặt cho tổ chức
- Ứng dụng gửi lại kết quả (40 độ F và trời mưa)
- Gemini gửi lại kết quả cho người gọi
Tóm lại, Gemini không gọi Hàm. Với tư cách là nhà phát triển, bạn phải gọi hàm và gửi kết quả lại cho Gemini.
Kiến thức bạn sẽ học được
- Cách hoạt động của chức năng gọi điện trên Gemini
- Cách triển khai ứng dụng bot trò chuyện dựa trên Gemini dưới dạng một dịch vụ của Cloud Run
2. Thiết lập và yêu cầu
Điều kiện tiên quyết
- Bạn đã đăng nhập vào Cloud Console.
- Trước đây, bạn đã triển khai một hàm thế hệ 2. Ví dụ: bạn có thể làm theo hướng dẫn khởi động nhanh chức năng đám mây thế hệ thứ 2 để bắt đầu.
Kích hoạt Cloud Shell
- Trong Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell .
Nếu đây là lần đầu tiên khởi động Cloud Shell, bạn sẽ thấy một màn hình trung gian mô tả về Cloud Shell. Nếu bạn nhìn thấy màn hình trung gian, hãy nhấp vào Tiếp tục.
Quá trình cấp phép và kết nối với Cloud Shell chỉ mất vài phút.
Máy ảo này được tải tất cả các công cụ phát triển cần thiết. Dịch vụ này cung cấp thư mục gốc có dung lượng ổn định 5 GB và chạy trên Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Nhiều (nếu không nói là) tất cả công việc của bạn trong lớp học lập trình này đều có thể thực hiện bằng trình duyệt.
Sau khi kết nối với Cloud Shell, bạn sẽ thấy mình đã được xác thực và dự án được đặt thành mã dự án.
- Chạy lệnh sau trong Cloud Shell để xác nhận rằng bạn đã được xác thực:
gcloud auth list
Kết quả lệnh
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Chạy lệnh sau trong Cloud Shell để xác nhận rằng lệnh gcloud biết về dự án của bạn:
gcloud config list project
Kết quả lệnh
[core] project = <PROJECT_ID>
Nếu chưa, bạn có thể thiết lập chế độ này bằng lệnh sau:
gcloud config set project <PROJECT_ID>
Kết quả lệnh
Updated property [core/project].
3. Thiết lập các biến môi trường và bật API
Thiết lập biến môi trường
Bạn có thể thiết lập các biến môi trường sẽ được dùng trong suốt lớp học lập trình này.
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
Bật API
Trước khi có thể bắt đầu sử dụng lớp học lập trình này, bạn sẽ cần bật một số API. Lớp học lập trình này yêu cầu sử dụng các API sau. Bạn có thể bật các API đó bằng cách chạy lệnh sau:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com
4. Tạo một tài khoản dịch vụ để gọi Vertex AI
Tài khoản dịch vụ này sẽ được Cloud Run sử dụng để gọi Vertex AI Gemini API.
Trước tiên, hãy tạo tài khoản dịch vụ bằng cách chạy lệnh sau:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Thứ hai, cấp vai trò Người dùng Vertex AI cho tài khoản dịch vụ.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
5. Tạo dịch vụ Cloud Run phụ trợ
Trước tiên, tạo một thư mục cho mã nguồn và cd vào thư mục đó.
mkdir -p gemini-function-calling/weatherservice gemini-function-calling/frontend && cd gemini-function-calling/weatherservice
Sau đó, hãy tạo một tệp package.json
với nội dung sau:
{ "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" } }
Tiếp theo, hãy tạo một tệp nguồn app.js
có nội dung như bên dưới. Tệp này chứa điểm truy cập cho dịch vụ và chứa logic chính cho ứng dụng.
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!"); });
Triển khai Dịch vụ thời tiết
Bạn có thể dùng lệnh này để triển khai dịch vụ thời tiết.
gcloud run deploy $WEATHER_SERVICE \ --source . \ --region $REGION \ --allow-unauthenticated
Kiểm tra Dịch vụ thời tiết
Bạn có thể xác minh thời tiết cho 2 vị trí bằng cách sử dụng curl:
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
Seattle là 40 độ F và mưa nhiều, còn New Orleans thì nhiệt độ 99 độ F và ẩm ướt, lúc nào cũng vậy.
6. Tạo dịch vụ Giao diện người dùng
Trước tiên, hãy đĩa cd vào thư mục giao diện người dùng.
cd gemini-function-calling/frontend
Sau đó, hãy tạo một tệp package.json
với nội dung sau:
{ "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" } }
Tiếp theo, hãy tạo một tệp nguồn app.js
có nội dung như bên dưới. Tệp này chứa điểm truy cập cho dịch vụ và chứa logic chính cho ứng dụng.
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); }); }
Tạo một tệp input.css
cho tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Tạo tệp tailwind.config.js
cho tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Tạo tệp metadataService.js
để lấy mã dự án và khu vực cho dịch vụ Cloud Run đã triển khai. Các giá trị này sẽ được dùng để tạo thực thể cho một thực thể của thư viện ứng dụng 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; } };
Tạo một tệp có tên là 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>`;
Tạo thư mục public
mới.
mkdir public cd public
Bây giờ, hãy tạo tệp index.html
cho giao diện người dùng, sử dụng htmx.
<!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. Chạy dịch vụ Giao diện người dùng cục bộ
Trước tiên, hãy đảm bảo bạn đang ở trong thư mục frontend
dành cho lớp học lập trình của mình.
cd .. && pwd
Sau đó, hãy cài đặt các phần phụ thuộc bằng cách chạy lệnh sau:
npm install
Sử dụng ADC khi chạy cục bộ
Nếu đang chạy trong Cloud Shell, tức là bạn đã chạy trên một máy ảo Google Compute Engine. Thông tin đăng nhập được liên kết với máy ảo này (như minh hoạ trong quá trình chạy gcloud auth list
) sẽ tự động được Thông tin xác thực mặc định của ứng dụng sử dụng. Vì vậy, bạn không cần phải sử dụng lệnh gcloud auth application-default login
. Bạn có thể chuyển đến phần Chạy ứng dụng cục bộ
Tuy nhiên, nếu đang chạy trên thiết bị đầu cuối cục bộ (tức là không chạy trong Cloud Shell), bạn sẽ cần phải sử dụng Thông tin xác thực mặc định của ứng dụng để xác thực với các API của Google. Bạn có thể 1) đăng nhập bằng thông tin đăng nhập của mình (miễn là bạn có cả vai trò Người dùng Vertex AI và Người dùng kho dữ liệu) hoặc 2) bạn có thể đăng nhập bằng cách mạo danh tài khoản dịch vụ dùng trong lớp học lập trình này.
Phương án 1) Sử dụng thông tin xác thực của bạn cho ADC
Nếu muốn sử dụng thông tin đăng nhập của mình thì trước tiên, bạn có thể chạy gcloud auth list
để xác minh cách bạn được xác thực trong gcloud. Tiếp theo, bạn có thể cần cấp vai trò Người dùng Vertex AI cho danh tính của bạn. Nếu danh tính của bạn có vai trò Chủ sở hữu thì bạn đã có vai trò người dùng Vertex AI này. Nếu không, bạn có thể chạy lệnh này để cấp vai trò của người dùng trong Vertex AI danh tính và vai trò Người dùng Datastore.
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
Sau đó chạy lệnh sau
gcloud auth application-default login
Lựa chọn 2) Mạo danh một tài khoản dịch vụ của ADC
Nếu muốn sử dụng tài khoản dịch vụ được tạo trong lớp học lập trình này, tài khoản người dùng của bạn cần phải có vai trò Người tạo mã thông báo tài khoản dịch vụ. Bạn có thể có được vai trò này bằng cách chạy lệnh sau:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Tiếp theo, bạn sẽ chạy lệnh sau để sử dụng ADC với tài khoản dịch vụ
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Chạy ứng dụng trên máy
Cuối cùng, bạn có thể khởi động ứng dụng bằng cách chạy tập lệnh sau. Tập lệnh dev này cũng sẽ tạo tệp output.css từ tailwindCSS.
npm run dev
Bạn có thể xem trước trang web bằng cách mở nút Web Preview và chọn Preview Port 8080
8. Triển khai và thử nghiệm dịch vụ Giao diện người dùng
Trước tiên, hãy chạy lệnh này để bắt đầu triển khai và chỉ định tài khoản dịch vụ sẽ được sử dụng. Nếu bạn không chỉ định tài khoản dịch vụ, thì tài khoản dịch vụ mặc định của Compute Engine sẽ được sử dụng.
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
Mở URL dịch vụ cho giao diện người dùng trong trình duyệt của bạn. Đặt câu hỏi "Thời tiết hiện tại ở Seattle như thế nào?" và Gemini sẽ trả lời "Hiện tại, nhiệt độ là 40 độ và có mưa". Nếu bạn hỏi "Thời tiết hiện tại ở Boston thế nào?", Gemini sẽ phản hồi là "Tôi không thể thực hiện yêu cầu này. API thời tiết hiện có không có dữ liệu cho Boston."
9. Xin chúc mừng!
Chúc mừng bạn đã hoàn thành lớp học lập trình!
Bạn nên tham khảo tài liệu về Cloud Run, API Gemini trong Vertex AI và tính năng gọi hàm.
Nội dung đã đề cập
- Cách hoạt động của chức năng gọi điện trên Gemini
- Cách triển khai ứng dụng bot trò chuyện dựa trên Gemini dưới dạng một dịch vụ của Cloud Run
10. Dọn dẹp
Để tránh các khoản phí vô tình (ví dụ: nếu dịch vụ Cloud Run này vô tình bị gọi nhiều lần hơn mức phân bổ lệnh gọi Cloud Run hằng tháng của bạn ở cấp miễn phí), bạn có thể xoá dịch vụ Cloud Run hoặc xoá dự án bạn đã tạo ở Bước 2.
Để xoá các dịch vụ Cloud Run, hãy truy cập vào Cloud Run Cloud Console tại https://console.cloud.google.com/functions/ rồi xoá các dịch vụ $WEATHER_SERVICE và $FRONTEND mà bạn đã tạo trong lớp học lập trình này.
Bạn cũng nên xoá tài khoản dịch vụ vertex-ai-caller
hoặc thu hồi vai trò của Người dùng Vertex AI để tránh việc vô tình gọi đến Gemini.
Nếu chọn xoá toàn bộ dự án, bạn có thể truy cập vào https://console.cloud.google.com/cloud-resource-manager, chọn dự án mà bạn đã tạo ở Bước 2 rồi chọn Xoá. Nếu xoá dự án, bạn sẽ phải thay đổi các dự án trong Cloud SDK của mình. Bạn có thể xem danh sách tất cả dự án hiện có bằng cách chạy gcloud projects list
.