Genkit 評価プラグインの作成

Firebase Genkit は、LLM を判定として使用して、または純粋にプログラムによって、テストケース出力のカスタム評価をサポートするように拡張できます。

評価者の定義

評価担当者は、LLM に与えられたコンテンツと生成されたコンテンツを評価する機能です。自動評価(テスト)には、ヒューリスティック評価と LLM ベースの評価という 2 つの主要なアプローチがあります。ヒューリスティックなアプローチでは、従来のソフトウェア開発の場合と同様に、決定論的な関数を定義します。LLM ベースの評価では、コンテンツが LLM にフィードバックされ、プロンプトに設定された基準に従って出力をスコアリングするように LLM に求められます。

LLM ベースの評価者

LLM ベースの評価者は LLM を利用して、生成 AI 機能の入力、コンテキスト、出力を評価します。

Genkit の LLM ベースの評価は、次の 3 つのコンポーネントで構成されています。

  • プロンプト
  • スコアリング関数
  • エバリュエータ アクション

プロンプトを定義する

この例のプロンプトでは、出力がどの程度おいしいか 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 が含まれており、outputcontext のオプション フィールドが含まれています。評価に必要なすべてのフィールドが存在することを検証するのは、評価者の責任です。

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

ヒューリスティック評価者

ヒューリスティック エバリュエータは、生成 AI 特徴の入力、コンテキスト、出力を評価するために使用される任意の関数です。

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 を使用して設定します。

この例では、2 つのエバリュエータ DELICIOUSNESSUS_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 を構成する

新しく定義したプラグインを Genkit 構成に追加します。

Gemini での評価の場合は、安全性設定を無効にし、評価担当者が有害な可能性があるコンテンツを受け入れ、検出し、スコア付けできるようにします。

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

テスト

生成 AI 機能の出力の品質の評価に適用されるのと同じ問題が、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 UI で結果を確認します。

genkit start

localhost:4000/evaluate に移動します。