Firebase Genkit peut être étendu pour prendre en charge l'évaluation personnalisée de la sortie du cas de test, soit en utilisant un LLM comme juge, soit de manière purement programmatique.
Définition de l'évaluateur
Les évaluateurs sont des fonctions qui évaluent le contenu fourni à un LLM et généré par celui-ci. Il existe deux principales approches d'évaluation automatisée (test): l'évaluation heuristique et l'évaluation basée sur les LLM. Dans l'approche heuristique, vous définissez une fonction déterministe comme celles du développement logiciel traditionnel. Dans une évaluation basée sur un LLM, le contenu est renvoyé à un LLM, qui est invité à évaluer le résultat en fonction des critères définis dans une requête.
Évaluateurs basés sur le LLM
Un évaluateur basé sur un LLM utilise un LLM pour évaluer l'entrée, le contexte ou la sortie 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'invite demandera au LLM d'évaluer la qualité de la sortie. Commencez par fournir 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. Voici comment configurer une invite d'évaluation avec definePrompt
.
const DELICIOUSNESS_VALUES = ['yes', 'no', 'maybe'] as const;
const DeliciousnessDetectionResponseSchema = z.object({
reason: z.string(),
verdict: z.enum(DELICIOUSNESS_VALUES),
});
type DeliciousnessDetectionResponse = z.infer<typeof DeliciousnessDetectionResponseSchema>;
const DELICIOUSNESS_PROMPT = ai.definePrompt(
{
name: 'deliciousnessPrompt',
inputSchema: z.object({
output: z.string(),
}),
outputSchema: 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:
{{output}}
Response:
`
);
Définir la fonction d'évaluation
Définissez maintenant la fonction qui utilisera un exemple incluant output
, comme l'invite l'exige, et évaluez le résultat. Les cas de test Genkit incluent input
comme champ obligatoire, avec des champs facultatifs pour output
et context
. Il est de la responsabilité de l'évaluateur de vérifier que tous les champs requis pour l'évaluation sont présents.
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
const finalPrompt = DELICIOUSNESS_PROMPT.renderText({
output: d.output as string,
});
// Call the LLM to generate an evaluation result
const response = await generate({
model: judgeLlm,
prompt: finalPrompt,
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 l'action d'évaluation elle-même.
import { BaseEvalDataPoint, EvaluatorAction } from 'genkit/evaluator';
/**
* Create the Deliciousness evaluator action.
*/
export function createDeliciousnessEvaluator<
ModelCustomOptions extends z.ZodTypeAny,
>(
judge: ModelReference<ModelCustomOptions>,
judgeConfig: z.infer<ModelCustomOptions>
): EvaluatorAction {
return defineEvaluator(
{
name: `myAwesomeEval/deliciousness`,
displayName: 'Deliciousness',
definition: 'Determines if output is considered delicous.',
},
async (datapoint: BaseEvalDataPoint) => {
const score = await deliciousnessScore(judge, datapoint, judgeConfig);
return {
testCaseId: datapoint.testCaseId,
evaluation: score,
};
}
);
}
Évaluateurs heuristiques
Un évaluateur heuristique peut être n'importe quelle fonction utilisée pour évaluer l'entrée, le contexte ou la sortie 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
Tout comme l'évaluateur basé sur le LLM, définissez la fonction d'évaluation. Dans ce cas, la fonction d'évaluation n'a pas besoin de connaître le LLM du juge ni sa configuration.
import { BaseEvalDataPoint, Score } from 'genkit/evaluator';
const US_PHONE_REGEX =
/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4}$/i;
/**
* Scores whether an individual datapoint matches a US Phone Regex.
*/
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 regex ${regex.source}`
: `Output did not match regex ${regex.source}`;
return {
score: matches,
details: { reasoning },
};
}
Définir l'action d'évaluation
import { BaseEvalDataPoint, EvaluatorAction } from 'genkit/evaluator';
/**
* Configures a regex evaluator to match a US phone number.
*/
export function createUSPhoneRegexEvaluator(
metrics: RegexMetric[]
): EvaluatorAction[] {
return metrics.map((metric) => {
const regexMetric = metric as RegexMetric;
return defineEvaluator(
{
name: `myAwesomeEval/${metric.name.toLocaleLowerCase()}`,
displayName: 'Regex Match',
definition:
'Runs the output against a regex and responds with 1 if a match is found and 0 otherwise.',
isBilled: false,
},
async (datapoint: BaseEvalDataPoint) => {
const score = await regexMatchScore(datapoint, regexMetric.regex);
return fillScores(datapoint, score);
}
);
});
}
Configuration
Options du plug-in
Définissez le PluginOptions
que le plug-in d'évaluation personnalisé utilisera. Cet objet n'a aucune exigence stricte et dépend des types d'évaluateurs définis.
Il doit au moins définir les métriques à enregistrer.
export enum MyAwesomeMetric {
WORD_COUNT = 'WORD_COUNT',
US_PHONE_REGEX_MATCH = 'US_PHONE_REGEX_MATCH',
}
export interface PluginOptions {
metrics?: Array<MyAwesomeMetric>;
}
Si ce nouveau plug-in utilise un LLM comme juge et qu'il permet de remplacer le LLM à utiliser, définissez des paramètres supplémentaires dans l'objet PluginOptions
.
export enum MyAwesomeMetric {
DELICIOUSNESS = 'DELICIOUSNESS',
US_PHONE_REGEX_MATCH = 'US_PHONE_REGEX_MATCH',
}
export interface PluginOptions<ModelCustomOptions extends z.ZodTypeAny> {
judge: ModelReference<ModelCustomOptions>;
judgeConfig?: z.infer<ModelCustomOptions>;
metrics?: Array<MyAwesomeMetric>;
}
Définition du plug-in
Les plug-ins sont enregistrés auprès du framework via le fichier genkit.config.ts
d'un projet. Pour pouvoir configurer un nouveau plug-in, définissez une fonction qui définit un GenkitPlugin
et le configure avec le PluginOptions
défini ci-dessus.
Dans ce cas, nous avons deux évaluateurs : DELICIOUSNESS
et US_PHONE_REGEX_MATCH
. C'est là que ces évaluateurs sont enregistrés auprès du plug-in et de Firebase Genkit.
export function myAwesomeEval<ModelCustomOptions extends z.ZodTypeAny>(
options: PluginOptions<ModelCustomOptions>
): PluginProvider {
// Define the new plugin
const plugin = (options?: MyPluginOptions<ModelCustomOptions>) => {
return genkitPlugin(
'myAwesomeEval',
async (ai: Genkit) => {
const { judge, judgeConfig, metrics } = options;
const evaluators: EvaluatorAction[] = metrics.map((metric) => {
switch (metric) {
case DELICIOUSNESS:
// This evaluator requires an LLM as judge
return createDeliciousnessEvaluator(ai, judge, judgeConfig);
case US_PHONE_REGEX_MATCH:
// This evaluator does not require an LLM
return createUSPhoneRegexEvaluator();
}
});
return { evaluators };
})
}
// Create the plugin with the passed options
return plugin(options);
}
export default myAwesomeEval;
Configurer Genkit
Ajoutez le plug-in nouvellement défini à 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 les contenus potentiellement dangereux.
import { gemini15Flash } from '@genkit-ai/googleai';
const ai = genkit({
plugins: [
...
myAwesomeEval({
judge: gemini15Flash,
judgeConfig: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_NONE',
},
],
},
metrics: [
MyAwesomeMetric.DELICIOUSNESS,
MyAwesomeMetric.US_PHONE_REGEX_MATCH
],
}),
],
...
});
Tests
Les mêmes problèmes que ceux qui s'appliquent à l'évaluation de la qualité du résultat d'une fonctionnalité d'IA générative s'appliquent à l'évaluation de la capacité d'évaluation d'un évaluateur basé sur un LLM.
Pour déterminer si l'évaluateur personnalisé fonctionne au niveau attendu, créez un ensemble de cas de test avec une bonne et une mauvaise réponse.
Par exemple, un fichier JSON deliciousness_dataset.json
peut ressembler à ceci:
[
{
"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."
}
]
Ces exemples peuvent être générés par un humain ou vous pouvez demander à un LLM de vous aider à créer un ensemble de cas de test pouvant être sélectionnés. De nombreux jeux de données de référence sont également disponibles.
Utilisez ensuite la CLI Genkit pour exécuter l'évaluateur sur ces scénarios de test.
genkit eval:run deliciousness_dataset.json
Affichez vos résultats dans l'interface utilisateur de Genkit.
genkit start
Accédez à localhost:4000/evaluate
.