Skip to main content
Glama
bias-learning-system.ts11.3 kB
/** * BiasLearningSystem - Improves bias detection through learning * * Implements a learning system that: * - Integrates user feedback on bias detections * - Adapts pattern recognition based on accuracy * - Personalizes sensitivity thresholds per user * - Tracks detection accuracy improvement over time * * Performance target: <10ms feedback integration */ import { BiasType, type AccuracyMetrics, type BiasFeedback, type BiasPattern, type LearningMetrics, type TimePeriod, type UserSensitivityProfile, } from "./types"; /** * BiasLearningSystem class * * Main class for learning from feedback and improving bias detection. */ export class BiasLearningSystem { private feedbackHistory: BiasFeedback[] = []; private userProfiles: Map<string, UserSensitivityProfile> = new Map(); private patternWeights: Map<BiasType, number> = new Map(); private baselineAccuracy: Map<BiasType, AccuracyMetrics> | null = null; constructor() { // Initialize default pattern weights for (const biasType of Object.values(BiasType)) { this.patternWeights.set(biasType, 1.0); } } /** * Integrate user feedback on a bias detection * * Records feedback and updates learning models. * Performance target: <10ms * * @param feedback - User feedback on a detection */ integrateFeedback(feedback: BiasFeedback): void { // Validate feedback if (!feedback.detectedBias || typeof feedback.correct !== "boolean") { throw new Error("Invalid feedback: missing required fields"); } // Store feedback this.feedbackHistory.push(feedback); // Update pattern weights based on feedback const biasType = feedback.detectedBias.type; const currentWeight = this.patternWeights.get(biasType) ?? 1.0; // Exponential moving average: adjust weight based on correctness const learningRate = 0.1; const adjustment = feedback.correct ? learningRate : -learningRate; const newWeight = Math.max(0.1, Math.min(2.0, currentWeight + adjustment)); this.patternWeights.set(biasType, newWeight); // Update user sensitivity if needed this.updateUserSensitivity(feedback); } /** * Update pattern weights based on accuracy * * Adjusts detection sensitivity for a bias type based on measured accuracy. * * @param biasType - Type of bias to adjust * @param accuracy - Measured accuracy (0-1) */ updatePatternWeights(biasType: BiasType, accuracy: number): void { if (accuracy < 0 || accuracy > 1) { throw new Error("Accuracy must be between 0 and 1"); } const currentWeight = this.patternWeights.get(biasType) ?? 1.0; // Adjust weight based on accuracy // High accuracy (>0.8): increase weight slightly // Low accuracy (<0.6): decrease weight let adjustment = 0; if (accuracy > 0.8) { adjustment = 0.1; } else if (accuracy < 0.6) { adjustment = -0.15; } const newWeight = Math.max(0.1, Math.min(2.0, currentWeight + adjustment)); this.patternWeights.set(biasType, newWeight); } /** * Adjust sensitivity for a specific user and bias type * * @param userId - User identifier * @param biasType - Type of bias * @param adjustment - Adjustment amount (-1 to +1) */ adjustSensitivity(userId: string, biasType: BiasType, adjustment: number): void { // Get or create user profile let profile = this.userProfiles.get(userId); if (!profile) { profile = { userId: userId, sensitivityByType: new Map(), lastUpdated: new Date(), }; this.userProfiles.set(userId, profile); } // Get current sensitivity (default 0.5) const currentSensitivity = profile.sensitivityByType.get(biasType) ?? 0.5; // Apply adjustment with bounds [0, 1] const newSensitivity = Math.max(0, Math.min(1, currentSensitivity + adjustment)); profile.sensitivityByType.set(biasType, newSensitivity); profile.lastUpdated = new Date(); } /** * Learn new pattern from feedback data * * Analyzes feedback to discover new bias patterns. * * @param feedback - Array of feedback items * @returns Discovered pattern or null if none found */ learnNewPattern(feedback: BiasFeedback[]): BiasPattern | null { if (feedback.length < 3) { return null; // Need minimum data } // Group feedback by bias type const byType = new Map<BiasType, BiasFeedback[]>(); for (const item of feedback) { const type = item.detectedBias.type; if (!byType.has(type)) { byType.set(type, []); } const typeArray = byType.get(type); if (typeArray) { typeArray.push(item); } } // Find patterns with consistent false negatives for (const [biasType, items] of byType) { const falseNegatives = items.filter((f) => !f.correct); if (falseNegatives.length >= 2) { // Discovered a pattern return { biasTypes: [biasType], frequency: falseNegatives.length, commonContexts: [], averageSeverity: 0.6, }; } } return null; } /** * Prune ineffective patterns * * Removes patterns that consistently cause false positives. */ pruneIneffectivePatterns(): void { // Analyze feedback for false positives const falsePositivesByType = new Map<BiasType, number>(); for (const feedback of this.feedbackHistory) { if (!feedback.correct) { const type = feedback.detectedBias.type; falsePositivesByType.set(type, (falsePositivesByType.get(type) ?? 0) + 1); } } // Reduce weight for types with high false positive rate for (const [biasType, count] of falsePositivesByType) { const totalForType = this.feedbackHistory.filter( (f) => f.detectedBias.type === biasType ).length; const falsePositiveRate = count / totalForType; if (falsePositiveRate > 0.3) { // High false positive rate - reduce weight const currentWeight = this.patternWeights.get(biasType) ?? 1.0; this.patternWeights.set(biasType, Math.max(0.1, currentWeight * 0.8)); } } } /** * Get accuracy metrics for a bias type or overall * * @param biasType - Optional bias type to filter by * @returns Accuracy metrics */ getAccuracyMetrics(biasType?: BiasType): AccuracyMetrics { // Filter feedback by bias type if specified const relevantFeedback = biasType ? this.feedbackHistory.filter((f) => f.detectedBias.type === biasType) : this.feedbackHistory; if (relevantFeedback.length === 0) { return { truePositives: 0, falsePositives: 0, trueNegatives: 0, falseNegatives: 0, precision: 0, recall: 0, f1Score: 0, }; } // Calculate metrics const truePositives = relevantFeedback.filter((f) => f.correct).length; const falsePositives = relevantFeedback.filter((f) => !f.correct).length; const trueNegatives = 0; // Not tracked in current implementation const falseNegatives = 0; // Not tracked in current implementation const precision = truePositives + falsePositives > 0 ? truePositives / (truePositives + falsePositives) : 0; const recall = truePositives + falseNegatives > 0 ? truePositives / (truePositives + falseNegatives) : 0; const f1Score = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0; return { truePositives, falsePositives, trueNegatives, falseNegatives, precision, recall, f1Score, }; } /** * Get improvement rate over a time period * * @param period - Time period to measure * @returns Improvement rate (0-1) */ getImprovementRate(period: TimePeriod): number { if (this.feedbackHistory.length < 10) { return 0; // Not enough data } // Calculate time window const now = Date.now(); let windowMs = 0; switch (period) { case "day": windowMs = 24 * 60 * 60 * 1000; break; case "week": windowMs = 7 * 24 * 60 * 60 * 1000; break; case "month": windowMs = 30 * 24 * 60 * 60 * 1000; break; case "all": windowMs = Number.MAX_SAFE_INTEGER; break; } // Split feedback into early and recent const cutoff = now - windowMs; const earlyFeedback = this.feedbackHistory.filter((f) => f.timestamp.getTime() < cutoff); const recentFeedback = this.feedbackHistory.filter((f) => f.timestamp.getTime() >= cutoff); if (earlyFeedback.length === 0 || recentFeedback.length === 0) { return 0; } // Calculate accuracy for each period const earlyAccuracy = earlyFeedback.filter((f) => f.correct).length / earlyFeedback.length; const recentAccuracy = recentFeedback.filter((f) => f.correct).length / recentFeedback.length; // Return improvement return Math.max(0, recentAccuracy - earlyAccuracy); } /** * Get user sensitivity for a bias type * * @param userId - User identifier * @param biasType - Type of bias * @returns Sensitivity threshold (0-1, default 0.5) */ getUserSensitivity(userId: string, biasType: BiasType): number { const profile = this.userProfiles.get(userId); if (!profile) { return 0.5; // Default sensitivity } return profile.sensitivityByType.get(biasType) ?? 0.5; } /** * Get overall learning metrics * * @returns Learning system metrics */ getLearningMetrics(): LearningMetrics { const currentAccuracy = this.getAccuracyMetrics(); const baselineF1 = this.baselineAccuracy ? Array.from(this.baselineAccuracy.values()).reduce((sum, m) => sum + m.f1Score, 0) / this.baselineAccuracy.size : 0; return { totalFeedback: this.feedbackHistory.length, accuracyImprovement: Math.max(0, currentAccuracy.f1Score - baselineF1), patternCount: this.patternWeights.size, userCount: this.userProfiles.size, }; } /** * Update user sensitivity based on feedback * * Automatically tunes sensitivity using Bayesian updating. * * @param feedback - User feedback */ private updateUserSensitivity(feedback: BiasFeedback): void { const userId = feedback.userId; const biasType = feedback.detectedBias.type; // Get user's feedback history for this bias type const userHistory = this.feedbackHistory.filter( (f) => f.userId === userId && f.detectedBias.type === biasType ); if (userHistory.length < 3) { return; // Need more data } // Calculate user's accuracy for this bias type const correct = userHistory.filter((f) => f.correct).length; const accuracy = correct / userHistory.length; // Adjust sensitivity based on accuracy // High accuracy: user is good at identifying this bias, increase sensitivity // Low accuracy: user has false positives, decrease sensitivity let adjustment = 0; if (accuracy > 0.8) { adjustment = 0.05; } else if (accuracy < 0.5) { adjustment = -0.05; } if (adjustment !== 0) { this.adjustSensitivity(userId, biasType, adjustment); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server