Créer des évaluations basées sur des règles

Automatisez les bases : utilisez le code pour détecter les erreurs simples.

Maintenant que vous savez quelles défaillances vous souhaitez détecter avec les évaluations basées sur des règles, il est temps d'implémenter les fonctions d'évaluation correspondantes :

  • evalDataFormat() : vérifie que le format des données est correct. Cela inclut un code JSON valide, toutes les clés présentes, aucune valeur vide, un slogan de moins de six mots et des couleurs hexadécimales.
  • evalContrastRatio() : vérifie que le rapport de contraste entre la couleur du texte et celle de l'arrière-plan est accessible.

Implémenter des évaluations basées sur des règles

Les critères d'évaluation sont binaires. Vos fonctions d'évaluation basées sur des règles doivent produire une sortie binaire, telle qu'un libellé PASS ou FAIL.

  • Sortie de l'application ThemeBuilder (objet de thème complet) → evalDataFormat() → PASS ou libellé FAIL. PASS si le format des données respecte toutes les contraintes. Sinon, FAIL.
  • Sortie de l'application ThemeBuilder (objet de palette de couleurs) → evalContrastRatio() → étiquette PASS ou FAIL . PASS si le ratio est supérieur à 4,5. Sinon, FAIL.

Définir un type d'évaluation

La métrique PASS ou FAIL est un booléen, mais vous pouvez choisir de l'implémenter sous forme de libellé de chaîne (catégorie) pour plus de lisibilité.

Pour plus de simplicité, vous pouvez utiliser le même type TypeScript pour vos évaluations basées sur des règles et celles du juge LLM que vous implémenterez plus tard. Créez un type EvalResult qui encapsule une catégorie EvalLabel binaire et un champ rationale pour que le modèle de jugement explique sa note.

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

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

Implémenter des évaluateurs

Zod est un excellent outil pour la validation de schéma, car il gère à la fois la structure JSON et les règles personnalisées. Il est déclaratif, ce qui rend le code de validation lisible. Définissez des rapports d'erreur détaillés avec des chemins d'accès et des raisons d'échec spécifiques pour faciliter le dépannage.

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

Rapport de contraste

Conservez la logique de domaine, comme les calculs du rapport de contraste, dans des fonctions utilitaires distinctes.

/*
 * 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?)." };
  }
}

Consultez notre code d'évaluation pour evalDataFormat() et evalContrastRatio().

Tester les évaluations basées sur des règles

Les évaluations basées sur des règles sont déterministes. Vous pouvez donc implémenter des tests unitaires classiques pour vérifier leur comportement. Créez vos tests pour exécuter différentes sorties via les évaluateurs et affirmer s'ils renvoient le libellé PASS ou FAIL que vous attendiez.

Si un cas de test attend de l'évaluateur qu'il renvoie FAIL et qu'il le fait, le test génère PASS, car l'évaluateur s'est comporté comme prévu.

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

Essayer