From 486fbf1977351fe7579fd8d6a6cd4199570847f2 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Mon, 7 Sep 2026 22:19:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(squad):=20=E5=A2=9E=E5=BC=BA=E9=A2=86?= =?UTF-8?q?=E5=9F=9F=E6=A0=B8=E5=BF=83=E6=A8=A1=E5=9E=8B=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=9D=A5=E6=BA=90=E3=80=81=E8=87=AA=E9=80=82?= =?UTF-8?q?=E5=BA=94=E6=AD=A5=E9=AA=A4=E4=B8=8E=E9=BB=91=E6=9D=BF=E5=9F=BA?= =?UTF-8?q?=E5=87=86=E5=81=87=E8=AE=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- internal/squad/models.go | 102 +++++++++++++++----- internal/squad/models_test.go | 177 ++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 23 deletions(-) create mode 100644 internal/squad/models_test.go diff --git a/internal/squad/models.go b/internal/squad/models.go index 883cb579..aadea7b3 100644 --- a/internal/squad/models.go +++ b/internal/squad/models.go @@ -23,16 +23,27 @@ type WorkspaceMetadata struct { Metadata map[string]string `json:"metadata,omitempty"` } +// RoleSource 标识角色来源 +type RoleSource string + +const ( + RoleSourceStandard RoleSource = "standard" // 预设标准化角色 (organizer, searcher, regulatory...) + RoleSourceCustom RoleSource = "custom" // 配置文件声明的业务扩展角色 + RoleSourceAdHoc RoleSource = "ad_hoc" // 规划器现场生成的特设专员 +) + // AgentSpec 声明单个专员角色在小队中的设定 type AgentSpec struct { - Role string `json:"role"` - Name string `json:"name"` - Model string `json:"model"` - Tools []string `json:"tools"` - BashPermission string `json:"bash_permission"` // allow, deny - Responsibilities []string `json:"responsibilities"` - ForbiddenActions []string `json:"forbidden_actions"` - InstructionsFile string `json:"instructions_file,omitempty"` + Role string `json:"role"` + Name string `json:"name"` + Source RoleSource `json:"source,omitempty"` + BaseRole string `json:"base_role,omitempty"` + Model string `json:"model"` + Tools []string `json:"tools"` + BashPermission string `json:"bash_permission"` // allow, deny + Responsibilities []string `json:"responsibilities"` + ForbiddenActions []string `json:"forbidden_actions"` + InstructionsFile string `json:"instructions_file,omitempty"` } // SquadConfig 激活的智能体小队编制 @@ -42,27 +53,40 @@ type SquadConfig struct { Agents []AgentSpec `json:"agents"` } +// PlanningMode 规划模式 +type PlanningMode string + +const ( + PlanModeFixedAdaptive PlanningMode = "fixed_adaptive" // 固定文档:基于基线模板,允许动态微调 + PlanModeAutonomousAdhoc PlanningMode = "autonomous_adhoc" // 非工作流:全自动动态规划 +) + // StepSpec 单个任务步骤规格定义 type StepSpec struct { - ID string `json:"id"` - Role string `json:"role"` - Title string `json:"title"` - SubWorkspace string `json:"sub_workspace"` // 相对子路径,如 tasks/01_organize - Mode string `json:"mode"` // execute, plan, review_plan, replan - Parallel bool `json:"parallel"` - Needs []string `json:"needs,omitempty"` - Accept []string `json:"accept,omitempty"` - Status string `json:"status"` // pending, running, completed, failed, skipped - DurationMs int64 `json:"duration_ms,omitempty"` - Error string `json:"error,omitempty"` + ID string `json:"id"` + Role string `json:"role"` + Title string `json:"title"` + SubWorkspace string `json:"sub_workspace"` // 相对子路径,如 tasks/01_organize + Mode string `json:"mode"` // execute, plan, review_plan, replan + Parallel bool `json:"parallel"` + Needs []string `json:"needs,omitempty"` + Accept []string `json:"accept,omitempty"` + Status string `json:"status"` // pending, running, completed, failed, skipped + DurationMs int64 `json:"duration_ms,omitempty"` + Error string `json:"error,omitempty"` + AllowDynamicAdjustment bool `json:"allow_dynamic_adjustment,omitempty"` + BaselineAssumptions []string `json:"baseline_assumptions,omitempty"` } // WorkflowPlan 动态任务编排清单 type WorkflowPlan struct { - WorkflowID string `json:"workflow_id"` - Status string `json:"status"` // pending, in_progress, completed, failed - CurrentStepIndex int `json:"current_step_index"` - Steps []StepSpec `json:"steps"` + WorkflowID string `json:"workflow_id"` + TaskKey string `json:"task_key,omitempty"` + Title string `json:"title,omitempty"` + PlanningMode PlanningMode `json:"planning_mode,omitempty"` + Status string `json:"status"` // pending, in_progress, completed, failed + CurrentStepIndex int `json:"current_step_index"` + Steps []StepSpec `json:"steps"` } // ContextItem 上下文项目元数据 @@ -126,15 +150,47 @@ type ImplicationItem struct { Statement string `json:"statement"` } +// AssumptionItem 黑板基准假设项(医疗器械工程/法规先验推断) +type AssumptionItem struct { + ID string `json:"id"` + Topic string `json:"topic"` + Statement string `json:"statement"` + Rationale string `json:"rationale"` // 行业先验推导依据 + Source string `json:"source"` // 默认 "inferred" +} + // BlackboardState 结构化共享黑板 type BlackboardState struct { Facts []FactItem `json:"facts"` Gaps []GapItem `json:"gaps"` Implications []ImplicationItem `json:"implications"` + Assumptions []AssumptionItem `json:"assumptions,omitempty"` Fields map[string]string `json:"fields"` UpdatedAt time.Time `json:"updated_at"` } +// StepAction 单步评估后的调整决策 +type StepAction string + +const ( + StepActionContinue StepAction = "CONTINUE" // 无异常,正常按既定计划继续 + StepActionAddStep StepAction = "ADD_STEP" // 发现关键事实或缺口,动态追加补充步骤 + StepActionUpdateStep StepAction = "UPDATE_STEP" // 调整后续步骤的验收标准或指派角色 + StepActionSkipStep StepAction = "SKIP_STEP" // 前序步骤已充分覆盖,跳过冗余步骤 +) + +// EvaluationResult 步骤评估报告 +type EvaluationResult struct { + StepID string `json:"step_id"` + Action StepAction `json:"action"` + Reason string `json:"reason"` + NewAssumptions []AssumptionItem `json:"new_assumptions,omitempty"` // 新增的基准假设 + ResolvedGaps []string `json:"resolved_gaps,omitempty"` + DynamicStepsToAdd []StepSpec `json:"dynamic_steps_to_add,omitempty"` + DynamicStepsToAlter []StepSpec `json:"dynamic_steps_to_alter,omitempty"` + StepsToSkip []string `json:"steps_to_skip,omitempty"` +} + // QualityReport 自动化质量门禁报告 type QualityReport struct { Passed bool `json:"passed"` diff --git a/internal/squad/models_test.go b/internal/squad/models_test.go new file mode 100644 index 00000000..45d95c96 --- /dev/null +++ b/internal/squad/models_test.go @@ -0,0 +1,177 @@ +package squad + +import ( + "encoding/json" + "testing" + "time" +) + +func TestAgentSpecEnhancements(t *testing.T) { + spec := AgentSpec{ + Role: "biocompatibility_expert", + Name: "生物相容性评估专员", + Source: RoleSourceAdHoc, + BaseRole: "engineer", + Model: "MiniMax-M3", + Tools: []string{"read", "write", "edit"}, + BashPermission: "deny", + Responsibilities: []string{"生物相容性风险评估"}, + ForbiddenActions: []string{"严禁越权发布正式检验报告"}, + } + + data, err := json.Marshal(spec) + if err != nil { + t.Fatalf("marshal AgentSpec failed: %v", err) + } + + var parsed AgentSpec + if err := json.Unmarshal(data, &parsed); err != nil { + t.Fatalf("unmarshal AgentSpec failed: %v", err) + } + + if parsed.Source != RoleSourceAdHoc { + t.Errorf("expected source %s, got %s", RoleSourceAdHoc, parsed.Source) + } + if parsed.BaseRole != "engineer" { + t.Errorf("expected base_role engineer, got %s", parsed.BaseRole) + } +} + +func TestStepSpecDynamicAdjustment(t *testing.T) { + step := StepSpec{ + ID: "step_01_search", + Role: "searcher", + Title: "文献与标准检索", + SubWorkspace: "tasks/01_search", + Mode: "execute", + AllowDynamicAdjustment: true, + } + + data, err := json.Marshal(step) + if err != nil { + t.Fatalf("marshal StepSpec failed: %v", err) + } + + var parsed StepSpec + if err := json.Unmarshal(data, &parsed); err != nil { + t.Fatalf("unmarshal StepSpec failed: %v", err) + } + + if !parsed.AllowDynamicAdjustment { + t.Errorf("expected AllowDynamicAdjustment to be true") + } +} + +func TestWorkflowPlanPlanningMode(t *testing.T) { + wf := WorkflowPlan{ + WorkflowID: "wf_test_123", + TaskKey: "cleanroom_design", + Title: "洁净厂房设计方案", + PlanningMode: PlanModeAutonomousAdhoc, + Status: "pending", + Steps: []StepSpec{ + { + ID: "step_01", + Role: "organizer", + }, + }, + } + + data, err := json.Marshal(wf) + if err != nil { + t.Fatalf("marshal WorkflowPlan failed: %v", err) + } + + var parsed WorkflowPlan + if err := json.Unmarshal(data, &parsed); err != nil { + t.Fatalf("unmarshal WorkflowPlan failed: %v", err) + } + + if parsed.PlanningMode != PlanModeAutonomousAdhoc { + t.Errorf("expected planning mode %s, got %s", PlanModeAutonomousAdhoc, parsed.PlanningMode) + } + if parsed.TaskKey != "cleanroom_design" { + t.Errorf("expected task_key cleanroom_design, got %s", parsed.TaskKey) + } +} + +func TestBlackboardAssumptions(t *testing.T) { + bb := BlackboardState{ + Facts: []FactItem{ + {ID: "f1", Claim: "产品为无菌二类导管", Verified: true}, + }, + Assumptions: []AssumptionItem{ + { + ID: "a1", + Topic: "sterilization_method", + Statement: "基准假设灭菌方式采用环氧乙烷(EO)灭菌,解析期设定为14天", + Rationale: "客户未明确灭菌方式,依据二类无源无菌高分子导管临床通行工程惯例设定", + Source: "inferred", + }, + }, + UpdatedAt: time.Now(), + } + + data, err := json.Marshal(bb) + if err != nil { + t.Fatalf("marshal BlackboardState failed: %v", err) + } + + var parsed BlackboardState + if err := json.Unmarshal(data, &parsed); err != nil { + t.Fatalf("unmarshal BlackboardState failed: %v", err) + } + + if len(parsed.Assumptions) != 1 { + t.Fatalf("expected 1 assumption, got %d", len(parsed.Assumptions)) + } + if parsed.Assumptions[0].Source != "inferred" { + t.Errorf("expected assumption source 'inferred', got '%s'", parsed.Assumptions[0].Source) + } +} + +func TestEvaluationResult(t *testing.T) { + res := EvaluationResult{ + StepID: "step_01_search", + Action: StepActionAddStep, + Reason: "检测到产品使用动物源牛心包,需动态追加生物相容性专题调研", + NewAssumptions: []AssumptionItem{ + { + ID: "a2", + Topic: "animal_origin", + Statement: "需执行GB/T 16886及ISO 22442动物源医疗器械病毒灭活评价", + Source: "inferred", + }, + }, + DynamicStepsToAdd: []StepSpec{ + { + ID: "step_01_biocompatibility", + Role: "biocompatibility_expert", + Title: "动物源生物相容性与病毒灭活专项评估", + SubWorkspace: "tasks/01_biocompatibility", + Mode: "execute", + }, + }, + StepsToSkip: []string{"step_02_market"}, + } + + data, err := json.Marshal(res) + if err != nil { + t.Fatalf("marshal EvaluationResult failed: %v", err) + } + + var parsed EvaluationResult + if err := json.Unmarshal(data, &parsed); err != nil { + t.Fatalf("unmarshal EvaluationResult failed: %v", err) + } + + if parsed.Action != StepActionAddStep { + t.Errorf("expected action %s, got %s", StepActionAddStep, parsed.Action) + } + if len(parsed.DynamicStepsToAdd) != 1 { + t.Errorf("expected 1 dynamic step to add, got %d", len(parsed.DynamicStepsToAdd)) + } + if len(parsed.StepsToSkip) != 1 || parsed.StepsToSkip[0] != "step_02_market" { + t.Errorf("expected steps to skip ['step_02_market'], got %v", parsed.StepsToSkip) + } +}