1. Introdução
Visão geral
Neste codelab, você vai aprender a conceder ao Gemini acesso a dados em tempo real usando um novo recurso chamado Chamada de função. Para simular dados em tempo real, você vai criar um endpoint de serviço meteorológico que retorna o clima atual para dois locais. Em seguida, você vai criar um app de chat, com a tecnologia do Gemini, que usa chamada de função para acessar a previsão do tempo atual.
Vamos usar uma imagem rápida para entender a chamada de função.
- Solicitações de comandos para locais atuais do clima em um determinado local
- Esse comando e o contrato da função de getWeather() são enviados ao Gemini
- O Gemini pergunta que o app de chatbot chama "getWeather(Seattle)" em nome da empresa
- O app envia os resultados de volta (40 graus F e chuvoso)
- O Gemini retorna os resultados para o autor da chamada
Recapitulando, o Gemini não chama a função. Como desenvolvedor, você precisa chamar a função e enviar os resultados ao Gemini.
O que você vai aprender
- Como a chamada de função do Gemini funciona
- Como implantar um app de chatbot com tecnologia do Gemini como um serviço do Cloud Run
2. Configuração e requisitos
Pré-requisitos
- Você fez login no console do Cloud.
- Você já implantou uma função de 2a geração. Por exemplo, siga o guia de início rápido do Cloud Functions (2a geração) para começar.
Ativar o Cloud Shell
- No Console do Cloud, clique em Ativar o Cloud Shell.
Se você estiver iniciando o Cloud Shell pela primeira vez, verá uma tela intermediária com a descrição dele. Se aparecer uma tela intermediária, clique em Continuar.
Leva apenas alguns instantes para provisionar e se conectar ao Cloud Shell.
Essa máquina virtual tem todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Grande parte do trabalho neste codelab, se não todo, pode ser feito em um navegador.
Depois de se conectar ao Cloud Shell, você verá sua autenticação e o projeto estará configurado com o ID do seu projeto.
- Execute o seguinte comando no Cloud Shell para confirmar se a conta está autenticada:
gcloud auth list
Resposta ao comando
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Execute o seguinte comando no Cloud Shell para confirmar que o comando gcloud sabe sobre seu projeto:
gcloud config list project
Resposta ao comando
[core] project = <PROJECT_ID>
Se o projeto não estiver configurado, configure-o usando este comando:
gcloud config set project <PROJECT_ID>
Resposta ao comando
Updated property [core/project].
3. Configurar variáveis de ambiente e ativar APIs
Configurar as variáveis de ambiente.
Você pode definir variáveis de ambiente que serão usadas neste 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
Ativar APIs
Antes de começar a usar este codelab, você precisa ativar várias APIs. Este codelab requer o uso das APIs a seguir. É possível ativar essas APIs executando o seguinte comando:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com
4. Crie uma conta de serviço para chamar a Vertex AI
Essa conta de serviço será usada pelo Cloud Run para chamar a API Gemini da Vertex AI.
Primeiro, execute este comando para criar a conta de serviço:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Depois, conceda o papel de usuário da Vertex AI à conta de serviço.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
5. Criar o serviço de back-end do Cloud Run
Primeiro, crie um diretório para o código-fonte e use cd nele.
mkdir -p gemini-function-calling/weatherservice gemini-function-calling/frontend && cd gemini-function-calling/weatherservice
Em seguida, crie um arquivo package.json
com o seguinte conteúdo:
{ "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" } }
Em seguida, crie um arquivo de origem app.js
com o conteúdo abaixo. Esse arquivo contém o ponto de entrada para o serviço e a lógica principal do app.
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!"); });
Implante o serviço de previsão do tempo
Use esse comando para implantar o serviço meteorológico.
gcloud run deploy $WEATHER_SERVICE \ --source . \ --region $REGION \ --allow-unauthenticated
Testar o serviço de previsão do tempo
Verifique o clima dos dois locais usando 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
A temperatura de Seattle é de 40 graus oF e chuvosa, enquanto Nova Orleans é de 40 graus Celsius e é sempre úmida.
6. Criar o serviço Frontend
Primeiro, use o comando cd para acessar o diretório do front-end.
cd gemini-function-calling/frontend
Em seguida, crie um arquivo package.json
com o seguinte conteúdo:
{ "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" } }
Em seguida, crie um arquivo de origem app.js
com o conteúdo abaixo. Esse arquivo contém o ponto de entrada para o serviço e a lógica principal do app.
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); }); }
Crie um arquivo input.css
para tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Crie o arquivo tailwind.config.js
para tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Crie o arquivo metadataService.js
para receber o ID do projeto e a região do serviço implantado do Cloud Run. Esses valores serão usados para instanciar uma instância das bibliotecas de cliente da 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; } };
Crie um arquivo com o nome 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>`;
Crie um novo diretório public
.
mkdir public cd public
Agora crie o arquivo index.html
para o front-end, que usará 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. Executar o serviço de front-end localmente
Primeiro, verifique se você está no diretório frontend
do codelab.
cd .. && pwd
Em seguida, instale as dependências executando o seguinte comando:
npm install
Como usar o ADC ao executar localmente
Se você estiver executando no Cloud Shell, já está em execução em uma máquina virtual do Google Compute Engine. Suas credenciais associadas a essa máquina virtual, conforme mostrado ao executar gcloud auth list
, serão usadas automaticamente pelo Application Default Credentials. Por isso, não é necessário usar o comando gcloud auth application-default login
. Pule para a seção Executar o app localmente.
No entanto, se você estiver executando no seu terminal local (ou seja, não no Cloud Shell), será necessário usar o Application Default Credentials para se autenticar nas APIs do Google. Você pode 1) fazer login usando suas credenciais (desde que tenha os papéis de usuário da Vertex AI e do Datastore) ou 2) fazer login representando a conta de serviço usada neste codelab.
Opção 1: usar suas credenciais para o ADC
Se você quiser usar suas credenciais, primeiro execute gcloud auth list
para verificar como você está autenticado na gcloud. Em seguida, pode ser necessário conceder à sua identidade o papel de Usuário da Vertex AI. Caso sua identidade tenha o papel de Proprietário, você já tem esse papel do usuário da Vertex AI. Caso contrário, execute este comando para conceder à sua identidade o papel de usuário da Vertex AI e o papel de usuário do 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
Em seguida, execute o seguinte comando:
gcloud auth application-default login
Opção 2: Como representar uma conta de serviço para o ADC
Para usar a conta de serviço criada neste codelab, sua conta de usuário precisará ter o papel de Criador de token da conta de serviço. Você pode conseguir esse papel executando o seguinte comando:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Em seguida, execute o comando a seguir para usar o ADC com a conta de serviço
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Executar o aplicativo localmente
Por fim, inicie o app executando o script a seguir. Esse script dev também gerará o arquivo output.css a partir do tailwindCSS.
npm run dev
Para visualizar o site, abra o botão "Visualização na Web" e selecione "Visualizar porta 8080"
8. Implante e teste o serviço Frontend
Primeiro, execute este comando para iniciar a implantação e especifique a conta de serviço a ser usada. Se nenhuma conta de serviço for especificada, será usada a conta de serviço padrão do Compute.
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
Abra o URL do serviço para o front-end no navegador. Faça uma pergunta "Qual é a previsão do tempo atual para Curitiba?" e o Gemini vai responder "Está 40 graus e chuvoso". Se você perguntar "Como está o tempo atual em Boston?", O Gemini vai responder com "Não posso atender a essa solicitação. A API de previsão do tempo disponível não tem dados para Boston."
9. Parabéns!
Parabéns por concluir o codelab.
Recomendamos que você consulte a documentação do Cloud Run, das APIs Gemini da Vertex AI e das chamadas de função.
O que vimos
- Como a chamada de função do Gemini funciona
- Como implantar um app de chatbot com tecnologia do Gemini como um serviço do Cloud Run
10. Limpar
Para evitar cobranças acidentais, por exemplo, se este serviço do Cloud Run for invocado mais vezes do que sua alocação mensal de invocação do Cloud Run no nível sem custo financeiro, exclua o serviço do Cloud Run ou o projeto criado na etapa 2.
Para excluir os serviços do Cloud Run, acesse o console do Cloud Run em https://console.cloud.google.com/functions/ e exclua os serviços $weather_SERVICE e $FRONTEND que você criou neste codelab.
Também é possível excluir a conta de serviço vertex-ai-caller
ou revogar o papel de Usuário da Vertex AI para evitar chamadas acidentais ao Gemini.
Se você optar por excluir o projeto inteiro, acesse https://console.cloud.google.com/cloud-resource-manager, selecione o projeto criado na etapa 2 e escolha "Excluir". Se você excluir o projeto, precisará alterar os projetos no SDK Cloud. Para conferir a lista de todos os projetos disponíveis, execute gcloud projects list
.