כתיבת פלאגין של Genkit Evaluator

אפשר להרחיב את Genkit ל-Firebase כך שיתמוך בהערכה מותאמת אישית של הפלט של מקרי הבדיקה, באמצעות שימוש ב-LLM בתור שופט, או על ידי שימוש פרוגרמטי בלבד.

הגדרת המעריך

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

מודלים להערכה מבוססי LLM

בודק שמבוסס על LLM משתמש ב-LLM כדי להעריך את הקלט, ההקשר או הפלט של תכונת ה-AI הגנרטיבית.

מעריכים המבוססים על LLM ב-Genkit מורכבים מ-3 רכיבים:

  • הנחיה
  • פונקציית ניקוד
  • פעולת מעריך

הגדרת ההנחיה

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

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

import { defineDotprompt } from '@genkit-ai/dotprompt';

// Define the expected output values
const DELICIOUSNESS_VALUES = ['yes', 'no', 'maybe'] as const;

// Define the response schema expected from the LLM
const DeliciousnessDetectionResponseSchema = z.object({
  reason: z.string(),
  verdict: z.enum(DELICIOUSNESS_VALUES),
});
type DeliciousnessDetectionResponse = z.infer<
  typeof DeliciousnessDetectionResponseSchema
>;

const DELICIOUSNESS_PROMPT = defineDotprompt(
  {
    input: {
      schema: z.object({
        output: z.string(),
      }),
    },
    output: {
      schema: DeliciousnessDetectionResponseSchema,
    },
  },
  `You are a food critic with a wide range in taste. Given the output, decide if it sounds delicious and provide your reasoning. Use only "yes" (if delicous), "no" (if not delicious), "maybe" (if you can't decide) as the verdict.

Here are a few examples:

Output:
Chicken parm sandwich
Response:
{ "reason": "This is a classic sandwich enjoyed by many - totally delicious", "verdict":"yes"}

Output:
Boston logan international airport tarmac
Response:
{ "reason": "This is not edible and definitely not delicious.", "verdict":"no"}

Output:
A juicy piece of gossip
Response:
{ "reason": "Gossip is sometimes metaphorically referred to as tasty.", "verdict":"maybe"}

Here is a new submission to assess:

Output:
{{output}}
Response:
`
);

הגדרת פונקציית הניקוד

עכשיו מגדירים את הפונקציה שתקבל דוגמה שכוללת את output כפי שנדרש בהנחיה, ותספק ציון לתוצאה. תרחישי הבדיקה של Genkit כוללים את השדה input כחובה, עם שדות אופציונליים ל-output ול-context. הבודק אחראי לוודא שכל השדות הנדרשים להערכה קיימים.

/**
 * Score an individual test case for delciousness.
 */
export async function deliciousnessScore<
  CustomModelOptions extends z.ZodTypeAny,
>(
  judgeLlm: ModelArgument<CustomModelOptions>,
  dataPoint: BaseDataPoint,
  judgeConfig?: CustomModelOptions
): Promise<Score> {
  const d = dataPoint;
  // Validate the input has required fields
  if (!d.output) {
    throw new Error('Output is required for Deliciousness detection');
  }

  //Hydrate the prompt
  const finalPrompt = DELICIOUSNESS_PROMPT.renderText({
    output: d.output as string,
  });

  // Call the LLM to generate an evaluation result
  const response = await generate({
    model: judgeLlm,
    prompt: finalPrompt,
    config: judgeConfig,
  });

  // Parse the output
  const parsedResponse = response.output();
  if (!parsedResponse) {
    throw new Error(`Unable to parse evaluator response: ${response.text()}`);
  }

  // Return a scored response
  return {
    score: parsedResponse.verdict,
    details: { reasoning: parsedResponse.reason },
  };
}

הגדרה של פעולת הבודק

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

/**
 * Create the Deliciousness evaluator action.
 */
export function createDeliciousnessEvaluator<
  ModelCustomOptions extends z.ZodTypeAny,
>(
  judge: ModelReference<ModelCustomOptions>,
  judgeConfig: z.infer<ModelCustomOptions>
): EvaluatorAction {
  return defineEvaluator(
    {
      name: `myAwesomeEval/deliciousness`,
      displayName: 'Deliciousness',
      definition: 'Determines if output is considered delicous.',
    },
    async (datapoint: BaseDataPoint) => {
      const score = await deliciousnessScore(judge, datapoint, judgeConfig);
      return {
        testCaseId: datapoint.testCaseId,
        evaluation: score,
      };
    }
  );
}

בודקים היוריסטיים

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

הכלי להערכה לפי שיטות ניתוח נתונים (heuristic) ב-Genkit מורכב משני רכיבים:

  • פונקציית ניקוד
  • פעולת מעריך

הגדרת פונקציית הניקוד

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

const US_PHONE_REGEX =
  /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4}$/i;

/**
 * Scores whether an individual datapoint matches a US Phone Regex.
 */
export async function usPhoneRegexScore(
  dataPoint: BaseDataPoint
): Promise<Score> {
  const d = dataPoint;
  if (!d.output || typeof d.output !== 'string') {
    throw new Error('String output is required for regex matching');
  }
  const matches = US_PHONE_REGEX.test(d.output as string);
  const reasoning = matches
    ? `Output matched regex ${regex.source}`
    : `Output did not match regex ${regex.source}`;
  return {
    score: matches,
    details: { reasoning },
  };
}

הגדרה של פעולת הבודק

/**
 * Configures a regex evaluator to match a US phone number.
 */
export function createUSPhoneRegexEvaluator(
  metrics: RegexMetric[]
): EvaluatorAction[] {
  return metrics.map((metric) => {
    const regexMetric = metric as RegexMetric;
    return defineEvaluator(
      {
        name: `myAwesomeEval/${metric.name.toLocaleLowerCase()}`,
        displayName: 'Regex Match',
        definition:
          'Runs the output against a regex and responds with 1 if a match is found and 0 otherwise.',
        isBilled: false,
      },
      async (datapoint: BaseDataPoint) => {
        const score = await regexMatchScore(datapoint, regexMetric.regex);
        return fillScores(datapoint, score);
      }
    );
  });
}

תצורה

אפשרויות פלאגין

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

לכל הפחות, צריך לבחור אילו מדדים יש לרשום.

export enum MyAwesomeMetric {
  WORD_COUNT = 'WORD_COUNT',
  US_PHONE_REGEX_MATCH = 'US_PHONE_REGEX_MATCH',
}

export interface PluginOptions {
  metrics?: Array<MyAwesomeMetric>;
}

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

export enum MyAwesomeMetric {
  DELICIOUSNESS = 'DELICIOUSNESS',
  US_PHONE_REGEX_MATCH = 'US_PHONE_REGEX_MATCH',
}

export interface PluginOptions<ModelCustomOptions extends z.ZodTypeAny> {
  judge: ModelReference<ModelCustomOptions>;
  judgeConfig?: z.infer<ModelCustomOptions>;
  metrics?: Array<MyAwesomeMetric>;
}

הגדרת הפלאגין

יישומי הפלאגין רשומים ב-framework באמצעות הקובץ genkit.config.ts בפרויקט. כדי שתהיה לך אפשרות להגדיר פלאגין חדש, צריך להגדיר פונקציה שמגדירה GenkitPlugin ומגדירה אותו עם ה-PluginOptions שמוגדר למעלה.

במקרה הזה יש לנו שני מעריכים, DELICIOUSNESS ו-US_PHONE_REGEX_MATCH. כאן מתבצע הרישום של הבודקים האלה ב-Plugin וב-Firebase Genkit.

export function myAwesomeEval<ModelCustomOptions extends z.ZodTypeAny>(
  params: PluginOptions<ModelCustomOptions>
): PluginProvider {
  // Define the new plugin
  const plugin = genkitPlugin(
    'myAwesomeEval',
    async (params: PluginOptions<ModelCustomOptions>) => {
      const { judge, judgeConfig, metrics } = params;
      const evaluators: EvaluatorAction[] = metrics.map((metric) => {
        // We'll create these functions in the next step
        switch (metric) {
          case DELICIOUSNESS:
            // This evaluator requires an LLM as judge
            return createDeliciousnessEvaluator(judge, judgeConfig);
          case US_PHONE_REGEX_MATCH:
            // This evaluator does not require an LLM
            return createUSPhoneRegexEvaluator();
        }
      });
      return { evaluators };
    }
  );

  // Create the plugin with the passed params
  return plugin(params);
}
export default myAwesomeEval;

הגדרת Genkit

מוסיפים את הפלאגין שהוגדר לאחרונה להגדרות של Genkit.

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

import { gemini15Flash } from '@genkit-ai/googleai';

export default configureGenkit({
  plugins: [
    ...
    myAwesomeEval({
      judge: gemini15Flash,
      judgeConfig: {
        safetySettings: [
          {
            category: 'HARM_CATEGORY_HATE_SPEECH',
            threshold: 'BLOCK_NONE',
          },
          {
            category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
            threshold: 'BLOCK_NONE',
          },
          {
            category: 'HARM_CATEGORY_HARASSMENT',
            threshold: 'BLOCK_NONE',
          },
          {
            category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
            threshold: 'BLOCK_NONE',
          },
        ],
      },
      metrics: [
        MyAwesomeMetric.DELICIOUSNESS,
        MyAwesomeMetric.US_PHONE_REGEX_MATCH
      ],
    }),
  ],
  ...
});

בדיקה

אותן בעיות שחלות על הערכת האיכות של הפלט של תכונה של AI גנרטיבי חלות גם על הערכת יכולת השיפוט של מעריך שמבוסס על LLM.

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

לדוגמה, קובץ JSON deliciousness_dataset.json יכול להיראות כך:

[
  {
    "testCaseId": "delicous_mango",
    "input": "What is a super delicious fruit",
    "output": "A perfectly ripe mango – sweet, juicy, and with a hint of tropical sunshine."
  },
  {
    "testCaseId": "disgusting_soggy_cereal",
    "input": "What is something that is tasty when fresh but less tasty after some time?",
    "output": "Stale, flavorless cereal that's been sitting in the box too long."
  }
]

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

לאחר מכן, משתמשים ב-CLI של Genkit כדי להריץ את הכלי להערכה מול תרחישי הבדיקה האלה.

genkit eval:run deliciousness_dataset.json

ניתן לראות את התוצאות בממשק המשתמש של Genkit.

genkit start

נווט אל localhost:4000/evaluate.