Pisanie oceniającego Genkit

Firebase Genkit można rozszerzyć, aby obsługiwał niestandardową ocenę danych wyjściowych testu, korzystając z modelu LLM jako sędziego lub wyłącznie programowo.

Definicja oceniającego

Oceniacze to funkcje, które oceniają treści przekazywane do modelu LLM i generowane przez niego. Istnieją 2 główne podejścia do automatycznej oceny (testowania): ocena heurystyczna i ocena na podstawie LLM. W przypadku podejścia heurystycznego definiujesz funkcję deterministyczną, taką jak w tradycyjnym rozwoju oprogramowania. W przypadku oceny opartej na LLM treści są przekazywane do tego modelu, który ocenia dane wyjściowe zgodnie z kryteriami określonymi w promptach.

Oceniacze oparte na LLM

Evaluator oparty na LLM korzysta z modelu LLM do oceny danych wejściowych, kontekstu lub danych wyjściowych funkcji generatywnej AI.

Oceny oparte na LLM w Genkit składają się z 3 komponentów:

  • prompt
  • Funkcja punktacji
  • Działanie instancji oceny

Definiowanie promptu

W tym przykładzie prompt poprosi LLM o ocena, jak smaczne są wyniki. Najpierw podaj kontekst LLM, a potem opisz, czego oczekujesz od modelu, i na koniec podaj kilka przykładów, na których może się on oprzeć.

Narzędzie definePrompt w Genkit umożliwia łatwe definiowanie promptów z weryfikacją danych wejściowych i wyjściowych. Oto jak skonfigurować prompt oceny za pomocą 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:
  `
);

Definiowanie funkcji punktacji

Teraz zdefiniuj funkcję, która przyjmie przykład zawierający output, zgodnie z wymaganiami promptu, i wyznacz wynik. Przypadki testowe Genkit zawierają pole input jako wymagane, a pola opcjonalne outputcontext. Oceniający musi sprawdzić, czy wszystkie pola wymagane do oceny są obecne.

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

Definiowanie działania oceniającego

Ostatnim krokiem jest napisanie funkcji, która definiuje samo działanie oceniającego.

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

Heurystyka

Heurystycznym oceniającym może być dowolna funkcja służąca do oceny danych wejściowych, kontekstu lub danych wyjściowych funkcji generatywnej AI.

Heurystyczne algorytmy oceniające w Genkit składają się z 2 komponentów:

  • Funkcja punktacji
  • Działanie instancji oceny

Definiowanie funkcji punktacji

Podobnie jak w przypadku oceniacza opartego na LLM, zdefiniuj funkcję punktacji. W tym przypadku funkcja oceniania nie musi znać LLM sędziego ani jego konfiguracji.

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

Definiowanie działania oceniającego

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

Konfiguracja

Opcje wtyczki

Określ PluginOptions, których będzie używać niestandardowy wtyczka oceniająca. Ten obiekt nie ma ścisłych wymagań i jest zależny od zdefiniowanych typów oceniaczy.

Musi ona zawierać co najmniej definicję danych, które mają być rejestrowane.

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

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

Jeśli nowa wtyczka używa LLM jako sędziego i obsługuje wymianę LLM, zdefiniuj dodatkowe parametry w obiekcie 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>;
}

Definicja wtyczki

Wtyczki są rejestrowane w ramach za pomocą pliku genkit.config.ts w projekcie. Aby móc skonfigurować nowy wtyczkę, zdefiniuj funkcję, która definiuje GenkitPlugin i konfiguruje ją za pomocą zdefiniowanego powyżej PluginOptions.

W tym przypadku mamy 2 oceniających: DELICIOUSNESSUS_PHONE_REGEX_MATCH. Tutaj są one rejestrowane w pluginie i w 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;

Konfigurowanie Genkit

Dodaj nowo zdefiniowaną wtyczkę do konfiguracji Genkit.

W przypadku oceny za pomocą Gemini wyłącz ustawienia bezpieczeństwa, aby oceniający mógł zaakceptować, wykryć i ocenić potencjalnie szkodliwe treści.

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
      ],
    }),
  ],
  ...
});

Testowanie

Ocena zdolności oceniania przez weryfikatora opartego na LLM jest związana z tymi samymi problemami, które występują w przypadku oceny jakości danych wyjściowych funkcji generatywnej AI.

Aby sprawdzić, czy funkcja niestandardowa działa zgodnie z oczekiwaniami, utwórz zestaw przypadków testowych z jasno określonymi prawidłowymi i nieprawidłowymi odpowiedziami.

Przykładowy plik json z wartością deliciousness może wyglądać tak: 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."
  }
]

Przykłady mogą być tworzone przez człowieka lub możesz poprosić LLM o utworzenie zestawu testów, które można dostosować. Dostępnych jest też wiele zbiorów danych porównawczych, których można użyć.

Następnie użyj interfejsu wiersza poleceń Genkit do uruchomienia oceniacza na tych przypadkach testowych.

genkit eval:run deliciousness_dataset.json

Wyświetl wyniki w interfejsie Genkit.

genkit start

Wejdź na localhost:4000/evaluate.