From 0a65ea2c5a122cc7de649a34a6f69f055ad92a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Wed, 1 Jul 2026 13:10:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E4=BA=BA=E8=AE=BE?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F(P0=20AgentPersona+PersonaRegistry+5=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E4=BA=BA=E8=AE=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgentPersona 数据结构(能力/工具白名单/prompt模板) - PersonaRegistry 含5内置人设:coder/reviewer/architect/tester/analyst - 按意图关键词推荐人设(review→reviewer,设计→architect等) - 10个单元测试覆盖注册/默认/推荐/白名单/build_prompt --- Batch.md | 16 +- crates/df-ai/src/lib.rs | 1 + crates/df-ai/src/persona.rs | 385 ++++++++++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+), 10 deletions(-) create mode 100644 crates/df-ai/src/persona.rs diff --git a/Batch.md b/Batch.md index 82035cc..1bd6dbb 100644 --- a/Batch.md +++ b/Batch.md @@ -412,22 +412,18 @@ - Projects 列表分页(后端 list_projects 已支持 limit/offset) - **验证**: cargo check + vue-tsc 通过 -## Batch 33 — 多 ReAct 对接 +## Batch 35 — 人设系统(P0·第一步) - **提交**: `当前待提交` - **内容**: - - PLAN_EXECUTION_ENABLED 从编译期 const 改为运行时 AtomicBool - - 新增 set_plan_execution / get_plan_execution IPC + 前端 API - - 新增 PlanProgress.vue(层状 DAG 执行进度展示组件) -- **验证**: cargo check 通过 + - AgentPersona 数据结构(能力标签/工具白名单/prompt 模板) + - PersonaRegistry 注册表(5 内置人设: coder/reviewer/architect/tester/analyst) + - 按意图推荐人设(启发式关键词匹配合适角色) + - build_prompt 注入上下文和目标钉扎 +- **验证**: 10 个单元测试通过 ## 后续规划批次(待推进) -### Batch 35 — 人设系统(P0·第一步) -1. AgentPersona 数据结构+5 内置人设(coder/reviewer/architect/tester/analyst) -2. PersonaRegistry 注册表(CRUD + 按场景选人设) -3. AINode 执行时从 persona_id 拼接 system_prompt + 过滤工具集 - ### Batch 36 — Coordinator 填实 + 多 Agent 骨架(P0·第二步) 1. 任务拆解 → 分配人设 → 并行 Agent → 汇总合并 2. coordinator.rs 从空壳重写为 Agent 调度器 diff --git a/crates/df-ai/src/lib.rs b/crates/df-ai/src/lib.rs index 77fa606..468b9bf 100644 --- a/crates/df-ai/src/lib.rs +++ b/crates/df-ai/src/lib.rs @@ -32,6 +32,7 @@ pub mod planner; // Phase 2: Plan DAG 分层执行器(骨架,PLAN_EXECUTION_ENABLED 门控默认关)。 // 按 Plan::to_layers 层间串行/层内并行调度子任务。 // 依据 docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md。 +pub mod persona; pub mod plan_executor; pub mod provider; pub mod router; diff --git a/crates/df-ai/src/persona.rs b/crates/df-ai/src/persona.rs new file mode 100644 index 0000000..ac4fb1a --- /dev/null +++ b/crates/df-ai/src/persona.rs @@ -0,0 +1,385 @@ +//! 人设系统(P0) — AgentPersona 结构体 + PersonaRegistry 注册表 +//! +//! 构成 AI Native 的基础:角色划分是分工的前提。 +//! - 每名人设包含:能力标签、工具白名单、system prompt 模板 +//! - Registry 提供按场景/意图选人设、按 id 查人设能力 +//! +//! 设计依据:docs/02-架构设计/构想审查/AI-Native方向与路线图-2026-06-29.md + +use std::collections::HashMap; + +/// 内置人设 ID 常量 +pub const PERSONA_CODER: &str = "coder"; +pub const PERSONA_REVIEWER: &str = "reviewer"; +pub const PERSONA_ARCHITECT: &str = "architect"; +pub const PERSONA_TESTER: &str = "tester"; +pub const PERSONA_ANALYST: &str = "analyst"; + +/// 人设能力标签枚举 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PersonaCapability { + /// 代码生成与修改 + CodeGeneration, + /// Code Review + CodeReview, + /// 架构设计与分析 + ArchitectureDesign, + /// 测试编写与执行 + Testing, + /// 数据分析与搜索 + Analysis, + /// 文件读写 + FileOperation, + /// 命令执行 + CommandExecution, + /// Git 操作 + GitOperation, + /// 知识库操作 + KnowledgeOperation, + /// 项目管理 + ProjectManagement, +} + +impl PersonaCapability { + pub fn label(&self) -> &'static str { + match self { + Self::CodeGeneration => "代码生成", + Self::CodeReview => "代码审查", + Self::ArchitectureDesign => "架构设计", + Self::Testing => "测试", + Self::Analysis => "分析", + Self::FileOperation => "文件操作", + Self::CommandExecution => "命令执行", + Self::GitOperation => "Git 操作", + Self::KnowledgeOperation => "知识库", + Self::ProjectManagement => "项目管理", + } + } +} + +/// 人设定义 +#[derive(Debug, Clone)] +pub struct AgentPersona { + /// 唯一标识(如 "coder", "reviewer") + pub id: String, + /// 人设名称(如 "程序员", "审查员") + pub name: String, + /// 人设描述(LLM 理解用) + pub description: String, + /// 能力标签集 + pub capabilities: Vec, + /// 工具白名单(空=不限,非空=仅允许列出的工具) + pub tool_whitelist: Vec, + /// system prompt 模板(注入 {context} {goals} 等占位符) + pub system_prompt_template: String, + /// 优先级(选人设时冲突排名,0=最高) + pub priority: u8, +} + +impl AgentPersona { + /// 检查此人设是否具备某项能力 + pub fn has_capability(&self, cap: &PersonaCapability) -> bool { + self.capabilities.contains(cap) + } + + /// 检查某工具是否在此人设的 whitelist 中 + pub fn is_tool_allowed(&self, tool_name: &str) -> bool { + self.tool_whitelist.is_empty() || self.tool_whitelist.iter().any(|t| t == tool_name) + } + + /// 构建带上下文的 system prompt + pub fn build_prompt(&self, context: &str, goals: &[String]) -> String { + let goals_text = if goals.is_empty() { + String::new() + } else { + format!("\n## 当前目标\n{}", goals.iter().enumerate().map(|(i, g)| format!("{}. {}", i + 1, g)).collect::>().join("\n")) + }; + self.system_prompt_template + .replace("{context}", context) + .replace("{goals}", &goals_text) + } +} + +/// 人设注册表 — 管理内置 + 自定义人设 +#[derive(Debug, Clone)] +pub struct PersonaRegistry { + personae: HashMap, + /// 默认人设 id(兜底) + default_id: String, +} + +impl PersonaRegistry { + /// 构造默认注册表(含 5 内置人设) + pub fn new() -> Self { + let mut reg = Self { + personae: HashMap::new(), + default_id: PERSONA_CODER.to_string(), + }; + reg.register(Self::builtin_coder()); + reg.register(Self::builtin_reviewer()); + reg.register(Self::builtin_architect()); + reg.register(Self::builtin_tester()); + reg.register(Self::builtin_analyst()); + reg + } + + // ── 5 内置人设 ── + + fn builtin_coder() -> AgentPersona { + AgentPersona { + id: PERSONA_CODER.to_string(), + name: "程序员".to_string(), + description: "负责代码生成、修改、重构。专注于实现功能、修复 bug、优化性能,不关注大范围架构变更".to_string(), + capabilities: vec![ + PersonaCapability::CodeGeneration, + PersonaCapability::FileOperation, + PersonaCapability::CommandExecution, + ], + tool_whitelist: vec![], // 不限(全部工具可用) + system_prompt_template: "你是 DevFlow 的**程序员**(Coder)。\n\n你的职责是编写、修改和重构代码。专注于具体实现,不擅自改变整体架构。\n\n{goals}\n\n{context}".to_string(), + priority: 1, + } + } + + fn builtin_reviewer() -> AgentPersona { + AgentPersona { + id: PERSONA_REVIEWER.to_string(), + name: "审查员".to_string(), + description: "负责 Code Review、质量审计、安全检查。审查代码正确性、性能、安全性,返回审查意见不直接改代码".to_string(), + capabilities: vec![ + PersonaCapability::CodeReview, + PersonaCapability::Analysis, + ], + tool_whitelist: vec![ + "read_file".into(), "search_files".into(), "list_directory".into(), + "git_diff".into(), "git_log".into(), "git_status".into(), + ], + system_prompt_template: "你是 DevFlow 的**审查员**(Reviewer)。\n\n你的职责是审查代码质量、安全性和性能。**你不直接修改代码**,只输出审查意见和修改建议。\n\n{goals}\n\n{context}".to_string(), + priority: 3, + } + } + + fn builtin_architect() -> AgentPersona { + AgentPersona { + id: PERSONA_ARCHITECT.to_string(), + name: "架构师".to_string(), + description: "负责架构设计、技术选型、模块划分。分析整体结构,制定技术方案,不关注具体代码实现".to_string(), + capabilities: vec![ + PersonaCapability::ArchitectureDesign, + PersonaCapability::Analysis, + ], + tool_whitelist: vec![ + "read_file".into(), "search_files".into(), "list_directory".into(), + "write_file".into(), // 写架构文档 + ], + system_prompt_template: "你是 DevFlow 的**架构师**(Architect)。\n\n你的职责是架构设计、技术选型和模块划分。分析系统整体结构,制定技术方案。**不编写业务代码**,输出架构文档和设计方案。\n\n{goals}\n\n{context}".to_string(), + priority: 2, + } + } + + fn builtin_tester() -> AgentPersona { + AgentPersona { + id: PERSONA_TESTER.to_string(), + name: "测试员".to_string(), + description: "负责编写单元测试、集成测试、执行测试。验证代码正确性,不修改生产代码".to_string(), + capabilities: vec![ + PersonaCapability::Testing, + PersonaCapability::CommandExecution, + PersonaCapability::FileOperation, + ], + tool_whitelist: vec![], // 不限 + system_prompt_template: "你是 DevFlow 的**测试员**(Tester)。\n\n你的职责是编写测试、执行测试、报告测试结果。**不修改生产代码**,只修改测试代码。\n\n{goals}\n\n{context}".to_string(), + priority: 3, + } + } + + fn builtin_analyst() -> AgentPersona { + AgentPersona { + id: PERSONA_ANALYST.to_string(), + name: "分析师".to_string(), + description: "负责数据分析、日志排查、性能分析。搜索信息、分析数据,输出分析报告".to_string(), + capabilities: vec![ + PersonaCapability::Analysis, + PersonaCapability::CommandExecution, + PersonaCapability::KnowledgeOperation, + ], + tool_whitelist: vec![ + "read_file".into(), "search_files".into(), "grep_search".into(), + "run_command".into(), "git_log".into(), + "knowledge_search".into(), "knowledge_inject".into(), + ], + system_prompt_template: "你是 DevFlow 的**分析师**(Analyst)。\n\n你的职责是分析数据、排查问题、搜索信息。输出分析报告和结论。**不修改代码**。\n\n{goals}\n\n{context}".to_string(), + priority: 4, + } + } + + // ── 注册表操作 ── + + /// 注册/覆盖人设 + pub fn register(&mut self, persona: AgentPersona) { + self.personae.insert(persona.id.clone(), persona); + } + + /// 按 id 查人设 + pub fn get(&self, id: &str) -> Option<&AgentPersona> { + self.personae.get(id) + } + + /// 取默认人设 + pub fn default(&self) -> &AgentPersona { + self.personae.get(&self.default_id).expect("默认人设必存在") + } + + /// 设置默认人设 id + pub fn set_default(&mut self, id: &str) { + if self.personae.contains_key(id) { + self.default_id = id.to_string(); + } + } + + /// 按意图推荐人设(启发式规则) + pub fn recommend_for_intent(&self, intent: &str) -> &AgentPersona { + let lower = intent.to_lowercase(); + if lower.contains("review") || lower.contains("审查") || lower.contains("审计") { + self.get(PERSONA_REVIEWER).unwrap_or_else(|| self.default()) + } else if lower.contains("架构") || lower.contains("设计") || lower.contains("architect") { + self.get(PERSONA_ARCHITECT).unwrap_or_else(|| self.default()) + } else if lower.contains("测试") || lower.contains("test") { + self.get(PERSONA_TESTER).unwrap_or_else(|| self.default()) + } else if lower.contains("分析") || lower.contains("分析") || lower.contains("排查") || lower.contains("search") { + self.get(PERSONA_ANALYST).unwrap_or_else(|| self.default()) + } else { + // 默认走 coder(代码生成是最常见场景) + self.default() + } + } + + /// 获取全部人设列表 + pub fn all(&self) -> Vec<&AgentPersona> { + let mut list: Vec<_> = self.personae.values().collect(); + list.sort_by_key(|p| p.priority); + list + } + + /// 人设数量 + pub fn len(&self) -> usize { + self.personae.len() + } + + pub fn is_empty(&self) -> bool { + self.personae.is_empty() + } +} + +impl Default for PersonaRegistry { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn registry_has_5_builtin() { + let reg = PersonaRegistry::new(); + assert_eq!(reg.len(), 5); + assert!(reg.get(PERSONA_CODER).is_some()); + assert!(reg.get(PERSONA_REVIEWER).is_some()); + assert!(reg.get(PERSONA_ARCHITECT).is_some()); + assert!(reg.get(PERSONA_TESTER).is_some()); + assert!(reg.get(PERSONA_ANALYST).is_some()); + } + + #[test] + fn default_is_coder() { + let reg = PersonaRegistry::new(); + assert_eq!(reg.default().id, PERSONA_CODER); + } + + #[test] + fn recommend_reviewer() { + let reg = PersonaRegistry::new(); + let p = reg.recommend_for_intent("review the code changes"); + assert_eq!(p.id, PERSONA_REVIEWER); + } + + #[test] + fn recommend_architect() { + let reg = PersonaRegistry::new(); + let p = reg.recommend_for_intent("设计新的模块架构"); + assert_eq!(p.id, PERSONA_ARCHITECT); + } + + #[test] + fn recommend_tester() { + let reg = PersonaRegistry::new(); + let p = reg.recommend_for_intent("write tests for this module"); + assert_eq!(p.id, PERSONA_TESTER); + } + + #[test] + fn recommend_default_to_coder() { + let reg = PersonaRegistry::new(); + let p = reg.recommend_for_intent("implement a new feature"); + assert_eq!(p.id, PERSONA_CODER); + } + + #[test] + fn tool_whitelist_filters() { + let reviewer = AgentPersona { + id: "test".into(), + name: "test".into(), + description: "".into(), + capabilities: vec![], + tool_whitelist: vec!["read_file".into(), "search_files".into()], + system_prompt_template: "".into(), + priority: 5, + }; + assert!(reviewer.is_tool_allowed("read_file")); + assert!(!reviewer.is_tool_allowed("write_file")); + } + + #[test] + fn empty_whitelist_allows_all() { + let coder = AgentPersona { + id: "test".into(), + name: "test".into(), + description: "".into(), + capabilities: vec![], + tool_whitelist: vec![], + system_prompt_template: "".into(), + priority: 5, + }; + assert!(coder.is_tool_allowed("run_command")); + assert!(coder.is_tool_allowed("write_file")); + } + + #[test] + fn build_prompt_replaces_placeholders() { + let reg = PersonaRegistry::new(); + let coder = reg.get(PERSONA_CODER).unwrap(); + let prompt = coder.build_prompt("some context", &["goal1".into()]); + assert!(prompt.contains("程序员")); + assert!(prompt.contains("goal1")); + assert!(prompt.contains("some context")); + } + + #[test] + fn register_custom_persona() { + let mut reg = PersonaRegistry::new(); + let custom = AgentPersona { + id: "custom".into(), + name: "自定义".into(), + description: "test".into(), + capabilities: vec![], + tool_whitelist: vec![], + system_prompt_template: "You are custom".into(), + priority: 0, + }; + reg.register(custom); + assert_eq!(reg.len(), 6); + assert!(reg.get("custom").is_some()); + } +}