mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +08:00
refactor: split backend into harness (deerflow.*) and app (app.*) (#1131)
* refactor: extract shared utils to break harness→app cross-layer imports Move _validate_skill_frontmatter to src/skills/validation.py and CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py. This eliminates the two reverse dependencies from client.py (harness layer) into gateway/routers/ (app layer), preparing for the harness/app package split. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: split backend/src into harness (deerflow.*) and app (app.*) Physically split the monolithic backend/src/ package into two layers: - **Harness** (`packages/harness/deerflow/`): publishable agent framework package with import prefix `deerflow.*`. Contains agents, sandbox, tools, models, MCP, skills, config, and all core infrastructure. - **App** (`app/`): unpublished application code with import prefix `app.*`. Contains gateway (FastAPI REST API) and channels (IM integrations). Key changes: - Move 13 harness modules to packages/harness/deerflow/ via git mv - Move gateway + channels to app/ via git mv - Rename all imports: src.* → deerflow.* (harness) / app.* (app layer) - Set up uv workspace with deerflow-harness as workspace member - Update langgraph.json, config.example.yaml, all scripts, Docker files - Add build-system (hatchling) to harness pyproject.toml - Add PYTHONPATH=. to gateway startup commands for app.* resolution - Update ruff.toml with known-first-party for import sorting - Update all documentation to reflect new directory structure Boundary rule enforced: harness code never imports from app. All 429 tests pass. Lint clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add harness→app boundary check test and update docs Add test_harness_boundary.py that scans all Python files in packages/harness/deerflow/ and fails if any `from app.*` or `import app.*` statement is found. This enforces the architectural rule that the harness layer never depends on the app layer. Update CLAUDE.md to document the harness/app split architecture, import conventions, and the boundary enforcement test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add config versioning with auto-upgrade on startup When config.example.yaml schema changes, developers' local config.yaml files can silently become outdated. This adds a config_version field and auto-upgrade mechanism so breaking changes (like src.* → deerflow.* renames) are applied automatically before services start. - Add config_version: 1 to config.example.yaml - Add startup version check warning in AppConfig.from_file() - Add scripts/config-upgrade.sh with migration registry for value replacements - Add `make config-upgrade` target - Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services - Add config error hints in service failure messages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix comments * fix: update src.* import in test_sandbox_tools_security to deerflow.* Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: handle empty config and search parent dirs for config.example.yaml Address Copilot review comments on PR #1131: - Guard against yaml.safe_load() returning None for empty config files - Search parent directories for config.example.yaml instead of only looking next to config.yaml, fixing detection in common setups Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct skills root path depth and config_version type coercion - loader.py: fix get_skills_root_path() to use 5 parent levels (was 3) after harness split, file lives at packages/harness/deerflow/skills/ so parent×3 resolved to backend/packages/harness/ instead of backend/ - app_config.py: coerce config_version to int() before comparison in _check_config_version() to prevent TypeError when YAML stores value as string (e.g. config_version: "1") - tests: add regression tests for both fixes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: update test imports from src.* to deerflow.*/app.* after harness refactor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,12 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage # noqa: F401
|
||||
|
||||
from src.client import DeerFlowClient
|
||||
from src.gateway.routers.mcp import McpConfigResponse
|
||||
from src.gateway.routers.memory import MemoryConfigResponse, MemoryStatusResponse
|
||||
from src.gateway.routers.models import ModelResponse, ModelsListResponse
|
||||
from src.gateway.routers.skills import SkillInstallResponse, SkillResponse, SkillsListResponse
|
||||
from src.gateway.routers.uploads import UploadResponse
|
||||
from app.gateway.routers.mcp import McpConfigResponse
|
||||
from app.gateway.routers.memory import MemoryConfigResponse, MemoryStatusResponse
|
||||
from app.gateway.routers.models import ModelResponse, ModelsListResponse
|
||||
from app.gateway.routers.skills import SkillInstallResponse, SkillResponse, SkillsListResponse
|
||||
from app.gateway.routers.uploads import UploadResponse
|
||||
from deerflow.client import DeerFlowClient
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
@@ -40,7 +40,7 @@ def mock_app_config():
|
||||
@pytest.fixture
|
||||
def client(mock_app_config):
|
||||
"""Create a DeerFlowClient with mocked config loading."""
|
||||
with patch("src.client.get_app_config", return_value=mock_app_config):
|
||||
with patch("deerflow.client.get_app_config", return_value=mock_app_config):
|
||||
return DeerFlowClient()
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestClientInit:
|
||||
assert client._agent is None
|
||||
|
||||
def test_custom_params(self, mock_app_config):
|
||||
with patch("src.client.get_app_config", return_value=mock_app_config):
|
||||
with patch("deerflow.client.get_app_config", return_value=mock_app_config):
|
||||
c = DeerFlowClient(
|
||||
model_name="gpt-4",
|
||||
thinking_enabled=False,
|
||||
@@ -73,15 +73,15 @@ class TestClientInit:
|
||||
|
||||
def test_custom_config_path(self, mock_app_config):
|
||||
with (
|
||||
patch("src.client.reload_app_config") as mock_reload,
|
||||
patch("src.client.get_app_config", return_value=mock_app_config),
|
||||
patch("deerflow.client.reload_app_config") as mock_reload,
|
||||
patch("deerflow.client.get_app_config", return_value=mock_app_config),
|
||||
):
|
||||
DeerFlowClient(config_path="/tmp/custom.yaml")
|
||||
mock_reload.assert_called_once_with("/tmp/custom.yaml")
|
||||
|
||||
def test_checkpointer_stored(self, mock_app_config):
|
||||
cp = MagicMock()
|
||||
with patch("src.client.get_app_config", return_value=mock_app_config):
|
||||
with patch("deerflow.client.get_app_config", return_value=mock_app_config):
|
||||
c = DeerFlowClient(checkpointer=cp)
|
||||
assert c._checkpointer is cp
|
||||
|
||||
@@ -109,7 +109,7 @@ class TestConfigQueries:
|
||||
skill.category = "public"
|
||||
skill.enabled = True
|
||||
|
||||
with patch("src.skills.loader.load_skills", return_value=[skill]) as mock_load:
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[skill]) as mock_load:
|
||||
result = client.list_skills()
|
||||
mock_load.assert_called_once_with(enabled_only=False)
|
||||
|
||||
@@ -124,13 +124,13 @@ class TestConfigQueries:
|
||||
}
|
||||
|
||||
def test_list_skills_enabled_only(self, client):
|
||||
with patch("src.skills.loader.load_skills", return_value=[]) as mock_load:
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[]) as mock_load:
|
||||
client.list_skills(enabled_only=True)
|
||||
mock_load.assert_called_once_with(enabled_only=True)
|
||||
|
||||
def test_get_memory(self, client):
|
||||
memory = {"version": "1.0", "facts": []}
|
||||
with patch("src.agents.memory.updater.get_memory_data", return_value=memory) as mock_mem:
|
||||
with patch("deerflow.agents.memory.updater.get_memory_data", return_value=memory) as mock_mem:
|
||||
result = client.get_memory()
|
||||
mock_mem.assert_called_once()
|
||||
assert result == memory
|
||||
@@ -355,10 +355,10 @@ class TestEnsureAgent:
|
||||
config = client._get_runnable_config("t1")
|
||||
|
||||
with (
|
||||
patch("src.client.create_chat_model"),
|
||||
patch("src.client.create_agent", return_value=mock_agent),
|
||||
patch("src.client._build_middlewares", return_value=[]),
|
||||
patch("src.client.apply_prompt_template", return_value="prompt"),
|
||||
patch("deerflow.client.create_chat_model"),
|
||||
patch("deerflow.client.create_agent", return_value=mock_agent),
|
||||
patch("deerflow.client._build_middlewares", return_value=[]),
|
||||
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
|
||||
patch.object(client, "_get_tools", return_value=[]),
|
||||
):
|
||||
client._ensure_agent(config)
|
||||
@@ -371,12 +371,12 @@ class TestEnsureAgent:
|
||||
config = client._get_runnable_config("t1")
|
||||
|
||||
with (
|
||||
patch("src.client.create_chat_model"),
|
||||
patch("src.client.create_agent", return_value=mock_agent) as mock_create_agent,
|
||||
patch("src.client._build_middlewares", return_value=[]),
|
||||
patch("src.client.apply_prompt_template", return_value="prompt"),
|
||||
patch("deerflow.client.create_chat_model"),
|
||||
patch("deerflow.client.create_agent", return_value=mock_agent) as mock_create_agent,
|
||||
patch("deerflow.client._build_middlewares", return_value=[]),
|
||||
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
|
||||
patch.object(client, "_get_tools", return_value=[]),
|
||||
patch("src.agents.checkpointer.get_checkpointer", return_value=mock_checkpointer),
|
||||
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=mock_checkpointer),
|
||||
):
|
||||
client._ensure_agent(config)
|
||||
|
||||
@@ -387,12 +387,12 @@ class TestEnsureAgent:
|
||||
config = client._get_runnable_config("t1")
|
||||
|
||||
with (
|
||||
patch("src.client.create_chat_model"),
|
||||
patch("src.client.create_agent", return_value=mock_agent) as mock_create_agent,
|
||||
patch("src.client._build_middlewares", return_value=[]),
|
||||
patch("src.client.apply_prompt_template", return_value="prompt"),
|
||||
patch("deerflow.client.create_chat_model"),
|
||||
patch("deerflow.client.create_agent", return_value=mock_agent) as mock_create_agent,
|
||||
patch("deerflow.client._build_middlewares", return_value=[]),
|
||||
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
|
||||
patch.object(client, "_get_tools", return_value=[]),
|
||||
patch("src.agents.checkpointer.get_checkpointer", return_value=None),
|
||||
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=None),
|
||||
):
|
||||
client._ensure_agent(config)
|
||||
|
||||
@@ -452,7 +452,7 @@ class TestMcpConfig:
|
||||
ext_config = MagicMock()
|
||||
ext_config.mcp_servers = {"github": server}
|
||||
|
||||
with patch("src.client.get_extensions_config", return_value=ext_config):
|
||||
with patch("deerflow.client.get_extensions_config", return_value=ext_config):
|
||||
result = client.get_mcp_config()
|
||||
|
||||
assert "mcp_servers" in result
|
||||
@@ -478,9 +478,9 @@ class TestMcpConfig:
|
||||
client._agent = MagicMock()
|
||||
|
||||
with (
|
||||
patch("src.client.ExtensionsConfig.resolve_config_path", return_value=tmp_path),
|
||||
patch("src.client.get_extensions_config", return_value=current_config),
|
||||
patch("src.client.reload_extensions_config", return_value=reloaded_config),
|
||||
patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=tmp_path),
|
||||
patch("deerflow.client.get_extensions_config", return_value=current_config),
|
||||
patch("deerflow.client.reload_extensions_config", return_value=reloaded_config),
|
||||
):
|
||||
result = client.update_mcp_config({"new-server": {"enabled": True, "type": "sse"}})
|
||||
|
||||
@@ -513,13 +513,13 @@ class TestSkillsManagement:
|
||||
|
||||
def test_get_skill_found(self, client):
|
||||
skill = self._make_skill()
|
||||
with patch("src.skills.loader.load_skills", return_value=[skill]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[skill]):
|
||||
result = client.get_skill("test-skill")
|
||||
assert result is not None
|
||||
assert result["name"] == "test-skill"
|
||||
|
||||
def test_get_skill_not_found(self, client):
|
||||
with patch("src.skills.loader.load_skills", return_value=[]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[]):
|
||||
result = client.get_skill("nonexistent")
|
||||
assert result is None
|
||||
|
||||
@@ -540,10 +540,10 @@ class TestSkillsManagement:
|
||||
client._agent = MagicMock()
|
||||
|
||||
with (
|
||||
patch("src.skills.loader.load_skills", side_effect=[[skill], [updated_skill]]),
|
||||
patch("src.client.ExtensionsConfig.resolve_config_path", return_value=tmp_path),
|
||||
patch("src.client.get_extensions_config", return_value=ext_config),
|
||||
patch("src.client.reload_extensions_config"),
|
||||
patch("deerflow.skills.loader.load_skills", side_effect=[[skill], [updated_skill]]),
|
||||
patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=tmp_path),
|
||||
patch("deerflow.client.get_extensions_config", return_value=ext_config),
|
||||
patch("deerflow.client.reload_extensions_config"),
|
||||
):
|
||||
result = client.update_skill("test-skill", enabled=False)
|
||||
assert result["enabled"] is False
|
||||
@@ -552,7 +552,7 @@ class TestSkillsManagement:
|
||||
tmp_path.unlink()
|
||||
|
||||
def test_update_skill_not_found(self, client):
|
||||
with patch("src.skills.loader.load_skills", return_value=[]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[]):
|
||||
with pytest.raises(ValueError, match="not found"):
|
||||
client.update_skill("nonexistent", enabled=True)
|
||||
|
||||
@@ -573,8 +573,8 @@ class TestSkillsManagement:
|
||||
(skills_root / "custom").mkdir(parents=True)
|
||||
|
||||
with (
|
||||
patch("src.skills.loader.get_skills_root_path", return_value=skills_root),
|
||||
patch("src.gateway.routers.skills._validate_skill_frontmatter", return_value=(True, "OK", "my-skill")),
|
||||
patch("deerflow.skills.loader.get_skills_root_path", return_value=skills_root),
|
||||
patch("deerflow.skills.validation._validate_skill_frontmatter", return_value=(True, "OK", "my-skill")),
|
||||
):
|
||||
result = client.install_skill(archive_path)
|
||||
|
||||
@@ -604,7 +604,7 @@ class TestSkillsManagement:
|
||||
class TestMemoryManagement:
|
||||
def test_reload_memory(self, client):
|
||||
data = {"version": "1.0", "facts": []}
|
||||
with patch("src.agents.memory.updater.reload_memory_data", return_value=data):
|
||||
with patch("deerflow.agents.memory.updater.reload_memory_data", return_value=data):
|
||||
result = client.reload_memory()
|
||||
assert result == data
|
||||
|
||||
@@ -618,7 +618,7 @@ class TestMemoryManagement:
|
||||
config.injection_enabled = True
|
||||
config.max_injection_tokens = 2000
|
||||
|
||||
with patch("src.config.memory_config.get_memory_config", return_value=config):
|
||||
with patch("deerflow.config.memory_config.get_memory_config", return_value=config):
|
||||
result = client.get_memory_config()
|
||||
|
||||
assert result["enabled"] is True
|
||||
@@ -637,8 +637,8 @@ class TestMemoryManagement:
|
||||
data = {"version": "1.0", "facts": []}
|
||||
|
||||
with (
|
||||
patch("src.config.memory_config.get_memory_config", return_value=config),
|
||||
patch("src.agents.memory.updater.get_memory_data", return_value=data),
|
||||
patch("deerflow.config.memory_config.get_memory_config", return_value=config),
|
||||
patch("deerflow.agents.memory.updater.get_memory_data", return_value=data),
|
||||
):
|
||||
result = client.get_memory_status()
|
||||
|
||||
@@ -720,8 +720,8 @@ class TestUploads:
|
||||
|
||||
with (
|
||||
patch.object(DeerFlowClient, "_get_uploads_dir", return_value=uploads_dir),
|
||||
patch("src.gateway.routers.uploads.CONVERTIBLE_EXTENSIONS", {".pdf"}),
|
||||
patch("src.gateway.routers.uploads.convert_file_to_markdown", side_effect=fake_convert),
|
||||
patch("deerflow.utils.file_conversion.CONVERTIBLE_EXTENSIONS", {".pdf"}),
|
||||
patch("deerflow.utils.file_conversion.convert_file_to_markdown", side_effect=fake_convert),
|
||||
patch("concurrent.futures.ThreadPoolExecutor", FakeExecutor),
|
||||
):
|
||||
result = asyncio.run(call_upload())
|
||||
@@ -793,7 +793,7 @@ class TestArtifacts:
|
||||
mock_paths = MagicMock()
|
||||
mock_paths.sandbox_user_data_dir.return_value = user_data_dir
|
||||
|
||||
with patch("src.client.get_paths", return_value=mock_paths):
|
||||
with patch("deerflow.client.get_paths", return_value=mock_paths):
|
||||
content, mime = client.get_artifact("t1", "mnt/user-data/outputs/result.txt")
|
||||
|
||||
assert content == b"artifact content"
|
||||
@@ -807,7 +807,7 @@ class TestArtifacts:
|
||||
mock_paths = MagicMock()
|
||||
mock_paths.sandbox_user_data_dir.return_value = user_data_dir
|
||||
|
||||
with patch("src.client.get_paths", return_value=mock_paths):
|
||||
with patch("deerflow.client.get_paths", return_value=mock_paths):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
client.get_artifact("t1", "mnt/user-data/outputs/nope.txt")
|
||||
|
||||
@@ -823,7 +823,7 @@ class TestArtifacts:
|
||||
mock_paths = MagicMock()
|
||||
mock_paths.sandbox_user_data_dir.return_value = user_data_dir
|
||||
|
||||
with patch("src.client.get_paths", return_value=mock_paths):
|
||||
with patch("deerflow.client.get_paths", return_value=mock_paths):
|
||||
with pytest.raises(PermissionError):
|
||||
client.get_artifact("t1", "mnt/user-data/../../../etc/passwd")
|
||||
|
||||
@@ -1028,7 +1028,7 @@ class TestScenarioFileLifecycle:
|
||||
mock_paths = MagicMock()
|
||||
mock_paths.sandbox_user_data_dir.return_value = user_data_dir
|
||||
|
||||
with patch("src.client.get_paths", return_value=mock_paths):
|
||||
with patch("deerflow.client.get_paths", return_value=mock_paths):
|
||||
content, mime = client.get_artifact("t-artifact", "mnt/user-data/outputs/analysis.json")
|
||||
|
||||
assert json.loads(content) == {"result": "processed"}
|
||||
@@ -1064,12 +1064,12 @@ class TestScenarioConfigManagement:
|
||||
skill.category = "public"
|
||||
skill.enabled = True
|
||||
|
||||
with patch("src.skills.loader.load_skills", return_value=[skill]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[skill]):
|
||||
skills_result = client.list_skills()
|
||||
assert len(skills_result["skills"]) == 1
|
||||
|
||||
# Get specific skill
|
||||
with patch("src.skills.loader.load_skills", return_value=[skill]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[skill]):
|
||||
detail = client.get_skill("web-search")
|
||||
assert detail is not None
|
||||
assert detail["enabled"] is True
|
||||
@@ -1091,9 +1091,9 @@ class TestScenarioConfigManagement:
|
||||
|
||||
client._agent = MagicMock() # Simulate existing agent
|
||||
with (
|
||||
patch("src.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("src.client.get_extensions_config", return_value=current_config),
|
||||
patch("src.client.reload_extensions_config", return_value=reloaded_config),
|
||||
patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("deerflow.client.get_extensions_config", return_value=current_config),
|
||||
patch("deerflow.client.reload_extensions_config", return_value=reloaded_config),
|
||||
):
|
||||
mcp_result = client.update_mcp_config({"my-mcp": {"enabled": True}})
|
||||
assert "my-mcp" in mcp_result["mcp_servers"]
|
||||
@@ -1120,10 +1120,10 @@ class TestScenarioConfigManagement:
|
||||
|
||||
client._agent = MagicMock() # Simulate re-created agent
|
||||
with (
|
||||
patch("src.skills.loader.load_skills", side_effect=[[skill], [toggled]]),
|
||||
patch("src.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("src.client.get_extensions_config", return_value=ext_config),
|
||||
patch("src.client.reload_extensions_config"),
|
||||
patch("deerflow.skills.loader.load_skills", side_effect=[[skill], [toggled]]),
|
||||
patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("deerflow.client.get_extensions_config", return_value=ext_config),
|
||||
patch("deerflow.client.reload_extensions_config"),
|
||||
):
|
||||
skill_result = client.update_skill("code-gen", enabled=False)
|
||||
assert skill_result["enabled"] is False
|
||||
@@ -1146,10 +1146,10 @@ class TestScenarioAgentRecreation:
|
||||
config_b = client._get_runnable_config("t1", model_name="claude-3")
|
||||
|
||||
with (
|
||||
patch("src.client.create_chat_model"),
|
||||
patch("src.client.create_agent", side_effect=fake_create_agent),
|
||||
patch("src.client._build_middlewares", return_value=[]),
|
||||
patch("src.client.apply_prompt_template", return_value="prompt"),
|
||||
patch("deerflow.client.create_chat_model"),
|
||||
patch("deerflow.client.create_agent", side_effect=fake_create_agent),
|
||||
patch("deerflow.client._build_middlewares", return_value=[]),
|
||||
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
|
||||
patch.object(client, "_get_tools", return_value=[]),
|
||||
):
|
||||
client._ensure_agent(config_a)
|
||||
@@ -1173,10 +1173,10 @@ class TestScenarioAgentRecreation:
|
||||
config = client._get_runnable_config("t1", model_name="gpt-4")
|
||||
|
||||
with (
|
||||
patch("src.client.create_chat_model"),
|
||||
patch("src.client.create_agent", side_effect=fake_create_agent),
|
||||
patch("src.client._build_middlewares", return_value=[]),
|
||||
patch("src.client.apply_prompt_template", return_value="prompt"),
|
||||
patch("deerflow.client.create_chat_model"),
|
||||
patch("deerflow.client.create_agent", side_effect=fake_create_agent),
|
||||
patch("deerflow.client._build_middlewares", return_value=[]),
|
||||
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
|
||||
patch.object(client, "_get_tools", return_value=[]),
|
||||
):
|
||||
client._ensure_agent(config)
|
||||
@@ -1197,10 +1197,10 @@ class TestScenarioAgentRecreation:
|
||||
config = client._get_runnable_config("t1")
|
||||
|
||||
with (
|
||||
patch("src.client.create_chat_model"),
|
||||
patch("src.client.create_agent", side_effect=fake_create_agent),
|
||||
patch("src.client._build_middlewares", return_value=[]),
|
||||
patch("src.client.apply_prompt_template", return_value="prompt"),
|
||||
patch("deerflow.client.create_chat_model"),
|
||||
patch("deerflow.client.create_agent", side_effect=fake_create_agent),
|
||||
patch("deerflow.client._build_middlewares", return_value=[]),
|
||||
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
|
||||
patch.object(client, "_get_tools", return_value=[]),
|
||||
):
|
||||
client._ensure_agent(config)
|
||||
@@ -1271,7 +1271,7 @@ class TestScenarioThreadIsolation:
|
||||
mock_paths = MagicMock()
|
||||
mock_paths.sandbox_user_data_dir.side_effect = lambda tid: data_a if tid == "thread-a" else data_b
|
||||
|
||||
with patch("src.client.get_paths", return_value=mock_paths):
|
||||
with patch("deerflow.client.get_paths", return_value=mock_paths):
|
||||
content, _ = client.get_artifact("thread-a", "mnt/user-data/outputs/result.txt")
|
||||
assert content == b"thread-a artifact"
|
||||
|
||||
@@ -1302,17 +1302,17 @@ class TestScenarioMemoryWorkflow:
|
||||
config.injection_enabled = True
|
||||
config.max_injection_tokens = 2000
|
||||
|
||||
with patch("src.agents.memory.updater.get_memory_data", return_value=initial_data):
|
||||
with patch("deerflow.agents.memory.updater.get_memory_data", return_value=initial_data):
|
||||
mem = client.get_memory()
|
||||
assert len(mem["facts"]) == 1
|
||||
|
||||
with patch("src.agents.memory.updater.reload_memory_data", return_value=updated_data):
|
||||
with patch("deerflow.agents.memory.updater.reload_memory_data", return_value=updated_data):
|
||||
refreshed = client.reload_memory()
|
||||
assert len(refreshed["facts"]) == 2
|
||||
|
||||
with (
|
||||
patch("src.config.memory_config.get_memory_config", return_value=config),
|
||||
patch("src.agents.memory.updater.get_memory_data", return_value=updated_data),
|
||||
patch("deerflow.config.memory_config.get_memory_config", return_value=config),
|
||||
patch("deerflow.agents.memory.updater.get_memory_data", return_value=updated_data),
|
||||
):
|
||||
status = client.get_memory_status()
|
||||
assert status["config"]["enabled"] is True
|
||||
@@ -1340,8 +1340,8 @@ class TestScenarioSkillInstallAndUse:
|
||||
|
||||
# Step 1: Install
|
||||
with (
|
||||
patch("src.skills.loader.get_skills_root_path", return_value=skills_root),
|
||||
patch("src.gateway.routers.skills._validate_skill_frontmatter", return_value=(True, "OK", "my-analyzer")),
|
||||
patch("deerflow.skills.loader.get_skills_root_path", return_value=skills_root),
|
||||
patch("deerflow.skills.validation._validate_skill_frontmatter", return_value=(True, "OK", "my-analyzer")),
|
||||
):
|
||||
result = client.install_skill(archive)
|
||||
assert result["success"] is True
|
||||
@@ -1355,7 +1355,7 @@ class TestScenarioSkillInstallAndUse:
|
||||
installed_skill.category = "custom"
|
||||
installed_skill.enabled = True
|
||||
|
||||
with patch("src.skills.loader.load_skills", return_value=[installed_skill]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[installed_skill]):
|
||||
skills_result = client.list_skills()
|
||||
assert any(s["name"] == "my-analyzer" for s in skills_result["skills"])
|
||||
|
||||
@@ -1375,10 +1375,10 @@ class TestScenarioSkillInstallAndUse:
|
||||
config_file.write_text("{}")
|
||||
|
||||
with (
|
||||
patch("src.skills.loader.load_skills", side_effect=[[installed_skill], [disabled_skill]]),
|
||||
patch("src.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("src.client.get_extensions_config", return_value=ext_config),
|
||||
patch("src.client.reload_extensions_config"),
|
||||
patch("deerflow.skills.loader.load_skills", side_effect=[[installed_skill], [disabled_skill]]),
|
||||
patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("deerflow.client.get_extensions_config", return_value=ext_config),
|
||||
patch("deerflow.client.reload_extensions_config"),
|
||||
):
|
||||
toggled = client.update_skill("my-analyzer", enabled=False)
|
||||
assert toggled["enabled"] is False
|
||||
@@ -1475,8 +1475,8 @@ class TestScenarioEdgeCases:
|
||||
|
||||
with (
|
||||
patch.object(DeerFlowClient, "_get_uploads_dir", return_value=uploads_dir),
|
||||
patch("src.gateway.routers.uploads.CONVERTIBLE_EXTENSIONS", {".pdf"}),
|
||||
patch("src.gateway.routers.uploads.convert_file_to_markdown", side_effect=Exception("conversion failed")),
|
||||
patch("deerflow.utils.file_conversion.CONVERTIBLE_EXTENSIONS", {".pdf"}),
|
||||
patch("deerflow.utils.file_conversion.convert_file_to_markdown", side_effect=Exception("conversion failed")),
|
||||
):
|
||||
result = client.upload_files("t-pdf-fail", [pdf_file])
|
||||
|
||||
@@ -1508,7 +1508,7 @@ class TestGatewayConformance:
|
||||
model.supports_thinking = False
|
||||
mock_app_config.models = [model]
|
||||
|
||||
with patch("src.client.get_app_config", return_value=mock_app_config):
|
||||
with patch("deerflow.client.get_app_config", return_value=mock_app_config):
|
||||
client = DeerFlowClient()
|
||||
|
||||
result = client.list_models()
|
||||
@@ -1525,7 +1525,7 @@ class TestGatewayConformance:
|
||||
mock_app_config.models = [model]
|
||||
mock_app_config.get_model_config.return_value = model
|
||||
|
||||
with patch("src.client.get_app_config", return_value=mock_app_config):
|
||||
with patch("deerflow.client.get_app_config", return_value=mock_app_config):
|
||||
client = DeerFlowClient()
|
||||
|
||||
result = client.get_model("test-model")
|
||||
@@ -1541,7 +1541,7 @@ class TestGatewayConformance:
|
||||
skill.category = "public"
|
||||
skill.enabled = True
|
||||
|
||||
with patch("src.skills.loader.load_skills", return_value=[skill]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[skill]):
|
||||
result = client.list_skills()
|
||||
|
||||
parsed = SkillsListResponse(**result)
|
||||
@@ -1556,7 +1556,7 @@ class TestGatewayConformance:
|
||||
skill.category = "public"
|
||||
skill.enabled = True
|
||||
|
||||
with patch("src.skills.loader.load_skills", return_value=[skill]):
|
||||
with patch("deerflow.skills.loader.load_skills", return_value=[skill]):
|
||||
result = client.get_skill("web-search")
|
||||
|
||||
assert result is not None
|
||||
@@ -1574,7 +1574,7 @@ class TestGatewayConformance:
|
||||
|
||||
custom_dir = tmp_path / "custom"
|
||||
custom_dir.mkdir()
|
||||
with patch("src.skills.loader.get_skills_root_path", return_value=tmp_path):
|
||||
with patch("deerflow.skills.loader.get_skills_root_path", return_value=tmp_path):
|
||||
result = client.install_skill(archive)
|
||||
|
||||
parsed = SkillInstallResponse(**result)
|
||||
@@ -1596,7 +1596,7 @@ class TestGatewayConformance:
|
||||
ext_config = MagicMock()
|
||||
ext_config.mcp_servers = {"test": server}
|
||||
|
||||
with patch("src.client.get_extensions_config", return_value=ext_config):
|
||||
with patch("deerflow.client.get_extensions_config", return_value=ext_config):
|
||||
result = client.get_mcp_config()
|
||||
|
||||
parsed = McpConfigResponse(**result)
|
||||
@@ -1622,9 +1622,9 @@ class TestGatewayConformance:
|
||||
config_file.write_text("{}")
|
||||
|
||||
with (
|
||||
patch("src.client.get_extensions_config", return_value=ext_config),
|
||||
patch("src.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("src.client.reload_extensions_config", return_value=ext_config),
|
||||
patch("deerflow.client.get_extensions_config", return_value=ext_config),
|
||||
patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=config_file),
|
||||
patch("deerflow.client.reload_extensions_config", return_value=ext_config),
|
||||
):
|
||||
result = client.update_mcp_config({"srv": server.model_dump.return_value})
|
||||
|
||||
@@ -1655,7 +1655,7 @@ class TestGatewayConformance:
|
||||
mem_cfg.injection_enabled = True
|
||||
mem_cfg.max_injection_tokens = 2000
|
||||
|
||||
with patch("src.config.memory_config.get_memory_config", return_value=mem_cfg):
|
||||
with patch("deerflow.config.memory_config.get_memory_config", return_value=mem_cfg):
|
||||
result = client.get_memory_config()
|
||||
|
||||
parsed = MemoryConfigResponse(**result)
|
||||
@@ -1689,8 +1689,8 @@ class TestGatewayConformance:
|
||||
}
|
||||
|
||||
with (
|
||||
patch("src.config.memory_config.get_memory_config", return_value=mem_cfg),
|
||||
patch("src.agents.memory.updater.get_memory_data", return_value=memory_data),
|
||||
patch("deerflow.config.memory_config.get_memory_config", return_value=mem_cfg),
|
||||
patch("deerflow.agents.memory.updater.get_memory_data", return_value=memory_data),
|
||||
):
|
||||
result = client.get_memory_status()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user