新增: 人设系统(P0 AgentPersona+PersonaRegistry+5内置人设)
- AgentPersona 数据结构(能力/工具白名单/prompt模板) - PersonaRegistry 含5内置人设:coder/reviewer/architect/tester/analyst - 按意图关键词推荐人设(review→reviewer,设计→architect等) - 10个单元测试覆盖注册/默认/推荐/白名单/build_prompt
This commit is contained in:
16
Batch.md
16
Batch.md
@@ -412,22 +412,18 @@
|
|||||||
- Projects 列表分页(后端 list_projects 已支持 limit/offset)
|
- Projects 列表分页(后端 list_projects 已支持 limit/offset)
|
||||||
- **验证**: cargo check + vue-tsc 通过
|
- **验证**: cargo check + vue-tsc 通过
|
||||||
|
|
||||||
## Batch 33 — 多 ReAct 对接
|
## Batch 35 — 人设系统(P0·第一步)
|
||||||
|
|
||||||
- **提交**: `当前待提交`
|
- **提交**: `当前待提交`
|
||||||
- **内容**:
|
- **内容**:
|
||||||
- PLAN_EXECUTION_ENABLED 从编译期 const 改为运行时 AtomicBool
|
- AgentPersona 数据结构(能力标签/工具白名单/prompt 模板)
|
||||||
- 新增 set_plan_execution / get_plan_execution IPC + 前端 API
|
- PersonaRegistry 注册表(5 内置人设: coder/reviewer/architect/tester/analyst)
|
||||||
- 新增 PlanProgress.vue(层状 DAG 执行进度展示组件)
|
- 按意图推荐人设(启发式关键词匹配合适角色)
|
||||||
- **验证**: cargo check 通过
|
- 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·第二步)
|
### Batch 36 — Coordinator 填实 + 多 Agent 骨架(P0·第二步)
|
||||||
1. 任务拆解 → 分配人设 → 并行 Agent → 汇总合并
|
1. 任务拆解 → 分配人设 → 并行 Agent → 汇总合并
|
||||||
2. coordinator.rs 从空壳重写为 Agent 调度器
|
2. coordinator.rs 从空壳重写为 Agent 调度器
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub mod planner;
|
|||||||
// Phase 2: Plan DAG 分层执行器(骨架,PLAN_EXECUTION_ENABLED 门控默认关)。
|
// Phase 2: Plan DAG 分层执行器(骨架,PLAN_EXECUTION_ENABLED 门控默认关)。
|
||||||
// 按 Plan::to_layers 层间串行/层内并行调度子任务。
|
// 按 Plan::to_layers 层间串行/层内并行调度子任务。
|
||||||
// 依据 docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md。
|
// 依据 docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md。
|
||||||
|
pub mod persona;
|
||||||
pub mod plan_executor;
|
pub mod plan_executor;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
|
|||||||
385
crates/df-ai/src/persona.rs
Normal file
385
crates/df-ai/src/persona.rs
Normal file
@@ -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<PersonaCapability>,
|
||||||
|
/// 工具白名单(空=不限,非空=仅允许列出的工具)
|
||||||
|
pub tool_whitelist: Vec<String>,
|
||||||
|
/// 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::<Vec<_>>().join("\n"))
|
||||||
|
};
|
||||||
|
self.system_prompt_template
|
||||||
|
.replace("{context}", context)
|
||||||
|
.replace("{goals}", &goals_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 人设注册表 — 管理内置 + 自定义人设
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PersonaRegistry {
|
||||||
|
personae: HashMap<String, AgentPersona>,
|
||||||
|
/// 默认人设 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user