fischer-agentkit/tests/unit/test_org_context.py

363 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""OrganizationContext 与 AgentDiscovery 单元测试"""
import pytest
from agentkit.org.context import AgentProfile, OrganizationContext
from agentkit.org.discovery import AgentDiscovery
from agentkit.skills.base import Skill, SkillConfig
from agentkit.skills.registry import SkillRegistry
# ---- Fixtures ----
@pytest.fixture
def org_context():
return OrganizationContext()
@pytest.fixture
def profile_rag():
return AgentProfile(
name="rag_agent",
agent_type="react",
capabilities=["rag", "search"],
skills=["rag_skill"],
execution_mode="react",
model="gpt-4",
)
@pytest.fixture
def profile_terminal():
return AgentProfile(
name="terminal_agent",
agent_type="react",
capabilities=["terminal", "shell"],
skills=["terminal_skill"],
execution_mode="react",
model="gpt-4",
)
@pytest.fixture
def profile_coder():
return AgentProfile(
name="coder_agent",
agent_type="rewoo",
capabilities=["rag", "terminal", "code_gen"],
skills=["coder_skill"],
execution_mode="rewoo",
model="claude-3",
max_concurrency=3,
)
# ---- OrganizationContext: 注册与注销 ----
class TestOrganizationContextRegister:
"""注册与注销 Agent 档案"""
def test_register_agent(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
assert org_context.get_agent_profile("rag_agent") is profile_rag
def test_unregister_agent(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
org_context.unregister_agent("rag_agent")
assert org_context.get_agent_profile("rag_agent") is None
def test_unregister_nonexistent_no_error(self, org_context):
org_context.unregister_agent("nonexistent") # should not raise
def test_register_overwrites_existing(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
updated = AgentProfile(
name="rag_agent",
agent_type="react",
capabilities=["rag", "search", "summarize"],
skills=["rag_skill"],
)
org_context.register_agent(updated)
profile = org_context.get_agent_profile("rag_agent")
assert profile is updated
assert "summarize" in profile.capabilities
def test_list_agents(self, org_context, profile_rag, profile_terminal):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_terminal)
agents = org_context.list_agents()
assert len(agents) == 2
names = {a.name for a in agents}
assert names == {"rag_agent", "terminal_agent"}
def test_list_agents_empty(self, org_context):
assert org_context.list_agents() == []
# ---- OrganizationContext: 能力查找 ----
class TestOrganizationContextFind:
"""find_best_agent() 测试"""
def test_find_by_required_capabilities(self, org_context, profile_rag, profile_terminal):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_terminal)
result = org_context.find_best_agent(["rag"])
assert result is not None
assert result.name == "rag_agent"
def test_find_exact_capability_match(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
# 两者都有 rag但 coder 还有 terminal
result = org_context.find_best_agent(["rag", "terminal"])
assert result is not None
assert result.name == "coder_agent"
def test_find_no_match_returns_none(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
result = org_context.find_best_agent(["nonexistent_capability"])
assert result is None
def test_find_excluded_agents_skipped(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
result = org_context.find_best_agent(["rag"], exclude=["coder_agent"])
assert result is not None
assert result.name == "rag_agent"
def test_find_unavailable_agents_skipped(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
org_context.set_availability("coder_agent", False)
result = org_context.find_best_agent(["rag", "terminal"])
assert result is None # coder is unavailable, rag doesn't have terminal
def test_find_best_agent_with_load_balancing(self, org_context):
low_load = AgentProfile(
name="low_load_agent",
agent_type="react",
capabilities=["rag"],
skills=["rag_skill"],
current_load=0,
)
high_load = AgentProfile(
name="high_load_agent",
agent_type="react",
capabilities=["rag"],
skills=["rag_skill"],
current_load=5,
)
org_context.register_agent(low_load)
org_context.register_agent(high_load)
result = org_context.find_best_agent(["rag"])
assert result is not None
assert result.name == "low_load_agent"
def test_find_capability_case_insensitive(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
result = org_context.find_best_agent(["RAG"])
assert result is not None
assert result.name == "rag_agent"
# ---- OrganizationContext: 负载与可用性 ----
class TestOrganizationContextLoadAvailability:
"""update_load() 和 set_availability() 测试"""
def test_update_load_increase(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
org_context.update_load("rag_agent", 3)
assert org_context.get_agent_profile("rag_agent").current_load == 3
def test_update_load_decrease(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
org_context.update_load("rag_agent", 5)
org_context.update_load("rag_agent", -2)
assert org_context.get_agent_profile("rag_agent").current_load == 3
def test_update_load_never_below_zero(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
org_context.update_load("rag_agent", -10)
assert org_context.get_agent_profile("rag_agent").current_load == 0
def test_update_load_nonexistent_no_error(self, org_context):
org_context.update_load("nonexistent", 1) # should not raise
def test_set_availability(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
org_context.set_availability("rag_agent", False)
assert org_context.get_agent_profile("rag_agent").availability is False
org_context.set_availability("rag_agent", True)
assert org_context.get_agent_profile("rag_agent").availability is True
def test_set_availability_nonexistent_no_error(self, org_context):
org_context.set_availability("nonexistent", False) # should not raise
# ---- OrganizationContext: from_agent_pool ----
class TestOrganizationContextFromPool:
"""from_agent_pool() 测试"""
def test_from_agent_pool_builds_context(self):
"""从 AgentPool + SkillRegistry 构建 OrganizationContext"""
skill_registry = SkillRegistry()
skill_config = SkillConfig(
name="my_skill",
agent_type="react",
capabilities=["rag", "search"],
execution_mode="react",
llm={"model": "gpt-4"},
max_concurrency=2,
prompt={"identity": "Test"},
)
skill = Skill(config=skill_config)
skill_registry.register(skill)
# Mock agent_pool
class FakeAgentPool:
def list_agents(self):
return [{"name": "my_skill", "agent_type": "react"}]
ctx = OrganizationContext.from_agent_pool(FakeAgentPool(), skill_registry)
profile = ctx.get_agent_profile("my_skill")
assert profile is not None
assert profile.agent_type == "react"
assert "rag" in profile.capabilities
assert "search" in profile.capabilities
assert profile.execution_mode == "react"
assert profile.model == "gpt-4"
assert profile.max_concurrency == 2
def test_from_agent_pool_none_graceful(self):
"""agent_pool 或 skill_registry 为 None 时返回空上下文"""
ctx = OrganizationContext.from_agent_pool(None, SkillRegistry())
assert ctx.list_agents() == []
class FakePool:
def list_agents(self):
return []
ctx = OrganizationContext.from_agent_pool(FakePool(), None)
assert ctx.list_agents() == []
def test_from_agent_pool_agent_not_in_registry(self):
"""Agent 不在 skill_registry 中时使用默认值"""
skill_registry = SkillRegistry()
class FakeAgentPool:
def list_agents(self):
return [{"name": "unknown_agent", "agent_type": "direct"}]
ctx = OrganizationContext.from_agent_pool(FakeAgentPool(), skill_registry)
profile = ctx.get_agent_profile("unknown_agent")
assert profile is not None
assert profile.agent_type == "direct"
assert profile.capabilities == []
assert profile.execution_mode == "react" # default
assert profile.model == "default"
# ---- AgentDiscovery ----
class TestAgentDiscoveryByCapability:
"""discover_by_capability() 测试"""
def test_discover_by_capability(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
discovery = AgentDiscovery(org_context)
result = discovery.discover_by_capability(["rag"])
names = {p.name for p in result}
assert names == {"rag_agent", "coder_agent"}
def test_discover_by_capability_no_match(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
discovery = AgentDiscovery(org_context)
result = discovery.discover_by_capability(["nonexistent"])
assert result == []
class TestAgentDiscoveryByMode:
"""discover_by_execution_mode() 测试"""
def test_discover_by_execution_mode(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
discovery = AgentDiscovery(org_context)
result = discovery.discover_by_execution_mode("rewoo")
assert len(result) == 1
assert result[0].name == "coder_agent"
def test_discover_by_execution_mode_no_match(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
discovery = AgentDiscovery(org_context)
result = discovery.discover_by_execution_mode("plan_exec")
assert result == []
class TestAgentDiscoveryAvailable:
"""discover_available() 测试"""
def test_discover_available(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
org_context.set_availability("coder_agent", False)
discovery = AgentDiscovery(org_context)
result = discovery.discover_available()
names = {p.name for p in result}
assert names == {"rag_agent"}
class TestAgentDiscoveryRecommend:
"""recommend_agent() 测试"""
def test_recommend_with_preferred_mode(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
discovery = AgentDiscovery(org_context)
result = discovery.recommend_agent(["rag"], preferred_mode="rewoo")
assert result is not None
assert result.name == "coder_agent"
def test_recommend_without_preferred_mode(self, org_context, profile_rag, profile_coder):
org_context.register_agent(profile_rag)
org_context.register_agent(profile_coder)
discovery = AgentDiscovery(org_context)
result = discovery.recommend_agent(["rag"])
assert result is not None
# Both have rag, should pick lower load
assert result.current_load == 0
def test_recommend_fallback_when_no_capability_match(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
discovery = AgentDiscovery(org_context)
result = discovery.recommend_agent(["nonexistent"])
# Falls back to any available agent
assert result is not None
assert result.name == "rag_agent"
def test_recommend_returns_none_when_no_available(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
org_context.set_availability("rag_agent", False)
discovery = AgentDiscovery(org_context)
result = discovery.recommend_agent(["rag"])
assert result is None
def test_recommend_preferred_mode_no_match_uses_any_match(self, org_context, profile_rag):
org_context.register_agent(profile_rag)
discovery = AgentDiscovery(org_context)
# rag_agent has react mode, but we prefer plan_exec
result = discovery.recommend_agent(["rag"], preferred_mode="plan_exec")
# No plan_exec match, but still has capability match
assert result is not None
assert result.name == "rag_agent"