feat(experts): add PhaseType enum and debate_config to PlanPhase
U1: Data model foundation for structured debate collaboration. - Add PhaseType enum (EXECUTION | DEBATE) - Add phase_type and debate_config fields to PlanPhase - Update to_dict/from_dict for serialization with backward compatibility - Add tests for PhaseType, debate phase creation, serialization, and mixed EXECUTION+DEBATE topological sort
This commit is contained in:
parent
4ea7801bcf
commit
e539122314
|
|
@ -55,6 +55,17 @@ class PhaseStatus(str, enum.Enum):
|
|||
FAILED = "failed"
|
||||
|
||||
|
||||
class PhaseType(str, enum.Enum):
|
||||
"""阶段类型
|
||||
|
||||
EXECUTION: 标准执行阶段,专家独立完成分配的任务
|
||||
DEBATE: 辩论阶段,Lead 主导指定专家就分歧点交锋,Lead 裁决
|
||||
"""
|
||||
|
||||
EXECUTION = "execution"
|
||||
DEBATE = "debate"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubTask:
|
||||
"""Lead Expert 分解出的子任务(hub-and-spoke 模式,向后兼容)
|
||||
|
|
@ -110,6 +121,12 @@ class PlanPhase:
|
|||
depends_on: 前置阶段 ID 列表(空列表表示无依赖)
|
||||
status: 当前状态
|
||||
result: 阶段输出结果
|
||||
phase_type: 阶段类型(EXECUTION 或 DEBATE)
|
||||
debate_config: 辩论阶段配置(仅 DEBATE 类型使用):
|
||||
- topic: 辩论主题
|
||||
- participants: 参与专家名称列表
|
||||
- max_rounds: 最大辩论轮次(默认 2,硬上限 4)
|
||||
- skip: 是否跳过辩论(逃生舱)
|
||||
"""
|
||||
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
|
|
@ -119,6 +136,8 @@ class PlanPhase:
|
|||
depends_on: list[str] = field(default_factory=list)
|
||||
status: PhaseStatus = PhaseStatus.PENDING
|
||||
result: dict[str, Any] | None = None
|
||||
phase_type: PhaseType = PhaseType.EXECUTION
|
||||
debate_config: dict[str, Any] | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""序列化为字典"""
|
||||
|
|
@ -137,6 +156,8 @@ class PlanPhase:
|
|||
"depends_on": list(self.depends_on),
|
||||
"status": self.status.value,
|
||||
"result": result_str,
|
||||
"phase_type": self.phase_type.value,
|
||||
"debate_config": self.debate_config,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
@ -150,6 +171,8 @@ class PlanPhase:
|
|||
depends_on=list(data.get("depends_on", [])),
|
||||
status=PhaseStatus(data.get("status", PhaseStatus.PENDING.value)),
|
||||
result=data.get("result"),
|
||||
phase_type=PhaseType(data.get("phase_type", PhaseType.EXECUTION.value)),
|
||||
debate_config=data.get("debate_config"),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import pytest
|
|||
from agentkit.experts.plan import (
|
||||
MergeStrategy,
|
||||
PhaseStatus,
|
||||
PhaseType,
|
||||
PlanPhase,
|
||||
PlanStatus,
|
||||
SubTask,
|
||||
|
|
@ -356,6 +357,19 @@ class TestPhaseStatus:
|
|||
assert PhaseStatus.FAILED == "failed"
|
||||
|
||||
|
||||
class TestPhaseType:
|
||||
"""PhaseType 枚举测试"""
|
||||
|
||||
def test_types_exist(self):
|
||||
"""阶段类型都存在"""
|
||||
assert PhaseType.EXECUTION == "execution"
|
||||
assert PhaseType.DEBATE == "debate"
|
||||
|
||||
def test_only_two_types(self):
|
||||
"""只有 EXECUTION 和 DEBATE 两种类型"""
|
||||
assert len(list(PhaseType)) == 2
|
||||
|
||||
|
||||
class TestPlanPhase:
|
||||
"""PlanPhase 数据模型测试"""
|
||||
|
||||
|
|
@ -438,6 +452,79 @@ class TestPlanPhase:
|
|||
# result is serialized to string to match frontend ITeamPlanPhase.result type
|
||||
assert d["result"] == "phase output data"
|
||||
|
||||
def test_default_phase_type_is_execution(self):
|
||||
"""默认 phase_type 为 EXECUTION"""
|
||||
phase = PlanPhase(name="测试阶段")
|
||||
assert phase.phase_type == PhaseType.EXECUTION
|
||||
assert phase.debate_config is None
|
||||
|
||||
def test_debate_phase_creation(self):
|
||||
"""创建 DEBATE 类型阶段"""
|
||||
debate_config = {
|
||||
"topic": "前端框架选型:React vs Vue",
|
||||
"participants": ["frontend_engineer", "tech_lead"],
|
||||
"max_rounds": 2,
|
||||
}
|
||||
phase = PlanPhase(
|
||||
name="框架选型辩论",
|
||||
assigned_expert="tech_lead",
|
||||
task_description="就前端框架选型进行辩论",
|
||||
phase_type=PhaseType.DEBATE,
|
||||
debate_config=debate_config,
|
||||
)
|
||||
assert phase.phase_type == PhaseType.DEBATE
|
||||
assert phase.debate_config == debate_config
|
||||
assert phase.debate_config["topic"] == "前端框架选型:React vs Vue"
|
||||
assert phase.debate_config["participants"] == ["frontend_engineer", "tech_lead"]
|
||||
assert phase.debate_config["max_rounds"] == 2
|
||||
|
||||
def test_debate_phase_serialization_roundtrip(self):
|
||||
"""DEBATE 阶段序列化往返"""
|
||||
debate_config = {
|
||||
"topic": "微服务 vs 单体",
|
||||
"participants": ["backend_engineer", "tech_lead"],
|
||||
"max_rounds": 3,
|
||||
}
|
||||
phase = PlanPhase(
|
||||
id="debate_1",
|
||||
name="架构辩论",
|
||||
assigned_expert="tech_lead",
|
||||
task_description="架构选型辩论",
|
||||
phase_type=PhaseType.DEBATE,
|
||||
debate_config=debate_config,
|
||||
)
|
||||
d = phase.to_dict()
|
||||
assert d["phase_type"] == "debate"
|
||||
assert d["debate_config"] == debate_config
|
||||
|
||||
restored = PlanPhase.from_dict(d)
|
||||
assert restored.phase_type == PhaseType.DEBATE
|
||||
assert restored.debate_config == debate_config
|
||||
assert restored.debate_config["topic"] == "微服务 vs 单体"
|
||||
|
||||
def test_backward_compatibility_no_phase_type(self):
|
||||
"""向后兼容:不带 phase_type 的旧 dict 默认为 EXECUTION"""
|
||||
old_dict = {
|
||||
"id": "old_phase",
|
||||
"name": "旧阶段",
|
||||
"assigned_expert": "dev",
|
||||
"task_description": "旧任务",
|
||||
"depends_on": [],
|
||||
"status": "pending",
|
||||
"result": None,
|
||||
}
|
||||
phase = PlanPhase.from_dict(old_dict)
|
||||
assert phase.phase_type == PhaseType.EXECUTION
|
||||
assert phase.debate_config is None
|
||||
|
||||
def test_debate_config_none_for_execution(self):
|
||||
"""EXECUTION 阶段的 debate_config 为 None"""
|
||||
phase = PlanPhase(name="执行阶段", phase_type=PhaseType.EXECUTION)
|
||||
assert phase.debate_config is None
|
||||
d = phase.to_dict()
|
||||
assert d["phase_type"] == "execution"
|
||||
assert d["debate_config"] is None
|
||||
|
||||
|
||||
class TestTeamPlanPhases:
|
||||
"""TeamPlan 流水线模式(phases)测试"""
|
||||
|
|
@ -633,6 +720,52 @@ class TestTopologicalSort:
|
|||
with pytest.raises(ValueError, match="non-existent phase"):
|
||||
plan.topological_sort()
|
||||
|
||||
def test_mixed_execution_and_debate_phases(self):
|
||||
"""混合 EXECUTION + DEBATE 阶段的拓扑排序
|
||||
|
||||
结构:
|
||||
Layer 0: [规划] (EXECUTION)
|
||||
Layer 1: [前端, 后端] (EXECUTION, 依赖规划)
|
||||
Layer 2: [架构辩论] (DEBATE, 依赖前端+后端)
|
||||
Layer 3: [QA] (EXECUTION, 依赖架构辩论)
|
||||
"""
|
||||
plan = TeamPlan(
|
||||
task="混合模式任务",
|
||||
phases=[
|
||||
PlanPhase(id="p1", name="规划", assigned_expert="tech_lead", depends_on=[]),
|
||||
PlanPhase(
|
||||
id="p2", name="前端", assigned_expert="frontend", depends_on=["p1"]
|
||||
),
|
||||
PlanPhase(
|
||||
id="p3", name="后端", assigned_expert="backend", depends_on=["p1"]
|
||||
),
|
||||
PlanPhase(
|
||||
id="d1",
|
||||
name="架构辩论",
|
||||
assigned_expert="tech_lead",
|
||||
depends_on=["p2", "p3"],
|
||||
phase_type=PhaseType.DEBATE,
|
||||
debate_config={
|
||||
"topic": "前后端接口设计",
|
||||
"participants": ["frontend", "backend"],
|
||||
"max_rounds": 2,
|
||||
},
|
||||
),
|
||||
PlanPhase(id="p4", name="QA", assigned_expert="qa", depends_on=["d1"]),
|
||||
],
|
||||
)
|
||||
layers = plan.topological_sort()
|
||||
assert len(layers) == 4
|
||||
assert [ph.id for ph in layers[0]] == ["p1"]
|
||||
assert set(ph.id for ph in layers[1]) == {"p2", "p3"}
|
||||
assert [ph.id for ph in layers[2]] == ["d1"]
|
||||
assert [ph.id for ph in layers[3]] == ["p4"]
|
||||
# Verify the debate phase is correctly typed
|
||||
debate_phase = plan.get_phase("d1")
|
||||
assert debate_phase is not None
|
||||
assert debate_phase.phase_type == PhaseType.DEBATE
|
||||
assert debate_phase.debate_config is not None
|
||||
|
||||
|
||||
class TestGetReadyPhases:
|
||||
"""get_ready_phases 就绪阶段测试"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue