Crea evaluaciones basadas en reglas

Automatiza los conceptos básicos: Usa código para detectar errores simples.

Ahora que sabes qué errores deseas detectar con las evaluaciones basadas en reglas, es hora de implementar las funciones de evaluación correspondientes:

  • evalDataFormat(): Verifica que el formato de los datos sea correcto. Esto incluye JSON válido, todas las claves presentes, sin valores vacíos, el lema con menos de seis palabras y colores hexadecimales.
  • evalContrastRatio(): Verifica que la relación de contraste entre el texto y el color de fondo sea accesible.

Implementa evaluaciones basadas en reglas

Elige un método de puntuación

Los criterios de evaluación son binarios. Tus funciones de evaluación basadas en reglas deben producir un resultado binario, como una etiqueta PASS o FAIL.

  • Salida de la app de ThemeBuilder (objeto de tema completo) → evalDataFormat() → etiqueta PASS o FAIL PASS si el formato de los datos cumple con todas las restricciones. FAIL en caso contrario
  • Salida de la app de ThemeBuilder (objeto de paleta de colores) → evalContrastRatio() → etiqueta PASS o FAIL .PASS si la proporción es > 4.5 FAIL en caso contrario

Cómo definir un tipo de evaluación

La métrica PASS o FAIL es booleana, pero puedes optar por implementarla como una etiqueta de cadena (categoría) para facilitar la lectura.

Para mantener la eficiencia, puedes usar el mismo tipo de TypeScript para las evaluaciones basadas en reglas y las evaluaciones del juez del LLM que implementarás más adelante. Crea un tipo EvalResult que encapsule una categoría binaria EvalLabel y un campo rationale para que el modelo de juez explique su calificación.

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

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

Implementa evaluadores

Zod es una excelente herramienta para la validación de esquemas, ya que controla tanto la estructura JSON como las reglas personalizadas. Es declarativo, lo que hace que el código de validación sea legible. Define informes de errores detallados con rutas y motivos específicos de las fallas para facilitar la solución de problemas.

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

Relación de contraste

Mantén la lógica del dominio, como los cálculos de la relación de contraste, en funciones de utilidad separadas.

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

Consulta nuestro código del evaluador para evalDataFormat() y evalContrastRatio().

Cómo probar las evaluaciones basadas en reglas

Las evaluaciones basadas en reglas son determinísticas, por lo que puedes implementar pruebas de unidades clásicas para verificar su comportamiento. Crea tus pruebas para ejecutar varios resultados a través de los evaluadores y confirmar si devuelven la etiqueta PASS o FAIL que esperabas.

Si un caso de prueba espera que el evaluador devuelva un FAIL y lo hace, la prueba genera un PASS porque el evaluador se comportó según lo previsto.

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

Probar