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

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

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

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

Метод ai.defineEvaluator , который вы используете для определения действия оценщика в Genkit, поддерживает любой подход. В этом документе рассматривается несколько примеров использования этого метода для эвристических оценок и оценок на основе LLM.

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

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

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

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

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

В этом примере оценщик использует LLM, чтобы определить, является ли еда ( output ) вкусной или нет. Сначала предоставьте LLM контекст, затем опишите, что вы от него хотите, и, наконец, дайте ему несколько примеров, на которых можно будет основывать его ответ.

Утилита definePrompt Genkit предоставляет простой способ определения подсказок с проверкой ввода и вывода. Следующий код представляет собой пример настройки запроса оценки с помощью 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:
    `
  );
}

Определить функцию оценки

Определите функцию, которая берет пример, включающий output , как того требует приглашение, и оценивает результат. Тестовые примеры Genkit включают input в качестве обязательного поля, а output и context — в качестве необязательных полей. Оценщик несет ответственность за проверку наличия всех полей, необходимых для оценки.

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

Определить действие оценщика

Последний шаг — написать функцию, определяющую 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,
      };
    }
  );
}

Метод defineEvaluator похож на другие конструкторы Genkit, такие как defineFlow и defineRetriever . Этот метод требует, чтобы EvaluatorFn был предоставлен в качестве обратного вызова. Метод EvaluatorFn принимает объект BaseEvalDataPoint , который соответствует одной записи в оцениваемом наборе данных, а также необязательный параметр настраиваемых параметров, если он указан. Функция обрабатывает точку данных и возвращает объект EvalResponse .

Схемы Zod для BaseEvalDataPoint и EvalResponse следующие.

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

Объект defineEvaluator позволяет пользователю указать имя, удобочитаемое отображаемое имя и определение для оценщика. Отображаемое имя и определение отображаются вместе с результатами оценки в пользовательском интерфейсе разработчика. Он также имеет необязательное поле isBilled , которое отмечает, может ли этот оценщик привести к выставлению счетов (например, использует ли он выставленный счет LLM или API). Если оценщику выставляется счет, пользовательский интерфейс запрашивает у пользователя подтверждение в CLI, прежде чем разрешить ему выполнить оценку. Этот шаг помогает защититься от непредвиденных расходов.

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

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

Эвристические оценщики в Genkit состоят из двух компонентов:

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

Определить функцию оценки

Как и в случае с оценщиком на основе LLM, определите функцию оценки. В этом случае для выставления оценок не требуется судья LLM.

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

Определить действие оценщика

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

Собираем это вместе

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

Плагины регистрируются в платформе путем их установки во время инициализации Genkit. Чтобы определить новый плагин, используйте вспомогательный метод genkitPlugin для создания экземпляров всех действий Genkit в контексте плагина.

В этом примере кода показаны два оценщика: оценщик вкусности на основе LLM и оценщик телефонных номеров США на основе регулярных выражений. Создание экземпляров этих оценщиков в контексте плагина регистрирует их в плагине.

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;

Настроить Генкит

Добавьте плагин myCustomEvals в вашу конфигурацию Genkit.

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

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

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

Использование собственных оценщиков

Как только вы создадите экземпляр своих пользовательских оценщиков в контексте приложения Genkit (с помощью плагина или напрямую), они будут готовы к использованию. В следующем примере показано, как опробовать оценщик вкусности с помощью нескольких примеров входных и выходных данных.

  • 1. Создайте 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."
  }
]
  • 2. Используйте Genkit CLI, чтобы запустить оценщик для этих тестовых случаев.
# Start your genkit runtime
genkit start -- <command to start your app>
genkit eval:run deliciousness_dataset.json --evaluators=myCustomEvals/deliciousnessEvaluator
  • 3. Перейдите к `localhost:4000/evaluate`, чтобы просмотреть результаты в пользовательском интерфейсе Genkit.

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