@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
234 lines • 8.45 kB
JavaScript
/**
* @file Base class for all rule-based scorers
* Provides common functionality for rule evaluation
*/
import { logger } from "../../../utils/logger.js";
import { BaseScorer, DEFAULT_SCORE_SCALE } from "../baseScorer.js";
/**
* Default rule scorer configuration
*/
export const DEFAULT_RULE_SCORER_CONFIG = {
enabled: true,
threshold: 0.7,
weight: 1.0,
timeout: 1000,
retries: 0,
ruleCombination: "all",
};
/**
* Abstract base class for rule-based scorers
*/
export class BaseRuleScorer extends BaseScorer {
_ruleConfig;
constructor(metadata, config) {
super(metadata, config);
this._ruleConfig = {
...DEFAULT_RULE_SCORER_CONFIG,
...metadata.defaultConfig,
...config,
};
}
/**
* Get rule-specific configuration
*/
get ruleConfig() {
return this._ruleConfig;
}
/**
* Main scoring method
*/
async score(input) {
return this.executeWithTiming(async () => {
// Validate input
const validation = this.validateInput(input);
if (!validation.valid) {
return this.createErrorResult(`Invalid input: ${validation.errors.join(", ")}`);
}
try {
const rules = this.getRules();
if (rules.length === 0) {
return this.createScoreResult(10, "No rules configured - passing by default");
}
// Evaluate all rules
const results = rules.map((rule) => ({
rule,
result: this.evaluateRule(rule, input),
}));
// Combine results based on configuration
const combinedScore = this.combineRuleResults(results.map((r) => ({
ruleId: r.rule.id,
passed: r.result.passed,
score: r.result.score,
})), rules);
// Generate reasoning
const reasoning = this.generateReasoning(results);
return this.createScoreResult(combinedScore, reasoning, {
metadata: {
ruleResults: results.map((r) => ({
ruleId: r.rule.id,
ruleDescription: r.rule.description,
passed: r.result.passed,
score: r.result.score,
})),
ruleCount: rules.length,
passedCount: results.filter((r) => r.result.passed).length,
combinationMethod: this._ruleConfig.ruleCombination ?? "all",
},
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
logger.error(`Rule scorer ${this._metadata.id} failed`, {
error: errorMessage,
});
return this.createErrorResult(errorMessage);
}
});
}
/**
* Combine rule results based on configuration
*/
combineRuleResults(results, rules) {
const combination = this._ruleConfig.ruleCombination ?? "all";
switch (combination) {
case "all": {
// All rules must pass for full score
const passedCount = results.filter((r) => r.passed).length;
return (passedCount / results.length) * DEFAULT_SCORE_SCALE.max;
}
case "any": {
// Any rule passing gives partial credit
const maxScore = Math.max(...results.map((r) => r.score));
return maxScore * DEFAULT_SCORE_SCALE.max;
}
case "weighted": {
// Weight-based combination
let totalWeight = 0;
let weightedScore = 0;
for (let i = 0; i < results.length; i++) {
const rule = rules[i];
const weight = rule.weight ?? 1.0;
totalWeight += weight;
weightedScore += results[i].score * weight;
}
return totalWeight > 0
? (weightedScore / totalWeight) * DEFAULT_SCORE_SCALE.max
: 0;
}
default:
return ((results.filter((r) => r.passed).length / results.length) *
DEFAULT_SCORE_SCALE.max);
}
}
/**
* Generate reasoning from rule results
*/
generateReasoning(results) {
const passed = results.filter((r) => r.result.passed);
const failed = results.filter((r) => !r.result.passed);
const parts = [];
if (passed.length > 0) {
parts.push(`${passed.length} rule(s) passed: ${passed.map((r) => r.rule.id).join(", ")}`);
}
if (failed.length > 0) {
parts.push(`${failed.length} rule(s) failed: ${failed.map((r) => r.rule.id).join(", ")}`);
}
return parts.join(". ");
}
/**
* Helper: Check if text matches a regex pattern
*/
matchesRegex(text, pattern, flags = "gi") {
if (pattern.length > 200) {
logger.warn(`[BaseRuleScorer] Regex pattern too long (${pattern.length} chars), skipping`);
return false;
}
try {
const regex = new RegExp(pattern, flags);
return regex.test(text);
}
catch {
logger.warn(`[BaseRuleScorer] Invalid regex pattern: ${pattern.substring(0, 50)}`);
return false;
}
}
/**
* Helper: Check if text contains keyword with word boundaries
*/
containsKeyword(text, keyword, caseInsensitive = true) {
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const flags = caseInsensitive ? "gi" : "g";
const regex = new RegExp(`\\b${escapedKeyword}\\b`, flags);
return regex.test(text);
}
/**
* Helper: Count occurrences of a pattern
*/
countOccurrences(text, pattern, caseInsensitive = true) {
if (pattern.length > 200) {
logger.warn(`[BaseRuleScorer] Regex pattern too long (${pattern.length} chars), skipping`);
return 0;
}
try {
const flags = caseInsensitive ? "gi" : "g";
const regex = new RegExp(pattern, flags);
const matches = text.match(regex);
return matches ? matches.length : 0;
}
catch {
logger.warn(`[BaseRuleScorer] Invalid regex pattern: ${pattern.substring(0, 50)}`);
return 0;
}
}
/**
* Helper: Get word count
*/
getWordCount(text) {
return text
.trim()
.split(/\s+/)
.filter((word) => word.length > 0).length;
}
/**
* Helper: Get character count (excluding whitespace)
*/
getCharacterCount(text, includeWhitespace = true) {
if (includeWhitespace) {
return text.length;
}
return text.replace(/\s/g, "").length;
}
/**
* Helper: Check text length is within bounds
*/
isWithinLengthBounds(text, minWords, maxWords, minChars, maxChars) {
const wordCount = this.getWordCount(text);
const charCount = text.length;
if (minWords !== undefined && wordCount < minWords) {
return {
passed: false,
reason: `Word count ${wordCount} is below minimum ${minWords}`,
};
}
if (maxWords !== undefined && wordCount > maxWords) {
return {
passed: false,
reason: `Word count ${wordCount} exceeds maximum ${maxWords}`,
};
}
if (minChars !== undefined && charCount < minChars) {
return {
passed: false,
reason: `Character count ${charCount} is below minimum ${minChars}`,
};
}
if (maxChars !== undefined && charCount > maxChars) {
return {
passed: false,
reason: `Character count ${charCount} exceeds maximum ${maxChars}`,
};
}
return { passed: true, reason: "Length within acceptable bounds" };
}
}
//# sourceMappingURL=baseRuleScorer.js.map