fix(growth): HIGH-3 FeedbackCollector 信任度持久化
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
根因: FeedbackCollector 用纯内存 HashMap 存储信任度记录,重启后归零。 修复: - FeedbackCollector 添加 viking: Option<Arc<VikingAdapter>> 字段 - 添加 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
This commit is contained in:
@@ -49,8 +49,8 @@ pub struct EvolutionEngine {
|
||||
impl EvolutionEngine {
|
||||
pub fn new(viking: Arc<VikingAdapter>) -> 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<ExperienceStore>) -> 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<usize> {
|
||||
self.feedback
|
||||
.load()
|
||||
.await
|
||||
.map_err(|e| zclaw_types::ZclawError::Internal(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -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<String, TrustRecord>,
|
||||
viking: Option<Arc<VikingAdapter>>,
|
||||
}
|
||||
|
||||
impl FeedbackCollector {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
trust_records: HashMap::new(),
|
||||
viking: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建带 VikingAdapter 的 FeedbackCollector
|
||||
pub fn with_viking(viking: Arc<VikingAdapter>) -> Self {
|
||||
Self {
|
||||
trust_records: HashMap::new(),
|
||||
viking: Some(viking),
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 VikingAdapter 加载已持久化的信任度记录
|
||||
pub async fn load(&mut self) -> Result<usize, String> {
|
||||
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::<TrustRecord>(&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<usize, String> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user