Scrivere un valutatore Genkit

Firebase Genkit può essere esteso per supportare la valutazione personalizzata dell'output del caso di test, utilizzando un LLM come giudice o in modo puramente programmatico.

Definizione del valutatore

I valutatori sono funzioni che valutano i contenuti forniti e generati da un LLM. Esistono due approcci principali alla valutazione automatica (test): valutazione basata su regole ed euristica e valutazione basata su LLM. Nell'approccio euristico, definisci una funzione deterministica come quelle dello sviluppo software tradizionale. In una valutazione basata su LLM, i contenuti vengono reintrodotti in un LLM a cui viene chiesto di assegnare un punteggio all'output in base ai criteri impostati in un prompt.

Valutatori basati su LLM

Un valutatore basato su LLM sfrutta un LLM per valutare l'input, il contesto o l'output della funzionalità di IA generativa.

I valutatori basati su LLM in Genkit sono costituiti da tre componenti:

  • Un prompt
  • Una funzione di punteggio
  • Un'azione di valutazione

Definisci il prompt

Per questo esempio, il prompt chiederà all'LLM di giudicare quanto sia delizioso l'output. Innanzitutto, fornisci il contesto all'LLM, poi descrivi cosa vuoi che faccia e, infine, fornisci alcuni esempi su cui basare la sua risposta.

L'utilità definePrompt di Genkit offre un modo semplice per definire i prompt con convalida di input e output. Ecco come configurare un prompt di valutazione con definePrompt.

const DELICIOUSNESS_VALUES = ['yes', 'no', 'maybe'] as const;

const DeliciousnessDetectionResponseSchema = z.object({
  reason: z.string(),
  verdict: z.enum(DELICIOUSNESS_VALUES),
});
type DeliciousnessDetectionResponse = z.infer<typeof DeliciousnessDetectionResponseSchema>;

const DELICIOUSNESS_PROMPT = ai.definePrompt(
  {
    name: 'deliciousnessPrompt',
    inputSchema: z.object({
      output: z.string(),
    }),
    outputSchema: DeliciousnessDetectionResponseSchema,
  },
  `You are a food critic. Assess whether the provided output sounds delicious, giving only "yes" (delicious), "no" (not delicious), or "maybe" (undecided) as the verdict.

  Examples:
  Output: Chicken parm sandwich
  Response: { "reason": "A classic and beloved dish.", "verdict": "yes" }

  Output: Boston Logan Airport tarmac
  Response: { "reason": "Not edible.", "verdict": "no" }

  Output: A juicy piece of gossip
  Response: { "reason": "Metaphorically 'tasty' but not food.", "verdict": "maybe" }

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

Definisci la funzione di punteggio

Ora definisci la funzione che prenderà un esempio che include output come richiesto dal prompt e assegnerà un punteggio al risultato. I casi di test di Genkit includono input come campo obbligatorio, con campi facoltativi per output e context. È responsabilità del valutatore verificare che siano presenti tutti i campi richiesti per la valutazione.

import { BaseEvalDataPoint, Score } from 'genkit/evaluator';

/**
 * Score an individual test case for delciousness.
 */
export async function deliciousnessScore<
  CustomModelOptions extends z.ZodTypeAny,
>(
  judgeLlm: ModelArgument<CustomModelOptions>,
  dataPoint: BaseEvalDataPoint,
  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 },
  };
}

Definisci l'azione dell'valutatore

Il passaggio finale consiste nello scrivere una funzione che definisce l'azione dello valutatore stesso.

import { BaseEvalDataPoint, EvaluatorAction } from 'genkit/evaluator';

/**
 * 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: BaseEvalDataPoint) => {
      const score = await deliciousnessScore(judge, datapoint, judgeConfig);
      return {
        testCaseId: datapoint.testCaseId,
        evaluation: score,
      };
    }
  );
}

Valutatori euristici

Un valutatore di euristiche può essere qualsiasi funzione utilizzata per valutare l'input, il contesto o l'output della funzionalità di AI generativa.

I valutatori euristici in Genkit sono costituiti da due componenti:

  • Una funzione di punteggio
  • Un'azione di valutazione

Definisci la funzione di punteggio

Come per l'valutatore basato su LLM, definisci la funzione di punteggio. In questo caso, la funzione di punteggio non deve conoscere l'LLM del giudice o la relativa configurazione.

import { BaseEvalDataPoint, Score } from 'genkit/evaluator';

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: BaseEvalDataPoint
): 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 },
  };
}

Definisci l'azione dell'valutatore

import { BaseEvalDataPoint, EvaluatorAction } from 'genkit/evaluator';

/**
 * 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: BaseEvalDataPoint) => {
        const score = await regexMatchScore(datapoint, regexMetric.regex);
        return fillScores(datapoint, score);
      }
    );
  });
}

Configurazione

Opzioni del plug-in

Definisci il PluginOptions che verrà utilizzato dal plug-in di valutazione personalizzata. Questo oggetto non ha requisiti rigorosi e dipende dai tipi di valutatori definiti.

Come minimo, dovrà includere la definizione delle metriche da registrare.

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

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

Se questo nuovo plug-in utilizza un LLM come giudice e supporta la sostituzione dell'LLM da utilizzare, definisci parametri aggiuntivi nell'oggetto 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>;
}

Definizione del plug-in

I plug-in vengono registrati nel framework tramite il file genkit.config.ts in un progetto. Per poter configurare un nuovo plug-in, definisci una funzione che definisca un GenkitPlugin e lo configuri con il PluginOptions definito sopra.

In questo caso abbiamo due valutatori, DELICIOUSNESS e US_PHONE_REGEX_MATCH. Qui vengono registrati gli valutatori con il plug-in e con Firebase Genkit.

export function myAwesomeEval<ModelCustomOptions extends z.ZodTypeAny>(
  options: PluginOptions<ModelCustomOptions>
): PluginProvider {
  // Define the new plugin
  const plugin = (options?: MyPluginOptions<ModelCustomOptions>) => {
    return genkitPlugin(
    'myAwesomeEval',
    async (ai: Genkit) => {
      const { judge, judgeConfig, metrics } = options;
      const evaluators: EvaluatorAction[] = metrics.map((metric) => {
        switch (metric) {
          case DELICIOUSNESS:
            // This evaluator requires an LLM as judge
            return createDeliciousnessEvaluator(ai, judge, judgeConfig);
          case US_PHONE_REGEX_MATCH:
            // This evaluator does not require an LLM
            return createUSPhoneRegexEvaluator();
        }
      });
      return { evaluators };
    })
  }
  // Create the plugin with the passed options
  return plugin(options);
}
export default myAwesomeEval;

Configura Genkit

Aggiungi il plug-in appena definito alla configurazione di Genkit.

Per la valutazione con Gemini, disattiva le impostazioni di sicurezza in modo che il valutatore possa accettare, rilevare e valutare i contenuti potenzialmente dannosi.

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

const ai = genkit({
  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
      ],
    }),
  ],
  ...
});

Test

Gli stessi problemi che si applicano alla valutazione della qualità dell'output di una funzionalità di IA generativa si applicano alla valutazione della capacità di giudizio di un valutatore basato su LLM.

Per capire se il valutatore personalizzato funziona al livello previsto, crea un insieme di casi di test con una risposta chiara corretta e sbagliata.

Un esempio di bontà potrebbe avere il seguente aspetto in un file 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."
  }
]

Questi esempi possono essere generati da persone fisiche oppure puoi chiedere a un modello LLM di aiutarti a creare un insieme di casi di test che possono essere selezionati. Esistono anche molti set di dati di benchmark che possono essere utilizzati.

Quindi, utilizza l'interfaccia a riga di comando Genkit per eseguire lo strumento di valutazione su questi casi di test.

genkit eval:run deliciousness_dataset.json

Visualizza i risultati nell'interfaccia utente di Genkit.

genkit start

Vai a localhost:4000/evaluate.