fix(growth): MEDIUM-12 ProfileUpdater 补齐 5 维度画像更新
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
根因: ProfileUpdater 只处理 industry 和 communication_style 2/5 维度, 跳过 recent_topic、pain_point、preferred_tool。 修复: - ProfileFieldUpdate 添加 kind 字段 (SetField | AppendArray) - collect_updates() 现在处理全部 5 个维度: - industry, communication_style → SetField (直接覆盖) - recent_topic, pain_point, preferred_tool → AppendArray (追加去重) - growth.rs 根据 ProfileUpdateKind 分派到不同的 UserProfileStore 方法: - SetField → update_field() - AppendArray → add_recent_topic() / add_pain_point() / add_preferred_tool() - ProfileUpdateKind re-exported from lib.rs 测试: test_collect_updates_all_five_dimensions 验证 5 个维度 + 2 种更新类型
This commit is contained in:
@@ -133,7 +133,7 @@ pub use retrieval::{EmbeddingClient, MemoryCache, QueryAnalyzer, SemanticScorer}
|
||||
pub use summarizer::SummaryLlmDriver;
|
||||
pub use experience_extractor::ExperienceExtractor;
|
||||
pub use json_utils::{extract_json_block, extract_string_array};
|
||||
pub use profile_updater::{ProfileFieldUpdate, UserProfileUpdater};
|
||||
pub use profile_updater::{ProfileFieldUpdate, ProfileUpdateKind, UserProfileUpdater};
|
||||
pub use pattern_aggregator::{AggregatedPattern, PatternAggregator};
|
||||
pub use skill_generator::{SkillCandidate, SkillGenerator};
|
||||
pub use quality_gate::{QualityGate, QualityReport};
|
||||
|
||||
@@ -4,11 +4,21 @@
|
||||
|
||||
use crate::types::CombinedExtraction;
|
||||
|
||||
/// 更新类型:字段覆盖 vs 数组追加
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ProfileUpdateKind {
|
||||
/// 直接覆盖字段值(industry, communication_style)
|
||||
SetField,
|
||||
/// 追加到 JSON 数组字段(recent_topic, pain_point, preferred_tool)
|
||||
AppendArray,
|
||||
}
|
||||
|
||||
/// 待更新的画像字段
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProfileFieldUpdate {
|
||||
pub field: String,
|
||||
pub value: String,
|
||||
pub kind: ProfileUpdateKind,
|
||||
}
|
||||
|
||||
/// 用户画像更新器
|
||||
@@ -22,11 +32,7 @@ impl UserProfileUpdater {
|
||||
}
|
||||
|
||||
/// 从提取结果中收集需要更新的画像字段
|
||||
/// 返回 (field, value) 列表,由调用方负责实际的异步写入
|
||||
///
|
||||
/// 注意:只收集 UserProfileStore::update_field() 支持的字段。
|
||||
/// ProfileSignals 中的 recent_topic / pain_point / preferred_tool
|
||||
/// 需要 update_field 扩展后才能写入,当前跳过。
|
||||
/// 返回 (field, value, kind) 列表,由调用方根据 kind 选择写入方式
|
||||
pub fn collect_updates(
|
||||
&self,
|
||||
extraction: &CombinedExtraction,
|
||||
@@ -38,6 +44,7 @@ impl UserProfileUpdater {
|
||||
updates.push(ProfileFieldUpdate {
|
||||
field: "industry".to_string(),
|
||||
value: industry.clone(),
|
||||
kind: ProfileUpdateKind::SetField,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +52,31 @@ impl UserProfileUpdater {
|
||||
updates.push(ProfileFieldUpdate {
|
||||
field: "communication_style".to_string(),
|
||||
value: style.clone(),
|
||||
kind: ProfileUpdateKind::SetField,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref topic) = signals.recent_topic {
|
||||
updates.push(ProfileFieldUpdate {
|
||||
field: "recent_topic".to_string(),
|
||||
value: topic.clone(),
|
||||
kind: ProfileUpdateKind::AppendArray,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref pain) = signals.pain_point {
|
||||
updates.push(ProfileFieldUpdate {
|
||||
field: "pain_point".to_string(),
|
||||
value: pain.clone(),
|
||||
kind: ProfileUpdateKind::AppendArray,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref tool) = signals.preferred_tool {
|
||||
updates.push(ProfileFieldUpdate {
|
||||
field: "preferred_tool".to_string(),
|
||||
value: tool.clone(),
|
||||
kind: ProfileUpdateKind::AppendArray,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +105,7 @@ mod tests {
|
||||
assert_eq!(updates.len(), 1);
|
||||
assert_eq!(updates[0].field, "industry");
|
||||
assert_eq!(updates[0].value, "healthcare");
|
||||
assert_eq!(updates[0].kind, ProfileUpdateKind::SetField);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -94,4 +127,31 @@ mod tests {
|
||||
|
||||
assert_eq!(updates.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collect_updates_all_five_dimensions() {
|
||||
let mut extraction = CombinedExtraction::default();
|
||||
extraction.profile_signals.industry = Some("healthcare".to_string());
|
||||
extraction.profile_signals.communication_style = Some("concise".to_string());
|
||||
extraction.profile_signals.recent_topic = Some("报表自动化".to_string());
|
||||
extraction.profile_signals.pain_point = Some("手动汇总太慢".to_string());
|
||||
extraction.profile_signals.preferred_tool = Some("researcher".to_string());
|
||||
|
||||
let updater = UserProfileUpdater::new();
|
||||
let updates = updater.collect_updates(&extraction);
|
||||
|
||||
assert_eq!(updates.len(), 5);
|
||||
let set_fields: Vec<_> = updates
|
||||
.iter()
|
||||
.filter(|u| u.kind == ProfileUpdateKind::SetField)
|
||||
.map(|u| u.field.as_str())
|
||||
.collect();
|
||||
let append_fields: Vec<_> = updates
|
||||
.iter()
|
||||
.filter(|u| u.kind == ProfileUpdateKind::AppendArray)
|
||||
.map(|u| u.field.as_str())
|
||||
.collect();
|
||||
assert_eq!(set_fields, vec!["industry", "communication_style"]);
|
||||
assert_eq!(append_fields, vec!["recent_topic", "pain_point", "preferred_tool"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,10 +325,40 @@ impl GrowthIntegration {
|
||||
let updates = self.profile_updater.collect_updates(&combined);
|
||||
let user_id = agent_id.to_string();
|
||||
for update in updates {
|
||||
if let Err(e) = profile_store
|
||||
let result = match update.kind {
|
||||
zclaw_growth::ProfileUpdateKind::SetField => {
|
||||
profile_store
|
||||
.update_field(&user_id, &update.field, &update.value)
|
||||
.await
|
||||
{
|
||||
}
|
||||
zclaw_growth::ProfileUpdateKind::AppendArray => {
|
||||
match update.field.as_str() {
|
||||
"recent_topic" => {
|
||||
profile_store
|
||||
.add_recent_topic(&user_id, &update.value, 10)
|
||||
.await
|
||||
}
|
||||
"pain_point" => {
|
||||
profile_store
|
||||
.add_pain_point(&user_id, &update.value, 10)
|
||||
.await
|
||||
}
|
||||
"preferred_tool" => {
|
||||
profile_store
|
||||
.add_preferred_tool(&user_id, &update.value, 10)
|
||||
.await
|
||||
}
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
"[GrowthIntegration] Unknown array field: {}",
|
||||
update.field
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Err(e) = result {
|
||||
tracing::warn!(
|
||||
"[GrowthIntegration] Profile update failed for {}: {}",
|
||||
update.field,
|
||||
|
||||
Reference in New Issue
Block a user