feat(tools): add SkillSearchTool and improve skill_install workflow
Add skill_search tool so agent can search for skills before installing. Update skill_install description to guide LLM to search first. Update system prompt to use skill_search -> skill_install flow. This fixes the issue where agent returns empty when asked to find a skill.
This commit is contained in:
parent
f770d65c7b
commit
87c59bb3e2
|
|
@ -21,6 +21,7 @@ from agentkit.skills.base import Skill
|
|||
from agentkit.skills.registry import SkillRegistry
|
||||
from agentkit.tools.registry import ToolRegistry
|
||||
from agentkit.tools.skill_install import SkillInstallTool
|
||||
from agentkit.tools.skill_search import SkillSearchTool
|
||||
from agentkit.server.config import ServerConfig, load_dotenv
|
||||
from agentkit.server.routes import (
|
||||
agents,
|
||||
|
|
@ -174,9 +175,8 @@ async def lifespan(app: FastAPI):
|
|||
"中文内容优先使用 baidu_search 工具,英文/国际内容使用 web_search。"
|
||||
"在能够搜索到真相的情况下,绝不猜测或编造答案。"
|
||||
"始终优先搜索而不是给出可能不正确的信息。\n\n"
|
||||
"技能安装:当需要安装技能时,使用 skill_install 工具,不要用 shell 执行 npm install。"
|
||||
"skill_install 的 source 参数格式为 owner/repo@skill,例如 vercel-labs/skills@find-skills。"
|
||||
"如果不知道完整 source,先用 shell 执行 `npx skills search <name>` 搜索。"
|
||||
"技能安装:当需要查找或安装技能时,先用 skill_search 搜索确认技能名称和来源,"
|
||||
"再用 skill_install 安装。不要用 shell 执行 npm install 或 npx skills install。"
|
||||
)
|
||||
effective_system_prompt = memory_store.build_system_prompt(memory_snapshot, base_prompt)
|
||||
|
||||
|
|
@ -227,6 +227,7 @@ async def lifespan(app: FastAPI):
|
|||
tool_registry=app.state.tool_registry,
|
||||
)
|
||||
)
|
||||
agent._tool_registry.register(SkillSearchTool())
|
||||
agent._tool_registry.register(BaiduSearchTool())
|
||||
agent._tool_registry.register(WebSearchTool(**search_api_keys))
|
||||
agent._tool_registry.register(WebCrawlTool())
|
||||
|
|
|
|||
|
|
@ -24,7 +24,11 @@ class SkillInstallTool(Tool):
|
|||
def __init__(
|
||||
self,
|
||||
name: str = "skill_install",
|
||||
description: str = "安装 Agent 技能包。使用 npx skills install 安装指定技能,不要用 npm install。",
|
||||
description: str = (
|
||||
"安装 Agent 技能包。使用 npx skills install 安装指定技能,不要用 npm install。"
|
||||
"重要:安装前应先用 skill_search 工具搜索确认技能名称和来源(source)。"
|
||||
"如果用户只提供了模糊名称,先用 skill_search 搜索,再根据搜索结果安装。"
|
||||
),
|
||||
input_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
version: str = "1.0.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
"""SkillSearchTool - Agent 可调用的技能搜索工具"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from agentkit.tools.base import Tool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SkillSearchTool(Tool):
|
||||
"""技能搜索工具
|
||||
|
||||
让 Agent 可以搜索可用的技能包,在安装之前先确认技能是否存在。
|
||||
使用 `npx skills search <keyword>` 搜索可用技能。
|
||||
|
||||
Usage:
|
||||
tool = SkillSearchTool()
|
||||
result = await tool.execute(keyword="ppt")
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "skill_search",
|
||||
description: str = (
|
||||
"搜索可用的 Agent 技能包。在安装技能之前,应先使用此工具搜索确认技能名称和来源。"
|
||||
"返回匹配的技能列表,包含名称、描述和安装来源。"
|
||||
),
|
||||
input_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
version: str = "1.0.0",
|
||||
tags: list[str] | None = None,
|
||||
):
|
||||
schema = input_schema or {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"keyword": {
|
||||
"type": "string",
|
||||
"description": "搜索关键词,如 ppt、code-review、find-skills",
|
||||
},
|
||||
},
|
||||
"required": ["keyword"],
|
||||
}
|
||||
super().__init__(
|
||||
name=name,
|
||||
description=description,
|
||||
input_schema=schema,
|
||||
output_schema=output_schema,
|
||||
version=version,
|
||||
tags=tags or ["skill", "search"],
|
||||
)
|
||||
|
||||
async def execute(self, **kwargs) -> dict:
|
||||
keyword = kwargs.get("keyword", "").strip()
|
||||
|
||||
if not keyword:
|
||||
return {
|
||||
"output": "错误: 必须提供 keyword 参数",
|
||||
"exit_code": 1,
|
||||
"is_error": True,
|
||||
}
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"npx", "skills@latest", "search", keyword,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)
|
||||
output = stdout.decode("utf-8", errors="replace")
|
||||
error_output = stderr.decode("utf-8", errors="replace")
|
||||
|
||||
if output.strip():
|
||||
# Parse and format the search results
|
||||
results = self._format_search_results(output, keyword)
|
||||
return {
|
||||
"output": results,
|
||||
"exit_code": 0,
|
||||
"is_error": False,
|
||||
}
|
||||
elif error_output.strip():
|
||||
# Some versions output to stderr
|
||||
results = self._format_search_results(error_output, keyword)
|
||||
return {
|
||||
"output": results,
|
||||
"exit_code": 0,
|
||||
"is_error": False,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"output": (
|
||||
f"未找到与 '{keyword}' 匹配的技能。\n\n"
|
||||
"建议:\n"
|
||||
"1. 尝试不同的关键词搜索\n"
|
||||
"2. 检查技能名称拼写\n"
|
||||
"3. 访问 https://github.com/topics/agent-skills 浏览可用技能"
|
||||
),
|
||||
"exit_code": 0,
|
||||
"is_error": False,
|
||||
}
|
||||
except asyncio.TimeoutError:
|
||||
return {
|
||||
"output": f"搜索技能 '{keyword}' 超时(60s),请稍后重试",
|
||||
"exit_code": -1,
|
||||
"is_error": True,
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return {
|
||||
"output": (
|
||||
"npx 命令未找到,请确保 Node.js 已安装。\n"
|
||||
"安装方式: https://nodejs.org/"
|
||||
),
|
||||
"exit_code": -1,
|
||||
"is_error": True,
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"output": f"搜索技能 '{keyword}' 时发生异常: {e}",
|
||||
"exit_code": -1,
|
||||
"is_error": True,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _format_search_results(raw_output: str, keyword: str) -> str:
|
||||
"""Format raw npx skills search output into a readable result."""
|
||||
# Clean up ANSI escape codes
|
||||
import re
|
||||
clean = re.sub(r"\x1b\[[0-9;]*m", "", raw_output)
|
||||
|
||||
if not clean.strip():
|
||||
return (
|
||||
f"未找到与 '{keyword}' 匹配的技能。\n\n"
|
||||
"建议:\n"
|
||||
"1. 尝试不同的关键词搜索\n"
|
||||
"2. 检查技能名称拼写\n"
|
||||
"3. 访问 https://github.com/topics/agent-skills 浏览可用技能"
|
||||
)
|
||||
|
||||
# Return the cleaned output with a helpful header
|
||||
return f"搜索 '{keyword}' 的结果:\n\n{clean.strip()}"
|
||||
Loading…
Reference in New Issue