Puoi estendere Firebase Genkit per supportare la valutazione personalizzata utilizzando un LLM come giudice o una valutazione programmatica (euristica).
Definizione di valutatore
I valutatori sono funzioni che valutano la risposta di un LLM. Esistono due approcci principali alla valutazione automatica: la valutazione basata su regole euristiche e la valutazione basata su LLM. Nell'approccio basato su heurismi, definisci una funzione deterministica. Al contrario, in una valutazione basata su LLM, i contenuti vengono reintrodotti in un LLM, e a questo viene chiesto di assegnare un punteggio all'output in base ai criteri impostati in un prompt.
Il metodo ai.defineEvaluator
, che utilizzi per definire un'azione di valutatore in Genkit, supporta entrambi gli approcci. Questo
documento illustra un paio di esempi di come utilizzare questo
metodo per le valutazioni basate su euristiche e LLM.
Valutatori basati su LLM
Un valutatore basato su LLM sfrutta un LLM per valutare
input
, context
e output
della funzionalità di AI generativa.
I valutatori basati su LLM in Genkit sono costituiti da tre componenti:
- Un prompt
- Una funzione di punteggio
- Un'azione di valutazione
Definisci il prompt
Per questo esempio, il valutatore utilizza un LLM per determinare se un
alimento (output
) è delizioso o meno. Innanzitutto, fornisci il contesto all'LLM, poi descrivi cosa vuoi che faccia e infine fornisci alcuni esempi su cui basare la sua risposta.
L'utilità definePrompt
di Genkit offre un modo semplice per definire prompt con convalida di input e output. Il seguente codice è un esempio di configurazione di un prompt di valutazione con 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:
`
);
}
Definisci la funzione di punteggio
Definisci una funzione che prende un esempio che include output
come richiesto dal prompt e assegna un punteggio al risultato. I test case di Genkit includono
input
come
campo obbligatorio, con output
e context
come campi facoltativi. È responsabilità del valutatore verificare che siano presenti tutti i campi richiesti per la valutazione.
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 },
};
}
Definisci l'azione dell'valutatore
Il passaggio finale consiste nello scrivere una funzione che definisce 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,
};
}
);
}
Il metodo defineEvaluator
è simile ad altri costruttori Genkit come
defineFlow
e defineRetriever
. Questo metodo richiede che venga fornito un EvaluatorFn
come callback. Il metodo EvaluatorFn
accetta un
oggetto BaseEvalDataPoint
, che corrisponde a una singola voce in un
set di dati in fase di valutazione, insieme a un parametro opzionale custom-options, se specificato. La funzione elabora il punto dati e
restituisce un oggetto EvalResponse
.
Gli schemi Zod per BaseEvalDataPoint
e EvalResponse
sono riportati di seguito.
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'oggetto defineEvaluator
consente all'utente di fornire un nome, un nome visualizzato leggibile dall'utente e una definizione per l'valutatore. Il nome visualizzato e la definizione vengono visualizzati insieme ai risultati della valutazione nell'interfaccia utente di sviluppo.
Dispone inoltre di un campo facoltativo isBilled
che indica se questo valutatore può comportare la fatturazione (ad es. utilizza un'API o un LLM fatturato). Se viene addebitato un valutatore, l'interfaccia utente chiede all'utente una conferma nella CLI prima di consentirgli di eseguire una valutazione. Questo passaggio aiuta a evitare spese indesiderate.
Valutatori di euristiche
Un valutatore di euristiche può essere qualsiasi funzione utilizzata per valutare input
, context
o output
della funzionalità di AI generativa.
I valutatori euristici in Genkit sono costituiti da due componenti:
- Una funzione di punteggio
- Un'azione di valutazione
Definisci la funzione di punteggio
Come per l'valutatore basato su LLM, definisci la funzione di punteggio. In questo caso, la funzione di punteggio non ha bisogno di un LLM giudice.
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 },
};
}
Definisci l'azione dell'valutatore
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,
};
}
);
}
Riepilogo
Definizione del plug-in
I plug-in vengono registrati nel framework installandoli al momento dell'inizializzazione di Genkit. Per definire un nuovo plug-in, utilizza il metodo di assistenza genkitPlugin
per creare istanze di tutte le azioni Genkit nel contesto del plug-in.
Questo esempio di codice mostra due valutatori: il valutatore di bontà basato su LLM e il valutatore di numeri di telefono statunitensi basato su regex. L'inizializzazione di questi valutatori nel contesto del plug-in li registra nel 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;
Configura Genkit
Aggiungi il plug-in myCustomEvals
alla configurazione di Genkit.
Per la valutazione con Gemini, disattiva le impostazioni di sicurezza in modo che il valutatore possa accettare, rilevare e assegnare un punteggio ai contenuti potenzialmente dannosi.
import { gemini15Pro } from '@genkit-ai/googleai';
const ai = genkit({
plugins: [
vertexAI(),
...
myCustomEvals({
judge: gemini15Pro,
}),
],
...
});
Utilizzare gli valutatori personalizzati
Una volta che hai creato i valutatori personalizzati nel contesto dell'app Genkit (tramite un plug-in o direttamente), sono pronti per essere utilizzati. L'esempio seguente illustra come provare lo valutatore di bontà con alcuni input e output di esempio.
- 1. Crea un file JSON "deliciousness_dataset.json" con i seguenti contenuti:
[
{
"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. Utilizza l'interfaccia a riga di comando Genkit per eseguire lo strumento di valutazione su questi casi di test.
# Start your genkit runtime genkit start -- <command to start your app>
genkit eval:run deliciousness_dataset.json --evaluators=myCustomEvals/deliciousnessEvaluator
- 3. Vai alla pagina "localhost:4000/evaluate" per visualizzare i risultati nell'interfaccia utente di Genkit.
È importante notare che l'affidabilità degli valutatori personalizzati aumenta man mano che li esegui il benchmark con set di dati o approcci standard. Esegui l'iterazione sui risultati di questi benchmark per migliorare il rendimento dei valutatori fino a raggiungere il livello di qualità target.