//! 反馈信号收集与信任度管理(Phase 5 反馈闭环) //! 收集用户对进化产物(技能/Pipeline)的显式/隐式反馈 //! 管理信任度衰减和优化循环 //! 信任度记录通过 VikingAdapter 持久化 use std::collections::HashMap; use std::sync::Arc; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::types::MemoryType; use crate::viking_adapter::VikingAdapter; /// 反馈信号类型 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum FeedbackSignal { /// 用户直接表达的意见 Explicit, /// 从使用行为推断 ImplicitUsage, /// 使用频率 UsageCount, /// 任务完成率 CompletionRate, } /// 情感倾向 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum Sentiment { Positive, Negative, Neutral, } /// 进化产物类型 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum EvolutionArtifact { Skill, Pipeline, } /// 单条反馈记录 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FeedbackEntry { pub artifact_id: String, pub artifact_type: EvolutionArtifact, pub signal: FeedbackSignal, pub sentiment: Sentiment, pub details: Option, pub timestamp: DateTime, } /// 信任度记录 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrustRecord { pub artifact_id: String, pub artifact_type: EvolutionArtifact, pub trust_score: f32, pub total_feedback: u32, pub positive_count: u32, pub negative_count: u32, pub last_updated: DateTime, } /// 反馈收集器 /// 管理反馈记录和信任度评分 /// 通过 VikingAdapter 持久化信任度记录(可选) pub struct FeedbackCollector { trust_records: HashMap, viking: Option>, } impl FeedbackCollector { pub fn new() -> Self { Self { trust_records: HashMap::new(), viking: None, } } /// 创建带 VikingAdapter 的 FeedbackCollector pub fn with_viking(viking: Arc) -> Self { Self { trust_records: HashMap::new(), viking: Some(viking), } } /// 从 VikingAdapter 加载已持久化的信任度记录 pub async fn load(&mut self) -> Result { let viking = match &self.viking { Some(v) => v, None => return Ok(0), }; // MemoryEntry::new("feedback", Session, artifact_id) 生成 // URI: agent://feedback/sessions/{artifact_id} let entries = viking .find_by_prefix("agent://feedback/sessions/") .await .map_err(|e| format!("Failed to load trust records: {}", e))?; let mut count = 0; for entry in entries { match serde_json::from_str::(&entry.content) { Ok(record) => { self.trust_records .insert(record.artifact_id.clone(), record); count += 1; } Err(e) => { tracing::warn!( "[FeedbackCollector] Failed to deserialize trust record at {}: {}", entry.uri, e ); } } } tracing::debug!( "[FeedbackCollector] Loaded {} trust records from storage", count ); Ok(count) } /// 将信任度记录持久化到 VikingAdapter pub async fn save(&self) -> Result { let viking = match &self.viking { Some(v) => v, None => return Ok(0), }; let mut saved = 0; for record in self.trust_records.values() { let content = serde_json::to_string(record).unwrap_or_default(); let entry = crate::types::MemoryEntry::new( "feedback", MemoryType::Session, &record.artifact_id, content, ) .with_importance((record.trust_score * 10.0) as u8); match viking.store(&entry).await { Ok(_) => saved += 1, Err(e) => { tracing::warn!( "[FeedbackCollector] Failed to save trust record {}: {}", record.artifact_id, e ); } } } tracing::debug!( "[FeedbackCollector] Saved {} trust records to storage", saved ); Ok(saved) } /// 提交一条反馈 pub fn submit_feedback(&mut self, entry: FeedbackEntry) -> TrustUpdate { let record = self .trust_records .entry(entry.artifact_id.clone()) .or_insert_with(|| TrustRecord { artifact_id: entry.artifact_id.clone(), artifact_type: entry.artifact_type.clone(), trust_score: 0.5, total_feedback: 0, positive_count: 0, negative_count: 0, last_updated: Utc::now(), }); // 更新计数 record.total_feedback += 1; match entry.sentiment { Sentiment::Positive => record.positive_count += 1, Sentiment::Negative => record.negative_count += 1, Sentiment::Neutral => {} } // 重新计算信任度 let old_score = record.trust_score; record.trust_score = Self::calculate_trust_internal( record.positive_count, record.negative_count, record.total_feedback, record.last_updated, ); record.last_updated = Utc::now(); let new_score = record.trust_score; let total = record.total_feedback; let action = Self::recommend_action_internal(new_score, total); TrustUpdate { artifact_id: entry.artifact_id.clone(), old_score, new_score, action, } } /// 获取信任度记录 pub fn get_trust(&self, artifact_id: &str) -> Option<&TrustRecord> { self.trust_records.get(artifact_id) } /// 获取所有需要优化的产物(信任度 < 0.4) pub fn get_artifacts_needing_optimization(&self) -> Vec<&TrustRecord> { self.trust_records .values() .filter(|r| r.trust_score < 0.4 && r.total_feedback >= 2) .collect() } /// 获取所有应该归档的产物(信任度 < 0.2 且反馈 >= 5) pub fn get_artifacts_to_archive(&self) -> Vec<&TrustRecord> { self.trust_records .values() .filter(|r| r.trust_score < 0.2 && r.total_feedback >= 5) .collect() } /// 获取所有高信任产物(信任度 >= 0.8) pub fn get_recommended_artifacts(&self) -> Vec<&TrustRecord> { self.trust_records .values() .filter(|r| r.trust_score >= 0.8) .collect() } fn calculate_trust_internal( positive: u32, negative: u32, total: u32, last_updated: DateTime, ) -> f32 { if total == 0 { return 0.5; } let positive_ratio = positive as f32 / total as f32; let negative_penalty = negative as f32 * 0.1; let days_since = (Utc::now() - last_updated).num_days().max(0) as f32; let time_decay = 1.0 - (days_since * 0.005).min(0.5); (positive_ratio * time_decay - negative_penalty).clamp(0.0, 1.0) } fn recommend_action_internal(trust_score: f32, total_feedback: u32) -> RecommendedAction { if trust_score >= 0.8 { RecommendedAction::Promote } else if trust_score < 0.2 && total_feedback >= 5 { RecommendedAction::Archive } else if trust_score < 0.4 && total_feedback >= 2 { RecommendedAction::Optimize } else { RecommendedAction::Monitor } } } impl Default for FeedbackCollector { fn default() -> Self { Self::new() } } /// 信任度更新结果 #[derive(Debug, Clone)] pub struct TrustUpdate { pub artifact_id: String, pub old_score: f32, pub new_score: f32, pub action: RecommendedAction, } /// 建议动作 #[derive(Debug, Clone, PartialEq)] pub enum RecommendedAction { /// 继续观察 Monitor, /// 需要优化 Optimize, /// 建议归档(降级为记忆) Archive, /// 建议提升为推荐技能 Promote, } #[cfg(test)] mod tests { use super::*; fn make_feedback(artifact_id: &str, sentiment: Sentiment) -> FeedbackEntry { FeedbackEntry { artifact_id: artifact_id.to_string(), artifact_type: EvolutionArtifact::Skill, signal: FeedbackSignal::Explicit, sentiment, details: None, timestamp: Utc::now(), } } #[test] fn test_initial_trust() { let collector = FeedbackCollector::new(); assert!(collector.get_trust("skill-1").is_none()); } #[test] fn test_positive_feedback_increases_trust() { let mut collector = FeedbackCollector::new(); collector.submit_feedback(make_feedback("skill-1", Sentiment::Positive)); let record = collector.get_trust("skill-1").unwrap(); assert!(record.trust_score > 0.5); assert_eq!(record.positive_count, 1); } #[test] fn test_negative_feedback_decreases_trust() { let mut collector = FeedbackCollector::new(); collector.submit_feedback(make_feedback("skill-1", Sentiment::Negative)); let record = collector.get_trust("skill-1").unwrap(); assert!(record.trust_score < 0.5); } #[test] fn test_mixed_feedback() { let mut collector = FeedbackCollector::new(); collector.submit_feedback(make_feedback("skill-1", Sentiment::Positive)); collector.submit_feedback(make_feedback("skill-1", Sentiment::Positive)); collector.submit_feedback(make_feedback("skill-1", Sentiment::Negative)); let record = collector.get_trust("skill-1").unwrap(); assert_eq!(record.total_feedback, 3); assert!(record.trust_score > 0.3); // 2/3 positive } #[test] fn test_recommend_optimize() { let mut collector = FeedbackCollector::new(); collector.submit_feedback(make_feedback("skill-1", Sentiment::Negative)); let update = collector.submit_feedback(make_feedback("skill-1", Sentiment::Negative)); assert_eq!(update.action, RecommendedAction::Optimize); } #[test] fn test_needs_optimization_filter() { let mut collector = FeedbackCollector::new(); collector.submit_feedback(make_feedback("bad-skill", Sentiment::Negative)); collector.submit_feedback(make_feedback("bad-skill", Sentiment::Negative)); collector.submit_feedback(make_feedback("good-skill", Sentiment::Positive)); let needs = collector.get_artifacts_needing_optimization(); assert_eq!(needs.len(), 1); assert_eq!(needs[0].artifact_id, "bad-skill"); } #[test] fn test_promote_recommendation() { let mut collector = FeedbackCollector::new(); for _ in 0..5 { collector.submit_feedback(make_feedback("great-skill", Sentiment::Positive)); } let recommended = collector.get_recommended_artifacts(); assert_eq!(recommended.len(), 1); } #[tokio::test] async fn test_save_and_load_roundtrip() { let viking = Arc::new(crate::VikingAdapter::in_memory()); // 写入阶段 let mut collector = FeedbackCollector::with_viking(viking.clone()); collector.submit_feedback(make_feedback("skill-a", Sentiment::Positive)); collector.submit_feedback(make_feedback("skill-a", Sentiment::Positive)); collector.submit_feedback(make_feedback("skill-b", Sentiment::Negative)); let saved = collector.save().await.unwrap(); assert_eq!(saved, 2); // 2 个 artifact // 读取阶段:新 collector 从存储加载 let mut collector2 = FeedbackCollector::with_viking(viking); let loaded = collector2.load().await.unwrap(); assert_eq!(loaded, 2); let record_a = collector2.get_trust("skill-a").unwrap(); assert_eq!(record_a.positive_count, 2); assert_eq!(record_a.total_feedback, 2); let record_b = collector2.get_trust("skill-b").unwrap(); assert_eq!(record_b.negative_count, 1); } #[tokio::test] async fn test_load_without_viking_returns_zero() { let mut collector = FeedbackCollector::new(); let loaded = collector.load().await.unwrap(); assert_eq!(loaded, 0); } #[tokio::test] async fn test_save_without_viking_returns_zero() { let collector = FeedbackCollector::new(); let saved = collector.save().await.unwrap(); assert_eq!(saved, 0); } }