Pisanie oceniającego Genkit

Możesz rozszerzyć Firebase Genkit, aby obsługiwał niestandardową ocenę, korzystając z modelu LLM jako sędziego lub z oceny programowej (heurystycznej).

Definicja oceniającego

Oceniacze to funkcje, które oceniają odpowiedź LLM. Istnieją 2 główne podejścia do automatycznej oceny: ocena heurystyczna i ocena na podstawie modeli LLM. W przypadku podejścia heurystycznego definiujesz funkcję deterministyczną. Natomiast w przypadku oceny opartej na modelu LLM treści są przekazywane do tego modelu, który ocenia dane wyjściowe zgodnie z kryteriami określonymi w promptzie.

Metoda ai.defineEvaluator, której używasz do definiowania działania evaluatora w Genkit, obsługuje oba podejścia. W tym dokumencie znajdziesz kilka przykładów stosowania tej metody do oceny za pomocą heurystyki i modeli LLM.

Oceniający na podstawie LLM

Evaluator oparty na LLM wykorzystuje model LLM do oceny input, context i output 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 weryfikator korzysta z LLM, aby określić, czy jedzenie (output) jest smaczne. Najpierw podaj kontekst LLM, a potem opisz, czego oczekujesz od modelu. Na koniec podaj kilka przykładów, na których może się on opierać.

Narzędzie definePrompt w Genkit ułatwia definiowanie promptów z weryfikacją danych wejściowych i wyjściowych. Poniższy kod to przykład konfigurowania promptu oceny za pomocą funkcji definePrompt.

import { z } from "genkit";

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

const DeliciousnessDetectionResponseSchema = z.object({
  reason: z.string(),
  verdict: z.enum(DELICIOUSNESS_VALUES),
});

function getDeliciousnessPrompt(ai: Genkit) {
  return  ai.definePrompt({
      name: 'deliciousnessPrompt',
      input: {
        schema: z.object({
          responseToTest: z.string(),
        }),
      },
      output: {
        schema: 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: {{ responseToTest }}
    Response:
    `
  );
}

Definiowanie funkcji punktacji

Zdefiniuj funkcję, która przyjmuje przykład z wartością output wymaganą przez prompt, i wyznacza wynik. Testy Genkit zawierają pole input jako pole wymagane, a pola outputcontext jako opcjonalne. Oceniający musi sprawdzić, czy wszystkie pola wymagane do oceny są obecne.

import { ModelArgument, z } from 'genkit';
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 and generate an evaluation result
  const deliciousnessPrompt = getDeliciousnessPrompt(ai);
  const response = await deliciousnessPrompt(
    {
      responseToTest: d.output as string,
    },
    {
      model: judgeLlm,
      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 EvaluatorAction.

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

/**
 * Create the Deliciousness evaluator action.
 */
export function createDeliciousnessEvaluator<
  ModelCustomOptions extends z.ZodTypeAny,
>(
  ai: Genkit,
  judge: ModelArgument<ModelCustomOptions>,
  judgeConfig?: z.infer<ModelCustomOptions>
): EvaluatorAction {
  return ai.defineEvaluator(
    {
      name: `myCustomEvals/deliciousnessEvaluator`,
      displayName: 'Deliciousness',
      definition: 'Determines if output is considered delicous.',
      isBilled: true,
    },
    async (datapoint: BaseEvalDataPoint) => {
      const score = await deliciousnessScore(judge, datapoint, judgeConfig);
      return {
        testCaseId: datapoint.testCaseId,
        evaluation: score,
      };
    }
  );
}

Metoda defineEvaluator jest podobna do innych konstruktorów Genkit, takich jak defineFlowdefineRetriever. Ta metoda wymaga przekazania parametru EvaluatorFn jako wywołania zwrotnego. Metoda EvaluatorFn przyjmuje obiekt BaseEvalDataPoint, który odpowiada pojedynczemu wpisowi w danym zbiorze danych, a także opcjonalny parametr custom-options (jeśli został podany). Funkcja przetwarza punkt danych i zwraca obiekt EvalResponse.

Schematy Zod dla BaseEvalDataPointEvalResponse są następujące:

BaseEvalDataPoint
export const BaseEvalDataPoint = z.object({
  testCaseId: z.string(),
  input: z.unknown(),
  output: z.unknown().optional(),
  context: z.array(z.unknown()).optional(),
  reference: z.unknown().optional(),
  testCaseId: z.string().optional(),
  traceIds: z.array(z.string()).optional(),
});

export const EvalResponse = z.object({
  sampleIndex: z.number().optional(),
  testCaseId: z.string(),
  traceId: z.string().optional(),
  spanId: z.string().optional(),
  evaluation: z.union([ScoreSchema, z.array(ScoreSchema)]),
});
ScoreSchema
const ScoreSchema = z.object({
  id: z.string().describe('Optional ID to differentiate multiple scores').optional(),
  score: z.union([z.number(), z.string(), z.boolean()]).optional(),
  error: z.string().optional(),
  details: z
    .object({
      reasoning: z.string().optional(),
    })
    .passthrough()
    .optional(),
});

Obiekt defineEvaluator umożliwia użytkownikowi podanie nazwy, czytelnej dla użytkownika nazwy wyświetlanej i definicji dla oceniającego. Wyświetlana nazwa i definicja są wyświetlane wraz z wynikami oceny w interfejsie programisty. Zawiera on też opcjonalne pole isBilled, które wskazuje, czy ta funkcja oceny może spowodować obciążenie (np. korzysta z opłacanego modelu LLM lub interfejsu API). Jeśli zostanie naliczona opłata, interfejs poprosi użytkownika o potwierdzenie w CLI, zanim pozwoli mu przeprowadzić ocenę. Dzięki temu unikniesz niechcianych wydatków.

Heurystyka

Heurystycznym weryfikatorem może być dowolna funkcja służąca do oceny input, context lub output funkcji opartej na 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 punktacji nie wymaga LLM sędziego.

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

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

/**
 * Scores whether a datapoint output contains a US Phone number.
 */
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 US_PHONE_REGEX`
    : `Output did not match US_PHONE_REGEX`;
  return {
    score: matches,
    details: { reasoning },
  };
}

Definiowanie działania weryfikującego

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

/**
 * Configures a regex evaluator to match a US phone number.
 */
export function createUSPhoneRegexEvaluator(ai: Genkit): EvaluatorAction {
  return ai.defineEvaluator(
    {
      name: `myCustomEvals/usPhoneRegexEvaluator`,
      displayName: "Regex Match for US PHONE NUMBER",
      definition: "Uses Regex to check if output matches a US phone number",
      isBilled: false,
    },
    async (datapoint: BaseEvalDataPoint) => {
      const score = await usPhoneRegexScore(datapoint);
      return {
        testCaseId: datapoint.testCaseId,
        evaluation: score,
      };
    }
  );
}

Podsumowanie

Definicja wtyczki

Wtyczki są rejestrowane w ramach frameworku przez zainstalowanie ich w momencie inicjowania Genkit. Aby zdefiniować nową wtyczkę, użyj metody pomocniczej genkitPlugin, aby utworzyć instancje wszystkich działań Genkit w kontekście wtyczki.

Ten przykład kodu zawiera 2 sprawdzacze: oparty na LLM sprawdzający pyszność oraz oparty na wyrażeniu regularnym sprawdzający numer telefonu w Stanach Zjednoczonych. Tworzenie instancji tych evaluatorów w kontekście wtyczki rejestruje je w ramach wtyczki.

import { GenkitPlugin, genkitPlugin } from 'genkit/plugin';

export function myCustomEvals<
  ModelCustomOptions extends z.ZodTypeAny
>(options: {
  judge: ModelArgument<ModelCustomOptions>;
  judgeConfig?: ModelCustomOptions;
}): GenkitPlugin {
  // Define the new plugin
  return genkitPlugin("myCustomEvals", async (ai: Genkit) => {
    const { judge, judgeConfig } = options;

    // The plugin instatiates our custom evaluators within the context
    // of the `ai` object, making them available
    // throughout our Genkit application.
    createDeliciousnessEvaluator(ai, judge, judgeConfig);
    createUSPhoneRegexEvaluator(ai);
  });
}
export default myCustomEvals;

Konfigurowanie Genkit

Dodaj wtyczkę myCustomEvals do konfiguracji Genkit.

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

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

const ai = genkit({
  plugins: [
    vertexAI(),
    ...
    myCustomEvals({
      judge: gemini15Pro,
    }),
  ],
  ...
});

Korzystanie z własnych funkcji oceny

Po uruchomieniu instancji własnych oceniaczy w kontekście aplikacji Genkit (za pomocą wtyczki lub bezpośrednio) są one gotowe do użycia. Ten przykład pokazuje, jak wypróbować oceniarkę pyszności na podstawie kilku przykładowych danych wejściowych i wyjściowych.

  • 1. Utwórz plik JSON o nazwie „deliciousness_dataset.json” z treścią:
[
  {
    "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."
  }
]
  • 2. Użyj interfejsu wiersza poleceń Genkit, aby uruchomić oceniarkę na tych przypadkach testowych.
# Start your genkit runtime
genkit start -- <command to start your app>
genkit eval:run deliciousness_dataset.json --evaluators=myCustomEvals/deliciousnessEvaluator
  • 3. Otwórz adres „localhost:4000/evaluate”, aby wyświetlić wyniki w interfejsie Genkit.

Pamiętaj, że wiarygodność niestandardowych oceniających rośnie wraz z ich porównywaniem ze standardowymi zbiorami danych lub metodami. Na podstawie wyników takich testów porównawczych możesz poprawiać wyniki swoich oceniających, aż osiągną docelowy poziom jakości.