ルールベースの評価を構築する

基本を自動化する: コードを使用して簡単なエラーを検出します。

ルールベースの評価でキャッチする失敗がわかったら、対応するエバリュエータ関数を実装します。

  • evalDataFormat(): データ形式が正しいことを確認します。これには、有効な JSON、すべてのキーが存在する、空の値がない、モットーが 6 語以内、16 進数の色が含まれます。
  • evalContrastRatio(): テキストと背景の色のコントラスト比がアクセシブルであることを確認します。

ルールベースの評価を実装する

スコアリング方法を選択する

評価基準はバイナリです。ルールベースの評価関数は、PASS ラベルや FAIL ラベルなどのバイナリ出力を生成する必要があります。

  • ThemeBuilder アプリの出力(完全なテーマ オブジェクト)→ evalDataFormat()PASS または FAIL ラベル。データ形式がすべての制約を満たしている場合は PASS。それ以外の場合は FAIL
  • ThemeBuilder アプリの出力(カラーパレット オブジェクト)→ evalContrastRatio()PASS または FAIL ラベル。比率が 4.5 より大きい場合は PASS。それ以外の場合は FAIL

評価タイプを定義する

PASS 指標または FAIL 指標はブール値ですが、読みやすさのために文字列ラベル(カテゴリ)として実装することもできます。

簡潔にするため、後で実装するルールベースの評価と LLM ジャッジ評価の両方に同じ TypeScript 型を使用できます。バイナリ EvalLabel カテゴリと、判定モデルが評価を説明するための rationale フィールドをラップする EvalResult タイプを作成します。

enum EvalLabel {
    PASS = "PASS",
    FAIL = "FAIL"
}

interface EvalResult {
    label: EvalLabel;
    rationale?: string;
}

エバリュエータを実装する

Zod は、JSON 構造とカスタムルールの両方を処理するため、スキーマ検証に最適なツールです。宣言型であるため、検証コードが読みやすくなります。トラブルシューティングを容易にするため、特定のパスと失敗の理由を含む詳細なエラーレポートを定義します。

import { z } from 'zod';
import { MAX_WORD_COUNT } from './app.config';

const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;

// Reusable schema for hex colors
const HexColor = z.string().regex(hexColorRegex, { message: "Invalid hex color code" });

// zod schema definition for AppOutput
export const AppOutputSchema = z.object({
  motto: z.string().min(1, { message: "Motto is missing or empty" }).refine((val) => {
    const words = val.replace(/[^\w\s]|_/g, "").trim();
    const count = words ? words.split(/\s+/).length : 0;
    return count > 0 && count <= MAX_WORD_COUNT;
  }, { message: `Motto must be between 1 and ${MAX_WORD_COUNT} words` }),
  colorPalette: z.object({
    textColor: HexColor,
    backgroundColor: HexColor,
    primary: HexColor,
    secondary: HexColor
  }).catchall(HexColor)
});

コントラスト比

コントラスト比の計算などのドメイン ロジックは、ユーティリティ関数に分離します。

/*
 * Input: ColorPalette {"textColor":"#333333","backgroundColor":"#000000", ...}
 * Output: EvalResult {"status":"FAIL","rationale":"Contrast ratio is 1.66:1 (must be >= 4.5:1)."}
 * minContrastRatio is an app config variable, MIN_CONTRAST_RATIO = 4.5
*/
export function evalContrastRatio(colorPalette: ColorPalette, minContrastRatio: number): EvalResult {
  if (!colorPalette || !colorPalette.textColor || !colorPalette.backgroundColor) {
    return { status: EvalLabel.FAIL, rationale: "Missing textColor or backgroundColor." };
  }
  try {
    const ratio = getContrastRatio(colorPalette.textColor, colorPalette.backgroundColor);
    const rationale = `Contrast ratio is ${ratio.toFixed(2)}:1 (must be >= ${minContrastRatio}:1).`;
    if (ratio < minContrastRatio) {
      return { status: EvalLabel.FAIL, rationale };
    }
    return { status: EvalLabel.PASS, rationale };
  } catch (e) {
    return { status: EvalLabel.FAIL, rationale: "Could not calculate contrast ratio (invalid hex?)." };
  }
}

evalDataFormat()evalContrastRatio()評価ツールコードをご覧ください。

ルールベースの評価をテストする

ルールベースの評価は決定的であるため、従来の単体テストを実装して動作をチェックできます。テストを構築して、さまざまな出力を評価ツールで実行し、期待どおりの PASS ラベルまたは FAIL ラベルが返されるかどうかをアサートします。

テストケースで評価ツールが FAIL を返すことが想定されており、実際に FAIL が返された場合、評価ツールが意図したとおりに動作したため、テストは PASS を出力します。

import { MIN_CONTRAST_RATIO } from '../src/app.config'; // 4.5

const testCases = [
  {
  // ...
      appOutput: {
        motto: "Test motto",
        colorPalette: {
          textColor: "#333333",
          backgroundColor: "#000000",
          primary: "#FF0000",
          secondary: "#333333"
        }
      },
    expected: {
      // Dark grey on black (low contrast): FAIL
      contrast: EvalLabel.FAIL
    }
  }
  // ... more test cases
];

testCases.forEach((testCase) => {
  const result = evalContrastRatio(
    testCase.appOutput.colorPalette as any, MIN_CONTRAST_RATIO
  );
  const actualEvalLabel = result.label;
  const expectedEvalLabel = testCase.expected.contrast;
  const isSuccess = actualEvalLabel === expectedEvalLabel;
 // ...
});

試してみる