Implantar fluxos usando o Cloud Functions para Firebase

O Cloud Functions para Firebase tem um método onCallGenkit que permite criar rapidamente uma função invocável com uma ação do Genkit (por exemplo, um fluxo). Essas funções podem ser chamadas usando genkit/beta/clientou o SDK do cliente do Functions, que adiciona automaticamente informações de autenticação.

Antes de começar

  • Você precisa conhecer o conceito de fluxos do Genkit e saber como programá-los. As instruções nesta página pressupõem que você já tenha alguns fluxos definidos que quer implantar.
  • Ter experiência com o Cloud Functions para Firebase é útil, mas não obrigatório.

1. Configurar um projeto do Firebase

Se você ainda não tiver um projeto do Firebase com o TypeScript Cloud Functions configurado, siga estas etapas:

  1. Crie um novo projeto do Firebase usando o console do Firebase ou escolha um projeto atual.

  2. Faça upgrade do projeto para o plano Blaze, que é necessário para implantar o Cloud Functions.

  3. Instale a CLI do Firebase.

  4. Faça login com a CLI do Firebase:

    firebase login
    firebase login --reauth # alternative, if necessary
    firebase login --no-localhost # if running in a remote shell
  5. Crie um novo diretório do projeto:

    export PROJECT_ROOT=~/tmp/genkit-firebase-project1
    mkdir -p $PROJECT_ROOT
  6. Inicialize um projeto do Firebase no diretório:

    cd $PROJECT_ROOT
    firebase init genkit

    O restante desta página pressupõe que você decidiu escrever suas funções em TypeScript, mas também é possível implantar seus fluxos do Genkit se você estiver usando JavaScript.

2. Envolver o fluxo no onCallGenkit

Depois de configurar um projeto do Firebase com o Cloud Functions, é possível copiar ou escrever definições de fluxo no diretório functions/src do projeto e exportá-las em index.ts.

Para que seus fluxos sejam implantáveis, é necessário envolvê-los em onCallGenkit. Esse método tem todos os recursos do onCall normal. Ele oferece suporte automático a streaming e respostas JSON.

Suponha que você tenha o seguinte fluxo:

const generatePoemFlow = ai.defineFlow(
  {
    name: "generatePoem",
    inputSchema: z.string(),
    outputSchema: z.string(),
  },
  async (subject: string) => {
    const { text } = await ai.generate(`Compose a poem about ${subject}.`);
    return text;
  }
);

É possível expor esse fluxo como uma função que pode ser chamada usando onCallGenkit:

import { onCallGenkit } from 'firebase-functions/https';

export generatePoem = onCallGenkit(generatePoemFlow);

Definir uma política de autorização

Todos os fluxos implantados, implantados no Firebase ou não, precisam ter uma política de autorização. Sem ela, qualquer pessoa pode invocar seus fluxos de IA generativa potencialmente caros. Para definir uma política de autorização, use o parâmetro authPolicy de onCallGenkit:

export const generatePoem = onCallGenkit({
  authPolicy: (auth) => auth?.token?.email_verified,
}, generatePoemFlow);

Este exemplo usa uma função manual como política de autenticação. Além disso, a biblioteca https exporta os auxiliares signedIn() e hasClaim(). Confira o mesmo código usando um desses auxiliares:

import { hasClaim } from 'firebase-functions/https';

export const generatePoem = onCallGenkit({
  authPolicy: hasClaim('email_verified'),
}, generatePoemFlow);

Disponibilizar credenciais da API para fluxos implantados

Depois de implantados, os fluxos precisam de uma forma de autenticação com todos os serviços remotos em que eles dependem. A maioria dos fluxos precisa, no mínimo, de credenciais para acessar o serviço de API de modelo que eles usam.

Para este exemplo, siga um destes procedimentos, dependendo do provedor de modelos escolhido:

Gemini (IA do Google)

  1. Verifique se a IA do Google está disponível na sua região.

  2. Gere uma chave de API para à API Gemini usando o Google AI Studio.

  3. Armazene sua chave de API no Cloud Secret Manager:

    firebase functions:secrets:set GOOGLE_GENAI_API_KEY

    Essa etapa é importante para evitar o vazamento acidental da chave de API, que concede acesso a um serviço potencialmente medido.

    Consulte Armazenar e acessar informações de configuração sensíveis para mais informações sobre como gerenciar secrets.

  4. Edite src/index.ts e adicione o seguinte após as importações atuais:

    import {defineSecret} from "firebase-functions/params";
    const googleAIapiKey = defineSecret("GOOGLE_GENAI_API_KEY");
    

    Em seguida, na definição do fluxo, declare que a função do Cloud precisa de acesso a esse valor secreto:

    export const generatePoem = onCallGenkit({
      secrets: [googleAIapiKey]
    }, generatePoemFlow);
    

Agora, quando você implanta essa função, a chave da API é armazenada no Cloud Secret Manager e fica disponível no ambiente do Cloud Functions.

Gemini (Vertex AI)

  1. No console do Cloud, ative a API Vertex AI para seu projeto do Firebase.

  2. Na página IAM, verifique se a Conta de serviço padrão do Compute recebeu o papel de Usuário da Vertex AI.

O único secret que você precisa configurar para este tutorial é para o provedor de modelo, mas, em geral, você precisa fazer algo semelhante para cada serviço que seu fluxo usa.

Adicionar a aplicação obrigatória do App Check

O Firebase App Check usa um mecanismo de atestado integrado para verificar se a API está sendo chamada apenas pelo aplicativo. O onCallGenkit oferece suporte à aplicação obrigatória do App Check de forma declarativa.

export const generatePoem = onCallGenkit({
  enforceAppCheck: true,
  // Optional. Makes App Check tokens only usable once. This adds extra security
  // at the expense of slowing down your app to generate a token for every API
  // call
  consumeAppCheckToken: true,
}, generatePoemFlow);

Definir uma política de CORS

Por padrão, as funções chamáveis permitem que qualquer domínio chame sua função. Se você quiser personalizar os domínios que podem fazer isso, use a opção cors. Com a autenticação adequada (especialmente o App Check), a CORS geralmente é desnecessária.

export const generatePoem = onCallGenkit({
  cors: 'mydomain.com',
}, generatePoemFlow);

Exemplo completo

Depois de fazer todas as mudanças descritas anteriormente, o fluxo de implantação ficará parecido com o exemplo abaixo:

import { genkit } from 'genkit';
import { onCallGenkit, hasClaim } from 'firebase-functions/https';
import { defineSecret } from 'firebase-functions/params';

const apiKey = defineSecret("GOOGLE_GENAI_API_KEY");

const generatePoemFlow = ai.defineFlow({
  name: "generatePoem",
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (subject: string) => {
  const { text } = await ai.generate(`Compose a poem about ${subject}.`);
  return text;
});

export const generateFlow = onCallGenkit({
  secrets: [apiKey],
  authPolicy: hasClaim("email_verified"),
  enforceAppCheck: true,
}, generatePoemFlow);

3. Implantar fluxos no Firebase

Depois de definir os fluxos usando onCallGenkit, você pode implantá-los da mesma forma que outros do Cloud Functions:

cd $PROJECT_ROOT
firebase deploy --only functions

Agora você implantou o fluxo como uma função do Cloud. No entanto, não é possível acessar o endpoint implantado com curl ou algo semelhante devido à política de autorização do fluxo. A próxima seção explica como acessar o fluxo com segurança.

Opcional: testar o fluxo implantado

Para testar o endpoint do fluxo, implante o seguinte exemplo mínimo de app da Web:

  1. Na seção Configurações do projeto do console do Firebase, adicione um novo app da Web, selecionando a opção para configurar também a hospedagem.

  2. Na seção Autenticação do console do Firebase, ative o provedor Google, usado neste exemplo.

  3. No diretório do projeto, configure o Firebase Hosting, onde você vai implantar o app de exemplo:

    cd $PROJECT_ROOT
    firebase init hosting

    Aceite os padrões de todas as solicitações.

  4. Substitua public/index.html por:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Genkit demo</title>
      </head>
      <body>
        <div id="signin" hidden>
          <button id="signinBtn">Sign in with Google</button>
        </div>
        <div id="callGenkit" hidden>
          Subject: <input type="text" id="subject" />
          <button id="generatePoem">Compose a poem on this subject</button>
          <p id="generatedPoem"></p>
        </div>
        <script type="module">
          import { initializeApp } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-app.js";
          import {
            getAuth,
            onAuthStateChanged,
            GoogleAuthProvider,
            signInWithPopup,
          } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-auth.js";
          import {
            getFunctions,
            httpsCallable,
          } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-functions.js";
    
          const firebaseConfig = await fetch("/__/firebase/init.json");
          initializeApp(await firebaseConfig.json());
    
          async function generatePoem() {
            const poemFlow = httpsCallable(getFunctions(), "generatePoem");
            const subject = document.querySelector("#subject").value;
            const response = await poemFlow(subject);
            document.querySelector("#generatedPoem").innerText = response.data;
          }
    
          function signIn() {
            signInWithPopup(getAuth(), new GoogleAuthProvider());
          }
    
          document.querySelector("#signinBtn").addEventListener("click", signIn);
          document
            .querySelector("#generatePoem")
            .addEventListener("click", generatePoem);
    
          const signinEl = document.querySelector("#signin");
          const genkitEl = document.querySelector("#callGenkit");
    
          onAuthStateChanged(getAuth(), (user) => {
            if (!user) {
              signinEl.hidden = false;
              genkitEl.hidden = true;
            } else {
              signinEl.hidden = true;
              genkitEl.hidden = false;
            }
          });
        </script>
      </body>
    </html>
    
  5. Implante o app da Web e a função do Cloud:

    cd $PROJECT_ROOT
    firebase deploy

Abra o app da Web acessando o URL impresso pelo comando deploy. O app exige que você faça login com uma Conta do Google. Depois disso, você pode iniciar solicitações de endpoint.

Opcional: executar fluxos na interface do desenvolvedor

É possível executar fluxos definidos usando onCallGenkit na interface do desenvolvedor, da mesma forma que você executa fluxos definidos usando defineFlow. Portanto, não é necessário alternar entre os dois entre a implantação e o desenvolvimento.

cd $PROJECT_ROOT/functions
npx genkit start -- npx tsx --watch src/index.ts

ou

cd $PROJECT_ROOT/functions
npm run genkit:start

Agora você pode acessar o URL impresso pelo comando genkit start.

Opcional: como desenvolver usando o Pacote de emuladores locais do Firebase

O Firebase oferece um pacote de emuladores para desenvolvimento local, que pode ser usado com o Genkit.

Para usar a interface do desenvolvedor do Genkit com o Pacote de emuladores do Firebase, inicie os emuladores do Firebase da seguinte maneira:

npx genkit start -- firebase emulators:start --inspect-functions

Esse comando executa o código no emulador e o framework do Genkit no modo de desenvolvimento. Isso inicia e expõe a API de reflexão do Genkit, mas não a interface do desenvolvedor.

Para conferir os rastros do Firestore na interface para desenvolvedores, navegue até a guia Inspect e ative a chave Dev/Prod. Quando alternado para prod, ele carrega rastros do Firestore.