mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
fix: improve skill system prompts and simplify tool descriptions
- Simplify skill-creator installation flow - Refine skill selection prompt for better matching - Add parameter alias and env variable hints for tools - Skip linkai-agent when unconfigured - Create skills/ dir in workspace on init
This commit is contained in:
@@ -90,7 +90,7 @@ bash <(curl -sS https://cdn.link-ai.tech/code/cow/run.sh)
|
|||||||
|
|
||||||
项目支持国内外主流厂商的模型接口,可选模型及配置说明参考:[模型说明](#模型说明)。
|
项目支持国内外主流厂商的模型接口,可选模型及配置说明参考:[模型说明](#模型说明)。
|
||||||
|
|
||||||
> 注:Agent模式下推荐使用以下模型,可根据效果及成本综合选择:MiniMAx(MiniMax-M2.1)、GLM(glm-4.7)、Qwen(qwen3-max)、Claude(claude-opus-4-6、claude-sonnet-4-5、claude-sonnet-4-0)、Gemini(gemini-3-flash-preview、gemini-3-pro-preview)
|
> 注:Agent模式下推荐使用以下模型,可根据效果及成本综合选择:GLM(glm-4.7)、MiniMAx(MiniMax-M2.1)、Qwen(qwen3-max)、Claude(claude-opus-4-6、claude-sonnet-4-5、claude-sonnet-4-0)、Gemini(gemini-3-flash-preview、gemini-3-pro-preview)
|
||||||
|
|
||||||
同时支持使用 **LinkAI平台** 接口,可灵活切换 OpenAI、Claude、Gemini、DeepSeek、Qwen、Kimi 等多种常用模型,并支持知识库、工作流、插件等Agent能力,参考 [接口文档](https://docs.link-ai.tech/platform/api)。
|
同时支持使用 **LinkAI平台** 接口,可灵活切换 OpenAI、Claude、Gemini、DeepSeek、Qwen、Kimi 等多种常用模型,并支持知识库、工作流、插件等Agent能力,参考 [接口文档](https://docs.link-ai.tech/platform/api)。
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ pip3 install -r requirements-optional.txt
|
|||||||
<details>
|
<details>
|
||||||
<summary>2. 其他配置</summary>
|
<summary>2. 其他配置</summary>
|
||||||
|
|
||||||
+ `model`: 模型名称,Agent模式下推荐使用 `MiniMax-M2.1`、`glm-4.7`、`qwen3-max`、`claude-opus-4-6`、`claude-sonnet-4-5`、`claude-sonnet-4-0`、`gemini-3-flash-preview`、`gemini-3-pro-preview`,全部模型名称参考[common/const.py](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/common/const.py)文件
|
+ `model`: 模型名称,Agent模式下推荐使用 `glm-4.7`、`MiniMax-M2.1`、`qwen3-max`、`claude-opus-4-6`、`claude-sonnet-4-5`、`claude-sonnet-4-0`、`gemini-3-flash-preview`、`gemini-3-pro-preview`,全部模型名称参考[common/const.py](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/common/const.py)文件
|
||||||
+ `character_desc`:普通对话模式下的机器人系统提示词。在Agent模式下该配置不生效,由工作空间中的文件内容构成。
|
+ `character_desc`:普通对话模式下的机器人系统提示词。在Agent模式下该配置不生效,由工作空间中的文件内容构成。
|
||||||
+ `subscribe_msg`:订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复, 可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成bot的触发词。
|
+ `subscribe_msg`:订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复, 可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成bot的触发词。
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -157,96 +157,66 @@ def _build_identity_section(base_persona: Optional[str], language: str) -> List[
|
|||||||
|
|
||||||
|
|
||||||
def _build_tooling_section(tools: List[Any], language: str) -> List[str]:
|
def _build_tooling_section(tools: List[Any], language: str) -> List[str]:
|
||||||
"""构建工具说明section"""
|
"""Build tooling section with concise tool list and call style guide."""
|
||||||
|
# One-line summaries for known tools (details are in the tool schema)
|
||||||
|
core_summaries = {
|
||||||
|
"read": "读取文件内容",
|
||||||
|
"write": "创建或覆盖文件",
|
||||||
|
"edit": "精确编辑文件",
|
||||||
|
"ls": "列出目录内容",
|
||||||
|
"grep": "搜索文件内容",
|
||||||
|
"find": "按模式查找文件",
|
||||||
|
"bash": "执行shell命令",
|
||||||
|
"terminal": "管理后台进程",
|
||||||
|
"web_search": "网络搜索",
|
||||||
|
"web_fetch": "获取URL内容",
|
||||||
|
"browser": "控制浏览器",
|
||||||
|
"memory_search": "搜索记忆",
|
||||||
|
"memory_get": "读取记忆内容",
|
||||||
|
"env_config": "管理API密钥和技能配置",
|
||||||
|
"scheduler": "管理定时任务和提醒",
|
||||||
|
"send": "发送文件给用户",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Preferred display order
|
||||||
|
tool_order = [
|
||||||
|
"read", "write", "edit", "ls", "grep", "find",
|
||||||
|
"bash", "terminal",
|
||||||
|
"web_search", "web_fetch", "browser",
|
||||||
|
"memory_search", "memory_get",
|
||||||
|
"env_config", "scheduler", "send",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Build name -> summary mapping for available tools
|
||||||
|
available = {}
|
||||||
|
for tool in tools:
|
||||||
|
name = tool.name if hasattr(tool, 'name') else str(tool)
|
||||||
|
available[name] = core_summaries.get(name, "")
|
||||||
|
|
||||||
|
# Generate tool lines: ordered tools first, then extras
|
||||||
|
tool_lines = []
|
||||||
|
for name in tool_order:
|
||||||
|
if name in available:
|
||||||
|
summary = available.pop(name)
|
||||||
|
tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}")
|
||||||
|
for name in sorted(available):
|
||||||
|
summary = available[name]
|
||||||
|
tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}")
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 工具系统",
|
"## 工具系统",
|
||||||
"",
|
"",
|
||||||
"你可以使用以下工具来完成任务。工具名称是大小写敏感的,请严格按照列表中的名称调用。",
|
"可用工具(名称大小写敏感,严格按列表调用):",
|
||||||
|
"\n".join(tool_lines),
|
||||||
"",
|
"",
|
||||||
"### 可用工具",
|
"工具调用风格:",
|
||||||
|
"",
|
||||||
|
"- 在多步骤任务、敏感操作或用户要求时简要解释决策过程",
|
||||||
|
"- 持续推进直到任务完成,完成后向用户报告结果。",
|
||||||
|
"- 回复中涉及密钥、令牌等敏感信息必须脱敏。",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
# 工具分类和排序
|
|
||||||
tool_categories = {
|
|
||||||
"文件操作": ["read", "write", "edit", "ls", "grep", "find"],
|
|
||||||
"命令执行": ["bash", "terminal"],
|
|
||||||
"网络搜索": ["web_search", "web_fetch", "browser"],
|
|
||||||
"记忆系统": ["memory_search", "memory_get"],
|
|
||||||
"其他": []
|
|
||||||
}
|
|
||||||
|
|
||||||
# 构建工具映射
|
|
||||||
tool_map = {}
|
|
||||||
tool_descriptions = {
|
|
||||||
"read": "读取文件内容",
|
|
||||||
"write": "创建新文件或完全覆盖现有文件(会删除原内容!追加内容请用 edit)。注意:单次 write 内容不要超过 10KB,超大文件请分步创建",
|
|
||||||
"edit": "精确编辑文件(追加、修改、删除部分内容)",
|
|
||||||
"ls": "列出目录内容",
|
|
||||||
"grep": "在文件中搜索内容",
|
|
||||||
"find": "按照模式查找文件",
|
|
||||||
"bash": "执行shell命令",
|
|
||||||
"terminal": "管理后台进程",
|
|
||||||
"web_search": "网络搜索(使用搜索引擎)",
|
|
||||||
"web_fetch": "获取URL内容",
|
|
||||||
"browser": "控制浏览器",
|
|
||||||
"memory_search": "搜索记忆文件",
|
|
||||||
"memory_get": "获取记忆文件内容",
|
|
||||||
"calculator": "计算器",
|
|
||||||
"current_time": "获取当前时间",
|
|
||||||
}
|
|
||||||
|
|
||||||
for tool in tools:
|
|
||||||
tool_name = tool.name if hasattr(tool, 'name') else str(tool)
|
|
||||||
tool_desc = tool.description if hasattr(tool, 'description') else tool_descriptions.get(tool_name, "")
|
|
||||||
tool_map[tool_name] = tool_desc
|
|
||||||
|
|
||||||
# 按分类添加工具
|
|
||||||
for category, tool_names in tool_categories.items():
|
|
||||||
category_tools = [(name, tool_map.get(name, "")) for name in tool_names if name in tool_map]
|
|
||||||
if category_tools:
|
|
||||||
lines.append(f"**{category}**:")
|
|
||||||
for name, desc in category_tools:
|
|
||||||
if desc:
|
|
||||||
lines.append(f"- `{name}`: {desc}")
|
|
||||||
else:
|
|
||||||
lines.append(f"- `{name}`")
|
|
||||||
del tool_map[name] # 移除已添加的工具
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# 添加其他未分类的工具
|
|
||||||
if tool_map:
|
|
||||||
lines.append("**其他工具**:")
|
|
||||||
for name, desc in sorted(tool_map.items()):
|
|
||||||
if desc:
|
|
||||||
lines.append(f"- `{name}`: {desc}")
|
|
||||||
else:
|
|
||||||
lines.append(f"- `{name}`")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# 工具使用指南
|
|
||||||
lines.extend([
|
|
||||||
"### 工具调用风格",
|
|
||||||
"",
|
|
||||||
"默认规则: 对于常规、低风险的工具调用,直接调用即可,无需叙述。",
|
|
||||||
"",
|
|
||||||
"需要叙述的情况:",
|
|
||||||
"- 多步骤、复杂的任务",
|
|
||||||
"- 敏感操作(如删除文件)",
|
|
||||||
"- 用户明确要求解释过程",
|
|
||||||
"",
|
|
||||||
"叙述要求: 保持简洁、信息密度高,避免重复显而易见的步骤。",
|
|
||||||
"",
|
|
||||||
"完成标准:",
|
|
||||||
"- 确保用户的需求得到实际解决,而不仅仅是制定计划。",
|
|
||||||
"- 当任务需要多次工具调用时,持续推进直到完成, 解决完后向用户报告结果或回复用户的问题",
|
|
||||||
"- 每次工具调用后,评估是否已获得足够信息来推进或完成任务",
|
|
||||||
"- 避免重复调用相同的工具和相同参数获取相同的信息,除非用户明确要求",
|
|
||||||
"",
|
|
||||||
"**安全提醒**: 回复中涉及密钥、令牌、密码等敏感信息时,必须脱敏处理,禁止直接显示完整内容。",
|
|
||||||
"",
|
|
||||||
])
|
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
@@ -265,16 +235,17 @@ def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], langua
|
|||||||
break
|
break
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"## 技能系统",
|
"## 技能系统(mandatory)",
|
||||||
"",
|
"",
|
||||||
"在回复之前:扫描下方 <available_skills> 中的 <description> 条目。",
|
"在回复之前:扫描下方 <available_skills> 中的 <description> 条目。",
|
||||||
"",
|
"",
|
||||||
f"- 如果恰好有一个技能明确适用:使用 `{read_tool_name}` 工具读取其 <location> 路径下的 SKILL.md 文件,然后遵循它",
|
f"- 如果恰好有一个技能(Skill)明确适用:使用 `{read_tool_name}` 读取其 <location> 处的 SKILL.md,然后严格遵循它",
|
||||||
"- 如果多个技能都适用:选择最具体的一个,然后读取并遵循",
|
"- 如果多个技能都适用则选择最匹配的一个,如果没有明确适用的则不要读取任何 SKILL.md",
|
||||||
"- 如果没有明确适用的:不要读取任何 SKILL.md",
|
"- 读取 SKILL.md 后直接按其指令执行,无需多余的预检查",
|
||||||
"",
|
"",
|
||||||
"**约束**: 永远不要一次性读取多个技能;只在选择后再读取。",
|
"**注意**: 永远不要一次性读取多个技能,只在选择后再读取。技能和工具不同,必须先读取其SKILL.md并按照文件内容运行。",
|
||||||
"",
|
"",
|
||||||
|
"以下是可用技能:"
|
||||||
]
|
]
|
||||||
|
|
||||||
# 添加技能列表(通过skill_manager获取)
|
# 添加技能列表(通过skill_manager获取)
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works
|
|||||||
|
|
||||||
# 创建memory子目录
|
# 创建memory子目录
|
||||||
os.makedirs(memory_dir, exist_ok=True)
|
os.makedirs(memory_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 创建skills子目录 (for workspace-level skills installed by agent)
|
||||||
|
skills_dir = os.path.join(workspace_dir, "skills")
|
||||||
|
os.makedirs(skills_dir, exist_ok=True)
|
||||||
|
|
||||||
# 如果需要,创建模板文件
|
# 如果需要,创建模板文件
|
||||||
if create_templates:
|
if create_templates:
|
||||||
|
|||||||
@@ -708,7 +708,6 @@ class AgentStreamExecutor:
|
|||||||
if not tool_id:
|
if not tool_id:
|
||||||
import uuid
|
import uuid
|
||||||
tool_id = f"call_{uuid.uuid4().hex[:24]}"
|
tool_id = f"call_{uuid.uuid4().hex[:24]}"
|
||||||
logger.debug(f"⚠️ Tool call missing ID for '{tc.get('name')}', generated fallback: {tool_id}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Safely get arguments, handle None case
|
# Safely get arguments, handle None case
|
||||||
|
|||||||
@@ -23,18 +23,15 @@ def format_skills_for_prompt(skills: List[Skill]) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"\n\nThe following skills provide specialized instructions for specific tasks.",
|
|
||||||
"Use the read tool to load a skill's file when the task matches its description.",
|
|
||||||
"",
|
"",
|
||||||
"<available_skills>",
|
"<available_skills>",
|
||||||
]
|
]
|
||||||
|
|
||||||
for skill in visible_skills:
|
for skill in visible_skills:
|
||||||
lines.append(" <skill>")
|
lines.append(" <skill>")
|
||||||
lines.append(f" <name>{_escape_xml(skill.name)}</name>")
|
lines.append(f" <name>{_escape_xml(skill.name)}</name>")
|
||||||
lines.append(f" <description>{_escape_xml(skill.description)}</description>")
|
lines.append(f" <description>{_escape_xml(skill.description)}</description>")
|
||||||
lines.append(f" <location>{_escape_xml(skill.file_path)}</location>")
|
lines.append(f" <location>{_escape_xml(skill.file_path)}</location>")
|
||||||
lines.append(f" <base_dir>{_escape_xml(skill.base_dir)}</base_dir>")
|
|
||||||
lines.append(" </skill>")
|
lines.append(" </skill>")
|
||||||
|
|
||||||
lines.append("</available_skills>")
|
lines.append("</available_skills>")
|
||||||
|
|||||||
@@ -188,16 +188,14 @@ class SkillLoader:
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
config_path = os.path.join(skill_dir, "config.json")
|
config_path = os.path.join(skill_dir, "config.json")
|
||||||
template_path = os.path.join(skill_dir, "config.json.template")
|
|
||||||
|
|
||||||
# Try to load config.json or fallback to template
|
# Without config.json, skip this skill entirely (return empty to trigger exclusion)
|
||||||
config_file = config_path if os.path.exists(config_path) else template_path
|
if not os.path.exists(config_path):
|
||||||
|
logger.debug(f"[SkillLoader] linkai-agent skipped: no config.json found")
|
||||||
if not os.path.exists(config_file):
|
return ""
|
||||||
return default_description
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(config_file, 'r', encoding='utf-8') as f:
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
apps = config.get("apps", [])
|
apps = config.get("apps", [])
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ class Bash(BaseTool):
|
|||||||
name: str = "bash"
|
name: str = "bash"
|
||||||
description: str = f"""Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last {DEFAULT_MAX_LINES} lines or {DEFAULT_MAX_BYTES // 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file.
|
description: str = f"""Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last {DEFAULT_MAX_LINES} lines or {DEFAULT_MAX_BYTES // 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file.
|
||||||
|
|
||||||
IMPORTANT SAFETY GUIDELINES:
|
ENVIRONMENT: All API keys from env_config are auto-injected. Use $VAR_NAME directly.
|
||||||
- You can freely create, modify, and delete files within the current workspace
|
|
||||||
- For operations outside the workspace or potentially destructive commands (rm -rf, system commands, etc.), always explain what you're about to do and ask for user confirmation first
|
SAFETY:
|
||||||
- When in doubt, describe the command's purpose and ask for permission before executing"""
|
- Freely create/modify/delete files within the workspace
|
||||||
|
- For destructive and out-of-workspace commands, explain and confirm first"""
|
||||||
|
|
||||||
params: dict = {
|
params: dict = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -92,13 +93,7 @@ IMPORTANT SAFETY GUIDELINES:
|
|||||||
logger.debug("[Bash] python-dotenv not installed, skipping .env loading")
|
logger.debug("[Bash] python-dotenv not installed, skipping .env loading")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[Bash] Failed to load .env: {e}")
|
logger.debug(f"[Bash] Failed to load .env: {e}")
|
||||||
|
|
||||||
# Debug logging
|
|
||||||
logger.debug(f"[Bash] CWD: {self.cwd}")
|
|
||||||
logger.debug(f"[Bash] Command: {command[:500]}")
|
|
||||||
logger.debug(f"[Bash] OPENAI_API_KEY in env: {'OPENAI_API_KEY' in env}")
|
|
||||||
logger.debug(f"[Bash] SHELL: {env.get('SHELL', 'not set')}")
|
|
||||||
logger.debug(f"[Bash] Python executable: {sys.executable}")
|
|
||||||
# getuid() only exists on Unix-like systems
|
# getuid() only exists on Unix-like systems
|
||||||
if hasattr(os, 'getuid'):
|
if hasattr(os, 'getuid'):
|
||||||
logger.debug(f"[Bash] Process UID: {os.getuid()}")
|
logger.debug(f"[Bash] Process UID: {os.getuid()}")
|
||||||
|
|||||||
@@ -202,7 +202,8 @@ class EnvConfig(BaseTool):
|
|||||||
"key": key,
|
"key": key,
|
||||||
"value": self._mask_value(value),
|
"value": self._mask_value(value),
|
||||||
"description": description,
|
"description": description,
|
||||||
"exists": True
|
"exists": True,
|
||||||
|
"note": f"Value is masked for security. In bash, use ${key} directly — it is auto-injected."
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
return ToolResult.success({
|
return ToolResult.success({
|
||||||
|
|||||||
@@ -67,10 +67,12 @@ class Read(BaseTool):
|
|||||||
:param args: Contains file path and optional offset/limit parameters
|
:param args: Contains file path and optional offset/limit parameters
|
||||||
:return: File content or error message
|
:return: File content or error message
|
||||||
"""
|
"""
|
||||||
path = args.get("path", "").strip()
|
# Support 'location' as alias for 'path' (LLM may use it from skill listing)
|
||||||
|
path = args.get("path", "") or args.get("location", "")
|
||||||
|
path = path.strip() if isinstance(path, str) else ""
|
||||||
offset = args.get("offset")
|
offset = args.get("offset")
|
||||||
limit = args.get("limit")
|
limit = args.get("limit")
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
return ToolResult.fail("Error: path parameter is required")
|
return ToolResult.fail("Error: path parameter is required")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"channel_type": "web",
|
"channel_type": "web",
|
||||||
"model": "MiniMax-M2.1",
|
"model": "glm-4.7",
|
||||||
"claude_api_key": "",
|
"claude_api_key": "",
|
||||||
"claude_api_base": "https://api.anthropic.com/v1",
|
"claude_api_base": "https://api.anthropic.com/v1",
|
||||||
"open_ai_api_key": "",
|
"open_ai_api_key": "",
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
---
|
|
||||||
name: bocha-search
|
|
||||||
description: High-quality web search with AI-optimized results. Use when user needs to search the internet for current information, news, or research topics.
|
|
||||||
homepage: https://open.bocha.cn/
|
|
||||||
metadata:
|
|
||||||
emoji: 🔍
|
|
||||||
requires:
|
|
||||||
bins: ["curl"]
|
|
||||||
env: ["BOCHA_API_KEY"]
|
|
||||||
primaryEnv: "BOCHA_API_KEY"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Bocha Search
|
|
||||||
|
|
||||||
High-quality web search powered by Bocha AI, optimized for AI consumption. Returns web pages, images, and detailed metadata.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
This skill requires a Bocha API key. If not configured:
|
|
||||||
|
|
||||||
1. Visit https://open.bocha.cn to get an API key
|
|
||||||
2. Set the key using: `env_config(action="set", key="BOCHA_API_KEY", value="your-key")`
|
|
||||||
3. Or manually add to `~/cow/.env`: `BOCHA_API_KEY=your-key`
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
**Important**: Scripts are located relative to this skill's base directory.
|
|
||||||
|
|
||||||
When you see this skill in `<available_skills>`, note the `<base_dir>` path.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# General pattern:
|
|
||||||
bash "<base_dir>/scripts/search.sh" "<query>" [count] [freshness] [summary]
|
|
||||||
|
|
||||||
# Parameters:
|
|
||||||
# - query: Search query (required)
|
|
||||||
# - count: Number of results (1-50, default: 10)
|
|
||||||
# - freshness: Time range filter (default: noLimit)
|
|
||||||
# Options: noLimit, oneDay, oneWeek, oneMonth, oneYear, YYYY-MM-DD..YYYY-MM-DD
|
|
||||||
# - summary: Include text summary (true/false, default: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Basic search
|
|
||||||
```bash
|
|
||||||
bash "<base_dir>/scripts/search.sh" "latest AI news"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search with more results
|
|
||||||
```bash
|
|
||||||
bash "<base_dir>/scripts/search.sh" "Python tutorials" 20
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search recent content with summary
|
|
||||||
```bash
|
|
||||||
bash "<base_dir>/scripts/search.sh" "阿里巴巴ESG报告" 10 oneWeek true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search specific date range
|
|
||||||
```bash
|
|
||||||
bash "<base_dir>/scripts/search.sh" "tech news" 15 "2025-01-01..2025-02-01"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response Format
|
|
||||||
|
|
||||||
The API returns structured data compatible with Bing Search API:
|
|
||||||
|
|
||||||
**Web Pages** (in `data.webPages.value`):
|
|
||||||
- `name`: Page title
|
|
||||||
- `url`: Page URL
|
|
||||||
- `snippet`: Short description
|
|
||||||
- `summary`: Full text summary (if requested)
|
|
||||||
- `siteName`: Website name
|
|
||||||
- `siteIcon`: Website icon URL
|
|
||||||
- `datePublished`: Publication date (UTC+8)
|
|
||||||
- `language`: Page language
|
|
||||||
|
|
||||||
**Images** (in `data.images.value`):
|
|
||||||
- `contentUrl`: Image URL
|
|
||||||
- `hostPageUrl`: Source page URL
|
|
||||||
- `width`, `height`: Image dimensions
|
|
||||||
- `thumbnailUrl`: Thumbnail URL
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- **Optimized for AI**: Results include summaries and structured metadata
|
|
||||||
- **Time range**: Use `noLimit` for best results (algorithm auto-optimizes time range)
|
|
||||||
- **Timeout**: 30 seconds
|
|
||||||
- **Rate limits**: Check your API plan at https://open.bocha.cn
|
|
||||||
- **Response format**: Compatible with Bing Search API for easy integration
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Bocha Web Search API wrapper
|
|
||||||
# API Docs: https://open.bocha.cn/
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
query="${1:-}"
|
|
||||||
count="${2:-10}"
|
|
||||||
freshness="${3:-noLimit}"
|
|
||||||
summary="${4:-false}"
|
|
||||||
|
|
||||||
if [ -z "$query" ]; then
|
|
||||||
echo '{"error": "Query is required", "usage": "bash search.sh <query> [count] [freshness] [summary]"}'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${BOCHA_API_KEY:-}" ]; then
|
|
||||||
echo '{"error": "BOCHA_API_KEY environment variable is not set", "help": "Visit https://open.bocha.cn to get an API key"}'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate count (1-50)
|
|
||||||
if ! [[ "$count" =~ ^[0-9]+$ ]] || [ "$count" -lt 1 ] || [ "$count" -gt 50 ]; then
|
|
||||||
count=10
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build JSON request body
|
|
||||||
request_body=$(cat <<EOF
|
|
||||||
{
|
|
||||||
"query": "$query",
|
|
||||||
"count": $count,
|
|
||||||
"freshness": "$freshness",
|
|
||||||
"summary": $summary
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
# Call Bocha API
|
|
||||||
response=$(curl -sS --max-time 30 \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: Bearer $BOCHA_API_KEY" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Accept: application/json" \
|
|
||||||
-d "$request_body" \
|
|
||||||
"https://api.bocha.cn/v1/web-search" 2>&1)
|
|
||||||
|
|
||||||
curl_exit_code=$?
|
|
||||||
|
|
||||||
if [ $curl_exit_code -ne 0 ]; then
|
|
||||||
echo "{\"error\": \"Failed to call Bocha API\", \"details\": \"$response\"}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Simple JSON validation - check if response starts with { or [
|
|
||||||
if [[ ! "$response" =~ ^[[:space:]]*[\{\[] ]]; then
|
|
||||||
echo "{\"error\": \"Invalid JSON response from API\", \"response\": \"$response\"}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract API code using grep and sed (basic JSON parsing)
|
|
||||||
api_code=$(echo "$response" | grep -o '"code"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*' | head -1)
|
|
||||||
|
|
||||||
# If code extraction failed or code is not 200, check for error
|
|
||||||
if [ -n "$api_code" ] && [ "$api_code" != "200" ]; then
|
|
||||||
# Try to extract error message
|
|
||||||
api_msg=$(echo "$response" | grep -o '"msg"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"msg"[[:space:]]*:[[:space:]]*"\(.*\)"/\1/' | head -1)
|
|
||||||
if [ -z "$api_msg" ]; then
|
|
||||||
api_msg="Unknown error"
|
|
||||||
fi
|
|
||||||
echo "{\"error\": \"API returned error\", \"code\": $api_code, \"message\": \"$api_msg\"}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Return the full response
|
|
||||||
echo "$response"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: skill-creator
|
name: skill-creator
|
||||||
description: Create or update skills. Use when designing, structuring, or packaging skills with scripts, references, and assets. COW simplified version - skills are used locally in workspace.
|
description: Create, install, or update skills in the workspace. Use when (1) installing a skill from a URL or remote source, (2) creating a new skill from scratch, (3) updating or restructuring existing skills. Always use this skill for any skill installation or creation task.
|
||||||
license: Complete terms in LICENSE.txt
|
license: Complete terms in LICENSE.txt
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -93,9 +93,16 @@ Do NOT create auxiliary documentation files:
|
|||||||
|
|
||||||
**Critical Rule**: Only create files that the agent will actually execute (scripts) or that are too large for SKILL.md (references). Documentation, examples, and guides ALL belong in SKILL.md.
|
**Critical Rule**: Only create files that the agent will actually execute (scripts) or that are too large for SKILL.md (references). Documentation, examples, and guides ALL belong in SKILL.md.
|
||||||
|
|
||||||
## Skill Creation Process
|
## Installing a Skill from URL
|
||||||
|
|
||||||
**COW Simplified Version** - Skills are used locally, no packaging/sharing needed.
|
1. Fetch the URL content (curl or web-fetch skill)
|
||||||
|
2. Extract `name` from YAML frontmatter
|
||||||
|
3. Create directory `<workspace>/skills/<name>/` and save content as `SKILL.md`
|
||||||
|
4. Check the saved SKILL.md for an installation/setup section — if it defines additional steps (e.g., downloading scripts, installing dependencies), execute them; otherwise installation is complete
|
||||||
|
|
||||||
|
The `<workspace>` is the working directory from the "工作空间" section.
|
||||||
|
|
||||||
|
## Skill Creation Process (from scratch)
|
||||||
|
|
||||||
1. **Understand** - Clarify use cases with concrete examples
|
1. **Understand** - Clarify use cases with concrete examples
|
||||||
2. **Plan** - Identify needed scripts, references, assets
|
2. **Plan** - Identify needed scripts, references, assets
|
||||||
@@ -181,11 +188,13 @@ scripts/init_skill.py <skill-name> --path <output-directory> [--resources script
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/init_skill.py my-skill --path ~/cow/skills
|
scripts/init_skill.py my-skill --path <workspace>/skills
|
||||||
scripts/init_skill.py my-skill --path ~/cow/skills --resources scripts,references
|
scripts/init_skill.py my-skill --path <workspace>/skills --resources scripts,references
|
||||||
scripts/init_skill.py my-skill --path ~/cow/skills --resources scripts --examples
|
scripts/init_skill.py my-skill --path <workspace>/skills --resources scripts --examples
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Where `<workspace>` is your workspace directory shown in the "工作空间" section of the system prompt.
|
||||||
|
|
||||||
The script:
|
The script:
|
||||||
|
|
||||||
- Creates the skill directory at the specified path
|
- Creates the skill directory at the specified path
|
||||||
@@ -195,7 +204,7 @@ The script:
|
|||||||
|
|
||||||
After initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files.
|
After initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files.
|
||||||
|
|
||||||
**Important**: Always create skills in workspace directory (`~/cow/skills`), NOT in project directory.
|
**Important**: Always create skills in workspace skills directory (`<workspace>/skills`), NOT in project directory. Check the "工作空间" section for the actual workspace path.
|
||||||
|
|
||||||
### Step 4: Edit the Skill
|
### Step 4: Edit the Skill
|
||||||
|
|
||||||
@@ -335,7 +344,7 @@ scripts/quick_validate.py <path/to/skill-folder>
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/quick_validate.py ~/cow/skills/weather-api
|
scripts/quick_validate.py <workspace>/skills/weather-api
|
||||||
```
|
```
|
||||||
|
|
||||||
Validation checks:
|
Validation checks:
|
||||||
|
|||||||
Reference in New Issue
Block a user