重构: df-ai-core trait下沉拆crate+导入历史项目批量扫描

This commit is contained in:
2026-06-16 20:19:55 +08:00
parent d00b30f0ba
commit 2069f79198
19 changed files with 1518 additions and 311 deletions

View File

@@ -5,9 +5,11 @@ edition = "2021"
[dependencies]
df-core = { path = "../df-core" }
df-ai-core = { path = "../df-ai-core" }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
async-trait = { workspace = true }
anyhow = { workspace = true }
chrono = { workspace = true }
tracing = { workspace = true }

View File

@@ -1,15 +1,40 @@
//! 对抗式评估系统 — 正反方辩论 + AI 分析师
//!
//! 当前为基于评分与内容信号的启发式实现(稳定、有区分度)。
//! TODO: 接入 df-ai LlmProvider 让正反方论点由 LLM 生成,启发式降级为 fallback
//! 双轨实现:
//! - **启发式**(默认/降级):基于评分与内容信号生成正反方论点,稳定有区分度
//! - **LLM**(注入 provider 后):调一次 `complete()` 让论点由 LLM 生成,失败自动降级启发式。
//!
//! 评估来源由 [`EvaluatedBy`] 三态标记:`Llm`LLM 深度评估)/ `Heuristic`(主动选启发式,
//! 无 provider/ `HeuristicFallback`LLM 调用失败降级)。前端可据此显示评估深度标签。
//!
//! LLM prompt 构造与 JSON 解析在 F-260614-03已由本任务解锁接入当前 `evaluate_with_llm`
//! 返回 Err 触发降级路径——机制完整,仅缺 prompt/解析实现。
use std::sync::Arc;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use df_ai_core::provider::LlmProvider;
use df_core::types::{IdeaId, Priority};
use crate::capture::Idea;
use crate::scoring::IdeaScores;
/// 评估来源标记
///
/// `Default = Heuristic`老数据F-07 之前)序列化时无 evaluated_by 字段,
/// 反序列化回落启发式(与 F-07 之前行为一致)。
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum EvaluatedBy {
/// LLM 深度评估
Llm,
/// 启发式评估(无 LLM 配置时的默认模式,也是老数据反序列化默认值)
#[default]
Heuristic,
/// 启发式降级LLM 调用失败后 fallback
HeuristicFallback,
}
/// 对抗评估结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdversarialEval {
@@ -19,6 +44,9 @@ pub struct AdversarialEval {
pub analyst: AnalystAnalysis,
pub final_score: f64,
pub recommendation: Recommendation,
/// 评估来源Llm / Heuristic / HeuristicFallback前端据此显示评估深度标签
#[serde(default)]
pub evaluated_by: EvaluatedBy,
}
/// 论点(正方/反方共用同一结构)
@@ -62,19 +90,64 @@ pub enum Recommendation {
}
/// 对抗评估引擎
pub struct AdversarialEngine;
pub struct AdversarialEngine {
/// 可选 LLM provider。Some → 优先 LLM 评估失败降级启发式None → 纯启发式。
/// 构造注入(与 IdeaPromoter::new(policy) 同一模式),批量评估复用同一 provider。
provider: Option<Arc<dyn LlmProvider>>,
}
impl AdversarialEngine {
/// 执行完整的对抗评估
#[allow(clippy::unused_async)] // 签名保留 async,待接 LLM 注入异步调用
pub async fn evaluate(idea: &Idea) -> Result<AdversarialEval> {
/// 注入 LLM provider 构造provider Some 时走 LLM调用失败自动降级启发式
pub fn new(provider: Arc<dyn LlmProvider>) -> Self {
Self { provider: Some(provider) }
}
/// 纯启发式构造(无 LLM 配置时的默认模式)
pub fn heuristic() -> Self {
Self { provider: None }
}
/// 执行完整的对抗评估(内部按 provider 有无调度 LLM / 启发式,失败降级)
pub async fn evaluate(&self, idea: &Idea) -> Result<AdversarialEval> {
match &self.provider {
Some(p) => match self.evaluate_with_llm(idea, p).await {
Ok(mut eval) => {
eval.evaluated_by = EvaluatedBy::Llm;
Ok(eval)
}
Err(e) => {
// LLM 调用失败/超时/格式异常 → 自动降级启发式,保证前端结构完整返回
tracing::warn!("LLM 对抗评估失败, 降级到启发式: {e}");
let mut eval = self.evaluate_heuristic(idea)?;
eval.evaluated_by = EvaluatedBy::HeuristicFallback;
Ok(eval)
}
},
None => {
let mut eval = self.evaluate_heuristic(idea)?;
eval.evaluated_by = EvaluatedBy::Heuristic;
Ok(eval)
}
}
}
/// LLM 对抗评估(注入 provider 后走此路)。
///
/// prompt 构造 + JSON 解析在 F-260614-03已由本任务解锁接入。当前返回 Err
/// 触发降级路径——降级机制与启发式评估路径完整,仅缺 LLM 调用实现。
async fn evaluate_with_llm(&self, _idea: &Idea, _provider: &Arc<dyn LlmProvider>) -> Result<AdversarialEval> {
anyhow::bail!("LLM 对抗评估尚未实现(F-260614-03)")
}
/// 启发式评估(基于评分与内容信号,稳定有区分度)
fn evaluate_heuristic(&self, idea: &Idea) -> Result<AdversarialEval> {
// 先做多维评分,作为正反方论点与置信度的依据
let scores = crate::scoring::ScoringEngine::compute_default(idea);
let positive = Self::generate_positive_argument(idea, &scores)?;
let negative = Self::generate_negative_argument(idea, &scores)?;
let analyst = Self::analyst_analysis(idea, &scores)?;
let recommendation = Self::recommendation_for(&analyst.final_assessment);
let positive = self.generate_positive_argument(idea, &scores)?;
let negative = self.generate_negative_argument(idea, &scores)?;
let analyst = self.analyst_analysis(idea, &scores)?;
let recommendation = self.recommendation_for(&analyst.final_assessment);
Ok(AdversarialEval {
idea_id: idea.id.clone(),
@@ -83,12 +156,14 @@ impl AdversarialEngine {
analyst,
final_score: scores.overall,
recommendation,
// 由 evaluate() 调用方按调度路径覆盖Heuristic / HeuristicFallback
evaluated_by: EvaluatedBy::Heuristic,
})
}
/// 生成正方观点(支持执行)— confidence 由可行性 + 影响力驱动
/// 注:返回 Result 为后续 LLM 注入失败预留,启发式阶段恒 Ok
fn generate_positive_argument(idea: &Idea, scores: &IdeaScores) -> Result<Argument> {
fn generate_positive_argument(&self, idea: &Idea, scores: &IdeaScores) -> Result<Argument> {
let desc = idea.description.trim();
let mut evidence = Vec::new();
evidence.push(format!("优先级:{}", priority_label(&idea.priority)));
@@ -126,7 +201,7 @@ impl AdversarialEngine {
}
/// 生成反方观点(反对或谨慎)— 论点基于想法实际缺陷confidence 随风险上升
fn generate_negative_argument(idea: &Idea, scores: &IdeaScores) -> Result<Argument> {
fn generate_negative_argument(&self, idea: &Idea, scores: &IdeaScores) -> Result<Argument> {
let desc = idea.description.trim();
let mut evidence = Vec::new();
if desc.is_empty() {
@@ -166,7 +241,7 @@ impl AdversarialEngine {
}
/// AI 分析师综合分析 — 评估等级由综合评分决定,优势/劣势按维度动态生成
fn analyst_analysis(idea: &Idea, scores: &IdeaScores) -> Result<AnalystAnalysis> {
fn analyst_analysis(&self, idea: &Idea, scores: &IdeaScores) -> Result<AnalystAnalysis> {
let final_assessment = match scores.overall {
x if x >= 7.5 => AssessmentLevel::StrongGo,
x if x >= 6.0 => AssessmentLevel::Recommended,
@@ -234,7 +309,7 @@ impl AdversarialEngine {
}
/// 评估等级 → 最终建议
fn recommendation_for(level: &AssessmentLevel) -> Recommendation {
fn recommendation_for(&self, level: &AssessmentLevel) -> Recommendation {
match level {
AssessmentLevel::StrongGo => Recommendation::ImmediateAction,
AssessmentLevel::Recommended => Recommendation::Soon,
@@ -302,7 +377,7 @@ mod tests {
let desc = "面向用户的核心功能,带来显著增长,大幅提升效率。集成成熟方案,复用已有组件。".repeat(3);
let idea = make_idea("AI增长引擎", &desc, Priority::Critical, vec!["增长", "核心"]);
let scores = ScoringEngine::compute_default(&idea);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a1] 高分想法 → 期望 ImmediateAction");
println!(" scores: feas={:.2} impact={:.2} urg={:.2} overall={:.2}", scores.feasibility, scores.impact, scores.urgency, scores.overall);
println!(" eval: final_score={:.2} recommendation={:?}", eval.final_score, eval.recommendation);
@@ -316,7 +391,7 @@ mod tests {
let desc = "面向用户的功能,集成已有方案,提升体验".to_string();
let idea = make_idea("体验优化", &desc, Priority::Medium, vec!["体验"]);
let scores = ScoringEngine::compute_default(&idea);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a2] 中分想法 → 期望 Soon");
println!(" scores overall={:.2} eval final_score={:.2} recommendation={:?}", scores.overall, eval.final_score, eval.recommendation);
assert_eq!(eval.recommendation, Recommendation::Soon);
@@ -327,7 +402,7 @@ mod tests {
let desc = "重构迁移大规模分布式重写从零全新架构高并发底层".to_string();
let idea = make_idea("过度工程", &desc, Priority::Low, vec![]);
let scores = ScoringEngine::compute_default(&idea);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a3] 低分想法 → 期望 Monitor");
println!(" scores overall={:.2} eval final_score={:.2} recommendation={:?}", scores.overall, eval.final_score, eval.recommendation);
assert!(eval.final_score < 3.0, "final_score 应<3.0, 实际 {:.2}", eval.final_score);
@@ -337,7 +412,7 @@ mod tests {
#[tokio::test]
async fn a4_confidence_ranges() {
let idea = make_idea("普通想法", "一般描述", Priority::Medium, vec!["标签"]);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a4] confidence 区间校验");
println!(" 正方={:.2} (应∈[0.1, 0.95]) 反方={:.2} (应∈[0.1, 0.9])", eval.positive.confidence, eval.negative.confidence);
assert!(eval.positive.confidence >= 0.1 && eval.positive.confidence <= 0.95);
@@ -347,7 +422,7 @@ mod tests {
#[tokio::test]
async fn a5_positive_thesis_contains_title() {
let idea = make_idea("独家创意", "描述内容", Priority::High, vec![]);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a5] 正方论点含标题");
println!(" thesis: {}", eval.positive.thesis);
assert!(eval.positive.thesis.contains("独家创意"), "正方 thesis 应含标题");
@@ -356,7 +431,7 @@ mod tests {
#[tokio::test]
async fn a6_negative_evidence_nonempty() {
let idea = make_idea("待质疑想法", "", Priority::Low, vec![]);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a6] 反方证据非空 ({} 条)", eval.negative.evidence.len());
for (i, e) in eval.negative.evidence.iter().enumerate() {
println!(" 证据{}: {}", i + 1, e);
@@ -369,7 +444,7 @@ mod tests {
let desc = "面向用户的核心功能".to_string();
let idea = make_idea("一致性测试", &desc, Priority::High, vec!["核心"]);
let scores = ScoringEngine::compute_default(&idea);
let eval = AdversarialEngine::evaluate(&idea).await.unwrap();
let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
println!("\n[a7] final_score == scores.overall 一致性");
println!(" scores.overall={:.2} eval.final_score={:.2}", scores.overall, eval.final_score);
println!(" analyst.summary: {}", eval.analyst.summary);