Menulis Evaluator Genkit

Anda dapat memperluas Firebase Genkit untuk mendukung evaluasi kustom, menggunakan LLM sebagai hakim, atau dengan evaluasi terprogram (heuristik).

Definisi evaluator

Evaluator adalah fungsi yang menilai respons LLM. Ada dua pendekatan utama untuk evaluasi otomatis: evaluasi heuristik dan evaluasi berbasis LLM. Dalam pendekatan heuristik, Anda menentukan fungsi deterministik. Sebaliknya, dalam penilaian berbasis LLM, konten akan dimasukkan kembali ke LLM, dan LLM diminta untuk menilai output sesuai dengan kriteria yang ditetapkan dalam perintah.

Metode ai.defineEvaluator, yang Anda gunakan untuk menentukan tindakan evaluator di Genkit, mendukung salah satu pendekatan tersebut. Dokumen ini membahas beberapa contoh cara menggunakan metode ini untuk evaluasi berbasis heuristik dan LLM.

Evaluator Berbasis LLM

Evaluator berbasis LLM memanfaatkan LLM untuk mengevaluasi input, context, dan output dari fitur AI generatif Anda.

Evaluator berbasis LLM di Genkit terdiri dari 3 komponen:

  • Perintah
  • Fungsi penskoran
  • Tindakan evaluator

Menentukan perintah

Untuk contoh ini, evaluator memanfaatkan LLM untuk menentukan apakah makanan (output) lezat atau tidak. Pertama, berikan konteks ke LLM, lalu jelaskan apa yang Anda inginkan, dan terakhir, berikan beberapa contoh untuk mendasarkan responsnya.

Utilitas definePrompt Genkit menyediakan cara mudah untuk menentukan perintah dengan validasi input dan output. Kode berikut adalah contoh menyiapkan perintah evaluasi dengan 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,
      }
      prompt: `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:
      `
  });
}

Menentukan fungsi penskoran

Tentukan fungsi yang menggunakan contoh yang menyertakan output seperti yang diperlukan oleh perintah, dan beri skor pada hasilnya. Pengujian kasus Genkit menyertakan input sebagai kolom wajib, dengan output dan context sebagai kolom opsional. Evaluator bertanggung jawab untuk memvalidasi bahwa semua kolom yang diperlukan untuk evaluasi ada.

import { ModelArgument } from 'genkit';
import { BaseEvalDataPoint, Score } from 'genkit/evaluator';

/**
 * Score an individual test case for delciousness.
 */
export async function deliciousnessScore<
  CustomModelOptions extends z.ZodTypeAny,
>(
  ai: Genkit,
  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 },
  };
}

Menentukan tindakan evaluator

Langkah terakhir adalah menulis fungsi yang menentukan EvaluatorAction.

import { 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(ai, judge, datapoint, judgeConfig);
      return {
        testCaseId: datapoint.testCaseId,
        evaluation: score,
      };
    }
  );
}

Metode defineEvaluator mirip dengan konstruktor Genkit lainnya seperti defineFlow dan defineRetriever. Metode ini memerlukan EvaluatorFn untuk diberikan sebagai callback. Metode EvaluatorFn menerima objek BaseEvalDataPoint, yang sesuai dengan satu entri dalam set data yang sedang dievaluasi, beserta parameter opsi kustom opsional jika ditentukan. Fungsi ini memproses titik data dan menampilkan objek EvalResponse.

Skema Zod untuk BaseEvalDataPoint dan EvalResponse adalah sebagai berikut.

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

Objek defineEvaluator memungkinkan pengguna memberikan nama, nama tampilan yang dapat dibaca pengguna, dan definisi untuk evaluator. Nama tampilan dan definisi ditampilkan bersama dengan hasil evaluasi di UI Dev. Evaluator ini juga memiliki kolom isBilled opsional yang menandai apakah evaluator ini dapat menghasilkan penagihan (misalnya, menggunakan LLM atau API yang ditagih). Jika evaluator ditagihkan, UI akan meminta konfirmasi kepada pengguna di CLI sebelum mengizinkannya menjalankan evaluasi. Langkah ini membantu mencegah biaya yang tidak diinginkan.

Evaluator Heuristik

Evaluator heuristik dapat berupa fungsi apa pun yang digunakan untuk mengevaluasi input, context, atau output fitur AI generatif Anda.

Evaluator heuristik di Genkit terdiri dari 2 komponen:

  • Fungsi penskoran
  • Tindakan evaluator

Menentukan fungsi penskoran

Seperti halnya evaluator berbasis LLM, tentukan fungsi penskoran. Dalam hal ini, fungsi penskoran tidak memerlukan LLM hakim.

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

Menentukan tindakan evaluator

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

Menyatukan

Definisi plugin

Plugin didaftarkan ke framework dengan menginstalnya pada saat melakukan inisialisasi Genkit. Untuk menentukan plugin baru, gunakan metode bantuan genkitPlugin untuk membuat instance semua tindakan Genkit dalam konteks plugin.

Contoh kode ini menunjukkan dua evaluator: evaluator kelezatan berbasis LLM, dan evaluator nomor telepon AS berbasis ekspresi reguler. Membuat instance evaluator ini dalam konteks plugin akan mendaftarkannya ke plugin.

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;

Mengonfigurasi Genkit

Tambahkan plugin myCustomEvals ke konfigurasi Genkit Anda.

Untuk evaluasi dengan Gemini, nonaktifkan setelan keamanan agar evaluator dapat menerima, mendeteksi, dan memberi skor konten yang berpotensi berbahaya.

import { gemini15Pro } from '@genkit-ai/googleai';

const ai = genkit({
  plugins: [
    vertexAI(),
    ...
    myCustomEvals({
      judge: gemini15Pro,
    }),
  ],
  ...
});

Menggunakan evaluator kustom

Setelah Anda membuat instance evaluator kustom dalam konteks aplikasi Genkit (baik melalui plugin maupun secara langsung), evaluator tersebut siap digunakan. Contoh berikut mengilustrasikan cara mencoba evaluator kelezatan dengan beberapa contoh input dan output.

  • 1. Buat file json `deliciousness_dataset.json` dengan konten berikut:
[
  {
    "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. Gunakan Genkit CLI untuk menjalankan evaluator terhadap kasus pengujian ini.
# Start your genkit runtime
genkit start -- <command to start your app>
genkit eval:run deliciousness_dataset.json --evaluators=myCustomEvals/deliciousnessEvaluator
  • 3. Buka `localhost:4000/evaluate` untuk melihat hasil Anda di UI Genkit.

Penting untuk diperhatikan bahwa kepercayaan pada evaluator kustom meningkat saat Anda melakukan benchmark dengan set data atau pendekatan standar. Lakukan iterasi pada hasil benchmark tersebut untuk meningkatkan performa evaluator Anda hingga mencapai tingkat kualitas yang ditargetkan.