时间:2026-03-23 21:25
人气:
作者:admin
有兴趣的朋友可以到我的知识星球“小龙虾孵化实验室”共同探索智能工具的实现与应用(落地与变现)。
注:小龙虾是我开发的类OpenClaw系统
技能动态加载 = AI 启动时自动扫描技能目录,解析技能定义,生成工具列表,无需修改代码。
传统方式(静态):
// 硬编码在代码中
this.toolDefinitions = [
{ name: 'read_file', ... },
{ name: 'write_file', ... },
// 添加新技能需要修改代码
];
动态加载:
// 运行时生成
this.toolDefinitions = this.loadSkillsFromDirectory();
// 添加新技能只需复制文件到 skills/ 目录
---
name: weather
description: Get current weather and forecasts (no API key required).
homepage: https://wttr.in/:help
metadata: {"clawdbot":{"emoji":"????️","requires":{"bins":["curl"]}}}
---
# Weather
Two free services, no API keys needed.
## wttr.in (primary)
```bash
curl -s "wttr.in/London?format=3"
curl -s "https://api.open-meteo.com/v1/forecast?..."
**结构说明**:
- **Frontmatter**(YAML):技能元数据
- `name` - 技能名称(工具调用名)
- `description` - 技能描述(AI 理解用途)
- `homepage` - 技能主页
- `metadata` - 扩展配置
- **Markdown 正文**:技能使用说明、示例代码
---
## OpenClaw 技能加载机制
### 目录结构
~/.openclaw/
├── workspace/
│ └── skills/ # 技能目录
│ ├── weather/ # 天气技能
│ │ └── SKILL.md
│ ├── stock-monitor/ # 股票监控
│ │ └── SKILL.md
│ └── README.md
├── config/
│ └── openclaw.json # 配置文件
└── ...
┌─────────────────────────────────────────────────────────┐
│ 1. OpenClaw Gateway 启动 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. 扫描 skills 目录 │
│ for skill_dir in workspace/skills/ │
│ if exists(skill_dir + '/SKILL.md'): │
│ skills.append(parseSKILL(skill_dir)) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. 解析 SKILL.md │
│ - 读取 frontmatter(YAML) │
│ - 提取 name, description, metadata │
│ - 验证技能格式 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 4. 注册技能到工具系统 │
│ tools.register({ │
│ name: skill.name, │
│ description: skill.description, │
│ handler: loadSkillHandler(skill.path) │
│ }) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 5. 生成系统提示词 │
│ prompt = ` │
│ 你是阿财,老大的助手。 │
│ │
│ ## 你有以下工具 │
│ ${tools.map(t => `- ${t.name}: ${t.description}`)}│
│ │
│ ... │
│ ` │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 6. 发送给大模型 │
│ - 大模型看到动态生成的工具列表 │
│ - 根据需要调用工具 │
│ - Gateway 执行对应技能 │
└─────────────────────────────────────────────────────────┘
// OpenClaw Gateway
class SkillManager {
constructor(workspace) {
this.skillsDir = path.join(workspace, 'skills');
this.skills = [];
}
async loadSkills() {
// 扫描技能目录
const dirs = await fs.readdir(this.skillsDir);
for (const dir of dirs) {
const skillPath = path.join(this.skillsDir, dir, 'SKILL.md');
if (await fs.exists(skillPath)) {
const skill = await this.parseSKILL(skillPath);
this.skills.push(skill);
// 注册工具
this.tools.register({
name: skill.name,
description: skill.description,
handler: this.createHandler(skill)
});
}
}
}
async parseSKILL(path) {
const content = await fs.readFile(path, 'utf-8');
const parts = content.split('---');
// 解析 YAML frontmatter
const frontmatter = yaml.parse(parts[1]);
return {
name: frontmatter.name,
description: frontmatter.description,
path: path,
body: parts[2]
};
}
buildPrompt() {
const lines = ['## 你有以下工具'];
for (const skill of this.skills) {
lines.push(`- ${skill.name}: ${skill.description}`);
}
return lines.join('\n');
}
}
| 特性 | 说明 |
|---|---|
| 技能存储 | ~/.openclaw/workspace/skills/ |
| 加载时机 | Gateway 启动时 |
| 技能格式 | SKILL.md(YAML frontmatter + Markdown) |
| 工具注册 | 自动注册到 Gateway 工具系统 |
| 提示词生成 | 运行时动态生成 |
| 技能执行 | Gateway 统一调度 |
simple-ai/
├── src/
│ ├── core/
│ │ ├── skill_loader.py # 技能加载器
│ │ └── plugin_manager.py # 插件管理器
│ └── assistant-v7.js # AI 核心
├── skills/ # 技能目录(与 OpenClaw 共享)
│ └── weather/
│ └── SKILL.md
└── ...
┌─────────────────────────────────────────────────────────┐
│ 1. 小龙虾启动(node src/server.js) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. 创建 AI 助手实例 │
│ const assistant = new SimpleAIAssistant(...) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. 调用异步初始化 │
│ await assistant.init() │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 4. 加载技能(调用 Python 加载器) │
│ spawn('python', ['skill_loader.py', skills_dir]) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 5. Python 技能加载器执行 │
│ - 扫描 skills 目录 │
│ - 解析 SKILL.md │
│ - 输出技能列表(JSON/文本) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 6. Node.js 解析输出 │
│ - 提取技能名和描述 │
│ - 构建工具定义(JSON Schema) │
│ - 更新 this.toolDefinitions │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 7. 生成系统提示词 │
│ this.buildToolsPrompt() │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 8. 调用大模型 API │
│ - 发送工具定义给千问 API │
│ - 接收工具调用请求 │
│ - 执行对应技能 │
└─────────────────────────────────────────────────────────┘
class SkillLoader:
def __init__(self, skills_dir):
self.skills_dir = skills_dir
self.skills = []
def scan_skills(self):
"""扫描所有技能"""
for item in os.listdir(self.skills_dir):
item_path = os.path.join(self.skills_dir, item)
if not os.path.isdir(item_path) or item.startswith('.'):
continue
skill_md = os.path.join(item_path, 'SKILL.md')
if os.path.exists(skill_md):
skill = self._parse_skill_md(skill_md)
if skill:
self.skills.append(skill)
print(f"[OK] 加载技能:{skill['name']}")
return self.skills
def _parse_skill_md(self, path):
"""解析 SKILL.md"""
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
# 提取 frontmatter
if content.startswith('---'):
parts = content.split('---', 2)
frontmatter_str = parts[1].strip()
frontmatter = self._parse_yaml(frontmatter_str)
return {
'name': frontmatter.get('name', ''),
'description': frontmatter.get('description', ''),
'path': path,
}
return None
def build_tools_definition(self):
"""构建工具定义(JSON Schema)"""
tools = []
for skill in self.skills:
tool = {
'type': 'function',
'function': {
'name': skill['name'],
'description': skill['description'],
'parameters': {
'type': 'object',
'properties': {
'city': {
'type': 'string',
'description': '城市名',
'default': 'Beijing'
}
}
}
}
}
tools.append(tool)
return tools
class SimpleAIAssistant {
constructor(apiKey, workspace) {
this.workspace = workspace;
this.skillsDir = path.join(workspace, '..', 'skills');
this.skills = [];
this.toolDefinitions = [];
}
async init() {
await this.loadSkills();
await this.loadMemoryToCache();
}
async loadSkills() {
log('正在加载技能...');
// 调用 Python 技能加载器
const result = await new Promise((resolve, reject) => {
const pyProcess = spawn('python', [
path.join(__dirname, 'core', 'skill_loader.py'),
this.skillsDir
], {
cwd: this.workspace,
encoding: 'utf-8'
});
let output = '';
pyProcess.stdout.on('data', (data) => { output += data; });
pyProcess.on('close', (code) => {
if (code === 0) resolve(output);
else reject(new Error('加载失败'));
});
});
// 解析输出,提取技能
const skillMatches = result.match(/\[OK\] 加载技能:(.+)/g);
if (skillMatches) {
for (const match of skillMatches) {
const skillName = match.replace('[OK] 加载技能:', '').trim();
this.skills.push({ name: skillName });
}
}
// 更新工具定义
this.toolDefinitions = this.buildToolDefinitions();
log(`加载完成:${this.skills.length} 个技能`);
}
buildToolDefinitions() {
// 基础工具
const baseTools = this.getBaseToolDefinitions();
// 技能工具
const skillTools = this.skills.map(skill => ({
type: 'function',
function: {
name: skill.name,
description: `调用${skill.name}技能`,
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名',
default: 'Beijing'
}
}
}
}
}));
return baseTools.concat(skillTools);
}
buildToolsPrompt() {
const lines = ['## 你有以下工具'];
// 基础工具
const baseTools = this.getBaseToolDefinitions();
for (const tool of baseTools) {
lines.push(`- ${tool.function.name}: ${tool.function.description}`);
}
// 技能工具
for (const skill of this.skills) {
lines.push(`- ${skill.name}: 调用${skill.name}技能`);
}
return lines.join('\n');
}
}
| 特性 | 说明 |
|---|---|
| 技能存储 | simple-ai/../skills/(可与 OpenClaw 共享) |
| 加载时机 | AI 启动时(异步) |
| 技能格式 | SKILL.md(与 OpenClaw 兼容) |
| 工具注册 | 动态生成 JSON Schema |
| 提示词生成 | 运行时动态生成 |
| 技能执行 | Node.js 直接执行或调用 Python |
| 维度 | OpenClaw | 小龙虾 |
|---|---|---|
| 运行环境 | Node.js Gateway | Node.js + Python |
| 技能目录 | ~/.openclaw/workspace/skills/ |
simple-ai/../skills/ |
| 加载方式 | 直接扫描 | 调用 Python 加载器 |
| 工具注册 | Gateway 内部注册 | JSON Schema 生成 |
| 技能执行 | Gateway 统一调度 | 直接执行/Python 调用 |
| 配置复杂度 | 中等 | 简单 |
优势:
劣势:
优势:
劣势:
【注:现已接入腾讯的skills社区】
推荐结构:
skills/
├── weather/ # 按功能分类
│ └── SKILL.md
├── stock-monitor/
│ └── SKILL.md
└── README.md # 技能说明文档
命名规范:
好的示例:
---
name: weather
description: 查询全球任何城市的当前天气和预报
homepage: https://wttr.in/
metadata: {"emoji":"????️"}
---
# Weather Skill
查询天气情况,无需 API 密钥。
## 使用方法
```bash
curl -s "wttr.in/Beijing?format=3"
city: 城市名(英文或拼音)format: 输出格式(3=简洁,T=详细)
**避免**:
- 描述过于简单("查天气")
- 没有使用示例
- 参数说明不清晰
### 3. 技能加载优化
**启动时预加载**:
```javascript
// 推荐:启动时加载
await assistant.init();
// 不推荐:每次请求都加载
async chat() {
await this.loadSkills(); // 太慢!
}
缓存技能列表:
# 技能变化时重新加载
if skills_changed:
loader.scan_skills()
else:
return cached_skills
技能加载失败:
try {
await this.loadSkills();
} catch (error) {
log(`技能加载失败:${error.message}`);
// 使用基础工具继续运行
this.toolDefinitions = this.getBaseToolDefinitions();
}
技能执行失败:
async executeSkill(skill, params) {
try {
return await skill.handler(params);
} catch (error) {
return `技能执行失败:${error.message}`;
}
}
现象:AI 说"我没有这个工具"
排查步骤:
检查技能目录是否存在
ls ~/.openclaw/workspace/skills/
检查 SKILL.md 格式
cat weather/SKILL.md
查看加载日志
tail -f logs/info.log | grep "技能"
手动测试加载器
python src/core/skill_loader.py ~/.openclaw/workspace/skills/
常见错误:
output is not defined → Promise 作用域问题EADDRINUSE → 端口被占用ENOENT → 文件路径错误解决方法:
排查步骤:
有兴趣的朋友可以到我的知识星球“小龙虾孵化实验室”共同探索智能工具的实现与应用(落地与变现)。