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

根因: 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:
iven
2026-04-18 23:03:31 +08:00
parent f358f14f12
commit 2c0602e0e6
2 changed files with 154 additions and 8 deletions

View File

@@ -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);
}
}