Genkit 평가자 작성

LLM을 판사로 사용하거나 프로그래매틱 (휴리스틱) 평가를 사용하여 맞춤 평가를 지원하도록 Firebase Genkit을 확장할 수 있습니다.

평가자 정의

평가자는 LLM의 응답을 평가하는 함수입니다. 자동 평가에는 휴리스틱 평가와 LLM 기반 평가라는 두 가지 주요 접근 방식이 있습니다. 휴리스틱 접근 방식에서는 확정적인 함수를 정의합니다. 반면 LLM 기반 평가에서는 콘텐츠가 LLM에 다시 제공되고 LLM에 프롬프트에 설정된 기준에 따라 출력에 점수를 매기도록 요청됩니다.

Genkit에서 평가자 작업을 정의하는 데 사용하는 ai.defineEvaluator 메서드는 두 가지 접근 방식을 모두 지원합니다. 이 문서에서는 휴리스틱 및 LLM 기반 평가에 이 메서드를 사용하는 방법의 몇 가지 예를 살펴봅니다.

LLM 기반 평가자

LLM 기반 평가자는 LLM을 활용하여 생성형 AI 기능의 input, context, output를 평가합니다.

Genkit의 LLM 기반 평가자는 다음 세 가지 구성요소로 구성됩니다.

  • 프롬프트
  • 점수 함수
  • 평가자 작업

프롬프트 정의

이 예시에서 평가자는 LLM을 활용하여 음식 (output)이 맛있는지 아닌지 판단합니다. 먼저 LLM에 컨텍스트를 제공한 다음 수행할 작업을 설명하고 마지막으로 응답의 기반이 될 몇 가지 예시를 제공합니다.

Genkit의 definePrompt 유틸리티는 입력 및 출력 유효성 검사를 사용하여 프롬프트를 간편하게 정의할 수 있는 방법을 제공합니다. 다음 코드는 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:
      `
  });
}

점수 함수 정의

프롬프트에 따라 output가 포함된 예시를 사용하고 결과에 점수를 부여하는 함수를 정의합니다. Genkit 테스트 사례에는 input가 필수 필드로, outputcontext가 선택적 필드로 포함됩니다. 평가에 필요한 모든 필드가 있는지 확인하는 것은 평가자의 책임입니다.

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

평가자 작업 정의

마지막 단계는 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,
      };
    }
  );
}

defineEvaluator 메서드는 defineFlowdefineRetriever과 같은 다른 Genkit 생성자와 유사합니다. 이 메서드에는 EvaluatorFn가 콜백으로 제공되어야 합니다. EvaluatorFn 메서드는 평가 중인 데이터 세트의 단일 항목에 해당하는 BaseEvalDataPoint 객체와 지정된 경우 선택적 맞춤 옵션 매개변수를 허용합니다. 이 함수는 데이터 포인트를 처리하고 EvalResponse 객체를 반환합니다.

BaseEvalDataPointEvalResponse의 Zod 스키마는 다음과 같습니다.

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

defineEvaluator 객체를 사용하면 사용자가 이름, 사용자가 읽을 수 있는 표시 이름, 평가자의 정의를 제공할 수 있습니다. 표시 이름과 정의는 Dev UI의 평가 결과와 함께 표시됩니다. 또한 이 평가자가 결제의 결과를 가져올 수 있는지 여부를 표시하는 선택적 isBilled 필드가 있습니다 (예: 결제된 LLM 또는 API를 사용함). 평가자에게 비용이 청구되는 경우 UI는 사용자에게 CLI에서 확인을 요청한 후 평가를 실행하도록 허용합니다. 이 단계를 통해 의도치 않은 지출을 방지할 수 있습니다.

휴리스틱 평가자

휴리스틱 평가자는 생성형 AI 기능의 input, context 또는 output를 평가하는 데 사용되는 모든 함수일 수 있습니다.

Genkit의 휴리스틱 평가자는 다음 두 가지 구성요소로 구성됩니다.

  • 점수 함수
  • 평가자 작업

점수 함수 정의

LLM 기반 평가자와 마찬가지로 점수 함수를 정의합니다. 이 경우 점수 함수에 심사 LLM이 필요하지 않습니다.

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

평가자 작업 정의

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

종합

플러그인 정의

플러그인은 Genkit을 초기화할 때 설치하여 프레임워크에 등록됩니다. 새 플러그인을 정의하려면 genkitPlugin 도우미 메서드를 사용하여 플러그인 컨텍스트 내에서 모든 Genkit 작업을 인스턴스화합니다.

이 코드 샘플은 LLM 기반 맛 평가기와 정규식 기반 미국 전화번호 평가기라는 두 가지 평가기를 보여줍니다. 플러그인 컨텍스트 내에서 이러한 평가자를 인스턴스화하면 플러그인에 등록됩니다.

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;

Genkit 구성

Genkit 구성에 myCustomEvals 플러그인을 추가합니다.

Gemini를 사용한 평가의 경우 평가자가 잠재적으로 유해한 콘텐츠를 수락, 감지, 평가할 수 있도록 안전 설정을 사용 중지합니다.

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

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

맞춤 평가자 사용

Genkit 앱 컨텍스트 내에서 커스텀 평가자를 인스턴스화하면(플러그인을 통해서 또는 직접) 사용할 준비가 됩니다. 다음 예는 몇 가지 샘플 입력과 출력으로 맛 평가자를 사용해 보는 방법을 보여줍니다.

  • 1. 다음 콘텐츠로 json 파일 `deliciousness_dataset.json` 을 만듭니다.
[
  {
    "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. Genkit CLI를 사용하여 이러한 테스트 사례에 대해 평가자를 실행합니다.
# Start your genkit runtime
genkit start -- <command to start your app>
genkit eval:run deliciousness_dataset.json --evaluators=myCustomEvals/deliciousnessEvaluator
  • 3. `localhost:4000/evaluate` 로 이동하여 Genkit UI에서 결과를 확인합니다.

맞춤 평가자를 표준 데이터 세트 또는 접근 방식으로 벤치마킹하면 맞춤 평가자에 대한 신뢰도가 높아진다는 점에 유의해야 합니다. 이러한 벤치마크의 결과를 반복하여 평가자의 실적이 타겟 품질 수준에 도달할 때까지 개선합니다.