deer-flow/backend/tests/test_lead_agent_skills.py
greatmengqi 6beea682d2 refactor(config): Phase 2 final cleanup — delete AppConfig.current() shim
Tail-end of Phase 2:
- Migrate ~70 remaining test sites off AppConfig.current(): drop
  dead monkey-patches (production no longer calls current), hoist the
  mocked config into a local variable and pass it explicitly. Verified
  with `grep -rn 'AppConfig\.current' backend/tests` → empty.
- Delete the AppConfig.current() classmethod entirely. The transitional
  raise-only shim is no longer needed now that no test references it.
- Update docs: plan marked shipped (P2-6..P2-10 in commit 84dccef2);
  backend/CLAUDE.md Config Lifecycle rewritten to describe the
  explicit-parameter design; gateway/deps.py docstrings no longer point
  at the removed current() surface.

AppConfig is now a pure Pydantic value object. Every consumer holds its
own captured instance — Gateway (app.state.config via Depends(get_config)),
DeerFlowClient (self._app_config), agent runtime (DeerFlowContext.app_config),
LangGraph Server bootstrap (AppConfig.from_file() inside make_lead_agent).

2337 non-e2e tests pass.
2026-04-17 11:42:27 +08:00

141 lines
5.8 KiB
Python

from pathlib import Path
from types import SimpleNamespace
from deerflow.agents.lead_agent.prompt import get_skills_prompt_section
from deerflow.config.agents_config import AgentConfig
from deerflow.skills.types import Skill
def _make_skill(name: str) -> Skill:
return Skill(
name=name,
description=f"Description for {name}",
license="MIT",
skill_dir=Path(f"/tmp/{name}"),
skill_file=Path(f"/tmp/{name}/SKILL.md"),
relative_path=Path(name),
category="public",
enabled=True,
)
_DEFAULT_SKILLS_CONFIG = SimpleNamespace(
skills=SimpleNamespace(container_path="/mnt/skills"),
skill_evolution=SimpleNamespace(enabled=False),
)
def _evolution_enabled_config() -> SimpleNamespace:
return SimpleNamespace(
skills=SimpleNamespace(container_path="/mnt/skills"),
skill_evolution=SimpleNamespace(enabled=True),
)
def test_get_skills_prompt_section_returns_empty_when_no_skills_match(monkeypatch):
skills = [_make_skill("skill1"), _make_skill("skill2")]
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: skills)
result = get_skills_prompt_section(_DEFAULT_SKILLS_CONFIG, available_skills={"non_existent_skill"})
assert result == ""
def test_get_skills_prompt_section_returns_empty_when_available_skills_empty(monkeypatch):
skills = [_make_skill("skill1"), _make_skill("skill2")]
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: skills)
result = get_skills_prompt_section(_DEFAULT_SKILLS_CONFIG, available_skills=set())
assert result == ""
def test_get_skills_prompt_section_returns_skills(monkeypatch):
skills = [_make_skill("skill1"), _make_skill("skill2")]
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: skills)
result = get_skills_prompt_section(_DEFAULT_SKILLS_CONFIG, available_skills={"skill1"})
assert "skill1" in result
assert "skill2" not in result
assert "[built-in]" in result
def test_get_skills_prompt_section_returns_all_when_available_skills_is_none(monkeypatch):
skills = [_make_skill("skill1"), _make_skill("skill2")]
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: skills)
result = get_skills_prompt_section(_DEFAULT_SKILLS_CONFIG, available_skills=None)
assert "skill1" in result
assert "skill2" in result
def test_get_skills_prompt_section_includes_self_evolution_rules(monkeypatch):
skills = [_make_skill("skill1")]
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: skills)
result = get_skills_prompt_section(_evolution_enabled_config(), available_skills=None)
assert "Skill Self-Evolution" in result
def test_get_skills_prompt_section_includes_self_evolution_rules_without_skills(monkeypatch):
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: [])
result = get_skills_prompt_section(_evolution_enabled_config(), available_skills=None)
assert "Skill Self-Evolution" in result
def test_get_skills_prompt_section_cache_respects_skill_evolution_toggle(monkeypatch):
skills = [_make_skill("skill1")]
monkeypatch.setattr("deerflow.agents.lead_agent.prompt._get_enabled_skills", lambda *a, **k: skills)
config = _evolution_enabled_config()
enabled_result = get_skills_prompt_section(config, available_skills=None)
assert "Skill Self-Evolution" in enabled_result
disabled_config = SimpleNamespace(
skills=SimpleNamespace(container_path="/mnt/skills"),
skill_evolution=SimpleNamespace(enabled=False),
)
disabled_result = get_skills_prompt_section(disabled_config, available_skills=None)
assert "Skill Self-Evolution" not in disabled_result
def test_make_lead_agent_empty_skills_passed_correctly(monkeypatch):
from unittest.mock import MagicMock
from deerflow.agents.lead_agent import agent as lead_agent_module
# Mock dependencies
monkeypatch.setattr(lead_agent_module, "_resolve_model_name", lambda app_config=None, x=None: "default-model")
monkeypatch.setattr(lead_agent_module, "create_chat_model", lambda **kwargs: "model")
monkeypatch.setattr("deerflow.tools.get_available_tools", lambda **kwargs: [])
monkeypatch.setattr(lead_agent_module, "_build_middlewares", lambda *args, **kwargs: [])
monkeypatch.setattr(lead_agent_module, "create_agent", lambda **kwargs: kwargs)
class MockModelConfig:
supports_thinking = False
mock_app_config = MagicMock()
mock_app_config.get_model_config.return_value = MockModelConfig()
captured_skills = []
def mock_apply_prompt_template(_app_config, *args, **kwargs):
captured_skills.append(kwargs.get("available_skills"))
return "mock_prompt"
monkeypatch.setattr(lead_agent_module, "apply_prompt_template", mock_apply_prompt_template)
# Case 1: Empty skills list
monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda x: AgentConfig(name="test", skills=[]))
lead_agent_module.make_lead_agent({"configurable": {"agent_name": "test"}}, app_config=mock_app_config)
assert captured_skills[-1] == set()
# Case 2: None skills list
monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda x: AgentConfig(name="test", skills=None))
lead_agent_module.make_lead_agent({"configurable": {"agent_name": "test"}}, app_config=mock_app_config)
assert captured_skills[-1] is None
# Case 3: Some skills list
monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda x: AgentConfig(name="test", skills=["skill1"]))
lead_agent_module.make_lead_agent({"configurable": {"agent_name": "test"}}, app_config=mock_app_config)
assert captured_skills[-1] == {"skill1"}