UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

352 lines 12.8 kB
/** * @file Length Scorer * Evaluates response length against configured constraints */ import { BaseRuleScorer, DEFAULT_RULE_SCORER_CONFIG, } from "./baseRuleScorer.js"; /** * Scorer metadata for length */ const LENGTH_METADATA = { id: "length", name: "Length Validator", description: "Evaluates response length against configured constraints (words, characters, sentences)", type: "rule", category: "quality", version: "1.0.0", defaultConfig: { ...DEFAULT_RULE_SCORER_CONFIG, threshold: 0.8, }, requiredInputs: ["response"], optionalInputs: ["query", "context"], }; /** * LengthScorer evaluates response length against configurable constraints */ export class LengthScorer extends BaseRuleScorer { _lengthConfig; constructor(config) { super(LENGTH_METADATA, config); this._lengthConfig = { unit: "words", constraintType: "range", minLength: 10, maxLength: 500, tolerance: 0.1, // 10% tolerance for exact match scoringMode: "proportional", ...config, }; } /** * Get length-specific configuration */ get lengthConfig() { return this._lengthConfig; } /** * Get rules for this scorer */ getRules() { const rules = []; const constraintType = this._lengthConfig.constraintType ?? "range"; switch (constraintType) { case "minimum": rules.push({ id: "length-minimum", description: `Minimum ${this._lengthConfig.unit} check`, type: "length", params: { check: "minimum", value: this._lengthConfig.minLength ?? 10, unit: this._lengthConfig.unit ?? "words", }, weight: 1.0, }); break; case "maximum": rules.push({ id: "length-maximum", description: `Maximum ${this._lengthConfig.unit} check`, type: "length", params: { check: "maximum", value: this._lengthConfig.maxLength ?? 500, unit: this._lengthConfig.unit ?? "words", }, weight: 1.0, }); break; case "exact": rules.push({ id: "length-exact", description: `Exact ${this._lengthConfig.unit} check`, type: "length", params: { check: "exact", value: this._lengthConfig.exactLength ?? 100, tolerance: this._lengthConfig.tolerance ?? 0.1, unit: this._lengthConfig.unit ?? "words", }, weight: 1.0, }); break; case "ratio": rules.push({ id: "length-ratio", description: `Length ratio check against ${this._lengthConfig.ratioReference}`, type: "length", params: { check: "ratio", target: this._lengthConfig.ratioTarget ?? 1.0, reference: this._lengthConfig.ratioReference ?? "query", unit: this._lengthConfig.unit ?? "words", }, weight: 1.0, }); break; case "range": default: rules.push({ id: "length-minimum", description: `Minimum ${this._lengthConfig.unit} check`, type: "length", params: { check: "minimum", value: this._lengthConfig.minLength ?? 10, unit: this._lengthConfig.unit ?? "words", }, weight: 0.5, }); rules.push({ id: "length-maximum", description: `Maximum ${this._lengthConfig.unit} check`, type: "length", params: { check: "maximum", value: this._lengthConfig.maxLength ?? 500, unit: this._lengthConfig.unit ?? "words", }, weight: 0.5, }); break; } return rules; } /** * Measure text length in various units */ _measureLength(text) { return { words: this.getWordCount(text), characters: this.getCharacterCount(text), sentences: this._countSentences(text), paragraphs: this._countParagraphs(text), estimatedTokens: this._estimateTokens(text), }; } /** * Get length in the configured unit */ _getLengthInUnit(text, unit) { const measurement = this._measureLength(text); switch (unit) { case "words": return measurement.words; case "characters": return measurement.characters; case "sentences": return measurement.sentences; case "paragraphs": return measurement.paragraphs; case "tokens": return measurement.estimatedTokens; default: return measurement.words; } } /** * Count sentences in text */ _countSentences(text) { // Match sentence-ending punctuation followed by space or end of string const sentences = text .split(/(?<=[.!?])\s+/) .filter((s) => s.trim().length > 0); return sentences.length; } /** * Count paragraphs in text */ _countParagraphs(text) { // Split by double newlines or paragraph markers const paragraphs = text.split(/\n\s*\n/).filter((p) => p.trim().length > 0); return Math.max(paragraphs.length, 1); // At least 1 paragraph if there's text } /** * Estimate token count (rough approximation) * GPT-style: ~4 characters per token on average */ _estimateTokens(text) { return Math.ceil(text.length / 4); } /** * Evaluate a single length rule */ evaluateRule(rule, input) { const check = rule.params.check; const unit = rule.params.unit ?? "words"; const responseLength = this._getLengthInUnit(input.response, unit); switch (check) { case "minimum": { const minValue = rule.params.value; if (responseLength >= minValue) { return { passed: true, score: 1.0 }; } // Proportional scoring if (this._lengthConfig.scoringMode === "proportional" && minValue > 0) { return { passed: false, score: Math.min(responseLength / minValue, 1.0), }; } return { passed: false, score: 0.0 }; } case "maximum": { const maxValue = rule.params.value; if (responseLength <= maxValue) { return { passed: true, score: 1.0 }; } // Proportional scoring (inverse) if (this._lengthConfig.scoringMode === "proportional" && responseLength > 0) { return { passed: false, score: Math.max(0, maxValue / responseLength), }; } return { passed: false, score: 0.0 }; } case "exact": { const exactValue = rule.params.value; const tolerance = rule.params.tolerance ?? 0.1; const lowerBound = exactValue * (1 - tolerance); const upperBound = exactValue * (1 + tolerance); if (responseLength >= lowerBound && responseLength <= upperBound) { return { passed: true, score: 1.0 }; } // Proportional scoring based on distance from target if (this._lengthConfig.scoringMode === "proportional" && exactValue > 0) { const distance = Math.abs(responseLength - exactValue) / exactValue; return { passed: false, score: Math.max(0, 1 - distance) }; } return { passed: false, score: 0.0 }; } case "ratio": { const target = rule.params.target; const reference = rule.params.reference; let referenceText = ""; if (reference === "query") { referenceText = input.query; } else if (reference === "context" && input.context) { referenceText = input.context.join(" "); } if (!referenceText) { return { passed: true, score: 1.0 }; // No reference, pass by default } const referenceLength = this._getLengthInUnit(referenceText, unit); if (referenceLength === 0) { return { passed: true, score: 1.0 }; } const actualRatio = responseLength / referenceLength; const tolerance = this._lengthConfig.tolerance ?? 0.2; if (actualRatio >= target * (1 - tolerance) && actualRatio <= target * (1 + tolerance)) { return { passed: true, score: 1.0 }; } // Proportional scoring if (this._lengthConfig.scoringMode === "proportional" && target > 0) { const ratioDistance = Math.abs(actualRatio - target) / target; return { passed: false, score: Math.max(0, 1 - ratioDistance) }; } return { passed: false, score: 0.0 }; } default: return { passed: true, score: 1.0 }; } } /** * Override score to add detailed length metrics */ async score(input) { const result = await super.score(input); // Add detailed measurements const measurement = this._measureLength(input.response); const unit = this._lengthConfig.unit ?? "words"; return { ...result, metadata: { ...result.metadata, lengthMeasurement: measurement, configuredUnit: unit, configuredConstraint: this._lengthConfig.constraintType ?? "range", actualLength: this._getLengthInUnit(input.response, unit), minLength: this._lengthConfig.minLength ?? null, maxLength: this._lengthConfig.maxLength ?? null, exactLength: this._lengthConfig.exactLength ?? null, }, }; } } /** * Factory function for creating LengthScorer instances */ export async function createLengthScorer(config) { return new LengthScorer(config); } /** * Pre-configured length scorer presets */ export const LengthScorerPresets = { /** Short response (50-150 words) */ short: () => new LengthScorer({ unit: "words", constraintType: "range", minLength: 50, maxLength: 150, }), /** Medium response (100-300 words) */ medium: () => new LengthScorer({ unit: "words", constraintType: "range", minLength: 100, maxLength: 300, }), /** Long response (200-500 words) */ long: () => new LengthScorer({ unit: "words", constraintType: "range", minLength: 200, maxLength: 500, }), /** Concise response (max 100 words) */ concise: () => new LengthScorer({ unit: "words", constraintType: "maximum", maxLength: 100, }), /** Detailed response (min 300 words) */ detailed: () => new LengthScorer({ unit: "words", constraintType: "minimum", minLength: 300, }), /** Tweet-length (max 280 characters) */ tweet: () => new LengthScorer({ unit: "characters", constraintType: "maximum", maxLength: 280, }), }; //# sourceMappingURL=lengthScorer.js.map