From 5e42996b3691075ed52f26ced0be4f3a7d8c2bfa Mon Sep 17 00:00:00 2001 From: zhayujie Date: Tue, 17 Mar 2026 18:34:09 +0800 Subject: [PATCH] fix: guide LLM to use matching skill when tool not found --- agent/protocol/agent_stream.py | 43 +++++++++++++++++++++++++++++++++- bridge/agent_bridge.py | 9 +++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/agent/protocol/agent_stream.py b/agent/protocol/agent_stream.py index 26f6fd4..6e1703c 100644 --- a/agent/protocol/agent_stream.py +++ b/agent/protocol/agent_stream.py @@ -875,7 +875,7 @@ class AgentStreamExecutor: try: tool = self.tools.get(tool_name) if not tool: - raise ValueError(f"Tool '{tool_name}' not found") + raise ValueError(self._build_tool_not_found_message(tool_name)) # Set tool context tool.model = self.model @@ -929,6 +929,47 @@ class AgentStreamExecutor: }) return error_result + def _build_tool_not_found_message(self, tool_name: str) -> str: + """Build a helpful error message when a tool is not found. + + If a skill with the same name exists in skill_manager, read its + SKILL.md and include the content so the LLM knows how to use it. + """ + available_tools = list(self.tools.keys()) + base_msg = f"Tool '{tool_name}' not found. Available tools: {available_tools}" + + skill_manager = getattr(self.agent, 'skill_manager', None) + if not skill_manager: + return base_msg + + skill_entry = skill_manager.get_skill(tool_name) + if not skill_entry: + return base_msg + + skill = skill_entry.skill + skill_md_path = skill.file_path + skill_content = "" + try: + with open(skill_md_path, 'r', encoding='utf-8') as f: + skill_content = f.read() + except Exception: + skill_content = skill.description + + logger.info( + f"[Agent] Tool '{tool_name}' not found, but matched skill '{skill.name}'. " + f"Guiding LLM to use the skill instead." + ) + + return ( + f"Tool '{tool_name}' is not a built-in tool, but a matching skill " + f"'{skill.name}' is available. You should use existing tools (e.g. bash with curl) " + f"to accomplish this task following the skill instructions below:\n\n" + f"--- SKILL: {skill.name} (path: {skill_md_path}) ---\n" + f"{skill_content}\n" + f"--- END SKILL ---\n\n" + f"Available tools: {available_tools}" + ) + def _validate_and_fix_messages(self): """Delegate to the shared sanitizer (see message_sanitizer.py).""" sanitize_claude_messages(self.messages) diff --git a/bridge/agent_bridge.py b/bridge/agent_bridge.py index e7458bd..2f27661 100644 --- a/bridge/agent_bridge.py +++ b/bridge/agent_bridge.py @@ -278,12 +278,13 @@ class AgentBridge: tools=tools, max_steps=kwargs.get("max_steps", 15), output_mode=kwargs.get("output_mode", "logger"), - workspace_dir=kwargs.get("workspace_dir"), # Pass workspace for skills loading - enable_skills=kwargs.get("enable_skills", True), # Enable skills by default - memory_manager=kwargs.get("memory_manager"), # Pass memory manager + workspace_dir=kwargs.get("workspace_dir"), + skill_manager=kwargs.get("skill_manager"), + enable_skills=kwargs.get("enable_skills", True), + memory_manager=kwargs.get("memory_manager"), max_context_tokens=kwargs.get("max_context_tokens"), context_reserve_tokens=kwargs.get("context_reserve_tokens"), - runtime_info=kwargs.get("runtime_info") # Pass runtime_info for dynamic time updates + runtime_info=kwargs.get("runtime_info"), ) # Log skill loading details