refactor(skill-review): use create_agent with skill_evolution disabled to allow autonomous skill creation
This commit is contained in:
1 parent
b5d80025e9
commit
3955bd72b5
1 file changed
+57
-29
+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",
|
||||||
|
|||||||
Reference in new issue
Block a user