UNPKG

judgeval

Version:

Judgment SDK for TypeScript/JavaScript

386 lines 16.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HallucinationScorer = void 0; const base_scorer_js_1 = require("../../base-scorer.js"); const constants_js_1 = require("../../../constants.js"); const logger_js_1 = require("../../../common/logger.js"); const prompts_js_1 = require("./prompts.js"); const index_js_1 = require("../../../judges/index.js"); // Required parameters for this scorer const required_params = ['actualOutput', 'context']; /** * HallucinationScorer evaluates whether an LLM's output contains hallucinations * by comparing it against provided context. * * The score is the fraction of context segments that contradict the output. * Lower scores are better (0 = no hallucinations, 1 = all contexts contradict the output). */ class HallucinationScorer extends base_scorer_js_1.JudgevalScorer { /** * Create a new HallucinationScorer * * @param threshold - Success threshold (default: 0.5) * @param model - Model to use for evaluation (default: DefaultJudge) * @param include_reason - Whether to include a reason for the score (default: true) * @param async_mode - Whether to use async mode (default: false) * @param strict_mode - Whether to use strict mode (default: false) * @param verbose_mode - Whether to include verbose logs (default: false) */ constructor(threshold = 0.5, model = undefined, include_reason = true, async_mode = false, strict_mode = false, verbose_mode = false) { super(constants_js_1.APIScorer.HALLUCINATION, strict_mode ? 1 : threshold, undefined, include_reason, async_mode, strict_mode, verbose_mode); this._verdicts = []; const { judge, usingNativeModel } = (0, index_js_1.createJudge)(model); this.model = judge; this.using_native_model = usingNativeModel; this.evaluation_model = this.model.getModelName(); this.requiredFields = ['actualOutput', 'context']; } /** * Generate verdicts for each context */ _aGenerateVerdicts(actualOutput, contexts) { return __awaiter(this, void 0, void 0, function* () { const prompt = prompts_js_1.HallucinationTemplate.generateVerdicts(actualOutput, contexts); if (this.using_native_model) { const res = yield this.model.aGenerate(prompt); try { const data = JSON.parse(res); return data.verdicts.map((item) => ({ verdict: item.verdict, reason: item.reason })); } catch (error) { throw new Error(`Failed to parse response: ${error}`); } } else { try { const parseVerdictsResponse = (response) => { const parsed = JSON.parse(response); const result = prompts_js_1.VerdictsSchema.safeParse(parsed); if (result.success) { return result.data; } throw new Error(`Invalid response format: ${result.error}`); }; const res = yield this.model.aGenerate(prompt); return parseVerdictsResponse(res).verdicts; } catch (error) { const res = yield this.model.aGenerate(prompt); try { const data = JSON.parse(res); return data.verdicts.map((item) => ({ verdict: item.verdict, reason: item.reason })); } catch (parseError) { throw new Error(`Failed to parse response: ${parseError}`); } } } }); } /** * Generate verdicts for each context (synchronous) */ _generateVerdicts(actualOutput, contexts) { const prompt = prompts_js_1.HallucinationTemplate.generateVerdicts(actualOutput, contexts); if (this.using_native_model) { const res = this.model.generate(prompt); try { const data = JSON.parse(res); return data.verdicts.map((item) => ({ verdict: item.verdict, reason: item.reason })); } catch (error) { throw new Error(`Failed to parse response: ${error}`); } } else { try { const parseVerdictsResponse = (response) => { const parsed = JSON.parse(response); const result = prompts_js_1.VerdictsSchema.safeParse(parsed); if (result.success) { return result.data; } throw new Error(`Invalid response format: ${result.error}`); }; const res = this.model.generate(prompt); return parseVerdictsResponse(res).verdicts; } catch (error) { const res = this.model.generate(prompt); try { const data = JSON.parse(res); return data.verdicts.map((item) => ({ verdict: item.verdict, reason: item.reason })); } catch (parseError) { throw new Error(`Failed to parse response: ${parseError}`); } } } } /** * Generate a reason for the score */ _aGenerateReason(actualOutput, contexts) { return __awaiter(this, void 0, void 0, function* () { if (!this.include_reason) { return "No reason provided (include_reason is false)"; } const prompt = prompts_js_1.HallucinationTemplate.generateReason(actualOutput, contexts); if (this.using_native_model) { const res = yield this.model.aGenerate(prompt); try { const data = JSON.parse(res); return data.reason || "No reason provided in response"; } catch (error) { throw new Error(`Failed to parse response: ${error}`); } } else { try { const parseReasonResponse = (response) => { const parsed = JSON.parse(response); const result = prompts_js_1.ReasonSchema.safeParse(parsed); if (result.success) { return result.data; } throw new Error(`Invalid response format: ${result.error}`); }; const res = yield this.model.aGenerate(prompt); return parseReasonResponse(res).reason; } catch (error) { const res = yield this.model.aGenerate(prompt); try { const data = JSON.parse(res); return data.reason || "No reason provided in response"; } catch (parseError) { throw new Error(`Failed to parse response: ${parseError}`); } } } }); } /** * Generate a reason for the score (synchronous) */ _generateReason(actualOutput, contexts) { if (!this.include_reason) { return "No reason provided (include_reason is false)"; } const prompt = prompts_js_1.HallucinationTemplate.generateReason(actualOutput, contexts); if (this.using_native_model) { const res = this.model.generate(prompt); try { const data = JSON.parse(res); return data.reason || "No reason provided in response"; } catch (error) { throw new Error(`Failed to parse response: ${error}`); } } else { try { const parseReasonResponse = (response) => { const parsed = JSON.parse(response); const result = prompts_js_1.ReasonSchema.safeParse(parsed); if (result.success) { return result.data; } throw new Error(`Invalid response format: ${result.error}`); }; const res = this.model.generate(prompt); return parseReasonResponse(res).reason; } catch (error) { const res = this.model.generate(prompt); try { const data = JSON.parse(res); return data.reason || "No reason provided in response"; } catch (parseError) { throw new Error(`Failed to parse response: ${parseError}`); } } } } /** * Calculate the hallucination score */ _computeScore() { if (this._verdicts.length === 0) { return 0; } let contradictions = 0; for (const verdict of this._verdicts) { if (verdict.verdict.trim().toLowerCase() === "no") { contradictions += 1; } } return contradictions / this._verdicts.length; } /** * Create verbose logs for debugging */ _createVerboseLogs() { if (!this.verbose_mode) { return null; } const steps = [ `Verdicts:\n${JSON.stringify(this._verdicts, null, 2)}`, `Score: ${this.score}\nReason: ${this.reason || "No reason provided"}` ]; return steps.join('\n\n'); } /** * Score an example synchronously */ syncScoreExample(example) { (0, logger_js_1.info)("Starting example scoring (sync mode)"); try { // Check required parameters this._checkExampleParams(example); // Process example const contexts = Array.isArray(example.context) ? example.context : [example.context || '']; this._verdicts = this._generateVerdicts(example.actualOutput, contexts); // Calculate score this.score = this._computeScore(); this.reason = this._generateReason(example.actualOutput, contexts) || ''; this.success = this._successCheck(); // Create verbose logs if enabled const verbose_logs = this._createVerboseLogs(); // Calculate evaluation cost const promptTokens = 600; // Estimate - in a real implementation, track actual tokens const completionTokens = 200; // Estimate - in a real implementation, track actual tokens this.evaluation_cost = this._calculateTokenCosts(this.evaluation_model || 'gpt-3.5-turbo', promptTokens, completionTokens); (0, logger_js_1.info)(`Scoring completed with score: ${this.score}`); // Return ScorerData object return { name: this.type, threshold: this.threshold, success: this.success, score: this.score, reason: this.reason, strict_mode: this.strict_mode, evaluation_model: this.evaluation_model || null, error: null, evaluation_cost: this.evaluation_cost || null, verbose_logs: verbose_logs, additional_metadata: { verdicts: this._verdicts } }; } catch (error) { // Handle errors const errorMessage = error instanceof Error ? error.message : String(error); this.error = errorMessage; return { name: this.type, threshold: this.threshold, success: false, score: 0, reason: `Error during scoring: ${errorMessage}`, strict_mode: this.strict_mode, evaluation_model: this.evaluation_model || null, error: errorMessage, evaluation_cost: null, verbose_logs: null, additional_metadata: {} }; } } /** * Score an example asynchronously */ scoreExample(example) { return __awaiter(this, void 0, void 0, function* () { if (!this.async_mode) { return this.syncScoreExample(example); } (0, logger_js_1.info)("Starting example scoring (async mode)"); try { // Check required parameters this._checkExampleParams(example); // Process example const contexts = Array.isArray(example.context) ? example.context : [example.context || '']; this._verdicts = yield this._aGenerateVerdicts(example.actualOutput, contexts); // Calculate score this.score = this._computeScore(); this.reason = (yield this._aGenerateReason(example.actualOutput, contexts)) || ''; this.success = this._successCheck(); // Create verbose logs if enabled const verbose_logs = this._createVerboseLogs(); // Calculate evaluation cost const promptTokens = 600; // Estimate - in a real implementation, track actual tokens const completionTokens = 200; // Estimate - in a real implementation, track actual tokens this.evaluation_cost = this._calculateTokenCosts(this.evaluation_model || 'gpt-3.5-turbo', promptTokens, completionTokens); (0, logger_js_1.info)(`Scoring completed with score: ${this.score}`); // Return ScorerData object return { name: this.type, threshold: this.threshold, success: this.success, score: this.score, reason: this.reason, strict_mode: this.strict_mode, evaluation_model: this.evaluation_model || null, error: null, evaluation_cost: this.evaluation_cost || null, verbose_logs: verbose_logs, additional_metadata: { verdicts: this._verdicts } }; } catch (error) { // Handle errors const errorMessage = error instanceof Error ? error.message : String(error); this.error = errorMessage; return { name: this.type, threshold: this.threshold, success: false, score: 0, reason: `Error during scoring: ${errorMessage}`, strict_mode: this.strict_mode, evaluation_model: this.evaluation_model || null, error: errorMessage, evaluation_cost: null, verbose_logs: null, additional_metadata: {} }; } }); } /** * Get the name of the scorer */ get name() { return "Hallucination"; } } exports.HallucinationScorer = HallucinationScorer; //# sourceMappingURL=hallucination.js.map