Genkit 評価プラグインの作成

Firebase Genkit を拡張して、テストケース出力のカスタム評価をサポートするには、LLM を判断基準として使用するか、純粋にプログラムで実施します。

評価者の定義

評価器とは、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 のヒューリスティック エバリュエータは、次の 2 つのコンポーネントで構成されています。

  • スコアリング関数
  • 評価者による操作

スコアリング関数を定義する

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 に移動します。