feat(squad): 实现角色资产中心RoleRegistry支持8大标准角色与临时专员派生
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
weichen
co-authored by
Cursor
parent
912ae4f782
commit
f1d602b089
@@ -0,0 +1,272 @@
|
||||
package squad
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// AdHocRoleRequest 特设临时角色派生请求
|
||||
type AdHocRoleRequest struct {
|
||||
RoleKey string `json:"role_key"`
|
||||
Name string `json:"name"`
|
||||
BaseRole string `json:"base_role"`
|
||||
DomainTopic string `json:"domain_topic"`
|
||||
Responsibilities []string `json:"responsibilities"`
|
||||
ForbiddenActions []string `json:"forbidden_actions"`
|
||||
}
|
||||
|
||||
// RoleRegistry 智能体小队角色资产中心
|
||||
type RoleRegistry struct {
|
||||
mu sync.RWMutex
|
||||
roles map[string]AgentSpec
|
||||
}
|
||||
|
||||
// NewRoleRegistry 初始化角色资产中心并载入 8 大标准专员
|
||||
func NewRoleRegistry() *RoleRegistry {
|
||||
r := &RoleRegistry{
|
||||
roles: make(map[string]AgentSpec),
|
||||
}
|
||||
r.registerDefaults()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RoleRegistry) registerDefaults() {
|
||||
standards := []AgentSpec{
|
||||
{
|
||||
Role: "organizer",
|
||||
Name: "材料整理专员",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"材料事实抽取", "缺口识别", "字段结构化回填", "材料提取与整理"},
|
||||
ForbiddenActions: []string{"严禁测算价格", "严禁出具法规终结裁定", "严禁撰写正文终稿"},
|
||||
InstructionsFile: "configs/agents/organizer.md",
|
||||
},
|
||||
{
|
||||
Role: "searcher",
|
||||
Name: "资料检索专员",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"公开资料检索", "法规标准核对", "出处与日期标注", "文献溯源与检索"},
|
||||
ForbiddenActions: []string{"严禁编造URL与标准号", "未检索到须标明尝试路径"},
|
||||
InstructionsFile: "configs/agents/searcher.md",
|
||||
},
|
||||
{
|
||||
Role: "regulatory",
|
||||
Name: "医疗器械法规专员",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"器械分类初筛", "注册路径比对", "关键标准提炼", "法规审评路径"},
|
||||
ForbiddenActions: []string{"严禁代做行政终局裁定", "必须标注需向窗口确认"},
|
||||
InstructionsFile: "configs/agents/regulatory.md",
|
||||
},
|
||||
{
|
||||
Role: "engineer",
|
||||
Name: "系统与方案工程师",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"系统架构设计", "关键技术选型", "工程实现可行性推导", "硬件模块与嵌入式结构方案设计"},
|
||||
ForbiddenActions: []string{"严禁虚构未验证技术可行性", "严禁忽视安全隔离要求"},
|
||||
InstructionsFile: "configs/agents/engineer.md",
|
||||
},
|
||||
{
|
||||
Role: "market",
|
||||
Name: "市场与竞品分析师",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"竞品同维度对比", "市场规模测算", "机会与风险分析", "竞品横向功能对比与市场容量测算"},
|
||||
ForbiddenActions: []string{"严禁编造市场份额与价格", "必须采用统一对比维度"},
|
||||
InstructionsFile: "configs/agents/market.md",
|
||||
},
|
||||
{
|
||||
Role: "finance",
|
||||
Name: "成本与定价测算专员",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"CDO定价核算", "WBS工时拆解", "成本核算与商务条款", "CDO成本核算与定价工时拆解"},
|
||||
ForbiddenActions: []string{"严禁脱离工时模型拍脑袋报价", "严禁擅自承诺超额商业折扣"},
|
||||
InstructionsFile: "configs/agents/finance.md",
|
||||
},
|
||||
{
|
||||
Role: "writer",
|
||||
Name: "文档撰写专员",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"严格四段式成文", "依据材料出处引用", "模板各章节写满", "撰写正式交付文档正文终稿"},
|
||||
ForbiddenActions: []string{"严禁写不做展开或从略", "严禁随意篡改前序事实"},
|
||||
InstructionsFile: "configs/agents/writer.md",
|
||||
},
|
||||
{
|
||||
Role: "leader",
|
||||
Name: "商务对接负责人",
|
||||
Source: RoleSourceStandard,
|
||||
Model: "MiniMax-M3",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"计划审核", "正文终审", "待确认清单汇总", "对外汇报", "商务合同把关与终审交付"},
|
||||
ForbiddenActions: []string{"严禁代用户做商业拍板", "严禁直接交付未审核初稿"},
|
||||
InstructionsFile: "configs/agents/leader.md",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range standards {
|
||||
r.roles[s.Role] = s
|
||||
}
|
||||
}
|
||||
|
||||
// GetRole 根据 Key 获取角色
|
||||
func (r *RoleRegistry) GetRole(key string) (AgentSpec, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
spec, ok := r.roles[key]
|
||||
return spec, ok
|
||||
}
|
||||
|
||||
// ListRoles 列出当前注册的所有角色
|
||||
func (r *RoleRegistry) ListRoles() []AgentSpec {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
res := make([]AgentSpec, 0, len(r.roles))
|
||||
for _, spec := range r.roles {
|
||||
res = append(res, spec)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// RegisterRole 显式注册或更新角色
|
||||
func (r *RoleRegistry) RegisterRole(spec AgentSpec) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if spec.BashPermission == "" {
|
||||
spec.BashPermission = "deny"
|
||||
}
|
||||
r.roles[spec.Role] = spec
|
||||
}
|
||||
|
||||
// RecommendRole 根据领域任务或文本描述推荐最贴切的标准角色
|
||||
func (r *RoleRegistry) RecommendRole(query string) (AgentSpec, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
q := strings.ToLower(query)
|
||||
|
||||
type scoreItem struct {
|
||||
spec AgentSpec
|
||||
score int
|
||||
}
|
||||
|
||||
var best scoreItem
|
||||
|
||||
// 规则特征优先字典
|
||||
ruleWeights := map[string][]string{
|
||||
"regulatory": {"法规", "注册", "审评", "分类界定", "临床评价", "二类", "三类", "药监局", "nmpa", "ce", "fda"},
|
||||
"searcher": {"检索", "文献", "标准", "标准号", "查证", "公开资料", "溯源"},
|
||||
"market": {"竞品", "市场", "容量", "份额", "对标", "优劣势", "商业模式"},
|
||||
"engineer": {"工程", "硬件", "软件", "结构", "架构", "技术路线", "嵌入式", "算法", "系统", "模块", "选型"},
|
||||
"organizer": {"整理", "事实", "缺口", "字段", "回填", "抽取", "材料梳理", "基线"},
|
||||
"finance": {"成本", "工时", "核算", "报价", "定价", "cdo", "价格", "利润", "商务条款"},
|
||||
"writer": {"撰写", "成文", "起草", "初稿", "正文", "章节", "四段式"},
|
||||
"leader": {"终审", "审核", "把关", "总览", "交付", "待确认清单", "对外汇报", "决策"},
|
||||
}
|
||||
|
||||
for roleKey, keywords := range ruleWeights {
|
||||
spec, exists := r.roles[roleKey]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
curScore := 0
|
||||
for _, kw := range keywords {
|
||||
if strings.Contains(q, kw) {
|
||||
curScore += 10
|
||||
}
|
||||
}
|
||||
// 职责辅助匹配
|
||||
for _, resp := range spec.Responsibilities {
|
||||
if strings.Contains(q, strings.ToLower(resp)) {
|
||||
curScore += 5
|
||||
}
|
||||
}
|
||||
|
||||
if curScore > best.score {
|
||||
best = scoreItem{spec: spec, score: curScore}
|
||||
}
|
||||
}
|
||||
|
||||
if best.score > 0 {
|
||||
return best.spec, true
|
||||
}
|
||||
|
||||
// 兜底退回 engineer 或 organizer
|
||||
if eng, ok := r.roles["engineer"]; ok {
|
||||
return eng, true
|
||||
}
|
||||
return AgentSpec{}, false
|
||||
}
|
||||
|
||||
// SpawnAdHocRole 自治派生特设临时专员角色
|
||||
func (r *RoleRegistry) SpawnAdHocRole(req AdHocRoleRequest) (AgentSpec, error) {
|
||||
if req.RoleKey == "" {
|
||||
return AgentSpec{}, fmt.Errorf("role_key cannot be empty")
|
||||
}
|
||||
|
||||
baseRoleKey := req.BaseRole
|
||||
if baseRoleKey == "" {
|
||||
baseRoleKey = "engineer"
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
baseSpec, exists := r.roles[baseRoleKey]
|
||||
if !exists {
|
||||
baseSpec = r.roles["engineer"]
|
||||
}
|
||||
|
||||
model := baseSpec.Model
|
||||
if model == "" {
|
||||
model = "MiniMax-M3"
|
||||
}
|
||||
|
||||
name := req.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("%s专项专员", req.RoleKey)
|
||||
}
|
||||
|
||||
responsibilities := append([]string{}, req.Responsibilities...)
|
||||
if len(responsibilities) == 0 && req.DomainTopic != "" {
|
||||
responsibilities = append(responsibilities, fmt.Sprintf("专责推演与论证【%s】", req.DomainTopic))
|
||||
}
|
||||
|
||||
forbidden := append([]string{}, req.ForbiddenActions...)
|
||||
forbidden = append(forbidden, "严禁违规越权执行未经规划的破坏性命令", "严禁直接代用户做终审拍板")
|
||||
|
||||
adHocSpec := AgentSpec{
|
||||
Role: req.RoleKey,
|
||||
Name: name,
|
||||
Source: RoleSourceAdHoc,
|
||||
BaseRole: baseRoleKey,
|
||||
Model: model,
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny", // 强制只读沙箱安全
|
||||
Responsibilities: responsibilities,
|
||||
ForbiddenActions: forbidden,
|
||||
InstructionsFile: baseSpec.InstructionsFile,
|
||||
}
|
||||
|
||||
r.roles[adHocSpec.Role] = adHocSpec
|
||||
return adHocSpec, nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package squad
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRoleRegistryDefaultRoles(t *testing.T) {
|
||||
reg := NewRoleRegistry()
|
||||
roles := reg.ListRoles()
|
||||
|
||||
if len(roles) < 8 {
|
||||
t.Fatalf("expected at least 8 standard roles, got %d", len(roles))
|
||||
}
|
||||
|
||||
expectedRoles := []string{
|
||||
"organizer",
|
||||
"searcher",
|
||||
"regulatory",
|
||||
"engineer",
|
||||
"market",
|
||||
"finance",
|
||||
"writer",
|
||||
"leader",
|
||||
}
|
||||
|
||||
for _, r := range expectedRoles {
|
||||
spec, found := reg.GetRole(r)
|
||||
if !found {
|
||||
t.Errorf("expected role %s to be registered", r)
|
||||
}
|
||||
if spec.Source != RoleSourceStandard {
|
||||
t.Errorf("role %s source expected standard, got %s", r, spec.Source)
|
||||
}
|
||||
if spec.BashPermission != "deny" {
|
||||
t.Errorf("role %s bash_permission must be deny, got %s", r, spec.BashPermission)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleRegistryRecommend(t *testing.T) {
|
||||
reg := NewRoleRegistry()
|
||||
|
||||
tests := []struct {
|
||||
query string
|
||||
expected string
|
||||
}{
|
||||
{"医疗器械分类界定与注册审评路径", "regulatory"},
|
||||
{"公开文献、标准号溯源检索", "searcher"},
|
||||
{"竞品横向功能对比与市场容量测算", "market"},
|
||||
{"系统硬件模块与嵌入式结构方案设计", "engineer"},
|
||||
{"材料提取、缺口与字段回填", "organizer"},
|
||||
{"CDO成本核算与定价工时拆解", "finance"},
|
||||
{"撰写正式交付文档正文终稿", "writer"},
|
||||
{"商务合同把关与终审交付", "leader"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
rec, found := reg.RecommendRole(tc.query)
|
||||
if !found {
|
||||
t.Errorf("recommendation failed for query: %s", tc.query)
|
||||
}
|
||||
if rec.Role != tc.expected {
|
||||
t.Errorf("for query '%s', expected %s, got %s", tc.query, tc.expected, rec.Role)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleRegistrySpawnAdHocRole(t *testing.T) {
|
||||
reg := NewRoleRegistry()
|
||||
|
||||
adHoc, err := reg.SpawnAdHocRole(AdHocRoleRequest{
|
||||
RoleKey: "biocompatibility_expert",
|
||||
Name: "生物相容性评估专员",
|
||||
BaseRole: "engineer",
|
||||
DomainTopic: "动物源性材料与生物学评价GB/T 16886",
|
||||
Responsibilities: []string{"生物相容性接触类型识别", "植入与血液相容性风险评估"},
|
||||
ForbiddenActions: []string{"严禁代替国家局检验中心出具最终报告"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("spawn ad hoc role failed: %v", err)
|
||||
}
|
||||
|
||||
if adHoc.Source != RoleSourceAdHoc {
|
||||
t.Errorf("expected source ad_hoc, got %s", adHoc.Source)
|
||||
}
|
||||
if adHoc.BaseRole != "engineer" {
|
||||
t.Errorf("expected base_role engineer, got %s", adHoc.BaseRole)
|
||||
}
|
||||
if adHoc.BashPermission != "deny" {
|
||||
t.Errorf("ad-hoc role must deny bash permission, got %s", adHoc.BashPermission)
|
||||
}
|
||||
|
||||
// 确认派生角色被登记入注册中心
|
||||
fetched, found := reg.GetRole("biocompatibility_expert")
|
||||
if !found {
|
||||
t.Fatalf("expected spawned role to be retrievable from registry")
|
||||
}
|
||||
if fetched.Name != "生物相容性评估专员" {
|
||||
t.Errorf("expected name 生物相容性评估专员, got %s", fetched.Name)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user