Como criar um plug-in de avaliação do Genkit

O Firebase Genkit pode ser estendido para oferecer suporte à avaliação personalizada da saída do caso de teste, seja usando um LLM como juiz ou puramente de forma programática.

Definição de avaliador

Avaliadores são funções que avaliam o conteúdo fornecido e gerado por um LLM. Existem duas abordagens principais para a avaliação automatizada (teste): a avaliação heurística e a baseada em LLM. Na abordagem heurística, você define uma função determinista como as do desenvolvimento de software tradicional. Em uma avaliação baseada em LLM, o conteúdo é enviado para um LLM, que pede para pontuar a saída de acordo com os critérios definidos em um comando.

Avaliadores baseados em LLM

Um avaliador baseado em LLM usa um LLM para avaliar a entrada, o contexto ou a saída do seu recurso de IA generativa.

Os avaliadores baseados em LLM no Genkit são compostos por três componentes:

  • Uma solicitação
  • Uma função de pontuação
  • Uma ação do avaliador

Definir o comando

Neste exemplo, o comando vai pedir que o LLM avalie se a saída é deliciosa. Primeiro, dê contexto ao LLM, descreva o que você quer que ele faça e, por fim, dê alguns exemplos para fundamentar a resposta.

O Genkit vem com o dotprompt, que oferece uma maneira fácil de definir e gerenciar comandos com recursos como validação de esquema de entrada/saída. Confira como usar dotprompt para definir um prompt de avaliação.

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:
`
);

Definir a função de pontuação

Agora, defina a função que vai usar um exemplo que inclui output conforme exigido pelo comando e pontuar o resultado. Os casos de teste do Genkit incluem input como campo obrigatório, com campos opcionais para output e context. É responsabilidade do avaliador validar se todos os campos necessários para a avaliação estão presentes.

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

Definir a ação do avaliador

A etapa final é escrever uma função que defina a própria ação do avaliador.

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

Avaliadores heurísticos

Um avaliador heurístico pode ser qualquer função usada para avaliar a entrada, o contexto ou a saída do atributo de IA generativa.

Os avaliadores heurísticos no Genkit têm dois componentes:

  • Uma função de pontuação
  • Uma ação do avaliador

Definir a função de pontuação

Assim como o avaliador baseado em LLM, defina a função de pontuação. Nesse caso, a função de pontuação não precisa conhecer o LLM do juiz ou a configuração dele.

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

Definir a ação do avaliador

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

Configuração

Opções de plug-in

Defina o PluginOptions que o plug-in do avaliador personalizado vai usar. Esse objeto não tem requisitos rígidos e depende dos tipos de avaliadores definidos.

No mínimo, será necessário definir quais métricas serão registradas.

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

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

Se esse novo plug-in usar um LLM como juiz e ele oferecer suporte à troca de qual LLM usar, defina outros parâmetros no objeto 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>;
}

Definição do plug-in

Os plug-ins são registrados com o framework por meio do arquivo genkit.config.ts em um projeto. Para configurar um novo plug-in, defina uma função que defina um GenkitPlugin e o configure com o PluginOptions definido acima.

Neste caso, temos dois avaliadores DELICIOUSNESS e US_PHONE_REGEX_MATCH. É aqui que esses avaliadores são registrados com o plug-in e com o 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;

Configurar Genkit

Adicione o plug-in recém-definido à configuração do Genkit.

Para a avaliação com o Gemini, desative as configurações de segurança para que o avaliador possa aceitar, detectar e pontuar conteúdo potencialmente nocivo.

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

Teste

As mesmas questões que se aplicam à avaliação da qualidade dos resultados de um recurso de IA generativa se aplicam à avaliação da capacidade de julgamento de um avaliador baseado em LLM.

Para ter uma noção do desempenho do avaliador personalizado no nível esperado, crie um conjunto de casos de teste que tenham respostas certas e erradas.

Por exemplo, ele pode se parecer com um arquivo 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."
  }
]

Esses exemplos podem ser gerados por humanos ou você pode pedir para um LLM ajudar a criar um conjunto de casos de teste que podem ser selecionados. Há muitos conjuntos de dados de referência disponíveis também.

Em seguida, use a CLI do Genkit para executar o avaliador nesses casos de teste.

genkit eval:run deliciousness_dataset.json

Confira os resultados na interface do Genkit.

genkit start

Acesse localhost:4000/evaluate.