diff --git a/model_tools.py b/model_tools.py index 9210e732e4..b5035ab329 100644 --- a/model_tools.py +++ b/model_tools.py @@ -41,7 +41,7 @@ from tools.terminal_hecate import terminal_hecate_tool, check_hecate_requirement from tools.vision_tools import vision_analyze_tool, check_vision_requirements from tools.mixture_of_agents_tool import mixture_of_agents_tool, check_moa_requirements from tools.image_generation_tool import image_generate_tool, check_image_generation_requirements -from tools.skills_tool import skills_categories, skills_list, skill_view, check_skills_requirements, SKILLS_TOOL_DESCRIPTION +from tools.skills_tool import skills_list, skill_view, check_skills_requirements, SKILLS_TOOL_DESCRIPTION # RL Training tools (Tinker-Atropos) from tools.rl_training_tool import ( rl_list_environments, @@ -143,7 +143,7 @@ TOOLSET_REQUIREMENTS = { "env_vars": [], # Just needs skills directory "check_fn": check_skills_requirements, "setup_url": None, - "tools": ["skills_categories", "skills_list", "skill_view"], + "tools": ["skills_list", "skill_view"], }, "rl": { "name": "RL Training (Tinker-Atropos)", @@ -432,24 +432,7 @@ def get_skills_tool_definitions() -> List[Dict[str, Any]]: "properties": { "category": { "type": "string", - "description": "Optional category filter (from skills_categories)" - } - }, - "required": [] - } - } - }, - { - "type": "function", - "function": { - "name": "skills_categories", - "description": "List available skill categories. Call this first to discover what skill categories exist, then use skills_list(category) to see skills in a category.", - "parameters": { - "type": "object", - "properties": { - "verbose": { - "type": "boolean", - "description": "If true, include skill counts per category. Default: false." + "description": "Optional category filter to narrow results" } }, "required": [] @@ -910,7 +893,7 @@ def get_all_tool_names() -> List[str]: # Skills tools if check_skills_requirements(): - tool_names.extend(["skills_categories", "skills_list", "skill_view"]) + tool_names.extend(["skills_list", "skill_view"]) # Browser automation tools if check_browser_requirements(): @@ -957,7 +940,6 @@ TOOL_TO_TOOLSET_MAP = { "mixture_of_agents": "moa_tools", "image_generate": "image_tools", # Skills tools - "skills_categories": "skills_tools", "skills_list": "skills_tools", "skill_view": "skills_tools", # Browser automation tools @@ -1109,7 +1091,7 @@ def get_tool_definitions( "vision_tools": ["vision_analyze"], "moa_tools": ["mixture_of_agents"], "image_tools": ["image_generate"], - "skills_tools": ["skills_categories", "skills_list", "skill_view"], + "skills_tools": ["skills_list", "skill_view"], "browser_tools": [ "browser_navigate", "browser_snapshot", "browser_click", "browser_type", "browser_scroll", "browser_back", @@ -1162,7 +1144,7 @@ def get_tool_definitions( "vision_tools": ["vision_analyze"], "moa_tools": ["mixture_of_agents"], "image_tools": ["image_generate"], - "skills_tools": ["skills_categories", "skills_list", "skill_view"], + "skills_tools": ["skills_list", "skill_view"], "browser_tools": [ "browser_navigate", "browser_snapshot", "browser_click", "browser_type", "browser_scroll", "browser_back", @@ -1391,11 +1373,7 @@ def handle_skills_function_call(function_name: str, function_args: Dict[str, Any Returns: str: Function result as JSON string """ - if function_name == "skills_categories": - verbose = function_args.get("verbose", False) - return skills_categories(verbose=verbose) - - elif function_name == "skills_list": + if function_name == "skills_list": category = function_args.get("category") return skills_list(category=category) @@ -1686,7 +1664,7 @@ def handle_function_call( return handle_image_function_call(function_name, function_args) # Route skills tools - elif function_name in ["skills_categories", "skills_list", "skill_view"]: + elif function_name in ["skills_list", "skill_view"]: return handle_skills_function_call(function_name, function_args) # Route browser automation tools @@ -1767,7 +1745,7 @@ def get_available_toolsets() -> Dict[str, Dict[str, Any]]: }, "skills_tools": { "available": check_skills_requirements(), - "tools": ["skills_categories", "skills_list", "skill_view"], + "tools": ["skills_list", "skill_view"], "description": "Access skill documents that provide specialized instructions, guidelines, or knowledge the agent can load on demand", "requirements": ["skills/ directory in repo root"] }, diff --git a/run_agent.py b/run_agent.py index 0a1af3fcbf..1d95a19f3c 100644 --- a/run_agent.py +++ b/run_agent.py @@ -550,14 +550,79 @@ def apply_anthropic_cache_control( # Default System Prompt Components # ============================================================================= -# Skills guidance - instructs the model to check skills before technical tasks -SKILLS_SYSTEM_PROMPT = """## Skills -Before answering technical questions about tools, frameworks, or workflows: -1. Check skills_categories to see if a relevant category exists -2. If a category matches your task, use skills_list with that category -3. If a skill matches, load it with skill_view and follow its instructions - -Skills contain vetted, up-to-date instructions for specific tools and workflows.""" +# Skills guidance - embeds a compact skill index in the system prompt so +# the model can match skills at a glance without extra tool calls. +def build_skills_system_prompt() -> str: + """ + Build a dynamic skills system prompt by scanning the skills/ directory. + + Returns a prompt section that lists all skill categories (with descriptions + from DESCRIPTION.md) and their skill names inline, so the model can + immediately see if a relevant skill exists and load it with a single + skill_view(name) call -- no discovery tool calls needed. + + Returns: + str: The skills system prompt section, or empty string if no skills found. + """ + import re + from pathlib import Path + + skills_dir = Path(__file__).parent / "skills" + if not skills_dir.exists(): + return "" + + # Scan for SKILL.md files grouped by category + skills_by_category = {} + for skill_file in skills_dir.rglob("SKILL.md"): + rel_path = skill_file.relative_to(skills_dir) + parts = rel_path.parts + if len(parts) >= 2: + category = parts[0] + skill_name = parts[-2] # Folder containing SKILL.md + else: + category = "general" + skill_name = skill_file.parent.name + skills_by_category.setdefault(category, []).append(skill_name) + + if not skills_by_category: + return "" + + # Load category descriptions from DESCRIPTION.md files (YAML frontmatter) + category_descriptions = {} + for category in skills_by_category: + desc_file = skills_dir / category / "DESCRIPTION.md" + if desc_file.exists(): + try: + content = desc_file.read_text(encoding="utf-8") + # Parse description from YAML frontmatter: ---\ndescription: ...\n--- + match = re.search(r"^---\s*\n.*?description:\s*(.+?)\s*\n.*?^---", content, re.MULTILINE | re.DOTALL) + if match: + category_descriptions[category] = match.group(1).strip() + except Exception: + pass + + # Build compact index: category with description + skill names + index_lines = [] + for category in sorted(skills_by_category.keys()): + desc = category_descriptions.get(category, "") + names = ", ".join(sorted(skills_by_category[category])) + if desc: + index_lines.append(f" {category}: {desc}") + else: + index_lines.append(f" {category}:") + index_lines.append(f" skills: {names}") + + return ( + "## Skills (mandatory)\n" + "Before replying, scan the skills below. If one clearly matches your task, " + "load it with skill_view(name) and follow its instructions.\n" + "\n" + "\n" + + "\n".join(index_lines) + "\n" + "\n" + "\n" + "If none match, proceed normally without loading a skill." + ) class KawaiiSpinner: @@ -1054,10 +1119,6 @@ class AIAgent: return f"{face} 🎨 creating '{prompt}'... {time_str}" # Skills - use large pool for variety - elif tool_name == "skills_categories": - face = random.choice(self.KAWAII_SKILL) - return f"{face} 📚 listing categories... {time_str}" - elif tool_name == "skills_list": category = args.get("category", "skills") face = random.choice(self.KAWAII_SKILL) @@ -1635,12 +1696,15 @@ class AIAgent: base_system_prompt = system_message if system_message is not None else self.ephemeral_system_prompt # Auto-include skills guidance if skills tools are available - has_skills_tools = any(name in self.valid_tool_names for name in ['skills_list', 'skills_categories', 'skill_view']) - if has_skills_tools: + # Embeds a compact category:names index so the model can match skills + # at a glance and load with a single skill_view(name) call. + has_skills_tools = any(name in self.valid_tool_names for name in ['skills_list', 'skill_view']) + skills_prompt = build_skills_system_prompt() if has_skills_tools else "" + if skills_prompt: if base_system_prompt: - active_system_prompt = f"{base_system_prompt}\n\n{SKILLS_SYSTEM_PROMPT}" + active_system_prompt = f"{base_system_prompt}\n\n{skills_prompt}" else: - active_system_prompt = SKILLS_SYSTEM_PROMPT + active_system_prompt = skills_prompt else: active_system_prompt = base_system_prompt @@ -2277,7 +2341,6 @@ class AIAgent: 'image_generate': ('sparkle', ['🎨', '✨', '🖼️', '🌟']), 'skill_view': ('star', ['📚', '📖', '🎓', '✨']), 'skills_list': ('pulse', ['📋', '📝', '📑', '📜']), - 'skills_categories': ('pulse', ['📂', '🗂️', '📁', '🏷️']), 'moa_query': ('brain', ['🧠', '💭', '🤔', '💡']), 'analyze_image': ('sparkle', ['👁️', '🔍', '📷', '✨']), } diff --git a/tools/__init__.py b/tools/__init__.py index 18d9cfcbf9..b69dbbce72 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -59,7 +59,6 @@ from .image_generation_tool import ( ) from .skills_tool import ( - skills_categories, skills_list, skill_view, check_skills_requirements, @@ -158,7 +157,6 @@ __all__ = [ 'image_generate_tool', 'check_image_generation_requirements', # Skills tools - 'skills_categories', 'skills_list', 'skill_view', 'check_skills_requirements', diff --git a/toolsets.py b/toolsets.py index 7dac5ff144..4f0e5ee287 100644 --- a/toolsets.py +++ b/toolsets.py @@ -69,7 +69,7 @@ TOOLSETS = { "skills": { "description": "Access skill documents with specialized instructions and knowledge", - "tools": ["skills_categories", "skills_list", "skill_view"], + "tools": ["skills_list", "skill_view"], "includes": [] }, @@ -142,7 +142,7 @@ TOOLSETS = { # MoA "mixture_of_agents", # Skills - "skills_categories", "skills_list", "skill_view", + "skills_list", "skill_view", # Browser "browser_navigate", "browser_snapshot", "browser_click", "browser_type", "browser_scroll", "browser_back", @@ -170,7 +170,7 @@ TOOLSETS = { # Vision - analyze images sent by users "vision_analyze", # Skills - access knowledge base - "skills_categories", "skills_list", "skill_view", + "skills_list", "skill_view", # Cronjob management - let users schedule tasks "schedule_cronjob", "list_cronjobs", "remove_cronjob" ], @@ -185,7 +185,7 @@ TOOLSETS = { # Vision - analyze images "vision_analyze", # Skills - access knowledge base - "skills_categories", "skills_list", "skill_view", + "skills_list", "skill_view", # Cronjob - let users schedule reminders "schedule_cronjob", "list_cronjobs", "remove_cronjob" ], @@ -204,7 +204,7 @@ TOOLSETS = { # Vision "vision_analyze", # Skills - "skills_categories", "skills_list", "skill_view", + "skills_list", "skill_view", # Cronjob management "schedule_cronjob", "list_cronjobs", "remove_cronjob" ],