Skip to main content

mofa_foundation/secretary/default/
clarifier.rs

1//! 需求澄清器 - 阶段2: 澄清需求,转换为项目文档
2
3use super::types::*;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7/// 需求澄清策略
8#[derive(Debug, Clone)]
9pub enum ClarificationStrategy {
10    /// 自动澄清(使用LLM分析)
11    Automatic,
12    /// 交互式澄清(需要人类确认)
13    Interactive,
14    /// 模板化澄清(使用预定义模板)
15    Template(String),
16}
17
18/// 澄清问题
19#[derive(Debug, Clone)]
20pub struct ClarificationQuestion {
21    /// 问题ID
22    pub id: String,
23    /// 问题内容
24    pub question: String,
25    /// 问题类型
26    pub question_type: QuestionType,
27    /// 可选答案(如果是选择题)
28    pub options: Option<Vec<String>>,
29    /// 默认答案
30    pub default_answer: Option<String>,
31    /// 是否必答
32    pub required: bool,
33}
34
35/// 问题类型
36#[derive(Debug, Clone)]
37pub enum QuestionType {
38    /// 开放性问题
39    OpenEnded,
40    /// 单选
41    SingleChoice,
42    /// 多选
43    MultipleChoice,
44    /// 确认(是/否)
45    Confirmation,
46    /// 数值范围
47    NumericRange { min: i64, max: i64 },
48}
49
50/// 澄清会话
51pub struct ClarificationSession {
52    /// 会话ID
53    pub session_id: String,
54    /// 关联的Todo ID
55    pub todo_id: String,
56    /// 原始想法
57    pub raw_idea: String,
58    /// 已回答的问题
59    pub answered_questions: Vec<(ClarificationQuestion, String)>,
60    /// 待回答的问题
61    pub pending_questions: Vec<ClarificationQuestion>,
62    /// 澄清后的需求(最终产出)
63    pub clarified_requirement: Option<ProjectRequirement>,
64}
65
66/// 需求澄清器
67pub struct RequirementClarifier {
68    /// 澄清策略
69    strategy: ClarificationStrategy,
70    /// LLM提示词模板
71    prompt_templates: HashMap<String, String>,
72    /// 自定义澄清处理器
73    clarifier_fn: Option<Arc<dyn Fn(&str) -> Vec<ClarificationQuestion> + Send + Sync>>,
74}
75
76impl RequirementClarifier {
77    /// 创建新的需求澄清器
78    pub fn new(strategy: ClarificationStrategy) -> Self {
79        let mut prompt_templates = HashMap::new();
80
81        prompt_templates.insert(
82            "analyze_requirement".to_string(),
83            r#"分析以下用户需求,提取关键信息:
84
85用户需求:{raw_idea}
86
87请回答以下问题:
881. 核心目标是什么?
892. 有哪些具体的功能要求?
903. 有哪些约束条件或限制?
914. 成功的验收标准是什么?
925. 是否有依赖项或前置条件?
93
94请以JSON格式返回分析结果。"#
95                .to_string(),
96        );
97
98        Self {
99            strategy,
100            prompt_templates,
101            clarifier_fn: None,
102        }
103    }
104
105    /// 设置自定义澄清处理器
106    pub fn with_custom_clarifier<F>(mut self, clarifier: F) -> Self
107    where
108        F: Fn(&str) -> Vec<ClarificationQuestion> + Send + Sync + 'static,
109    {
110        self.clarifier_fn = Some(Arc::new(clarifier));
111        self
112    }
113
114    /// 添加或更新提示词模板
115    pub fn add_prompt_template(&mut self, name: &str, template: &str) {
116        self.prompt_templates
117            .insert(name.to_string(), template.to_string());
118    }
119
120    /// 开始澄清会话
121    pub async fn start_session(&self, todo_id: &str, raw_idea: &str) -> ClarificationSession {
122        let session_id = format!(
123            "clarify_{}",
124            std::time::SystemTime::now()
125                .duration_since(std::time::UNIX_EPOCH)
126                .unwrap_or_default()
127                .as_millis()
128        );
129
130        let pending_questions = self.generate_questions(raw_idea).await;
131
132        ClarificationSession {
133            session_id,
134            todo_id: todo_id.to_string(),
135            raw_idea: raw_idea.to_string(),
136            answered_questions: Vec::new(),
137            pending_questions,
138            clarified_requirement: None,
139        }
140    }
141
142    /// 生成澄清问题
143    async fn generate_questions(&self, raw_idea: &str) -> Vec<ClarificationQuestion> {
144        if let Some(ref clarifier) = self.clarifier_fn {
145            return clarifier(raw_idea);
146        }
147
148        match &self.strategy {
149            ClarificationStrategy::Automatic => self.generate_automatic_questions(raw_idea),
150            ClarificationStrategy::Interactive => self.generate_interactive_questions(raw_idea),
151            ClarificationStrategy::Template(template_name) => {
152                self.generate_template_questions(raw_idea, template_name)
153            }
154        }
155    }
156
157    fn generate_automatic_questions(&self, _raw_idea: &str) -> Vec<ClarificationQuestion> {
158        vec![
159            ClarificationQuestion {
160                id: "scope".to_string(),
161                question: "请描述这个需求的具体范围和边界?".to_string(),
162                question_type: QuestionType::OpenEnded,
163                options: None,
164                default_answer: None,
165                required: true,
166            },
167            ClarificationQuestion {
168                id: "priority".to_string(),
169                question: "这个需求的紧急程度如何?".to_string(),
170                question_type: QuestionType::SingleChoice,
171                options: Some(vec![
172                    "紧急(今天完成)".to_string(),
173                    "高优先级(本周完成)".to_string(),
174                    "中优先级(本月完成)".to_string(),
175                    "低优先级(有空再做)".to_string(),
176                ]),
177                default_answer: Some("中优先级(本月完成)".to_string()),
178                required: true,
179            },
180            ClarificationQuestion {
181                id: "acceptance".to_string(),
182                question: "如何判断这个需求已经完成?有什么验收标准?".to_string(),
183                question_type: QuestionType::OpenEnded,
184                options: None,
185                default_answer: None,
186                required: true,
187            },
188            ClarificationQuestion {
189                id: "dependencies".to_string(),
190                question: "完成这个需求是否需要其他前置条件或依赖?".to_string(),
191                question_type: QuestionType::OpenEnded,
192                options: None,
193                default_answer: Some("无特殊依赖".to_string()),
194                required: false,
195            },
196        ]
197    }
198
199    fn generate_interactive_questions(&self, _raw_idea: &str) -> Vec<ClarificationQuestion> {
200        vec![
201            ClarificationQuestion {
202                id: "confirm_understanding".to_string(),
203                question: "我理解您想要...,这个理解正确吗?".to_string(),
204                question_type: QuestionType::Confirmation,
205                options: None,
206                default_answer: None,
207                required: true,
208            },
209            ClarificationQuestion {
210                id: "additional_details".to_string(),
211                question: "是否有其他需要补充的细节?".to_string(),
212                question_type: QuestionType::OpenEnded,
213                options: None,
214                default_answer: None,
215                required: false,
216            },
217        ]
218    }
219
220    fn generate_template_questions(
221        &self,
222        _raw_idea: &str,
223        template_name: &str,
224    ) -> Vec<ClarificationQuestion> {
225        match template_name {
226            "software_feature" => vec![
227                ClarificationQuestion {
228                    id: "user_story".to_string(),
229                    question: "请用「作为...我希望...以便...」的格式描述需求".to_string(),
230                    question_type: QuestionType::OpenEnded,
231                    options: None,
232                    default_answer: None,
233                    required: true,
234                },
235                ClarificationQuestion {
236                    id: "affected_modules".to_string(),
237                    question: "这个功能会影响哪些模块或组件?".to_string(),
238                    question_type: QuestionType::MultipleChoice,
239                    options: Some(vec![
240                        "前端UI".to_string(),
241                        "后端API".to_string(),
242                        "数据库".to_string(),
243                        "第三方集成".to_string(),
244                    ]),
245                    default_answer: None,
246                    required: true,
247                },
248            ],
249            _ => self.generate_automatic_questions(_raw_idea),
250        }
251    }
252
253    /// 回答问题
254    pub async fn answer_question(
255        &self,
256        session: &mut ClarificationSession,
257        question_id: &str,
258        answer: &str,
259    ) -> anyhow::Result<()> {
260        let idx = session
261            .pending_questions
262            .iter()
263            .position(|q| q.id == question_id);
264
265        if let Some(idx) = idx {
266            let question = session.pending_questions.remove(idx);
267            session
268                .answered_questions
269                .push((question, answer.to_string()));
270            Ok(())
271        } else {
272            Err(anyhow::anyhow!("Question not found: {}", question_id))
273        }
274    }
275
276    /// 完成澄清,生成需求文档
277    pub async fn finalize_requirement(
278        &self,
279        session: &mut ClarificationSession,
280    ) -> anyhow::Result<ProjectRequirement> {
281        let requirement = self.synthesize_requirement(session).await?;
282        session.clarified_requirement = Some(requirement.clone());
283        Ok(requirement)
284    }
285
286    async fn synthesize_requirement(
287        &self,
288        session: &ClarificationSession,
289    ) -> anyhow::Result<ProjectRequirement> {
290        let mut acceptance_criteria = Vec::new();
291        for (question, answer) in &session.answered_questions {
292            if question.id == "acceptance" {
293                for line in answer.lines() {
294                    let line = line.trim();
295                    if !line.is_empty() {
296                        acceptance_criteria.push(line.to_string());
297                    }
298                }
299            }
300        }
301
302        if acceptance_criteria.is_empty() {
303            acceptance_criteria.push("功能按预期工作".to_string());
304            acceptance_criteria.push("无明显错误".to_string());
305        }
306
307        let subtasks = self.decompose_into_subtasks(&session.raw_idea);
308
309        let mut dependencies = Vec::new();
310        for (question, answer) in &session.answered_questions {
311            if question.id == "dependencies" && answer != "无特殊依赖" {
312                dependencies.push(answer.clone());
313            }
314        }
315
316        Ok(ProjectRequirement {
317            title: self.generate_title(&session.raw_idea),
318            description: session.raw_idea.clone(),
319            acceptance_criteria,
320            subtasks,
321            dependencies,
322            estimated_effort: None,
323            resources: Vec::new(),
324        })
325    }
326
327    fn generate_title(&self, raw_idea: &str) -> String {
328        let title: String = raw_idea.chars().take(50).collect();
329        if raw_idea.len() > 50 {
330            format!("{}...", title)
331        } else {
332            title
333        }
334    }
335
336    fn decompose_into_subtasks(&self, raw_idea: &str) -> Vec<Subtask> {
337        let mut subtasks = Vec::new();
338        let idea_lower = raw_idea.to_lowercase();
339
340        if idea_lower.contains("api") || idea_lower.contains("接口") {
341            subtasks.push(Subtask {
342                id: "subtask_api_design".to_string(),
343                description: "设计API接口规范".to_string(),
344                required_capabilities: vec!["api_design".to_string()],
345                order: 1,
346                depends_on: Vec::new(),
347            });
348            subtasks.push(Subtask {
349                id: "subtask_api_impl".to_string(),
350                description: "实现API接口".to_string(),
351                required_capabilities: vec!["backend".to_string()],
352                order: 2,
353                depends_on: vec!["subtask_api_design".to_string()],
354            });
355        }
356
357        if idea_lower.contains("ui") || idea_lower.contains("界面") || idea_lower.contains("前端")
358        {
359            subtasks.push(Subtask {
360                id: "subtask_ui_design".to_string(),
361                description: "设计UI界面".to_string(),
362                required_capabilities: vec!["ui_design".to_string()],
363                order: 1,
364                depends_on: Vec::new(),
365            });
366            subtasks.push(Subtask {
367                id: "subtask_ui_impl".to_string(),
368                description: "实现UI界面".to_string(),
369                required_capabilities: vec!["frontend".to_string()],
370                order: 2,
371                depends_on: vec!["subtask_ui_design".to_string()],
372            });
373        }
374
375        if subtasks.is_empty() {
376            subtasks.push(Subtask {
377                id: "subtask_main".to_string(),
378                description: raw_idea.to_string(),
379                required_capabilities: vec!["general".to_string()],
380                order: 1,
381                depends_on: Vec::new(),
382            });
383        }
384
385        subtasks
386    }
387
388    /// 快速澄清(跳过交互,直接生成需求)
389    pub async fn quick_clarify(
390        &self,
391        todo_id: &str,
392        raw_idea: &str,
393    ) -> anyhow::Result<ProjectRequirement> {
394        let mut session = self.start_session(todo_id, raw_idea).await;
395
396        let pending = session.pending_questions.clone();
397        for question in pending {
398            let answer = question
399                .default_answer
400                .clone()
401                .unwrap_or_else(|| "待定".to_string());
402            self.answer_question(&mut session, &question.id, &answer)
403                .await?;
404        }
405
406        self.finalize_requirement(&mut session).await
407    }
408}
409
410impl Default for RequirementClarifier {
411    fn default() -> Self {
412        Self::new(ClarificationStrategy::Automatic)
413    }
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419
420    #[tokio::test]
421    async fn test_start_session() {
422        let clarifier = RequirementClarifier::new(ClarificationStrategy::Automatic);
423        let session = clarifier.start_session("todo_1", "Build a REST API").await;
424
425        assert_eq!(session.todo_id, "todo_1");
426        assert!(!session.pending_questions.is_empty());
427    }
428
429    #[tokio::test]
430    async fn test_quick_clarify() {
431        let clarifier = RequirementClarifier::new(ClarificationStrategy::Automatic);
432        let requirement = clarifier
433            .quick_clarify("todo_1", "Build a REST API")
434            .await
435            .unwrap();
436
437        assert!(!requirement.title.is_empty());
438        assert!(!requirement.acceptance_criteria.is_empty());
439    }
440}