新增: 人设系统(P0 AgentPersona+PersonaRegistry+5内置人设)
- AgentPersona 数据结构(能力/工具白名单/prompt模板) - PersonaRegistry 含5内置人设:coder/reviewer/architect/tester/analyst - 按意图关键词推荐人设(review→reviewer,设计→architect等) - 10个单元测试覆盖注册/默认/推荐/白名单/build_prompt
This commit is contained in:
@@ -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;
|
||||
|
||||
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