"""U6 测试: BaseAgent v2 集成 — LLM Gateway + Skill + Quality Gate + ReAct""" import json from datetime import datetime, timezone from typing import Any from unittest.mock import AsyncMock, MagicMock import pytest from agentkit.core.base import BaseAgent from agentkit.core.protocol import ( AgentCapability, AgentStatus, TaskMessage, TaskResult, TaskStatus, ) from agentkit.llm.gateway import LLMGateway from agentkit.llm.protocol import LLMResponse, TokenUsage, ToolCall from agentkit.quality.gate import QualityGate, QualityResult, QualityCheck from agentkit.quality.output import OutputStandardizer, StandardOutput from agentkit.skills.base import Skill, SkillConfig, QualityGateConfig, IntentConfig # ── Helpers ────────────────────────────────────────────── def _make_task(task_type: str = "echo", input_data: dict | None = None) -> TaskMessage: return TaskMessage( task_id="test-001", agent_name="test_agent", task_type=task_type, priority=0, input_data=input_data or {}, callback_url=None, created_at=datetime.now(timezone.utc), ) def _make_skill_config( name: str = "test_skill", execution_mode: str = "react", quality_gate: dict | None = None, prompt: dict | None = None, ) -> SkillConfig: return SkillConfig( name=name, agent_type="test", task_mode="llm_generate", prompt=prompt or {"identity": "Test skill", "instructions": "Do test things"}, execution_mode=execution_mode, quality_gate=quality_gate, ) class SimpleV2Agent(BaseAgent): """测试用 v2 Agent""" def __init__(self): super().__init__(name="v2_agent", agent_type="test", version="2.0.0") self.last_task = None self.last_feedback = None async def handle_task(self, task: TaskMessage) -> dict: self.last_task = task return {"result": "ok", "task_type": task.task_type} async def handle_task_with_feedback(self, task: TaskMessage, feedback: str) -> dict: self.last_feedback = feedback return {"result": "retry_ok", "feedback": feedback} def get_capabilities(self) -> AgentCapability: return AgentCapability( agent_name=self.name, agent_type=self.agent_type, version=self.version, supported_tasks=["echo"], max_concurrency=1, description="V2 test agent", ) # ── BaseAgent v2 属性测试 ──────────────────────────────── class TestBaseAgentV2Properties: """测试 BaseAgent 新增的 v2 属性""" def test_llm_gateway_property_default_none(self): agent = SimpleV2Agent() assert agent.llm_gateway is None def test_llm_gateway_setter(self): agent = SimpleV2Agent() gateway = LLMGateway() agent.llm_gateway = gateway assert agent.llm_gateway is gateway def test_skill_property_default_none(self): agent = SimpleV2Agent() assert agent.skill is None def test_skill_setter(self): agent = SimpleV2Agent() skill_config = _make_skill_config() skill = Skill(config=skill_config) agent.skill = skill assert agent.skill is skill assert agent.skill.name == "test_skill" def test_quality_gate_property_default(self): agent = SimpleV2Agent() qg = agent.quality_gate assert qg is not None assert isinstance(qg, QualityGate) # ── Quality Gate 集成测试 ──────────────────────────────── class TestQualityGateIntegration: """测试 execute() 中的 Quality Gate 集成""" @pytest.mark.asyncio async def test_quality_passes_no_retry(self): """Quality Gate 通过时不重试""" agent = SimpleV2Agent() skill_config = _make_skill_config( quality_gate={"required_fields": ["result"], "max_retries": 2} ) skill = Skill(config=skill_config) agent.skill = skill task = _make_task() result = await agent.execute(task) assert result.status == TaskStatus.COMPLETED assert result.output_data == {"result": "ok", "task_type": "echo"} # handle_task 只被调用一次(没有重试) assert agent.last_feedback is None @pytest.mark.asyncio async def test_quality_fails_triggers_retry(self): """Quality Gate 失败时触发重试""" agent = SimpleV2Agent() skill_config = _make_skill_config( quality_gate={"required_fields": ["missing_field"], "max_retries": 2} ) skill = Skill(config=skill_config) agent.skill = skill task = _make_task() result = await agent.execute(task) # 即使质量检查失败,execute 仍返回结果(重试后仍可能失败) assert result.status == TaskStatus.COMPLETED # handle_task_with_feedback 应该被调用了 assert agent.last_feedback is not None @pytest.mark.asyncio async def test_quality_retry_stops_on_pass(self): """Quality Gate 重试后通过则停止""" class RetryAgent(BaseAgent): def __init__(self): super().__init__(name="retry_agent", agent_type="test", version="1.0.0") self.call_count = 0 async def handle_task(self, task: TaskMessage) -> dict: self.call_count += 1 if self.call_count == 1: return {"content": "short"} # 第一次:字数不够 return {"content": "this is a longer response that meets the minimum word count requirement"} async def handle_task_with_feedback(self, task: TaskMessage, feedback: str) -> dict: self.call_count += 1 return {"content": "this is a longer response that meets the minimum word count requirement"} def get_capabilities(self) -> AgentCapability: return AgentCapability( agent_name=self.name, agent_type=self.agent_type, version=self.version, supported_tasks=["test"], max_concurrency=1, description="Retry test agent", ) agent = RetryAgent() skill_config = _make_skill_config( quality_gate={"min_word_count": 5, "max_retries": 3} ) skill = Skill(config=skill_config) agent.skill = skill task = _make_task() result = await agent.execute(task) assert result.status == TaskStatus.COMPLETED # 应该调用了 handle_task 1次 + handle_task_with_feedback 1次 = 2次 assert agent.call_count == 2 @pytest.mark.asyncio async def test_quality_no_retry_when_max_retries_zero(self): """max_retries=0 时不重试""" agent = SimpleV2Agent() skill_config = _make_skill_config( quality_gate={"required_fields": ["missing_field"], "max_retries": 0} ) skill = Skill(config=skill_config) agent.skill = skill task = _make_task() result = await agent.execute(task) assert result.status == TaskStatus.COMPLETED assert agent.last_feedback is None # 没有重试 @pytest.mark.asyncio async def test_no_quality_check_without_skill(self): """没有 Skill 时不执行 Quality Gate""" agent = SimpleV2Agent() # 不设置 skill task = _make_task() result = await agent.execute(task) assert result.status == TaskStatus.COMPLETED assert result.output_data == {"result": "ok", "task_type": "echo"} # ── handle_task_with_feedback 测试 ─────────────────────── class TestHandleTaskWithFeedback: """测试 handle_task_with_feedback 默认行为""" @pytest.mark.asyncio async def test_default_handle_task_with_feedback(self): """默认 handle_task_with_feedback 回退到 handle_task""" class DefaultFeedbackAgent(BaseAgent): def __init__(self): super().__init__(name="fb_agent", agent_type="test", version="1.0.0") async def handle_task(self, task: TaskMessage) -> dict: return {"result": "default"} def get_capabilities(self) -> AgentCapability: return AgentCapability( agent_name=self.name, agent_type=self.agent_type, version=self.version, supported_tasks=["test"], max_concurrency=1, description="Feedback test agent", ) agent = DefaultFeedbackAgent() task = _make_task() result = await agent.handle_task_with_feedback(task, "quality feedback") assert result == {"result": "default"} # ── _build_quality_feedback 测试 ───────────────────────── class TestBuildQualityFeedback: """测试质量反馈构建""" @pytest.mark.asyncio async def test_build_quality_feedback(self): """_build_quality_feedback 正确构建反馈字符串""" agent = SimpleV2Agent() quality_result = QualityResult( passed=False, checks=[ QualityCheck(name="required_field:title", passed=False, message="Field 'title' is missing"), QualityCheck(name="min_word_count", passed=False, message="Word count 2 < minimum 10"), ], can_retry=True, ) feedback = agent._build_quality_feedback(quality_result) assert "title" in feedback assert "minimum 10" in feedback assert "Quality check failed" in feedback # ── Backward Compatibility 测试 ────────────────────────── class TestBackwardCompatibility: """测试向后兼容性""" @pytest.mark.asyncio async def test_execute_without_v2_features(self): """不使用 v2 功能时,execute 行为与 v1 一致""" agent = SimpleV2Agent() task = _make_task("echo", {"msg": "hello"}) result = await agent.execute(task) assert result.status == TaskStatus.COMPLETED assert result.output_data == {"result": "ok", "task_type": "echo"} assert result.error_message is None assert result.metrics["task_type"] == "echo" @pytest.mark.asyncio async def test_execute_failure_still_works(self): """v1 的失败路径仍然正常""" class FailAgent(BaseAgent): def __init__(self): super().__init__(name="fail_agent", agent_type="test", version="1.0.0") async def handle_task(self, task: TaskMessage) -> dict: raise ValueError("intentional failure") def get_capabilities(self) -> AgentCapability: return AgentCapability( agent_name=self.name, agent_type=self.agent_type, version=self.version, supported_tasks=["test"], max_concurrency=1, description="Fail test agent", ) agent = FailAgent() task = _make_task() result = await agent.execute(task) assert result.status == TaskStatus.FAILED assert result.error_message == "intentional failure" @pytest.mark.asyncio async def test_lifecycle_hooks_still_work(self): """v1 的生命周期钩子仍然正常""" class HookAgent(BaseAgent): def __init__(self): super().__init__(name="hook_agent", agent_type="test", version="1.0.0") self.started = False self.completed = False self.failed = False async def handle_task(self, task: TaskMessage) -> dict: return {"ok": True} async def on_task_start(self, task): self.started = True async def on_task_complete(self, task, output): self.completed = True async def on_task_failed(self, task, error): self.failed = True def get_capabilities(self) -> AgentCapability: return AgentCapability( agent_name=self.name, agent_type=self.agent_type, version=self.version, supported_tasks=["test"], max_concurrency=1, description="Hook test agent", ) agent = HookAgent() task = _make_task() await agent.execute(task) assert agent.started is True assert agent.completed is True assert agent.failed is False