"""U11+U12 测试: Evolution 生命周期集成 + EvolutionConfig 覆盖: - EvolutionConfig 默认值与自定义值 - SkillConfig 的 evolution 字段 - ConfigDrivenAgent 集成 EvolutionMixin - 生命周期钩子触发进化 - 进化失败不影响主任务流程 """ from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch import pytest from agentkit.core.protocol import TaskMessage, TaskResult, TaskStatus # ── Helpers ────────────────────────────────────────────── def _make_task(**overrides) -> TaskMessage: defaults = dict( task_id="test-task-001", agent_name="test_agent", task_type="generate", priority=1, input_data={"query": "hello"}, callback_url=None, created_at=datetime.now(timezone.utc), ) defaults.update(overrides) return TaskMessage(**defaults) def _make_task_result(**overrides) -> TaskResult: defaults = dict( task_id="test-task-001", agent_name="test_agent", status=TaskStatus.COMPLETED, output_data={"result": "ok"}, error_message=None, started_at=datetime.now(timezone.utc), completed_at=datetime.now(timezone.utc), ) defaults.update(overrides) return TaskResult(**defaults) # ── EvolutionConfig 测试 ────────────────────────────────── class TestEvolutionConfig: """U12: EvolutionConfig 数据类测试""" def test_default_values(self): """默认 EvolutionConfig — enabled=False""" from agentkit.skills.base import EvolutionConfig config = EvolutionConfig() assert config.enabled is False assert config.reflect_on_failure is True assert config.auto_apply is False assert config.min_quality_threshold == 0.5 def test_from_dict_all_fields(self): """EvolutionConfig 从字典创建 — 所有字段设置""" from agentkit.skills.base import EvolutionConfig config = EvolutionConfig( enabled=True, reflect_on_failure=False, auto_apply=True, min_quality_threshold=0.8, ) assert config.enabled is True assert config.reflect_on_failure is False assert config.auto_apply is True assert config.min_quality_threshold == 0.8 def test_from_dict_partial(self): """EvolutionConfig 部分字段 — 缺失字段使用默认值""" from agentkit.skills.base import EvolutionConfig config = EvolutionConfig(enabled=True) assert config.enabled is True assert config.reflect_on_failure is True # default assert config.auto_apply is False # default assert config.min_quality_threshold == 0.5 # default # ── SkillConfig evolution 字段测试 ───────────────────────── class TestSkillConfigEvolution: """U12: SkillConfig 的 evolution 字段""" def test_skill_config_without_evolution(self): """SkillConfig 无 evolution — 默认 enabled=False""" from agentkit.skills.base import SkillConfig config = SkillConfig( name="test_agent", agent_type="test", task_mode="llm_generate", prompt={"identity": "test", "instructions": "test"}, ) assert config.evolution.enabled is False def test_skill_config_with_evolution(self): """SkillConfig 有 evolution 配置 — 正确解析""" from agentkit.skills.base import SkillConfig config = SkillConfig( name="test_agent", agent_type="test", task_mode="llm_generate", prompt={"identity": "test", "instructions": "test"}, evolution={"enabled": True, "auto_apply": True, "min_quality_threshold": 0.7}, ) assert config.evolution.enabled is True assert config.evolution.auto_apply is True assert config.evolution.min_quality_threshold == 0.7 def test_skill_config_to_dict_includes_evolution(self): """SkillConfig.to_dict 包含 evolution 字段""" from agentkit.skills.base import SkillConfig config = SkillConfig( name="test_agent", agent_type="test", task_mode="llm_generate", prompt={"identity": "test", "instructions": "test"}, evolution={"enabled": True}, ) d = config.to_dict() assert "evolution" in d assert d["evolution"]["enabled"] is True assert d["evolution"]["reflect_on_failure"] is True assert d["evolution"]["auto_apply"] is False assert d["evolution"]["min_quality_threshold"] == 0.5 def test_skill_config_from_dict_with_evolution(self): """SkillConfig.from_dict 正确解析 evolution""" from agentkit.skills.base import SkillConfig data = { "name": "test_agent", "agent_type": "test", "task_mode": "llm_generate", "prompt": {"identity": "test", "instructions": "test"}, "evolution": {"enabled": True, "reflect_on_failure": False}, } config = SkillConfig.from_dict(data) assert config.evolution.enabled is True assert config.evolution.reflect_on_failure is False # ── ConfigDrivenAgent evolution 集成测试 ────────────────── class TestConfigDrivenAgentEvolution: """U11: ConfigDrivenAgent 集成 EvolutionMixin""" def _make_agent_config(self, evolution=None): from agentkit.core.config_driven import AgentConfig config = AgentConfig( name="test_agent", agent_type="test", task_mode="llm_generate", prompt={"identity": "test", "instructions": "test"}, ) if evolution is not None: config.evolution = evolution return config def _make_skill_config(self, evolution=None): from agentkit.skills.base import SkillConfig return SkillConfig( name="test_agent", agent_type="test", task_mode="llm_generate", prompt={"identity": "test", "instructions": "test"}, evolution=evolution, ) def test_agent_without_evolution_config(self): """Agent 无 evolution 配置 — _evolution_enabled=False""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config() agent = ConfigDrivenAgent(config=config) assert agent._evolution_enabled is False def test_agent_with_evolution_enabled(self): """Agent 有 evolution 且 enabled=True — _evolution_enabled=True""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config(evolution={"enabled": True}) agent = ConfigDrivenAgent(config=config) assert agent._evolution_enabled is True def test_agent_with_evolution_disabled(self): """Agent 有 evolution 但 enabled=False — _evolution_enabled=False""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config(evolution={"enabled": False}) agent = ConfigDrivenAgent(config=config) assert agent._evolution_enabled is False async def test_on_task_complete_evolution_disabled(self): """on_task_complete 进化禁用 — 不调用 evolve_after_task""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config() agent = ConfigDrivenAgent(config=config) task = _make_task() output = {"result": "ok"} # Should not raise and should not call evolve_after_task await agent.on_task_complete(task, output) async def test_on_task_complete_evolution_enabled(self): """on_task_complete 进化启用 — 调用 evolve_after_task""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config(evolution={"enabled": True}) agent = ConfigDrivenAgent(config=config) task = _make_task() output = {"result": "ok"} with patch.object(agent, "evolve_after_task", new_callable=AsyncMock) as mock_evolve: await agent.on_task_complete(task, output) mock_evolve.assert_called_once() # Verify the TaskResult passed to evolve_after_task call_args = mock_evolve.call_args result_arg = call_args[0][1] # second positional arg is TaskResult assert result_arg.status == TaskStatus.COMPLETED assert result_arg.output_data == output async def test_on_task_failed_evolution_enabled(self): """on_task_failed 进化启用 — 调用 evolve_after_task""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config(evolution={"enabled": True}) agent = ConfigDrivenAgent(config=config) task = _make_task() error = ValueError("test error") with patch.object(agent, "evolve_after_task", new_callable=AsyncMock) as mock_evolve: await agent.on_task_failed(task, error) mock_evolve.assert_called_once() # Verify the TaskResult passed to evolve_after_task call_args = mock_evolve.call_args result_arg = call_args[0][1] # second positional arg is TaskResult assert result_arg.status == TaskStatus.FAILED assert result_arg.error_message == "test error" async def test_evolution_failure_does_not_break_task(self): """进化失败不影响任务完成""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config(evolution={"enabled": True}) agent = ConfigDrivenAgent(config=config) task = _make_task() output = {"result": "ok"} with patch.object(agent, "evolve_after_task", new_callable=AsyncMock, side_effect=RuntimeError("evolution crashed")): # Should NOT raise — evolution failure is caught await agent.on_task_complete(task, output) async def test_evolution_failure_on_task_failed_does_not_break(self): """进化失败不影响 on_task_failed""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_agent_config(evolution={"enabled": True}) agent = ConfigDrivenAgent(config=config) task = _make_task() error = ValueError("task error") with patch.object(agent, "evolve_after_task", new_callable=AsyncMock, side_effect=RuntimeError("evolution crashed")): # Should NOT raise await agent.on_task_failed(task, error) def test_skill_config_evolution_propagated(self): """SkillConfig 的 evolution 配置传递到 ConfigDrivenAgent""" from agentkit.core.config_driven import ConfigDrivenAgent config = self._make_skill_config(evolution={"enabled": True}) agent = ConfigDrivenAgent(config=config) assert agent._evolution_enabled is True # ── EvolutionMixin 集成测试 ─────────────────────────────── class TestEvolutionMixinIntegration: """U11: EvolutionMixin 方法集成到 ConfigDrivenAgent""" def _make_agent_with_evolution(self): from agentkit.core.config_driven import AgentConfig, ConfigDrivenAgent config = AgentConfig( name="test_agent", agent_type="test", task_mode="llm_generate", prompt={"identity": "test", "instructions": "test"}, ) config.evolution = {"enabled": True} return ConfigDrivenAgent(config=config) def test_agent_has_get_evolution_history(self): """Agent 继承 get_evolution_history 方法""" from agentkit.core.config_driven import ConfigDrivenAgent from agentkit.evolution.lifecycle import EvolutionMixin agent = self._make_agent_with_evolution() assert hasattr(agent, "get_evolution_history") assert callable(agent.get_evolution_history) def test_agent_has_set_current_module(self): """Agent 继承 set_current_module 方法""" from agentkit.core.config_driven import ConfigDrivenAgent from agentkit.evolution.lifecycle import EvolutionMixin agent = self._make_agent_with_evolution() assert hasattr(agent, "set_current_module") assert callable(agent.set_current_module) def test_get_evolution_history_empty_initially(self): """get_evolution_history 初始返回空列表""" agent = self._make_agent_with_evolution() history = agent.get_evolution_history() assert history == [] def test_set_current_module_works(self): """set_current_module 正常工作""" from agentkit.evolution.prompt_optimizer import Module, Signature agent = self._make_agent_with_evolution() signature = Signature( input_fields={"query": "user query"}, output_fields={"result": "result"}, instruction="test instructions", ) module = Module(name="test_module", signature=signature) agent.set_current_module(module) assert agent._current_module is not None assert agent._current_module.name == "test_module" def test_mro_correct(self): """MRO 正确: ConfigDrivenAgent → BaseAgent → EvolutionMixin""" from agentkit.core.config_driven import ConfigDrivenAgent from agentkit.core.base import BaseAgent from agentkit.evolution.lifecycle import EvolutionMixin mro = ConfigDrivenAgent.__mro__ # BaseAgent should come before EvolutionMixin in MRO base_idx = mro.index(BaseAgent) mixin_idx = mro.index(EvolutionMixin) assert base_idx < mixin_idx, f"BaseAgent (idx={base_idx}) should come before EvolutionMixin (idx={mixin_idx}) in MRO"