1. Введение
Обзор
В этом практическом занятии вы узнаете, как предоставить Gemini доступ к данным в реальном времени, используя новую функцию, называемую вызовом функций . Для имитации данных в реальном времени вы создадите конечную точку службы погоды, которая возвращает текущую погоду для двух местоположений. Затем вы создадите чат-приложение на базе Gemini, которое использует вызов функций для получения текущей погоды.
Давайте воспользуемся наглядным примером, чтобы понять, что такое вызов функции.
- Запросы на получение информации о текущих погодных условиях в заданном месте
- Этот запрос + контракт функции getWeather() отправляются в Gemini.
- Gemini просит приложение чат-бота вызвать от его имени функцию "getWeather(Seattle)".
- Приложение отправляет результаты (40 градусов по Фаренгейту и дождь).
- Gemini отправляет результаты обратно звонящему.
Подводя итог, Gemini не вызывает функцию. Вы, как разработчик, должны вызвать функцию и отправить результаты обратно в Gemini.

Что вы узнаете
- Как работает вызов функции Gemini
- Как развернуть чат-бот на базе Gemini в качестве сервиса Cloud Run.
2. Настройка и требования
Предварительные требования
- Вы вошли в облачную консоль.
- Вы уже развернули функцию второго поколения. Например, для начала работы вы можете воспользоваться инструкцией по быстрому развертыванию облачной функции второго поколения .
Активировать Cloud Shell
- В консоли Cloud нажмите «Активировать Cloud Shell» .
.

Если вы запускаете Cloud Shell впервые, вам будет показан промежуточный экран с описанием его возможностей. Если вам был показан промежуточный экран, нажмите «Продолжить» .

Подготовка и подключение к Cloud Shell займут всего несколько минут.

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог объемом 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, ваша работа в этом практическом задании может быть выполнена с помощью браузера.
После подключения к Cloud Shell вы увидите, что прошли аутентификацию и что проект настроен на ваш идентификатор проекта.
- Выполните следующую команду в 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.
Настройка переменных среды
Вы можете установить переменные окружения, которые будут использоваться на протяжении всего этого практического занятия.
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
Прежде чем начать использовать этот практический пример, вам потребуется включить несколько API. Для работы с этим практическим примером необходимы следующие API. Вы можете включить эти API, выполнив следующую команду:
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
aiplatform.googleapis.com
4. Создайте учетную запись службы для вызова Vertex AI.
Этот сервисный аккаунт будет использоваться Cloud Run для вызова API Vertex AI Gemini.
Сначала создайте учетную запись службы, выполнив следующую команду:
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.
Сначала создайте директорию для исходного кода и перейдите в неё с помощью команды `cd`.
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!");
});
Развернуть метеорологическую службу
Эту команду можно использовать для развертывания службы погоды.
gcloud run deploy $WEATHER_SERVICE \ --source . \ --region $REGION \ --allow-unauthenticated
Проверьте работу метеорологической службы.
Проверить погоду в этих двух местах можно с помощью команды 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
В Сиэтле 40 градусов по Фаренгейту и дождливо, а в Новом Орлеане 99 градусов по Фаренгейту и всегда высокая влажность.
6. Создайте службу внешнего интерфейса.
Сначала перейдите в каталог frontend.
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);
});
}
Создайте файл input.css для tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Создайте файл tailwind.config.js для tailwindCSS.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.{html,js}"],
theme: {
extend: {}
},
plugins: []
};
Создайте файл metadataService.js для получения идентификатора проекта и региона для развернутой службы Cloud Run. Эти значения будут использоваться для создания экземпляра клиентских библиотек 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
Теперь создайте файл index.html для внешнего интерфейса, который будет использовать 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. Запустите службу Frontend локально.
Во-первых, убедитесь, что вы находитесь в директории, frontend вашему практическому заданию.
cd .. && pwd
Затем установите зависимости, выполнив следующую команду:
npm install
Использование АЦП при локальном запуске
Если вы работаете в Cloud Shell, вы уже используете виртуальную машину Google Compute Engine. Ваши учетные данные, связанные с этой виртуальной машиной (как показано при выполнении команды gcloud auth list ), будут автоматически использоваться в Application Default Credentials, поэтому нет необходимости использовать команду gcloud auth application-default login . Вы можете перейти к разделу «Запуск приложения локально».
Однако, если вы работаете на локальном терминале (то есть не в Cloud Shell), вам потребуется использовать учетные данные приложения по умолчанию для аутентификации в API Google. Вы можете либо 1) войти в систему, используя свои учетные данные (при условии, что у вас есть роли пользователя Vertex AI и пользователя хранилища данных), либо 2) войти в систему, выдав себя за учетную запись службы, используемую в этом практическом задании.
Вариант 1) Использование ваших учетных данных для ADC
Если вы хотите использовать свои учетные данные, сначала выполните gcloud auth list , чтобы проверить, как вы прошли аутентификацию в gcloud. Затем вам может потребоваться предоставить вашей учетной записи роль пользователя Vertex AI. Если у вашей учетной записи есть роль владельца, значит, у вас уже есть эта роль пользователя Vertex AI. В противном случае вы можете выполнить эту команду, чтобы предоставить вашей учетной записи роль пользователя Vertex AI и роль пользователя хранилища данных.
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
Если вы хотите использовать учетную запись службы, созданную в этом практическом задании, вашей учетной записи пользователя потребуется роль «Создатель токена учетной записи службы» . Получить эту роль можно, выполнив следующую команду:
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
Запустите приложение локально
Наконец, вы можете запустить приложение, выполнив следующий скрипт. Этот скрипт для разработчиков также сгенерирует файл output.css из tailwindCSS.
npm run dev
Вы можете предварительно просмотреть веб-сайт, открыв кнопку «Предварительный просмотр веб-сайта» и выбрав «Предварительный просмотр порта 8080».

8. Разверните и протестируйте службу Frontend.
Сначала выполните эту команду, чтобы начать развертывание и указать используемую учетную запись службы. Если учетная запись службы не указана, будет использоваться учетная запись вычислительной службы по умолчанию.
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
Откройте URL-адрес сервиса для внешнего интерфейса в браузере. Задайте вопрос: «Какая сейчас погода в Сиэтле?», и Gemini должен ответить: «Сейчас 40 градусов и идет дождь». Если вы спросите: «Какая сейчас погода в Бостоне?», Gemini ответит: «Я не могу выполнить этот запрос. Доступный API погоды не содержит данных для Бостона».
9. Поздравляем!
Поздравляем с завершением практического занятия!
Рекомендуем ознакомиться с документацией по Cloud Run , API Vertex AI Gemini и вызову функций .
Что мы рассмотрели
- Как работает вызов функции Gemini
- Как развернуть чат-бот на базе Gemini в качестве сервиса Cloud Run.
10. Уборка
Чтобы избежать непреднамеренных списаний средств (например, если эта служба Cloud Run будет случайно запущена больше раз, чем предусмотрено вашим ежемесячным лимитом вызовов Cloud Run в бесплатном тарифе ), вы можете либо удалить службу Cloud Run, либо удалить проект, созданный на шаге 2.
Чтобы удалить службы Cloud Run, перейдите в консоль Cloud Run по адресу https://console.cloud.google.com/functions/ и удалите службы $WEATHER_SERVICE и $FRONTEND, созданные вами в этом практическом задании.
Также вы можете удалить учетную запись службы vertex-ai-caller или отозвать роль пользователя Vertex AI, чтобы избежать случайных звонков в Gemini.
Если вы решите удалить весь проект, перейдите по ссылке https://console.cloud.google.com/cloud-resource-manager , выберите проект, созданный на шаге 2, и нажмите «Удалить». После удаления проекта вам потребуется изменить проекты в вашем Cloud SDK. Список всех доступных проектов можно просмотреть, выполнив gcloud projects list .