feat(evolution): Phase A - lifecycle hooks + EvolutionConfig
U11: EvolutionMixin integrated into ConfigDrivenAgent lifecycle - on_task_complete triggers evolve_after_task - on_task_failed records failure patterns - Evolution errors never break main task flow U12: EvolutionConfig added to SkillConfig - enabled, reflect_on_failure, auto_apply, min_quality_threshold - Backward compatible: defaults to enabled=False 21 new tests passing, no regression.
This commit is contained in:
parent
2844eeb548
commit
acec8ff743
|
|
@ -16,6 +16,8 @@ import yaml
|
||||||
from agentkit.core.base import BaseAgent
|
from agentkit.core.base import BaseAgent
|
||||||
from agentkit.core.exceptions import ConfigValidationError
|
from agentkit.core.exceptions import ConfigValidationError
|
||||||
from agentkit.core.protocol import AgentCapability, TaskMessage
|
from agentkit.core.protocol import AgentCapability, TaskMessage
|
||||||
|
from agentkit.evolution.lifecycle import EvolutionMixin
|
||||||
|
from agentkit.evolution.reflector import Reflector
|
||||||
from agentkit.prompts.section import PromptSection
|
from agentkit.prompts.section import PromptSection
|
||||||
from agentkit.prompts.template import PromptTemplate
|
from agentkit.prompts.template import PromptTemplate
|
||||||
from agentkit.tools.base import Tool
|
from agentkit.tools.base import Tool
|
||||||
|
|
@ -153,7 +155,7 @@ class AgentConfig:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class ConfigDrivenAgent(BaseAgent):
|
class ConfigDrivenAgent(BaseAgent, EvolutionMixin):
|
||||||
"""配置驱动的 Agent
|
"""配置驱动的 Agent
|
||||||
|
|
||||||
从 YAML/Dict 配置自动组装,支持三种任务模式:
|
从 YAML/Dict 配置自动组装,支持三种任务模式:
|
||||||
|
|
@ -247,6 +249,28 @@ class ConfigDrivenAgent(BaseAgent):
|
||||||
from agentkit.quality.gate import QualityGate
|
from agentkit.quality.gate import QualityGate
|
||||||
self._quality_gate = QualityGate()
|
self._quality_gate = QualityGate()
|
||||||
|
|
||||||
|
# v2: Initialize Evolution if configured
|
||||||
|
evolution_config = getattr(config, 'evolution', None)
|
||||||
|
if evolution_config is not None:
|
||||||
|
# Support both dict and EvolutionConfig
|
||||||
|
if isinstance(evolution_config, dict):
|
||||||
|
is_enabled = evolution_config.get("enabled", False)
|
||||||
|
else:
|
||||||
|
is_enabled = getattr(evolution_config, 'enabled', False)
|
||||||
|
else:
|
||||||
|
is_enabled = False
|
||||||
|
|
||||||
|
if is_enabled:
|
||||||
|
reflector = Reflector()
|
||||||
|
EvolutionMixin.__init__(
|
||||||
|
self,
|
||||||
|
reflector=reflector,
|
||||||
|
)
|
||||||
|
self._evolution_enabled = True
|
||||||
|
else:
|
||||||
|
EvolutionMixin.__init__(self) # Initialize with no components
|
||||||
|
self._evolution_enabled = False
|
||||||
|
|
||||||
# v2: Initialize Output Standardizer
|
# v2: Initialize Output Standardizer
|
||||||
from agentkit.quality.output import OutputStandardizer
|
from agentkit.quality.output import OutputStandardizer
|
||||||
self._output_standardizer = OutputStandardizer()
|
self._output_standardizer = OutputStandardizer()
|
||||||
|
|
@ -278,6 +302,44 @@ class ConfigDrivenAgent(BaseAgent):
|
||||||
def prompt_template(self) -> PromptTemplate | None:
|
def prompt_template(self) -> PromptTemplate | None:
|
||||||
return self._prompt_template
|
return self._prompt_template
|
||||||
|
|
||||||
|
async def on_task_complete(self, task: TaskMessage, output: dict) -> None:
|
||||||
|
"""Task complete hook - trigger evolution if enabled"""
|
||||||
|
if self._evolution_enabled:
|
||||||
|
try:
|
||||||
|
from agentkit.core.protocol import TaskResult, TaskStatus
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
result = TaskResult(
|
||||||
|
task_id=task.task_id,
|
||||||
|
agent_name=self.name,
|
||||||
|
status=TaskStatus.COMPLETED,
|
||||||
|
output_data=output,
|
||||||
|
error_message=None,
|
||||||
|
started_at=datetime.now(timezone.utc),
|
||||||
|
completed_at=datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
await self.evolve_after_task(task, result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Evolution after task failed: {e}")
|
||||||
|
|
||||||
|
async def on_task_failed(self, task: TaskMessage, error: Exception) -> None:
|
||||||
|
"""Task failed hook - record failure for evolution"""
|
||||||
|
if self._evolution_enabled:
|
||||||
|
try:
|
||||||
|
from agentkit.core.protocol import TaskResult, TaskStatus
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
result = TaskResult(
|
||||||
|
task_id=task.task_id,
|
||||||
|
agent_name=self.name,
|
||||||
|
status=TaskStatus.FAILED,
|
||||||
|
output_data=None,
|
||||||
|
error_message=str(error),
|
||||||
|
started_at=datetime.now(timezone.utc),
|
||||||
|
completed_at=datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
await self.evolve_after_task(task, result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Evolution after task failure failed: {e}")
|
||||||
|
|
||||||
def _bind_tools(self) -> None:
|
def _bind_tools(self) -> None:
|
||||||
"""根据配置绑定工具"""
|
"""根据配置绑定工具"""
|
||||||
for tool_name in self._config.tools:
|
for tool_name in self._config.tools:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,16 @@ from agentkit.tools.base import Tool
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EvolutionConfig:
|
||||||
|
"""Evolution configuration"""
|
||||||
|
|
||||||
|
enabled: bool = False
|
||||||
|
reflect_on_failure: bool = True # Whether to reflect on failed tasks
|
||||||
|
auto_apply: bool = False # Whether to auto-apply optimizations (without AB test)
|
||||||
|
min_quality_threshold: float = 0.5 # Minimum quality score to trigger optimization
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IntentConfig:
|
class IntentConfig:
|
||||||
"""意图配置"""
|
"""意图配置"""
|
||||||
|
|
@ -59,6 +69,7 @@ class SkillConfig(AgentConfig):
|
||||||
quality_gate: dict[str, Any] | None = None,
|
quality_gate: dict[str, Any] | None = None,
|
||||||
execution_mode: str = "react",
|
execution_mode: str = "react",
|
||||||
max_steps: int = 5,
|
max_steps: int = 5,
|
||||||
|
evolution: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
|
|
@ -80,6 +91,7 @@ class SkillConfig(AgentConfig):
|
||||||
self.quality_gate = QualityGateConfig(**(quality_gate or {}))
|
self.quality_gate = QualityGateConfig(**(quality_gate or {}))
|
||||||
self.execution_mode = execution_mode
|
self.execution_mode = execution_mode
|
||||||
self.max_steps = max_steps
|
self.max_steps = max_steps
|
||||||
|
self.evolution = EvolutionConfig(**(evolution or {}))
|
||||||
self._validate_v2()
|
self._validate_v2()
|
||||||
|
|
||||||
def _validate_v2(self) -> None:
|
def _validate_v2(self) -> None:
|
||||||
|
|
@ -116,6 +128,7 @@ class SkillConfig(AgentConfig):
|
||||||
quality_gate=data.get("quality_gate"),
|
quality_gate=data.get("quality_gate"),
|
||||||
execution_mode=data.get("execution_mode", "react"),
|
execution_mode=data.get("execution_mode", "react"),
|
||||||
max_steps=data.get("max_steps", 5),
|
max_steps=data.get("max_steps", 5),
|
||||||
|
evolution=data.get("evolution"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -149,6 +162,12 @@ class SkillConfig(AgentConfig):
|
||||||
}
|
}
|
||||||
d["execution_mode"] = self.execution_mode
|
d["execution_mode"] = self.execution_mode
|
||||||
d["max_steps"] = self.max_steps
|
d["max_steps"] = self.max_steps
|
||||||
|
d["evolution"] = {
|
||||||
|
"enabled": self.evolution.enabled,
|
||||||
|
"reflect_on_failure": self.evolution.reflect_on_failure,
|
||||||
|
"auto_apply": self.evolution.auto_apply,
|
||||||
|
"min_quality_threshold": self.evolution.min_quality_threshold,
|
||||||
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,368 @@
|
||||||
|
"""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"
|
||||||
Loading…
Reference in New Issue