From f5bc02d598b09769c0a0ec937965c060971ded92 Mon Sep 17 00:00:00 2001 From: xuhd Date: Thu, 14 May 2026 15:43:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20skills=20=E8=B7=AF=E7=94=B1=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E5=B0=8F=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit skills.py: - update_curator_config 直接 await get_curator_config() 报错 ('Depends' object has no attribute 'skill_evolution')。FastAPI 路由 默认参数 Depends(...) 只在请求时被框架解析,直接调用拿到的是依赖标记。 抽出 _build_curator_config_response(config) 让 GET 和 PUT 共用。 skill_manage_tool.py: - 模型把 action='list' 当成可用动作误调 skill_manage 报 Unsupported action。补两个 read-only 别名 list/view,分别委派到 skill_list_tool 和 skill_view_tool,docstring 同步更新。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../backend/app/gateway/routers/skills.py | 39 +++++++++++-------- .../deerflow/tools/skill_manage_tool.py | 15 +++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/offline-backend-20260512/backend/app/gateway/routers/skills.py b/offline-backend-20260512/backend/app/gateway/routers/skills.py index c2736e31..2d1b4122 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/skills.py +++ b/offline-backend-20260512/backend/app/gateway/routers/skills.py @@ -608,21 +608,26 @@ class CuratorLogsResponse(BaseModel): last_run_at: str | None +def _build_curator_config_response(config: AppConfig) -> CuratorConfigResponse: + """Pure helper so both the GET route and the post-PUT refresh share one shape.""" + evo = config.skill_evolution + return CuratorConfigResponse( + enabled=evo.enabled, + moderation_model_name=evo.moderation_model_name, + curator=CuratorSubConfig( + enabled=evo.curator.enabled, + interval_hours=evo.curator.interval_hours, + stale_after_days=evo.curator.stale_after_days, + archive_after_days=evo.curator.archive_after_days, + ), + ) + + @router.get("/curator/config", response_model=CuratorConfigResponse, summary="Get Skill Evolution Config") async def get_curator_config(config: AppConfig = Depends(get_config)) -> CuratorConfigResponse: """Return the current effective skill_evolution config (yaml defaults + skill_evolution.json overrides).""" try: - evo = config.skill_evolution - return CuratorConfigResponse( - enabled=evo.enabled, - moderation_model_name=evo.moderation_model_name, - curator=CuratorSubConfig( - enabled=evo.curator.enabled, - interval_hours=evo.curator.interval_hours, - stale_after_days=evo.curator.stale_after_days, - archive_after_days=evo.curator.archive_after_days, - ), - ) + return _build_curator_config_response(config) except Exception as e: logger.error("Failed to read curator config: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to read curator config: {str(e)}") @@ -636,6 +641,7 @@ async def update_curator_config(body: CuratorConfigRequest) -> CuratorConfigResp fields are rejected by the Pydantic model_validate call. """ try: + from deerflow.config.app_config import get_app_config, reset_app_config from deerflow.config.skill_evolution_config import SkillEvolutionConfig from deerflow.config.skill_evolution_runtime import save_overrides @@ -644,13 +650,12 @@ async def update_curator_config(body: CuratorConfigRequest) -> CuratorConfigResp SkillEvolutionConfig.model_validate(payload) save_overrides(payload) - # Force the next get_app_config() call to pick up the new overrides. - # mtime check inside get_app_config() will trigger reload automatically, - # but resetting here makes the response below reflect the new state. - from deerflow.config.app_config import reset_app_config + # Reset cache so the next get_app_config() picks up the new overrides + # (the mtime check would catch it too, but resetting makes the response + # below deterministically reflect the new state). reset_app_config() - - return await get_curator_config() + fresh_config = get_app_config() + return _build_curator_config_response(fresh_config) except HTTPException: raise except Exception as e: diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py b/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py index 74477871..1d6859c7 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py @@ -78,6 +78,10 @@ async def _skill_manage_impl( Args: action: One of create, patch, edit, delete, write_file, remove_file. + For listing or viewing skills, prefer the dedicated + ``skill_list`` / ``skill_view`` tools — but ``action='list'`` + and ``action='view'`` are accepted here as convenience + aliases that delegate to those tools. name: Skill name in hyphen-case. content: New file content for create, edit, or write_file. path: Supporting file path for write_file or remove_file. @@ -85,6 +89,17 @@ async def _skill_manage_impl( replace: Replacement text for patch. expected_count: Optional expected number of replacements for patch. """ + # Read-only aliases — delegate to the dedicated tools so a model that + # over-uses skill_manage doesn't trip on Unsupported action errors. + if action == "list": + from deerflow.tools.builtins.skill_tools import skill_list_tool + + return await _to_thread(skill_list_tool.invoke, {}) + if action == "view": + from deerflow.tools.builtins.skill_tools import skill_view_tool + + return await _to_thread(skill_view_tool.invoke, {"name": name}) + name = SkillStorage.validate_skill_name(name) lock = _get_lock(name) thread_id = _get_thread_id(runtime)