refactor(skill-review): use make_lead_agent instead of create_react_agent

Reuses the full middleware chain (ToolErrorHandlingMiddleware,
LLMErrorHandlingMiddleware, etc.) so tool errors like 'already exists'
and invalid SKILL.md are converted to ToolMessages by the middleware,
not silently swallowed or crashing the review task.
This commit is contained in:
xuhd committed 2026-06-05 13:32:19 +08:00
1 parent b2ed6f1acf
commit ad5bd22c5e
1 file changed
+38 -17
@@ -108,17 +108,28 @@ _REVIEW_SYSTEM_PROMPT = textwrap.dedent("""\
- The frontmatter block must start with `---` on its own line and end with `---`.
- Do NOT add extra keys like "tags", "category", "type", etc.
## Avoiding duplicate-creation errors
Before calling skill_manage with action="create":
1. Always call skill_list first.
2. If a skill with the same name already exists, use action="patch" instead.
Creating over an existing skill will raise an error.
3. If you get a tool error, read the message carefully and fix the issue
(wrong name, bad frontmatter, duplicate, etc.) before retrying.
Be conservative. Fewer high-quality skills beat many noisy ones.
""")
async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "AppConfig") -> None:
"""Core review logic: spin up a minimal React agent and invoke it."""
from langgraph.prebuilt import create_react_agent
"""Core review logic: run a minimal lead agent with full middleware support.
from deerflow.models.factory import create_chat_model
from deerflow.tools.builtins.skill_tools import skill_list_tool
from deerflow.tools.skill_manage_tool import skill_manage_tool
Reuses make_lead_agent so that ToolErrorHandlingMiddleware, LLMErrorHandlingMiddleware,
and the rest of the middleware chain handle all error cases uniformly — no custom
handle_tool_errors hack needed.
"""
from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent
evolution_cfg = app_config.skill_evolution
timeout = evolution_cfg.review_worker.timeout_seconds
@@ -126,26 +137,36 @@ async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "
model_name = evolution_cfg.review_worker.model_name
conversation_text = _format_messages_for_review(messages, max_messages)
try:
model = create_chat_model(model_name)
except Exception:
logger.warning("skill_review_worker: could not load model %r, aborting review", model_name)
return
tools = [skill_list_tool, skill_manage_tool]
agent = create_react_agent(model, tools, prompt=_REVIEW_SYSTEM_PROMPT)
user_message = (
"Review this conversation and apply any skill changes you deem appropriate.\n\n"
_REVIEW_SYSTEM_PROMPT
+ "\n\nReview this conversation and apply any skill changes you deem appropriate.\n\n"
f"<conversation>\n{conversation_text}\n</conversation>"
)
# Use a synthetic thread id so this review run is isolated from the source thread.
review_thread_id = f"__skill-review__{thread_id}"
configurable: dict = {
"thread_id": review_thread_id,
"is_bootstrap": True, # skip MemoryMiddleware injection
"is_scheduled_run": True, # skip TitleMiddleware
"subagent_enabled": False, # no task-delegation tool
"app_config": app_config,
}
if model_name:
configurable["model_name"] = model_name
run_config = RunnableConfig(configurable=configurable)
agent = make_lead_agent(run_config)
try:
await asyncio.wait_for(
asyncio.get_event_loop().run_in_executor(
None,
lambda: agent.invoke({"messages": [("user", user_message)]}),
lambda: agent.invoke(
{"messages": [("user", user_message)]},
config=run_config,
),
),
timeout=timeout,
)