Files

144 lines
4.9 KiB
Python

from unittest.mock import MagicMock, patch
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from deerflow.config.skill_evolution_config import SkillEvolutionConfig
def test_review_worker_config_defaults():
cfg = SkillEvolutionConfig()
assert cfg.review_worker.enabled is False
assert cfg.review_worker.min_tool_calls == 5
assert cfg.review_worker.timeout_seconds == 120
assert cfg.review_worker.model_name is None
assert cfg.review_worker.max_messages == 60
def test_review_worker_config_from_dict():
cfg = SkillEvolutionConfig.model_validate({
"enabled": True,
"review_worker": {"enabled": True, "min_tool_calls": 5, "model_name": "my-model"},
})
assert cfg.review_worker.enabled is True
assert cfg.review_worker.min_tool_calls == 5
assert cfg.review_worker.model_name == "my-model"
def _make_ai_message(tool_calls_data: list[dict]):
msg = MagicMock()
msg.__class__.__name__ = "AIMessage"
tcs = []
for tc in tool_calls_data:
m = MagicMock()
m.name = tc["name"]
m.args = tc.get("args", {})
tcs.append(m)
msg.tool_calls = tcs
return msg
def test_extract_tool_call_count_counts_all():
from deerflow.skills.review_worker import extract_tool_call_count
msgs = [
HumanMessage(content="do something"),
_make_ai_message([{"name": "bash"}, {"name": "read_file"}]),
_make_ai_message([{"name": "write_file"}]),
]
assert extract_tool_call_count(msgs) == 3
def test_extract_tool_call_count_empty():
from deerflow.skills.review_worker import extract_tool_call_count
assert extract_tool_call_count([]) == 0
def test_internal_write_handshake_is_excluded_from_review_count_and_prompt():
from deerflow.skills.review_worker import _format_messages_for_review, extract_tool_call_count
messages = [
HumanMessage(content="写报告"),
AIMessage(
content="好的,我来写。",
tool_calls=[
{
"id": "begin-1",
"name": "begin_write_file",
"args": {"path": "/mnt/user-data/outputs/report.md"},
"type": "tool_call",
}
],
),
ToolMessage(
content='{"status":"ready"}',
name="begin_write_file",
tool_call_id="begin-1",
),
AIMessage(
content="",
tool_calls=[
{
"id": "write-1",
"name": "write_file",
"args": {"path": "/mnt/user-data/outputs/report.md", "content": "# 报告"},
"type": "tool_call",
}
],
),
]
assert extract_tool_call_count(messages) == 1
review_text = _format_messages_for_review(messages, max_messages=20)
assert "begin_write_file" not in review_text
assert '"status":"ready"' not in review_text
assert "write_file()" in review_text
def test_maybe_start_review_disabled_returns_false():
from deerflow.skills.review_worker import maybe_start_review
cfg = MagicMock()
cfg.skill_evolution.enabled = False
cfg.skill_evolution.review_worker.enabled = True
cfg.skill_evolution.review_worker.min_tool_calls = 2
msgs = [_make_ai_message([{"name": "bash"}, {"name": "read_file"}, {"name": "write_file"}])]
assert maybe_start_review("thread-1", msgs, cfg) is False
def test_maybe_start_review_too_few_tool_calls_returns_false():
from deerflow.skills.review_worker import maybe_start_review
cfg = MagicMock()
cfg.skill_evolution.enabled = True
cfg.skill_evolution.review_worker.enabled = True
cfg.skill_evolution.review_worker.min_tool_calls = 5
msgs = [_make_ai_message([{"name": "bash"}])]
assert maybe_start_review("thread-4", msgs, cfg) is False
def test_maybe_start_review_starts_task():
from deerflow.skills import review_worker as rw
# Reset module state so thread-3 has no lock
rw._locks.pop("thread-3", None)
rw._pending.pop("thread-3", None)
cfg = MagicMock()
cfg.skill_evolution.enabled = True
cfg.skill_evolution.review_worker.enabled = True
cfg.skill_evolution.review_worker.min_tool_calls = 2
cfg.skill_evolution.review_worker.timeout_seconds = 30
cfg.skill_evolution.review_worker.max_messages = 20
cfg.skill_evolution.review_worker.model_name = None
msgs = [_make_ai_message([{"name": "bash"}, {"name": "read_file"}, {"name": "write_file"}])]
with patch("deerflow.skills.review_worker.asyncio") as mock_asyncio:
fake_lock = MagicMock()
fake_lock.locked.return_value = False
mock_asyncio.Lock.return_value = fake_lock
mock_asyncio.create_task = MagicMock()
result = rw.maybe_start_review("thread-3", msgs, cfg)
assert result is True
mock_asyncio.create_task.assert_called_once()
scheduled_coro = mock_asyncio.create_task.call_args.args[0]
scheduled_coro.close()