Files
deerflow-code/offline-backend-20260512/backend/tests/test_skill_review_worker.py
T
xuhdandClaude Opus 4.7 c493639119 feat(skill-review): strict-mode prompt + raise min_tool_calls 3 to 5
The review agent was creating too many low-value skills. Three changes
tighten the funnel:

- review_worker prompt: rewritten in "保守模式". Default decision is
  do-nothing. To create, all five AND conditions must hold (>=5 tool
  calls with step dependencies, an explicit fail->fix->success episode,
  cross-scenario reusability, no >=70% overlap with skill_list, can
  produce >=300 chars of substance). Adds 7 explicit NO categories
  (single-tool loops, one-off queries, LLM common knowledge, etc.)
  and a 4-question self-check before any create. Hard cap of 1 new
  skill per review, with patch preferred over create.
- min_tool_calls: 3 -> 5. Matches the prompt's ">=5 steps" hard
  condition so trivial turns never reach the LLM reviewer.
- Updated config.yaml, the default-value assertion in
  test_skill_review_worker, and the middleware docstring's "(default
  3)" annotation to keep all three references in sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 16:00:29 +08:00

103 lines
3.5 KiB
Python

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"
from unittest.mock import AsyncMock, MagicMock, patch
import asyncio
import pytest
from langchain_core.messages import HumanMessage
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_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()