Genkit エバリュエータの作成

Firebase Genkit を拡張して、LLM をジャッジとして使用するか、プログラムによる(ヒューリスティックな)評価によって、カスタム評価をサポートできます。

評価者の定義

エバリュエータは、LLM のレスポンスを評価する関数です。自動評価には、ヒューリスティック評価と LLM ベースの評価の 2 つの主なアプローチがあります。ヒューリスティック アプローチでは、確定的関数を定義します。一方、LLM ベースの評価では、コンテンツが LLM にフィードバックされ、LLM はプロンプトで設定された基準に従って出力のスコア付けを求められます。

Genkit でエバリュエータ アクションを定義するために使用する ai.defineEvaluator メソッドは、どちらのアプローチもサポートしています。このドキュメントでは、ヒューリスティックと LLM ベースの評価にこの方法を使用する方法の例をいくつか紹介します。

LLM ベースのエバリュエータ

LLM ベースの評価ツールは、LLM を利用して生成 AI 特徴の inputcontextoutput を評価します。

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

  • プロンプト
  • スコアリング関数
  • 評価者のアクション

プロンプトを定義する

この例では、評価ツールは LLM を利用して、食べ物(output)がおいしいかどうかを判断します。まず、LLM にコンテキストを提供し、次に LLM に何をさせたいかを説明します。最後に、回答の基になる例をいくつか示します。

Genkit の definePrompt ユーティリティを使用すると、入力と出力の検証を使用してプロンプトを簡単に定義できます。次のコードは、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 が必須フィールドとして含まれ、outputcontext がオプション フィールドとして含まれます。評価に必要なすべてのフィールドが存在することを検証するのは評価者の責任です。

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 メソッドは、defineFlowdefineRetriever などの他の Genkit コンストラクタと似ています。このメソッドでは、コールバックとして EvaluatorFn を指定する必要があります。EvaluatorFn メソッドは、評価対象のデータセット内の単一のエントリに対応する BaseEvalDataPoint オブジェクトと、指定されている場合はオプションのカスタム オプション パラメータを受け取ります。この関数はデータポイントを処理し、EvalResponse オブジェクトを返します。

BaseEvalDataPointEvalResponse の Zod スキーマは次のとおりです。

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 オブジェクトを使用すると、名前、ユーザーが読み取り可能な表示名、エバリュエータの定義を指定できます。表示名と定義は、評価結果とともにデベロッパー UI に表示されます。また、このエバリュエータが課金につながる可能性があるかどうか(課金対象の LLM または API を使用しているかどうかなど)を示すオプションの isBilled フィールドもあります。評価者に請求が発生する場合は、評価を実行する前に、CLI で確認を求めるメッセージが UI に表示されます。この手順を行うことで、意図しない費用の発生を防ぐことができます。

ヒューリスティック エバリュエータ

ヒューリスティクス評価ツールは、生成 AI 機能の inputcontextoutput の評価に使用される任意の関数にできます。

Genkit のヒューリスティクス評価ツールは、次の 2 つのコンポーネントで構成されています。

  • スコアリング関数
  • 評価者のアクション

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

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 ベースのおいしさ評価ツールと正規表現ベースの米国の電話番号評価ツールの 2 つの評価ツールが示されています。これらのエバリュエータをプラグイン コンテキスト内でインスタンス化すると、プラグインに登録されます。

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;

Genkit を構成する

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 UI で結果を確認します。

標準のデータセットやアプローチでベンチマークを行うと、カスタム評価ツールの信頼性が高まることに注意してください。このようなベンチマークの結果を反復処理して、評価者のパフォーマンスを目標の品質レベルに達するまで改善します。