Published manifests now use bd_/pm_/tm_/sw_ prefixes so workflows cannot share a doc id. Frozen instances keep the old keys; lookup, templates, and duty checks treat the two names as the same task. Co-authored-by: Cursor <cursoragent@cursor.com>
384 lines
13 KiB
Go
384 lines
13 KiB
Go
package squad
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/template"
|
|
)
|
|
|
|
// PlannerRequest 任务规划请求
|
|
type PlannerRequest struct {
|
|
TaskKey string `json:"task_key"`
|
|
Title string `json:"title"`
|
|
ProjectName string `json:"project_name"`
|
|
CustomerName string `json:"customer_name"`
|
|
DeviceType string `json:"device_type"`
|
|
IntendedUse string `json:"intended_use"`
|
|
TargetMarket string `json:"target_market"`
|
|
Materials []MaterialItem `json:"materials"`
|
|
KnowledgeHits []KnowledgeHit `json:"knowledge_hits"`
|
|
Fields map[string]string `json:"fields"`
|
|
CustomSteps []StepSpec `json:"custom_steps,omitempty"`
|
|
}
|
|
|
|
// PlannerResult 任务规划产出
|
|
type PlannerResult struct {
|
|
TaskKey string `json:"task_key"`
|
|
Title string `json:"title"`
|
|
PlanningMode PlanningMode `json:"planning_mode"`
|
|
Squad SquadConfig `json:"squad"`
|
|
Workflow WorkflowPlan `json:"workflow"`
|
|
TemplateContent string `json:"template_content"`
|
|
}
|
|
|
|
// AdaptivePlanner 自适应任务规划器
|
|
type AdaptivePlanner struct {
|
|
registry *RoleRegistry
|
|
}
|
|
|
|
// NewAdaptivePlanner 创建自适应任务规划器
|
|
func NewAdaptivePlanner(reg *RoleRegistry) *AdaptivePlanner {
|
|
if reg == nil {
|
|
reg = NewRoleRegistry()
|
|
}
|
|
return &AdaptivePlanner{registry: reg}
|
|
}
|
|
|
|
// Plan 执行动态规划:区分固定工作流自适应微调 vs 非标文档自主规划
|
|
func (p *AdaptivePlanner) Plan(req PlannerRequest) (*PlannerResult, error) {
|
|
if isStandardTask(req.TaskKey) {
|
|
return p.planFixedAdaptive(req)
|
|
}
|
|
return p.planAutonomousAdhoc(req)
|
|
}
|
|
|
|
func isStandardTask(taskKey string) bool {
|
|
return taskKnown(taskKey)
|
|
}
|
|
|
|
// planFixedAdaptive 固定工作流动态微调
|
|
func (p *AdaptivePlanner) planFixedAdaptive(req PlannerRequest) (*PlannerResult, error) {
|
|
title := TaskArtifactTitle(req.TaskKey)
|
|
if req.Title != "" {
|
|
title = req.Title
|
|
}
|
|
|
|
steps := req.CustomSteps
|
|
if len(steps) == 0 {
|
|
if strings.HasPrefix(req.TaskKey, "rd_") {
|
|
steps = rdevWorkflowSteps(req.TaskKey, rdevComplexityTier(req.Fields))
|
|
} else {
|
|
steps = DefaultWorkflowSteps(req.TaskKey)
|
|
}
|
|
}
|
|
|
|
var agents []AgentSpec
|
|
if strings.HasPrefix(req.TaskKey, "rd_") {
|
|
agents = rdevSquadAgents(req.TaskKey, rdevComplexityTier(req.Fields))
|
|
} else {
|
|
agents = DefaultSquadAgents(req.TaskKey)
|
|
}
|
|
|
|
// 材料嗅探感知 (Material Sniffing)
|
|
var matBuilder strings.Builder
|
|
for _, m := range req.Materials {
|
|
matBuilder.Write(m.Content)
|
|
matBuilder.WriteString(" ")
|
|
}
|
|
matText := strings.ToLower(matBuilder.String())
|
|
|
|
// 嗅探 1: 动物源与生物相容性 (植入/牛心包/猪源/GB/T 16886/ISO 10993)
|
|
if strings.Contains(matText, "牛心包") || strings.Contains(matText, "动物源") ||
|
|
strings.Contains(matText, "生物相容性") || strings.Contains(matText, "16886") ||
|
|
strings.Contains(matText, "植入接触血液") {
|
|
|
|
// 检查是否已包含专门步骤
|
|
hasBioStep := false
|
|
for _, s := range steps {
|
|
if strings.Contains(s.ID, "biocompatibility") || strings.Contains(s.Title, "生物相容性") {
|
|
hasBioStep = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasBioStep {
|
|
bioStep := StepSpec{
|
|
ID: "step_01_biocompatibility",
|
|
Role: "biocompatibility_expert",
|
|
Title: "动物源生物相容性与病毒灭活专项评估",
|
|
SubWorkspace: "tasks/01_biocompatibility",
|
|
Mode: "execute",
|
|
Parallel: true,
|
|
AllowDynamicAdjustment: true,
|
|
Accept: []string{
|
|
"识别植入接触途径与接触时间级别",
|
|
"依据GB/T 16886提炼必测生物学指标",
|
|
"出具动物源性病毒灭活与免疫原性风险控制建议",
|
|
},
|
|
}
|
|
|
|
// 派生特设专员
|
|
bioRole, _ := p.registry.SpawnAdHocRole(AdHocRoleRequest{
|
|
RoleKey: "biocompatibility_expert",
|
|
Name: "生物相容性评估专员",
|
|
BaseRole: "engineer",
|
|
DomainTopic: "动物源性材料与生物相容性评价",
|
|
Responsibilities: []string{"生物相容性接触级别界定", "动物源材料病毒灭活路径核对"},
|
|
ForbiddenActions: []string{"严禁代替国家检验机构出具报告"},
|
|
})
|
|
|
|
// 插入到 steps 列表(插入在 organize/search 之后,draft 之前)
|
|
draftIdx := len(steps) - 1
|
|
for idx, s := range steps {
|
|
if s.Role == "writer" || strings.Contains(s.ID, "draft") {
|
|
draftIdx = idx
|
|
break
|
|
}
|
|
}
|
|
newSteps := make([]StepSpec, 0, len(steps)+1)
|
|
newSteps = append(newSteps, steps[:draftIdx]...)
|
|
newSteps = append(newSteps, bioStep)
|
|
newSteps = append(newSteps, steps[draftIdx:]...)
|
|
steps = newSteps
|
|
|
|
// 向小队追加专员
|
|
hasBioAgent := false
|
|
for _, a := range agents {
|
|
if a.Role == "biocompatibility_expert" {
|
|
hasBioAgent = true
|
|
break
|
|
}
|
|
}
|
|
if !hasBioAgent {
|
|
agents = append(agents, bioRole)
|
|
}
|
|
}
|
|
}
|
|
|
|
tmplContent, err := template.GetDocTemplate(req.TaskKey)
|
|
if err != nil {
|
|
tmplContent = fmt.Sprintf("# %s\n\n## 1. 结论\n\n待确认\n\n## 2. 正文\n\n待确认\n", title)
|
|
}
|
|
|
|
now := time.Now()
|
|
wfPlan := WorkflowPlan{
|
|
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
|
|
TaskKey: req.TaskKey,
|
|
Title: title,
|
|
PlanningMode: PlanModeFixedAdaptive,
|
|
Status: "pending",
|
|
CurrentStepIndex: 0,
|
|
Steps: steps,
|
|
}
|
|
|
|
squadCfg := SquadConfig{
|
|
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
|
|
Version: "1.0.0",
|
|
Agents: agents,
|
|
}
|
|
|
|
return &PlannerResult{
|
|
TaskKey: req.TaskKey,
|
|
Title: title,
|
|
PlanningMode: PlanModeFixedAdaptive,
|
|
Squad: squadCfg,
|
|
Workflow: wfPlan,
|
|
TemplateContent: tmplContent,
|
|
}, nil
|
|
}
|
|
|
|
// planAutonomousAdhoc 非工作流文档自主规划
|
|
func (p *AdaptivePlanner) planAutonomousAdhoc(req PlannerRequest) (*PlannerResult, error) {
|
|
title := req.Title
|
|
if title == "" {
|
|
title = req.TaskKey
|
|
}
|
|
|
|
// 匹配或派生领域专家角色
|
|
specialistRole := p.resolveSpecialistRole(req)
|
|
|
|
// 准备小队成员:整理专员、领域专员、文档专员、商务把关负责人
|
|
orgSpec, _ := p.registry.GetRole("organizer")
|
|
writerSpec, _ := p.registry.GetRole("writer")
|
|
leaderSpec, _ := p.registry.GetRole("leader")
|
|
|
|
agents := []AgentSpec{orgSpec, specialistRole, writerSpec, leaderSpec}
|
|
|
|
// 组装标准 4-stage WBS 步骤
|
|
steps := []StepSpec{
|
|
{
|
|
ID: "step_01_organize",
|
|
Role: "organizer",
|
|
Title: "梳理输入背景、提取事实与缺口",
|
|
SubWorkspace: "tasks/01_organize",
|
|
Mode: "execute",
|
|
AllowDynamicAdjustment: true,
|
|
Accept: []string{
|
|
"梳理材料中的客户需求与产品边界条件",
|
|
"提取结构化字段并沉淀至黑板 facts",
|
|
"标记待论证的工程/法规/环境参数缺口",
|
|
},
|
|
},
|
|
{
|
|
ID: "step_02_specialist",
|
|
Role: specialistRole.Role,
|
|
Title: fmt.Sprintf("开展【%s】专业推演与核心技术规程制定", title),
|
|
SubWorkspace: fmt.Sprintf("tasks/02_%s", specialistRole.Role),
|
|
Mode: "execute",
|
|
Needs: []string{"tasks/01_organize/output.md"},
|
|
AllowDynamicAdjustment: true,
|
|
Accept: []string{
|
|
"针对核心技术指标与环境/工程要求给出详尽设计方案",
|
|
"针对缺失信息依据行业先验推断基准假设(Baseline Assumptions)",
|
|
"给出关键参数验证与监控路径",
|
|
},
|
|
},
|
|
{
|
|
ID: "step_03_draft",
|
|
Role: "writer",
|
|
Title: fmt.Sprintf("按标准化架构撰写《%s》正文终稿", title),
|
|
SubWorkspace: "tasks/03_draft",
|
|
Mode: "execute",
|
|
Needs: []string{fmt.Sprintf("tasks/02_%s/output.md", specialistRole.Role)},
|
|
Accept: []string{
|
|
"严格按模板章节成文,杜绝敷衍套话",
|
|
"详实展开技术与管理条款,写满正文",
|
|
"文末集中挂载《基准假设与待确认清单》",
|
|
},
|
|
},
|
|
{
|
|
ID: "step_04_review",
|
|
Role: "leader",
|
|
Title: "全篇一致性终审与商务合规把关",
|
|
SubWorkspace: "tasks/04_review",
|
|
Mode: "execute",
|
|
Needs: []string{"tasks/03_draft/output.md"},
|
|
Accept: []string{
|
|
"校验全文逻辑闭环与标准出处标注",
|
|
"汇总待客户最终拍板的决策项",
|
|
"交付终稿 output.md",
|
|
},
|
|
},
|
|
}
|
|
|
|
// 自动生成结构完备的文档 Markdown 模板
|
|
tmpl := p.generateAdhocTemplate(title)
|
|
|
|
now := time.Now()
|
|
wfPlan := WorkflowPlan{
|
|
WorkflowID: fmt.Sprintf("wf_adhoc_%s_%d", req.TaskKey, now.UnixNano()),
|
|
TaskKey: req.TaskKey,
|
|
Title: title,
|
|
PlanningMode: PlanModeAutonomousAdhoc,
|
|
Status: "pending",
|
|
CurrentStepIndex: 0,
|
|
Steps: steps,
|
|
}
|
|
|
|
squadCfg := SquadConfig{
|
|
SquadName: fmt.Sprintf("%s_dynamic_squad", req.TaskKey),
|
|
Version: "1.0.0",
|
|
Agents: agents,
|
|
}
|
|
|
|
return &PlannerResult{
|
|
TaskKey: req.TaskKey,
|
|
Title: title,
|
|
PlanningMode: PlanModeAutonomousAdhoc,
|
|
Squad: squadCfg,
|
|
Workflow: wfPlan,
|
|
TemplateContent: tmpl,
|
|
}, nil
|
|
}
|
|
|
|
// resolveSpecialistRole 智能匹配或派生领域专家
|
|
func (p *AdaptivePlanner) resolveSpecialistRole(req PlannerRequest) AgentSpec {
|
|
var matText strings.Builder
|
|
for _, m := range req.Materials {
|
|
matText.WriteString(m.Filename)
|
|
matText.WriteString(" ")
|
|
matText.Write(m.Content)
|
|
matText.WriteString(" ")
|
|
}
|
|
query := strings.ToLower(fmt.Sprintf("%s %s %s %s %s %s",
|
|
req.Title, req.TaskKey, req.ProjectName, req.DeviceType, req.IntendedUse, matText.String()))
|
|
|
|
// 特殊垂直领域主动派生
|
|
if strings.Contains(query, "洁净") || strings.Contains(query, "净化") || strings.Contains(query, "车间") ||
|
|
strings.Contains(query, "厂房") || strings.Contains(query, "gmp") || strings.Contains(query, "cleanroom") {
|
|
adHoc, err := p.registry.SpawnAdHocRole(AdHocRoleRequest{
|
|
RoleKey: "cleanroom_expert",
|
|
Name: "洁净工程与环境控制专员",
|
|
BaseRole: "engineer",
|
|
DomainTopic: "十万级/万级洁净厂房建设与GMP环境控制",
|
|
Responsibilities: []string{"净化空调通风系统设计", "车间压差梯度与气流流型规划", "悬浮粒子与沉降菌监测规程"},
|
|
ForbiddenActions: []string{"严禁虚报洁净度等级"},
|
|
})
|
|
if err == nil {
|
|
return adHoc
|
|
}
|
|
}
|
|
|
|
if strings.Contains(query, "伦理") || strings.Contains(query, "知情同意") || strings.Contains(query, "临床试验") {
|
|
adHoc, err := p.registry.SpawnAdHocRole(AdHocRoleRequest{
|
|
RoleKey: "clinical_ethics_specialist",
|
|
Name: "临床试验伦理合规专员",
|
|
BaseRole: "regulatory",
|
|
DomainTopic: "临床试验伦理审查与知情同意(ICF)",
|
|
Responsibilities: []string{"伦理委员会前置审查要点自查", "知情同意书合规条款审核"},
|
|
ForbiddenActions: []string{"严禁代行伦理委员会裁定"},
|
|
})
|
|
if err == nil {
|
|
return adHoc
|
|
}
|
|
}
|
|
|
|
if rec, found := p.registry.RecommendRole(query); found {
|
|
return rec
|
|
}
|
|
|
|
eng, _ := p.registry.GetRole("engineer")
|
|
return eng
|
|
}
|
|
|
|
// generateAdhocTemplate 生成自解释标准化文档模板
|
|
func (p *AdaptivePlanner) generateAdhocTemplate(title string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("# {{project_name}} - %s\n\n", title))
|
|
sb.WriteString("> 编制单位:北京水木东方医疗器械研发小队 \n")
|
|
sb.WriteString("> 生成日期:{{generated_at}} \n")
|
|
sb.WriteString("> 项目编号:{{snapshot_id}} \n\n")
|
|
sb.WriteString("---\n\n")
|
|
|
|
sb.WriteString("## 1. 概述与背景目标\n\n")
|
|
sb.WriteString("### 1.1 编制目的与适用范围\n\n")
|
|
sb.WriteString("{{scope_and_purpose}}\n\n")
|
|
sb.WriteString("### 1.2 产品与项目基本信息\n\n")
|
|
sb.WriteString("- 产品名称:{{product_name}}\n")
|
|
sb.WriteString("- 客户名称:{{customer_name}}\n")
|
|
sb.WriteString("- 设备分类:{{device_type}}\n")
|
|
sb.WriteString("- 预期用途:{{intended_use}}\n\n")
|
|
|
|
sb.WriteString("## 2. 核心技术规范与规程细则\n\n")
|
|
sb.WriteString("### 2.1 关键指标体系与系统要求\n\n")
|
|
sb.WriteString("{{technical_specifications}}\n\n")
|
|
sb.WriteString("### 2.2 实施路径与工序控制要求\n\n")
|
|
sb.WriteString("{{procedure_and_control}}\n\n")
|
|
|
|
sb.WriteString("## 3. 风险防范与法规质量合规\n\n")
|
|
sb.WriteString("### 3.1 关键风险识别与防御策略\n\n")
|
|
sb.WriteString("{{risk_control}}\n\n")
|
|
sb.WriteString("### 3.2 适用标准与规范清单\n\n")
|
|
sb.WriteString("{{compliance_standards}}\n\n")
|
|
|
|
sb.WriteString("## 4. 基准假设与待确认清单\n\n")
|
|
sb.WriteString("### 4.1 系统先行推断的基准假设 (Baseline Assumptions)\n\n")
|
|
sb.WriteString("{{baseline_assumptions}}\n\n")
|
|
sb.WriteString("### 4.2 待客户决策与补充清单 (Pending Checklist)\n\n")
|
|
sb.WriteString("{{pending_checklist}}\n\n")
|
|
|
|
return sb.String()
|
|
}
|