יצירת הערכות מבוססות-כללים

אוטומציה של הפעולות הבסיסיות: שימוש בקוד כדי לזהות שגיאות פשוטות.

אחרי שקובעים אילו כשלים רוצים לזהות באמצעות הערכות מבוססות-כללים, הגיע הזמן להטמיע את פונקציות ההערכה המתאימות:

  • evalDataFormat(): בדיקה שפורמט הנתונים נכון. הערך צריך להיות JSON תקין, כל המפתחות צריכים להיות נוכחים, לא יכולים להיות ערכים ריקים, המוטו צריך להיות באורך של עד שש מילים, והצבעים צריכים להיות הקסדצימליים.
  • evalContrastRatio(): בדיקה של יחס הניגודיות בין צבע הטקסט לצבע הרקע כדי לוודא שהוא נגיש.

הטמעה של הערכות מבוססות-כללים

בחירת שיטת ניקוד

קריטריוני ההערכה הם בינאריים. פונקציות ההערכה שמבוססות על כללים צריכות להפיק פלט בינארי, כמו תווית PASS או FAIL.

  • פלט של אפליקציית ThemeBuilder (אובייקט עיצוב מלא) ← evalDataFormat() ← תווית PASS או FAIL. ‫PASS אם פורמט הנתונים עומד בכל האילוצים. FAIL אחרת.
  • פלט של אפליקציית ThemeBuilder (אובייקט של לוח צבעים) ← evalContrastRatio()PASS או תווית FAIL .‫PASS אם היחס גדול מ-4.5. FAIL אחרת.
.

הגדרת סוג של הערכות

המדד PASS או FAIL הוא בוליאני, אבל אפשר להטמיע אותו כתווית מחרוזת (קטגוריה) כדי שיהיה קל יותר לקרוא אותו.

כדי לשמור על יעילות, אפשר להשתמש באותו סוג TypeScript גם עבור ההערכות מבוססות-הכללים וגם עבור ההערכות של שופט ה-LLM שתטמיעו בהמשך. יוצרים סוג EvalResult שעוטף קטגוריה בינארית EvalLabel ושדה rationale למודל השופט כדי להסביר את הדירוג שלו.

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

אני רוצה לנסות