From 2c0602e0e68e50765b535fa723dd640b39c4f726 Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 18 Apr 2026 23:03:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(growth):=20HIGH-3=20FeedbackCollector=20?= =?UTF-8?q?=E4=BF=A1=E4=BB=BB=E5=BA=A6=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: FeedbackCollector 用纯内存 HashMap 存储信任度记录,重启后归零。 修复: - FeedbackCollector 添加 viking: Option> 字段 - 添加 with_viking() 构造器 - 添加 save(): 遍历 trust_records → MemoryEntry → VikingAdapter 存储 - 添加 load(): find_by_prefix 反序列化回 HashMap - EvolutionEngine::new()/from_experience_store() 传入 VikingAdapter - submit_feedback() 改为 async,提交后自动调用 save() - 添加 load_feedback() 供启动时恢复 测试: save_and_load_roundtrip + load_without_viking + save_without_viking --- crates/zclaw-growth/src/evolution_engine.rs | 28 +++- crates/zclaw-growth/src/feedback_collector.rs | 134 +++++++++++++++++- 2 files changed, 154 insertions(+), 8 deletions(-) diff --git a/crates/zclaw-growth/src/evolution_engine.rs b/crates/zclaw-growth/src/evolution_engine.rs index bd9f902..0ce4165 100644 --- a/crates/zclaw-growth/src/evolution_engine.rs +++ b/crates/zclaw-growth/src/evolution_engine.rs @@ -49,8 +49,8 @@ pub struct EvolutionEngine { impl EvolutionEngine { pub fn new(viking: Arc) -> Self { Self { - viking, - feedback: FeedbackCollector::new(), + viking: viking.clone(), + feedback: FeedbackCollector::with_viking(viking), config: EvolutionConfig::default(), } } @@ -58,9 +58,10 @@ impl EvolutionEngine { /// Backward-compatible constructor /// 从 ExperienceStore 中提取共享的 VikingAdapter 实例 pub fn from_experience_store(experience_store: Arc) -> Self { + let viking = experience_store.viking().clone(); Self { - viking: experience_store.viking().clone(), - feedback: FeedbackCollector::new(), + viking: viking.clone(), + feedback: FeedbackCollector::with_viking(viking), config: EvolutionConfig::default(), } } @@ -142,9 +143,14 @@ impl EvolutionEngine { // 反馈闭环 // ----------------------------------------------------------------------- - /// 提交反馈并获取信任度更新 - pub fn submit_feedback(&mut self, entry: FeedbackEntry) -> TrustUpdate { - self.feedback.submit_feedback(entry) + /// 提交反馈并获取信任度更新,自动持久化 + pub async fn submit_feedback(&mut self, entry: FeedbackEntry) -> TrustUpdate { + let update = self.feedback.submit_feedback(entry); + // 非阻塞持久化:失败仅打日志,不影响返回值 + if let Err(e) = self.feedback.save().await { + tracing::warn!("[EvolutionEngine] Failed to persist trust records: {}", e); + } + update } /// 获取需要优化的进化产物 @@ -178,6 +184,14 @@ impl EvolutionEngine { pub fn feedback(&self) -> &FeedbackCollector { &self.feedback } + + /// 启动时加载已持久化的信任度记录 + pub async fn load_feedback(&mut self) -> Result { + self.feedback + .load() + .await + .map_err(|e| zclaw_types::ZclawError::Internal(e)) + } } #[cfg(test)] diff --git a/crates/zclaw-growth/src/feedback_collector.rs b/crates/zclaw-growth/src/feedback_collector.rs index d08fcb5..9b8ecaf 100644 --- a/crates/zclaw-growth/src/feedback_collector.rs +++ b/crates/zclaw-growth/src/feedback_collector.rs @@ -1,12 +1,17 @@ //! 反馈信号收集与信任度管理(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 { @@ -60,18 +65,105 @@ pub struct TrustRecord { /// 反馈收集器 /// 管理反馈记录和信任度评分 -/// 内存存储,可持久化到 SQLite(后续版本) +/// 通过 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 @@ -281,4 +373,44 @@ mod tests { 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); + } }