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 {
|
impl EvolutionEngine {
|
||||||
pub fn new(viking: Arc<VikingAdapter>) -> Self {
|
pub fn new(viking: Arc<VikingAdapter>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
viking,
|
viking: viking.clone(),
|
||||||
feedback: FeedbackCollector::new(),
|
feedback: FeedbackCollector::with_viking(viking),
|
||||||
config: EvolutionConfig::default(),
|
config: EvolutionConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,9 +58,10 @@ impl EvolutionEngine {
|
|||||||
/// Backward-compatible constructor
|
/// Backward-compatible constructor
|
||||||
/// 从 ExperienceStore 中提取共享的 VikingAdapter 实例
|
/// 从 ExperienceStore 中提取共享的 VikingAdapter 实例
|
||||||
pub fn from_experience_store(experience_store: Arc<ExperienceStore>) -> Self {
|
pub fn from_experience_store(experience_store: Arc<ExperienceStore>) -> Self {
|
||||||
|
let viking = experience_store.viking().clone();
|
||||||
Self {
|
Self {
|
||||||
viking: experience_store.viking().clone(),
|
viking: viking.clone(),
|
||||||
feedback: FeedbackCollector::new(),
|
feedback: FeedbackCollector::with_viking(viking),
|
||||||
config: EvolutionConfig::default(),
|
config: EvolutionConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,9 +143,14 @@ impl EvolutionEngine {
|
|||||||
// 反馈闭环
|
// 反馈闭环
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
/// 提交反馈并获取信任度更新
|
/// 提交反馈并获取信任度更新,自动持久化
|
||||||
pub fn submit_feedback(&mut self, entry: FeedbackEntry) -> TrustUpdate {
|
pub async fn submit_feedback(&mut self, entry: FeedbackEntry) -> TrustUpdate {
|
||||||
self.feedback.submit_feedback(entry)
|
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 {
|
pub fn feedback(&self) -> &FeedbackCollector {
|
||||||
&self.feedback
|
&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)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
//! 反馈信号收集与信任度管理(Phase 5 反馈闭环)
|
//! 反馈信号收集与信任度管理(Phase 5 反馈闭环)
|
||||||
//! 收集用户对进化产物(技能/Pipeline)的显式/隐式反馈
|
//! 收集用户对进化产物(技能/Pipeline)的显式/隐式反馈
|
||||||
//! 管理信任度衰减和优化循环
|
//! 管理信任度衰减和优化循环
|
||||||
|
//! 信任度记录通过 VikingAdapter 持久化
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::types::MemoryType;
|
||||||
|
use crate::viking_adapter::VikingAdapter;
|
||||||
|
|
||||||
/// 反馈信号类型
|
/// 反馈信号类型
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum FeedbackSignal {
|
pub enum FeedbackSignal {
|
||||||
@@ -60,18 +65,105 @@ pub struct TrustRecord {
|
|||||||
|
|
||||||
/// 反馈收集器
|
/// 反馈收集器
|
||||||
/// 管理反馈记录和信任度评分
|
/// 管理反馈记录和信任度评分
|
||||||
/// 内存存储,可持久化到 SQLite(后续版本)
|
/// 通过 VikingAdapter 持久化信任度记录(可选)
|
||||||
pub struct FeedbackCollector {
|
pub struct FeedbackCollector {
|
||||||
trust_records: HashMap<String, TrustRecord>,
|
trust_records: HashMap<String, TrustRecord>,
|
||||||
|
viking: Option<Arc<VikingAdapter>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FeedbackCollector {
|
impl FeedbackCollector {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
trust_records: HashMap::new(),
|
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 {
|
pub fn submit_feedback(&mut self, entry: FeedbackEntry) -> TrustUpdate {
|
||||||
let record = self
|
let record = self
|
||||||
@@ -281,4 +373,44 @@ mod tests {
|
|||||||
let recommended = collector.get_recommended_artifacts();
|
let recommended = collector.get_recommended_artifacts();
|
||||||
assert_eq!(recommended.len(), 1);
|
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