Files
Agent-System/internal/squad/planner.go
T
weichenandCursor dba8139d56 feat(workflow): namespace task keys without breaking old instances
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>
2026-09-19 10:50:25 +00:00

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()
}