Vous pouvez étendre Firebase Genkit pour prendre en charge l'évaluation personnalisée, en utilisant un LLM comme juge ou par évaluation programmatique (heuristique).
Définition de l'évaluateur
Les évaluateurs sont des fonctions qui évaluent la réponse d'un LLM. Il existe deux approches principales de l'évaluation automatisée: l'évaluation heuristique et l'évaluation basée sur les LLM. Dans l'approche heuristique, vous définissez une fonction déterministe. À l'inverse, dans une évaluation basée sur un LLM, le contenu est renvoyé à un LLM, et celui-ci est invité à évaluer la sortie en fonction des critères définis dans une requête.
La méthode ai.defineEvaluator
, que vous utilisez pour définir une action d'évaluation dans Genkit, accepte les deux approches. Ce document présente quelques exemples d'utilisation de cette méthode pour les évaluations heuristiques et basées sur des LLM.
Évaluateurs basés sur le LLM
Un évaluateur basé sur un LLM exploite un LLM pour évaluer les input
, context
et output
de votre fonctionnalité d'IA générative.
Les évaluateurs basés sur le LLM dans Genkit se composent de trois composants:
- Une requête
- Une fonction de notation
- Action de l'évaluateur
Définir l'invite
Dans cet exemple, l'évaluateur utilise un LLM pour déterminer si un aliment (output
) est délicieux ou non. Tout d'abord, fournissez un contexte au LLM, puis décrivez ce que vous voulez qu'il fasse, et enfin, donnez-lui quelques exemples sur lesquels baser sa réponse.
L'utilitaire definePrompt
de Genkit permet de définir facilement des requêtes avec validation des entrées et des sorties. Le code suivant est un exemple de configuration d'une invite d'évaluation avec 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,
}
},
`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:
`
);
}
Définir la fonction d'évaluation
Définissez une fonction qui reçoit un exemple qui inclut output
comme l'exige l'invite, puis évalue le résultat. Les cas de test Genkit incluent input
comme champ obligatoire, avec output
et context
comme champs facultatifs. Il est de la responsabilité de l'évaluateur de vérifier que tous les champs requis pour l'évaluation sont présents.
import { ModelArgument, z } from 'genkit';
import { BaseEvalDataPoint, Score } from 'genkit/evaluator';
/**
* Score an individual test case for delciousness.
*/
export async function deliciousnessScore<
CustomModelOptions extends z.ZodTypeAny,
>(
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 },
};
}
Définir l'action d'évaluation
La dernière étape consiste à écrire une fonction qui définit EvaluatorAction
.
import { Genkit, z } from 'genkit';
import { BaseEvalDataPoint, 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(judge, datapoint, judgeConfig);
return {
testCaseId: datapoint.testCaseId,
evaluation: score,
};
}
);
}
La méthode defineEvaluator
est semblable aux autres constructeurs Genkit, comme defineFlow
et defineRetriever
. Cette méthode nécessite qu'un EvaluatorFn
soit fourni en tant que rappel. La méthode EvaluatorFn
accepte un objet BaseEvalDataPoint
, qui correspond à une seule entrée dans un ensemble de données en cours d'évaluation, ainsi qu'un paramètre facultatif "custom-options", le cas échéant. La fonction traite le point de données et renvoie un objet EvalResponse
.
Les schémas Zod pour BaseEvalDataPoint
et EvalResponse
sont les suivants.
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(),
});
L'objet defineEvaluator
permet à l'utilisateur de fournir un nom, un nom à afficher lisible par l'utilisateur et une définition pour l'évaluateur. Le nom à afficher et la définition sont affichés avec les résultats de l'évaluation dans l'UI de développement.
Il comporte également un champ isBilled
facultatif qui indique si cet évaluateur peut entraîner une facturation (par exemple, s'il utilise un LLM ou une API facturés). Si un évaluateur est facturé, l'UI demande à l'utilisateur une confirmation dans la CLI avant de lui permettre d'exécuter une évaluation. Cette étape permet d'éviter les dépenses involontaires.
Évaluateurs heuristiques
Un évaluateur heuristique peut être n'importe quelle fonction utilisée pour évaluer les input
, context
ou output
de votre fonctionnalité d'IA générative.
Les évaluateurs heuristiques de Genkit se composent de deux composants:
- Une fonction de notation
- Action de l'évaluateur
Définir la fonction d'évaluation
Comme pour l'évaluateur basé sur le LLM, définissez la fonction d'évaluation. Dans ce cas, la fonction d'évaluation n'a pas besoin d'un LLM d'évaluation.
import { EvalResponses } from 'genkit';
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 },
};
}
Définir l'action d'évaluation
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,
};
}
);
}
Pour aller plus loin
Définition du plug-in
Les plug-ins sont enregistrés auprès du framework en les installant au moment de l'initialisation de Genkit. Pour définir un nouveau plug-in, utilisez la méthode d'assistance genkitPlugin
pour instancier toutes les actions Genkit dans le contexte du plug-in.
Cet exemple de code présente deux évaluateurs: l'évaluateur de la saveur basé sur le LLM et l'évaluateur de numéro de téléphone américain basé sur une expression régulière. L'instanciation de ces évaluateurs dans le contexte du plug-in les enregistre auprès du plug-in.
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;
Configurer Genkit
Ajoutez le plug-in myCustomEvals
à votre configuration Genkit.
Pour l'évaluation avec Gemini, désactivez les paramètres de sécurité afin que l'évaluateur puisse accepter, détecter et évaluer le contenu potentiellement nuisible.
import { gemini15Pro } from '@genkit-ai/googleai';
const ai = genkit({
plugins: [
vertexAI(),
...
myCustomEvals({
judge: gemini15Pro,
}),
],
...
});
Utiliser vos évaluateurs personnalisés
Une fois que vous avez instancié vos évaluateurs personnalisés dans le contexte de l'application Genkit (via un plug-in ou directement), ils sont prêts à être utilisés. L'exemple suivant montre comment essayer l'évaluateur de la saveur avec quelques exemples d'entrées et de sorties.
- 1. Créez un fichier JSON "deliciousness_dataset.json" avec le contenu suivant:
[
{
"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. Utilisez la CLI Genkit pour exécuter l'évaluateur sur ces scénarios de test.
# Start your genkit runtime genkit start -- <command to start your app>
genkit eval:run deliciousness_dataset.json --evaluators=myCustomEvals/deliciousnessEvaluator
- 3. Accédez à "localhost:4000/evaluate" pour afficher vos résultats dans l'interface utilisateur de Genkit.
Il est important de noter que la confiance dans les évaluateurs personnalisés augmente à mesure que vous les comparez à des ensembles de données ou à des approches standards. Itérez sur les résultats de ces benchmarks pour améliorer les performances de vos évaluateurs jusqu'à ce qu'ils atteignent le niveau de qualité ciblé.