fischer-agentkit/tests/unit/test_evolution_integration.py

369 lines
14 KiB
Python

"""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"