refactor(skill-review): use create_agent with skill_evolution disabled to allow autonomous skill creation

This commit is contained in:
xuhd committed 2026-06-05 13:32:19 +08:00
1 parent b5d80025e9
commit 3955bd72b5
1 file changed
+57 -29
@@ -123,15 +123,26 @@ _REVIEW_SYSTEM_PROMPT = textwrap.dedent("""\
async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "AppConfig") -> None: async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "AppConfig") -> None:
"""Core review logic: run a minimal lead agent with full middleware support. """Core review logic: create_agent with full middleware chain, no 'silent creation' constraint.
Reuses make_lead_agent so that ToolErrorHandlingMiddleware, LLMErrorHandlingMiddleware, Uses create_agent (the deerflow agent factory) directly so we can:
and the rest of the middleware chain handle all error cases uniformly — no custom - Reuse the full middleware chain (ToolErrorHandlingMiddleware, etc.)
handle_tool_errors hack needed. - Reuse the full tool set
- But pass app_config with skill_evolution.enabled=False to strip the
"Never create skills silently without user confirmation" constraint
from the system prompt.
Review instructions are injected via PromptPrefixMiddleware through
additional_kwargs["prompt_prefix"] on the HumanMessage.
""" """
from langchain_core.runnables import RunnableConfig from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from deerflow.agents.lead_agent.agent import make_lead_agent from deerflow.agents.lead_agent.prompt import apply_prompt_template
from deerflow.agents.middlewares.prompt_prefix_middleware import PromptPrefixMiddleware
from deerflow.agents.middlewares.tool_error_handling_middleware import build_lead_runtime_middlewares
from deerflow.agents.thread_state import ThreadState
from deerflow.models import create_chat_model
from deerflow.tools.tools import get_available_tools
evolution_cfg = app_config.skill_evolution evolution_cfg = app_config.skill_evolution
timeout = evolution_cfg.review_worker.timeout_seconds timeout = evolution_cfg.review_worker.timeout_seconds
@@ -139,36 +150,53 @@ async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "
model_name = evolution_cfg.review_worker.model_name model_name = evolution_cfg.review_worker.model_name
conversation_text = _format_messages_for_review(messages, max_messages) conversation_text = _format_messages_for_review(messages, max_messages)
user_message = (
_REVIEW_SYSTEM_PROMPT # Disable skill_evolution so the system prompt does NOT include
+ "\n\nReview this conversation and apply any skill changes you deem appropriate.\n\n" # "Never create skills silently without user confirmation".
f"<conversation>\n{conversation_text}\n</conversation>" # Also prevents SkillReviewMiddleware from registering (no recursive reviews).
review_app_config = app_config.model_copy(deep=True)
review_app_config.skill_evolution.enabled = False
try:
model = create_chat_model(name=model_name, app_config=review_app_config)
except Exception:
logger.warning("skill_review_worker: could not load model %r, aborting review", model_name)
return
tools = get_available_tools(app_config=review_app_config, subagent_enabled=False)
middlewares = build_lead_runtime_middlewares(app_config=review_app_config)
middlewares.append(PromptPrefixMiddleware())
system_prompt = apply_prompt_template(
subagent_enabled=False,
max_concurrent_subagents=0,
app_config=review_app_config,
memory_injection_enabled=False,
) )
# Use a synthetic thread id so this review run is isolated from the source thread. agent = create_agent(
review_thread_id = f"__skill-review__{thread_id}" model=model,
tools=tools,
middleware=middlewares,
system_prompt=system_prompt,
state_schema=ThreadState,
)
configurable: dict = { # Review instructions arrive as prompt_prefix: PromptPrefixMiddleware prepends
"thread_id": review_thread_id, # them before every model call without persisting them to state.
"is_bootstrap": True, # skip MemoryMiddleware injection first_message = HumanMessage(
"is_scheduled_run": True, # skip TitleMiddleware content=(
"subagent_enabled": False, # no task-delegation tool "Review this conversation and apply any skill changes you deem appropriate.\n\n"
"app_config": app_config, f"<conversation>\n{conversation_text}\n</conversation>"
} ),
if model_name: additional_kwargs={"prompt_prefix": _REVIEW_SYSTEM_PROMPT},
configurable["model_name"] = model_name )
run_config = RunnableConfig(configurable=configurable)
agent = make_lead_agent(run_config)
try: try:
result = await asyncio.wait_for( result = await asyncio.wait_for(
asyncio.get_event_loop().run_in_executor( asyncio.get_event_loop().run_in_executor(
None, None,
lambda: agent.invoke( lambda: agent.invoke({"messages": [first_message]}),
{"messages": [("user", user_message)]},
config=run_config,
),
), ),
timeout=timeout, timeout=timeout,
) )
@@ -178,7 +206,7 @@ async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "
for msg in reversed(final_msgs): for msg in reversed(final_msgs):
content = getattr(msg, "content", None) content = getattr(msg, "content", None)
if content and isinstance(content, str) and content.strip(): if content and isinstance(content, str) and content.strip():
final_text = content.strip()[:300] final_text = content.strip()[:1000]
break break
logger.info( logger.info(
"skill_review_worker: review completed for thread %s — agent said: %s", "skill_review_worker: review completed for thread %s — agent said: %s",