OpenClaw技能动态加载机制详解
·
有兴趣的朋友可以到我的知识星球“小龙虾孵化实验室”共同探索智能工具的实现与应用(落地与变现)。
目录
注:小龙虾是我开发的类OpenClaw系统
核心概念
什么是技能动态加载?
技能动态加载 = AI 启动时自动扫描技能目录,解析技能定义,生成工具列表,无需修改代码。
传统方式(静态):
// 硬编码在代码中
this.toolDefinitions = [
{ name: 'read_file', ... },
{ name: 'write_file', ... },
// 添加新技能需要修改代码
];
动态加载:
// 运行时生成
this.toolDefinitions = this.loadSkillsFromDirectory();
// 添加新技能只需复制文件到 skills/ 目录
技能文件格式(SKILL.md)
---
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"
Open-Meteo (fallback, JSON)
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 │
│ - 接收工具调用请求 │
│ - 执行对应技能 │
└─────────────────────────────────────────────────────────┘
核心代码
Python 技能加载器(skill_loader.py)
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
Node.js AI 核心(assistant-v7.js)
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 调用 |
| 配置复杂度 | 中等 | 简单 |
优劣势分析
OpenClaw
优势:
- 统一调度,资源管理好
- 技能执行环境隔离
- 支持复杂技能编排
- 有完整的技能市场
劣势:
- 架构复杂,学习成本高
- 需要 Gateway 常驻运行
- 技能执行有额外开销
小龙虾
优势:
- 架构简单,易于理解
- 直接执行,响应快
- Python + Node.js 灵活组合
- 可与 OpenClaw 共享技能
劣势:
- 技能执行需要自己管理
- 没有统一的调度系统
- 技能市场依赖 OpenClaw
【注:现已接入腾讯的skills社区】
实践
1. 技能目录管理
推荐结构:
skills/
├── weather/ # 按功能分类
│ └── SKILL.md
├── stock-monitor/
│ └── SKILL.md
└── README.md # 技能说明文档
命名规范:
- 技能名用小写字母和连字符
- 避免中文和空格
- 描述要清晰简洁
2. SKILL.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
4. 错误处理
技能加载失败:
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}`;
}
}
🔧 故障排查
问题 1:技能未加载
现象: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/
问题 2:技能加载报错
常见错误:
output is not defined→ Promise 作用域问题EADDRINUSE→ 端口被占用ENOENT→ 文件路径错误
解决方法:
- 检查代码中的变量作用域
- 停止占用的进程
- 使用绝对路径
问题 3:技能执行失败
排查步骤:
- 检查技能依赖(如 curl、python 包)
- 查看技能执行日志
- 手动测试技能命令
- 检查权限问题
总结
核心要点
- 技能即文件 - 每个技能是独立的 SKILL.md 文件
- 动态发现 - 启动时自动扫描,无需修改代码
- 提示词生成 - 运行时构建工具列表
- 零配置 - 用户安装技能后重启即可用
计划
- 技能热加载 - 无需重启即可加载新技能
- 技能依赖管理 - 自动安装技能依赖
- 技能沙箱 - 更安全地执行第三方技能
- 技能市场 - 一键安装社区技能
有兴趣的朋友可以到我的知识星球“小龙虾孵化实验室”共同探索智能工具的实现与应用(落地与变现)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)