feat(growth): 新增 Evolution Engine 核心类型 — ExperienceCandidate/CombinedExtraction/EvolutionEvent
This commit is contained in:
@@ -394,6 +394,81 @@ pub struct DecayResult {
|
|||||||
pub archived: u64,
|
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.
|
/// Compute effective importance with time decay.
|
||||||
///
|
///
|
||||||
/// Uses exponential decay: each 30-day period of non-access reduces
|
/// Uses exponential decay: each 30-day period of non-access reduces
|
||||||
@@ -524,4 +599,61 @@ mod tests {
|
|||||||
assert!(!result.is_empty());
|
assert!(!result.is_empty());
|
||||||
assert_eq!(result.total_count(), 1);
|
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