Написание плагина оценщика Genkit

Firebase Genkit можно расширить для поддержки пользовательской оценки результатов тестовых примеров либо с использованием LLM в качестве судьи, либо чисто программно.

Определение оценщика

Оценщики — это функции, которые оценивают контент, предоставленный и созданный LLM. Существует два основных подхода к автоматизированной оценке (тестированию): эвристическая оценка и оценка на основе LLM. При эвристическом подходе вы определяете детерминированную функцию, подобную функциям традиционной разработки программного обеспечения. При оценке на основе LLM контент передается обратно в LLM, и LLM предлагается оценить результат в соответствии с критериями, установленными в подсказке.

Оценщики на базе LLM

Оценщик на основе LLM использует LLM для оценки входных данных, контекста или выходных данных вашей функции генеративного ИИ.

Оценщики на основе LLM в Genkit состоят из трех компонентов:

  • Подсказка
  • Функция подсчета очков
  • Действия оценщика

Определите подсказку

В этом примере подсказка попросит 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,
      };
    }
  );
}

Эвристические оценщики

Эвристическим оценщиком может быть любая функция, используемая для оценки входных данных, контекста или выходных данных вашей генеративной функции ИИ.

Эвристические оценщики в 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>;
}

Определение плагина

Плагины регистрируются в платформе через файл genkit.config.ts в проекте. Чтобы иметь возможность настроить новый плагин, определите функцию, которая определяет GenkitPlugin и настраивает его с помощью PluginOptions определенных выше.

В этом случае у нас есть два оценщика DELICIOUSNESS и US_PHONE_REGEX_MATCH . Здесь эти оценщики регистрируются в плагине и 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.

Для оценки с помощью Gemini отключите настройки безопасности, чтобы оценщик мог принимать, обнаруживать и оценивать потенциально вредный контент.

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

export default configureGenkit({
  plugins: [
    ...
    myAwesomeEval({
      judge: geminiPro,
      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
      ],
    }),
  ],
  ...
});

Тестирование

Те же проблемы, которые применяются к оценке качества результатов функции генеративного ИИ, применимы и к оценке способности оценивать оценщика на основе 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 помочь создать набор тестовых примеров, которые можно будет курировать. Существует множество доступных наборов эталонных данных, которые также можно использовать.

Затем используйте Genkit CLI, чтобы запустить оценщик для этих тестовых случаев.

genkit eval:run deliciousness_dataset.json

Просмотрите свои результаты в пользовательском интерфейсе Genkit.

genkit start

Перейдите к localhost:4000/evaluate .