פריסת תהליכים באמצעות Cloud Functions for Firebase

Genkit כולל פלאגין שעוזר לפרוס את התהליכים ב-Cloud Functions for Firebase. אחרי הפריסה, תהליכים זמינים כנקודות קצה מסוג HTTPS, וניתן לגשת אליהם כפונקציות שניתן להפעיל באמצעות ספריות הלקוח של Cloud Functions.

לפני שמתחילים

  • מתקינים את Firebase CLI.
  • כדאי שתכירו את המושג flows ב-Genkit ואת האופן שבו כותבים אותם. ההוראות בדף הזה מבוססות על ההנחה שכבר הגדרתם תהליכים מסוימים שאתם רוצים לפרוס.
  • מומלץ, אבל לא חובה, שכבר השתמשתם ב-Cloud Functions for Firebase.

1. הגדרת פרויקט Firebase

אם עדיין אין לכם פרויקט Firebase עם הגדרה של TypeScript Cloud Functions, עליכם לפעול לפי השלבים הבאים:

  1. יוצרים פרויקט Firebase חדש באמצעות מסוף Firebase או בוחרים פרויקט קיים.

  2. משדרגים את הפרויקט לתוכנית Blaze, שנדרשת לפריסה של Cloud Functions.

  3. מתחברים באמצעות CLI של Firebase:

    firebase login
    firebase login --reauth # alternative, if necessary
    firebase login --no-localhost # if running in a remote shell
  4. יוצרים ספריית פרויקט חדשה:

    export PROJECT_ROOT=~/tmp/genkit-firebase-project1
    mkdir -p $PROJECT_ROOT
  5. מפעילים פרויקט Firebase בספרייה:

    cd $PROJECT_ROOT
    firebase init genkit

    בהמשך הדף נניח שבחרתם לכתוב את הפונקציות ב-TypeScript, אבל אפשר לפרוס את תהליכי העבודה של Genkit גם אם משתמשים ב-JavaScript.

2. עדכון הגדרות התהליך

אחרי שמגדירים פרויקט Firebase עם Cloud Functions, אפשר להעתיק או לכתוב הגדרות של תהליכים בספרייה functions/src של הפרויקט ולייצא אותן ב-index.ts.

כדי שתוכלו לפרוס את התהליכים, תצטרכו לבצע כמה שינויים קטנים באופן ההגדרה שלהם. הלוגיקה המרכזית תישאר זהה, אבל תצטרכו להוסיף מידע נוסף כדי שתוכלו לפרוס אותם בצורה חלקה ולהבטיח אבטחה גבוהה יותר אחרי הפריסה.

נניח שיש לכם את התהליך הבא:

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;
  }
);

בקטעים הבאים מתוארים השינויים שצריך לבצע לפני שאפשר לפרוס אותו.

הגדרת תהליכים באמצעות onFlow

במקום להגדיר את התהליך באמצעות Genkit.defineFlow(), צריך להשתמש בפונקציה onFlow() של הפלאגין של Firebase. כשמשתמשים בפונקציה הזו, לוגיק התהליך עוטף בטיפול בבקשות של Cloud Functions, בדומה ל-onCall.

import { onFlow } from "@genkit-ai/firebase/functions";

export const generatePoem = onFlow(
  ai,
  {
    // ...
  },
  async (subject: string) => {
    // ...
  }
);

שימו לב ש-onFlow היא לא שיטה של Genkit, אלא פונקציה שמקבלת מכונה של Genkit כפרמטר הראשון שלה. אחרת, התחביר דומה ל-defineFlow.

הגדרת מדיניות הרשאה

לכל תהליכי העבודה שנפרסו, בין שהם נפרסו ב-Firebase ובין שלא, צריכה להיות מדיניות הרשאה. בלי מדיניות כזו, כל אחד יוכל להפעיל את תהליכי העבודה של ה-AI הגנרטיבי, שעשויים להיות יקרים. כדי להגדיר מדיניות הרשאה, משתמשים בפרמטר authPolicy בהגדרת התהליך:

import { firebaseAuth } from "@genkit-ai/firebase/auth";

export const generatePoem = onFlow(
  ai,
  {
    name: "generatePoem",
    // ...
    authPolicy: firebaseAuth((user, input) => {
      if (!user.email_verified) {
        throw new Error("Verified email required to run flow");
      }
    }),
  },
  async (subject: string) => {
    // ...
  }
);

המדיניות הזו משתמשת בעזרה של firebaseAuth() כדי לאפשר גישה רק למשתמשים רשומים באפליקציה עם כתובות אימייל מאומתות. בצד הלקוח, צריך להגדיר את הכותרת Authorization: Bearer לאסימון מזהה של Firebase שעומד בדרישות המדיניות. ערכות ה-SDK של לקוחות Cloud Functions מספקות שיטות של פונקציות שניתן להפעיל, שמבצעות את הפעולה הזו באופן אוטומטי. תוכלו למצוא דוגמה בקטע ניסיון התהליך שנפרס.

איך מאפשרים לפרטי כניסה ל-API להיות זמינים לתהליכים שנפרסו

אחרי הפריסה, תהליכי העבודה צריכים למצוא דרך לאמת את עצמם מול שירותים מרוחקים שהם מסתמכים עליהם. ברוב התהליכים נדרשים לפחות פרטי כניסה כדי לגשת לשירות ה-API של המודל שבו נעשה שימוש.

בדוגמה הזו, מבצעים אחת מהפעולות הבאות, בהתאם לספק המודל שבחרתם:

Gemini (AI מבית Google)

  1. מוודאים ש-Google AI זמין באזור שלכם.

  2. יוצרים מפתח API ל-Gemini API באמצעות Google AI Studio.

  3. אחסון מפתח ה-API ב-Cloud Secret Manager:

    firebase functions:secrets:set GOOGLE_GENAI_API_KEY

    השלב הזה חשוב כדי למנוע דליפת מפתח ה-API בטעות, שמעניק גישה לשירות שעשוי להיות מותאם לחיוב.

    למידע נוסף על ניהול סודות, ראו אחסון של פרטי הגדרה רגישים וגישה אליהם.

  4. עורכים את הקובץ src/index.ts ומוסיפים את הקטע הבא אחרי הייבוא הקיים:

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

    לאחר מכן, בהגדרת התהליך, מכריזים שלפונקציית הענן צריכה להיות גישה לערך הסודי הזה:

    export const generatePoem = onFlow(
      {
        name: "generatePoem",
        // ...
        httpsOptions: {
          secrets: [googleAIapiKey],  // Add this line.
        },
      },
      async (subject) => {
        // ...
      }
    );
    

עכשיו, כשפורסים את הפונקציה הזו, מפתח ה-API נשמר ב-Cloud Secret Manager וזמין בסביבת Cloud Functions.

Gemini‏ (Vertex AI)

  1. במסוף Cloud, מפעילים את Vertex AI API לפרויקט ב-Firebase.

  2. בדף IAM, מוודאים שלחשבון השירות המוגדר כברירת מחדל ל-Compute הוקצה התפקיד Vertex AI User.

הסוד היחיד שצריך להגדיר במדריך הזה הוא של ספק המודל, אבל באופן כללי צריך לעשות משהו דומה לכל שירות שבו נעשה שימוש בתהליך.

הגדרת מדיניות CORS

אם תיגשו לתהליך מאפליקציית אינטרנט (כפי שתעשו בקטע ניסיון התהליך שנפרס), צריך להגדיר מדיניות CORS בפרמטר httpsOptions:

export const generatePoem = onFlow(
  ai,
  {
    name: "generatePoem",
    // ...
    httpsOptions: {
      cors: '*',
    },
  },
  async (subject: string) => {
    // ...
  }
);

סביר להניח שתרצו להגדיר מדיניות מגבילה יותר לאפליקציות בסביבת הייצור, אבל המדיניות הזו תעשה את העבודה במדריך הזה.

דוגמה מלאה

אחרי שמבצעים את כל השינויים שמפורטים למעלה, התהליך לפריסה ייראה בערך כך:

const googleAIapiKey = defineSecret("GOOGLE_GENAI_API_KEY");

export const generatePoem = onFlow(
  ai,
  {
    name: "generatePoem",
    inputSchema: z.string(),
    outputSchema: z.string(),
    authPolicy: firebaseAuth((user, input) => {
      if (!user.email_verified) {
        throw new Error("Verified email required to run flow");
      }
    }),
    httpsOptions: {
      secrets: [googleAIapiKey],
      cors: '*',
    },
  },
  async (subject: string) => {
    const { text } = await ai.generate(`Compose a poem about ${subject}.`);
    return text;
  }
);

3. פריסת תהליכים ב-Firebase

אחרי שמגדירים תהליכים באמצעות onFlow, אפשר לפרוס אותם כמו שפורסים פונקציות אחרות של Cloud Functions:

cd $PROJECT_ROOT
firebase deploy --only functions

סיימתם לפרוס את התהליך כפונקציית Cloud. עם זאת, לא תוכלו לגשת לנקודת הקצה שנפרסה באמצעות curl או כלי דומה, בגלל מדיניות ההרשאה של התהליך. בקטע הבא מוסבר איך לגשת לתהליך בצורה מאובטחת.

אופציונלי: ניסיון בתהליך הפריסה

כדי לנסות את נקודת הקצה של התהליך, אפשר לפרוס את אפליקציית האינטרנט לדוגמה הבאה:

  1. בקטע Project settings (הגדרות הפרויקט) במסוף Firebase, מוסיפים אפליקציית אינטרנט חדשה ובוחרים גם להגדיר אירוח.

  2. בקטע Authentication במסוף Firebase, מפעילים את הספק Google שבו תשתמשו בדוגמה הזו.

  3. בספריית הפרויקט, מגדירים את Firebase Hosting, שבו תפרסו את האפליקציה לדוגמה:

    cd $PROJECT_ROOT
    firebase init hosting

    מאשרים את הגדרות ברירת המחדל לכל ההנחיות.

  4. מחליפים את public/index.html בקוד הבא:

    <!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. פורסים את אפליקציית האינטרנט ואת הפונקציה של Cloud Functions:

    cd $PROJECT_ROOT
    firebase deploy

פותחים את אפליקציית האינטרנט על ידי כניסה לכתובת ה-URL שמודפסת על ידי הפקודה deploy. באפליקציה צריך להיכנס באמצעות חשבון Google, ואז אפשר להתחיל לשלוח בקשות לנקודות קצה.

אופציונלי: הפעלת תהליכים בממשק המשתמש למפתחים

אפשר להריץ תהליכים שהוגדרו באמצעות onFlow בממשק המשתמש למפתחים, בדיוק באותו אופן שבו מפעילים תהליכים שהוגדרו באמצעות defineFlow, כך שאין צורך לעבור בין השניים בין הפריסה לפיתוח.

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

או

cd $PROJECT_ROOT/functions
npm run genkit:start

עכשיו אפשר לנווט לכתובת ה-URL שנדפסה על ידי הפקודה genkit start כדי לגשת אליה.

אופציונלי: פיתוח באמצעות Firebase Local Emulator Suite

ב-Firebase יש חבילת אמולטורים לפיתוח מקומי, שאפשר להשתמש בה עם Genkit.

כדי להשתמש בממשק המשתמש של Genkit Dev עם Firebase Emulator Suite, מפעילים את אמולטורים של Firebase באופן הבא:

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

הפקודה הזו תריץ את הקוד במהדורת ה-emulator ותריץ את מסגרת Genkit במצב פיתוח, שתפעיל ותחשוף את Genkit reflection API (אבל לא את Dev UI).

כדי לראות את הטרייסים מ-Firestore בממשק המשתמש של הפיתוח, עוברים לכרטיסייה 'בדיקה' ומעבירים את המתג 'פיתוח/ייצור'. כשמגדירים את המצב ל-'prod', המערכת תטען נתוני מעקב מ-Firestore.