feat(growth): 新增 Evolution Engine 核心类型 — ExperienceCandidate/CombinedExtraction/EvolutionEvent
This commit is contained in:
@@ -394,6 +394,81 @@ pub struct DecayResult {
|
||||
pub archived: u64,
|
||||
}
|
||||
|
||||
// === Evolution Engine Types ===
|
||||
|
||||
/// 经验提取结果
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExperienceCandidate {
|
||||
pub pain_pattern: String,
|
||||
pub context: String,
|
||||
pub solution_steps: Vec<String>,
|
||||
pub outcome: Outcome,
|
||||
pub confidence: f32,
|
||||
pub tools_used: Vec<String>,
|
||||
pub industry_context: Option<String>,
|
||||
}
|
||||
|
||||
/// 结果状态
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Outcome {
|
||||
Success,
|
||||
Partial,
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// 合并提取结果(单次 LLM 调用的全部输出)
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CombinedExtraction {
|
||||
pub memories: Vec<ExtractedMemory>,
|
||||
pub experiences: Vec<ExperienceCandidate>,
|
||||
pub profile_signals: ProfileSignals,
|
||||
}
|
||||
|
||||
/// 画像更新信号(从提取结果中推断,不额外调用 LLM)
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProfileSignals {
|
||||
pub industry: Option<String>,
|
||||
pub recent_topic: Option<String>,
|
||||
pub pain_point: Option<String>,
|
||||
pub preferred_tool: Option<String>,
|
||||
pub communication_style: Option<String>,
|
||||
}
|
||||
|
||||
/// 进化事件
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EvolutionEvent {
|
||||
pub id: String,
|
||||
pub event_type: EvolutionEventType,
|
||||
pub artifact_type: ArtifactType,
|
||||
pub artifact_id: String,
|
||||
pub status: EvolutionStatus,
|
||||
pub confidence: f32,
|
||||
pub user_feedback: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum EvolutionEventType {
|
||||
SkillGenerated,
|
||||
SkillOptimized,
|
||||
WorkflowGenerated,
|
||||
WorkflowOptimized,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ArtifactType {
|
||||
Skill,
|
||||
Pipeline,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum EvolutionStatus {
|
||||
Pending,
|
||||
Confirmed,
|
||||
Rejected,
|
||||
Optimized,
|
||||
}
|
||||
|
||||
/// Compute effective importance with time decay.
|
||||
///
|
||||
/// Uses exponential decay: each 30-day period of non-access reduces
|
||||
@@ -524,4 +599,61 @@ mod tests {
|
||||
assert!(!result.is_empty());
|
||||
assert_eq!(result.total_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_experience_candidate_roundtrip() {
|
||||
let candidate = ExperienceCandidate {
|
||||
pain_pattern: "报表生成".to_string(),
|
||||
context: "月度销售报表".to_string(),
|
||||
solution_steps: vec!["查询数据库".to_string(), "格式化输出".to_string()],
|
||||
outcome: Outcome::Success,
|
||||
confidence: 0.85,
|
||||
tools_used: vec!["researcher".to_string()],
|
||||
industry_context: Some("healthcare".to_string()),
|
||||
};
|
||||
let json = serde_json::to_string(&candidate).unwrap();
|
||||
let decoded: ExperienceCandidate = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(decoded.pain_pattern, "报表生成");
|
||||
assert_eq!(decoded.outcome, Outcome::Success);
|
||||
assert_eq!(decoded.solution_steps.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evolution_event_roundtrip() {
|
||||
let event = EvolutionEvent {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
event_type: EvolutionEventType::SkillGenerated,
|
||||
artifact_type: ArtifactType::Skill,
|
||||
artifact_id: "daily-report".to_string(),
|
||||
status: EvolutionStatus::Pending,
|
||||
confidence: 0.8,
|
||||
user_feedback: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
};
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let decoded: EvolutionEvent = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(decoded.event_type, EvolutionEventType::SkillGenerated);
|
||||
assert_eq!(decoded.status, EvolutionStatus::Pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combined_extraction_default() {
|
||||
let combined = CombinedExtraction::default();
|
||||
assert!(combined.memories.is_empty());
|
||||
assert!(combined.experiences.is_empty());
|
||||
assert!(combined.profile_signals.industry.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_profile_signals() {
|
||||
let signals = ProfileSignals {
|
||||
industry: Some("healthcare".to_string()),
|
||||
recent_topic: Some("报表".to_string()),
|
||||
pain_point: None,
|
||||
preferred_tool: Some("researcher".to_string()),
|
||||
communication_style: Some("concise".to_string()),
|
||||
};
|
||||
assert_eq!(signals.industry.as_deref(), Some("healthcare"));
|
||||
assert!(signals.pain_point.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user