generalbots/botserver/src/llm/hallucination_detector.rs
Rodrigo Rodriguez (Pragmatismo) 037db5c381 feat: Major workspace reorganization and documentation update
- Add comprehensive documentation in botbook/ with 12 chapters
- Add botapp/ Tauri desktop application
- Add botdevice/ IoT device support
- Add botlib/ shared library crate
- Add botmodels/ Python ML models service
- Add botplugin/ browser extension
- Add botserver/ reorganized server code
- Add bottemplates/ bot templates
- Add bottest/ integration tests
- Add botui/ web UI server
- Add CI/CD workflows in .forgejo/workflows/
- Add AGENTS.md and PROD.md documentation
- Add dependency management scripts (DEPENDENCIES.sh/ps1)
- Remove legacy src/ structure and migrations
- Clean up temporary and backup files
2026-04-19 08:14:25 -03:00

125 lines
3.3 KiB
Rust

//! Simple Hallucination Loop Detector
//!
//! Detects when an LLM gets stuck in a repetition loop.
//! Only triggers when the same pattern repeats 50+ times consecutively.
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use log::warn;
const THRESHOLD: usize = 50;
const WINDOW: Duration = Duration::from_secs(60);
#[derive(Debug, Clone)]
pub struct HallucinationConfig {
pub threshold: usize,
pub window: Duration,
}
impl Default for HallucinationConfig {
fn default() -> Self {
Self {
threshold: THRESHOLD,
window: WINDOW,
}
}
}
#[derive(Debug, Clone)]
pub struct HallucinationDetector {
config: HallucinationConfig,
pattern_counts: Arc<Mutex<HashMap<String, (usize, Instant)>>>,
}
impl Default for HallucinationDetector {
fn default() -> Self {
Self::new(HallucinationConfig::default())
}
}
impl HallucinationDetector {
pub fn new(config: HallucinationConfig) -> Self {
Self {
config,
pattern_counts: Arc::new(Mutex::new(HashMap::new())),
}
}
/// Check if a pattern is hallucinating (repeating 50+ times)
pub async fn check(&self, pattern: &str) -> bool {
let trimmed = pattern.trim();
// Ignore short patterns
if trimmed.is_empty() || trimmed.len() < 3 {
return false;
}
// Ignore Markdown formatting patterns
let md_patterns = ["**", "__", "*", "_", "`", "~~", "---", "***"];
if md_patterns.contains(&trimmed) {
return false;
}
// Ignore patterns that are just Markdown formatting (e.g., " **", "* ", "__")
if trimmed.chars().all(|c| c == '*' || c == '_' || c == '`' || c == '~' || c == '-') {
return false;
}
let mut counts = self.pattern_counts.lock().await;
let now = Instant::now();
// Clean old entries
counts.retain(|_, (_, time)| now.duration_since(*time) < self.config.window);
// Increment count for this pattern
let (count, _) = counts.entry(trimmed.to_string()).or_insert((0, now));
*count += 1;
if *count >= self.config.threshold {
warn!("Hallucination detected: pattern {:?} repeated {} times", trimmed, count);
true
} else {
false
}
}
/// Reset all counts
pub async fn reset(&self) {
let mut counts = self.pattern_counts.lock().await;
counts.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_no_hallucination_below_threshold() {
let detector = HallucinationDetector::default();
for _ in 0..49 {
assert!(!detector.check("test_pattern").await);
}
}
#[tokio::test]
async fn test_hallucination_at_threshold() {
let detector = HallucinationDetector::default();
for _ in 0..50 {
detector.check("test_pattern").await;
}
assert!(detector.check("test_pattern").await);
}
#[tokio::test]
async fn test_reset() {
let detector = HallucinationDetector::default();
for _ in 0..50 {
detector.check("pattern").await;
}
detector.reset().await;
assert!(!detector.check("pattern").await);
}
}