הרצה של סקריפטים מותאמים אישית בצד השרת לפני ואחרי בקשות


אתם יכולים להריץ סקריפטים מותאמים אישית בצד השרת לפני ואחרי כל בקשה שהאפליקציה שולחת אל Gemini API דרך Firebase AI Logic – בלי לשנות את קוד הלקוח. אתם מטמיעים את הסקריפטים האלה כפונקציות בסגנון קריאה חוזרת (callback) שנפרסות ב-Cloud Functions for Firebase.

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

יש שני סוגים של אירועים:

  • ‫beforeGenerateContent: פועל לפני שהבקשה מגיעה אל Gemini API. הפונקציה יכולה לבדוק או לשנות את הבקשה, או לחסום אותה לגמרי על ידי הפעלת שגיאה.

  • ‫afterGenerateContent: פועלת אחרי שהתגובה נשלחת בחזרה מ-Gemini API ולפני שהיא מוחזרת לאפליקציית הלקוח. הפונקציה יכולה לבדוק או לשנות את התגובה, לחסום אותה לגמרי או רק לצפות בה (למשל, לצורך רישום ביומן או ביקורת).

אחרי שפורסים את הסקריפטים כפונקציות ב-Cloud Functions for Firebase, הם רשומים כטריגרים של Firebase AI Logic. כלומר, הם יופעלו בכל בקשה של generateContent בפרויקט אל Gemini API דרך Firebase AI Logic (כולל בקשות שבוצעו באמצעות תבניות לפרומפטים בשרת).

הפונקציות האלה לא מופעלות על ידי בקשות שנשלחות אל Gemini API ולא דרך Firebase AI Logic.

דרישות מוקדמות

שלב 1: הגדרת הפרויקט ל-Cloud Functions for Firebase

אם אף פעם לא השתמשתם ב-Cloud Functions for Firebase בפרויקט Firebase, אתם צריכים להשלים את ההגדרה הבאה.

  1. מוודאים שהפרויקט ב-Firebase מוגדר לתוכנית התמחור Blaze בתשלום לפי שימוש (נדרשת כדי להשתמש ב-Cloud Functions for Firebase).

  2. מתקינים את ממשקי שורת הפקודה (CLI): gcloud CLI ו- Firebase CLI

  3. מקצים לחשבון השירות שמוגדר כברירת מחדל ב-Compute את התפקיד חשבון שירות של Cloud Build (roles/cloudbuild.builds.builder) שנדרש כדי ליצור את הפונקציה. מריצים את הפקודה הבאה ב-gcloud CLI:

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member="serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
      --role="roles/cloudbuild.builds.builder"
    
  4. מפעילים את Cloud Functions for Firebase בפרויקט Firebase:

    1. מריצים את הפקודה הבאה ב-CLI של Firebase:

      firebase init functions
      
    2. כשמופיעה בקשה, בוחרים באפשרות TypeScript.

    3. מוודאים ש-firebase-functions ב-functions/package.json הוא מגרסה 6.3.0 ואילך. כך בודקים את הגרסה:

      npm --prefix functions list firebase-functions
      

שלב 2: כותבים את הפונקציות

כתיבת פונקציה לפני הבקשה (beforeGenerateContent) כתיבת פונקציה אחרי הבקשה (afterGenerateContent)

כתיבת פונקציה לפני הבקשה (beforeGenerateContent)

עם סוג האירוע beforeGenerateContent, הפונקציה מופעלת כששרת ה-proxy של Firebase AI Logic מקבל בקשת generateContent. הפונקציה פועלת על הבקשה לפני שהבקשה נשלחת אל Gemini API. הפונקציה יכולה לשנות את הבקשה או לחסום אותה לחלוטין.

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

דוגמה

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

  • מציינת שהפונקציה צריכה לפעול רק כשהבקשה מיועדת לספק Gemini API ספציפי.

  • בודק אם ההנחיה מכילה נושאים חסומים, ואם כן, דוחה את הבקשה ומחזיר שגיאה.

  • מגביל את מספר הטוקנים המקסימלי בפלט של מודלים ליצירת טקסט.

import { logger } from "firebase-functions";
import {
  beforeGenerateContent,
  HttpsError,
  vertexV1Beta1,
  type VertexV1Beta1GenerateContentRequest,
} from "firebase-functions/v2/ai";

const BLOCKED_TOPICS = ["weapon", "explosive", "self-harm"];
const MAX_OUTPUT_TOKENS = 4000;

export const guardPrompts = beforeGenerateContent((event) => {
  // 1. Optional: If you want the function to only run for a specific Gemini API provider, specify it here.
  if (event.data.api !== vertexV1Beta1) return;
  const request = event.data.request as VertexV1Beta1GenerateContentRequest;

  // 2. Read the prompt: contents[] -> parts[] -> text
  const prompt = (request.contents ?? [])
    .flatMap((c) => c.parts ?? [])
    .map((p) => ("text" in p ? p.text : "") ?? "")
    .join(" ")
    .toLowerCase();

  // 3. Throwing rejects the request. The request is never sent to the Gemini API.
  const blocked = BLOCKED_TOPICS.find((t) => prompt.includes(t));
  if (blocked) {
    logger.warn("Blocked a prompt", { topic: blocked });
    throw new HttpsError("invalid-argument", `We don't return content about ${blocked}.`);
  }

  logger.info("Allowing generation", {
    model: event.data.model,
    authType: event.authType,
    authId: event.authId,
    appId: event.appId,
  });

  // 4. The next step truncates the response, but that will break images.
  if (event.data.model.includes("image")) return;

  // 5. Return the WHOLE request, edited. Returning nothing leaves it untouched.
  return {
    ...request,
    generationConfig: {
      ...request.generationConfig,
      maxOutputTokens: Math.min(
        request.generationConfig?.maxOutputTokens ?? MAX_OUTPUT_TOKENS,
        MAX_OUTPUT_TOKENS,
      ),
    },
  };
});

נקודות מרכזיות לגבי פונקציות של בקשות מראש

  • מציינים את הספק של Gemini API: event.data.request יכול להיות של Gemini Developer API או של Agent Platform Gemini API (formerly Vertex AI). לאובייקטים של הבקשות בממשקי ה-API השונים האלה יש צורות שונות. כדי לעבוד בבטחה עם אובייקט הבקשה, צריך לבדוק את event.data.api (לדוגמה, להשוות ל-geminiV1Beta או ל-vertexV1Beta1, בהתאמה).

  • הפעלת שגיאה חוסמת את הבקשה: אם מפעילים HttpsError, הבקשה תידחה.

  • החזרת הבקשה כולה: אם הפונקציה משנה את הבקשה, צריך להחזיר את אובייקט הבקשה המלא והמשתנה. אם לא מחזירים כלום (או מחזירים undefined), הבקשה לא משתנה.

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

כתיבת פונקציה של בקשת POST‏ (afterGenerateContent)

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

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

דוגמה

הנה דוגמה לפונקציית post-request שמתעדת את השימוש בטוקנים ואת סיבת הסיום:

import { logger } from "firebase-functions";
import {
  afterGenerateContent,
  vertexV1Beta1,
  type VertexV1Beta1GenerateContentResponse,
} from "firebase-functions/v2/ai";

export const recordGenerationUsage = afterGenerateContent((event) => {
  // Optional: If you want the function to only run for a specific Gemini API provider, specify it here.
  if (event.data.api !== vertexV1Beta1) return;
  const response = event.data.response as VertexV1Beta1GenerateContentResponse;

  logger.info("Generation finished", {
    model: event.data.model,
    promptTokens: response.usageMetadata?.promptTokenCount,
    totalTokens: response.usageMetadata?.totalTokenCount,
    finishReason: response.candidates?.[0]?.finishReason,
  });

  // To leave the response untouched, return nothing.
  // To modify the response, return a modified response object here.
});

נקודות מרכזיות לגבי פונקציות של בקשות POST

  • מציינים את הספק של Gemini API: event.data.response יכול להיות Gemini Developer API או Agent Platform Gemini API (formerly Vertex AI). כדי להמיר את אובייקט הבקשה ולעבוד איתו בצורה בטוחה, צריך לבדוק את event.data.api (לדוגמה, להשוות ל-geminiV1Beta או ל-vertexV1Beta1, בהתאמה).

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

שלב 3: פריסת הפונקציות

פריסת הפונקציות ב-Firebase נותנת לסוכן השירות של Firebase AI Logic הרשאה להפעיל את הפונקציות האלה, ורושמת כל פונקציה בתור טריגר של Firebase AI Logic.

  1. פורסים את הפונקציות באמצעות ה-CLI של Firebase:

    firebase deploy --only functions
    
  2. אחרי הפריסה, מוודאים שהפונקציות נפרסו ב-Firebase:

    firebase functions:list
    
  3. אם אתם צריכים לחזור על הפונקציה:

    מעדכנים את הפונקציה בספריית הפרויקט ומריצים שוב את הפקודה firebase deploy --only functions.

הפסקת ההפעלה של פונקציה

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

  • אפשרות 1: מחיקת הפונקציה באופן משתמע

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

    2. מריצים את הפקודה הבאה ב-CLI של Firebase:

      firebase deploy --only functions
      
  • אפשרות 2: מחיקת הפונקציה באופן מפורש

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

    2. מריצים את הפקודה הבאה ב-CLI של Firebase:

      firebase functions:delete FUNCTION_NAME
      



הפניה לנתוני אירועים

גם beforeGenerateContent וגם afterGenerateContent מקבלים אובייקט AIBlockingEvent שמכיל הקשר ומטא-נתונים לגבי הבקשה.

מטא-נתונים של בקשה ברמה העליונה (AIBlockingEvent)

האובייקט AIBlockingEvent ברמה העליונה מספק מידע על הגורם שמפעיל את הפונקציה ועל הסביבה שבה היא מופעלת:

  • ‫event.authType: מצב האימות של המתקשר: "app_user",‏ "unauthenticated" או "unknown".
  • ‫event.authId: מזהה המשתמש של הגורם המבצע את הקריאה החוזרת (callback) באימות ב-Firebase, אם הוא מחובר לחשבון.
  • ‫event.authClaims: טענות האימות המותאמות אישית של מבצע הקריאה, אם יש כאלה.
  • ‫event.appId: מזהה האפליקציה ב-Firebase ששלחה את הבקשה.
  • ‫event.androidPackageName או event.iosBundleId: שם החבילה או מזהה החבילה של אפליקציית השיחות (רלוונטי לפלטפורמות של Android או של אפל, בהתאמה).
  • ‫event.data: המטען הייעודי (payload) של האירוע, שמשתנה בין פונקציות לפני בקשה לפונקציות אחרי בקשה:

נתוני אירועים לפני הבקשה (beforeGenerateContent)

בפונקציה beforeGenerateContent, הפרמטר event.data מאוכלס באובייקט BeforeGenerateContentData:

  • ‫event.data.api: ספק ה-Gemini API: geminiV1Beta (Gemini Developer API) או vertexV1Beta1 (Agent Platform Gemini API (formerly Vertex AI)).
  • ‫event.data.model: הנתיב המלא למשאב המודל (לדוגמה, projects/{PROJECT_ID}/locations/global/publishers/google/models/gemini-3.8-flash).
  • ‫event.data.template: מטא-נתונים על תבנית לפרומפט בשרת שנעשה בה שימוש (PromptTemplateInfo), אם רלוונטי.
  • ‫event.data.request: המטען הייעודי (payload) של הבקשה היוצאת. סוג האובייקט והמאפיינים תלויים בספק של Gemini API:

נתוני אירועים של בקשות POST‏ (afterGenerateContent)

בפונקציה afterGenerateContent, הפרמטר event.data מאוכלס באובייקט AfterGenerateContentData. האובייקט הזה מרחיב את BeforeGenerateContentData (ומספק את api, model, template ו-request), ומוסיף את התשובה של המודל:

  • ‫event.data.response: המטען הייעודי (payload) של התשובה מהמודל. סוג האובייקט והמאפיינים תלויים בספק של Gemini API:



מגבלות והתנהגויות

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

  • בקשות generateContent בלבד: אפשר להפעיל את הפונקציות האלה רק באמצעות בקשות generateContent אל Gemini API דרך Firebase AI Logic.

    הפעולות הבאות לא יפעילו את הפונקציות האלה, והפונקציות יבוצעו באופן שקט בבקשה הזו:

    • בקשות אל generateContentStream לא יפעילו את הפונקציות האלה.

    • בקשות אל Gemini Live API לא יפעילו את הפונקציות האלה.

  • אין שינויים בקוד בצד הלקוח: מלבד לוודא שאתם משתמשים בבקשות generateContent כשאתם רוצים להריץ את הפונקציות האלה, לא נדרשים שינויים בבסיס הקוד בצד הלקוח.

    הפונקציות האלה נפרסות בשרתים שלנו, והן רשומות כטריגרים של Firebase AI Logic כדי ששרת ה-proxy של Firebase AI Logic יוכל ליירט בקשות ותגובות בצד השרת.

  • היקף ברמת הפרויקט: אפשר לפרוס לכל היותר פונקציה אחת של beforeGenerateContent ופונקציה אחת של afterGenerateContent בכל פרויקט ב-Firebase.

  • מיקומי ברירת מחדל: הפונקציות האלה ייפרסו ב-us-central1 כברירת מחדל (מידע נוסף על מיקומים של פונקציות). עם זאת, הפונקציה תירשם כטריגר של Firebase AI Logic באזור global, בלי קשר למקום שבו תפרסו את הפונקציה.