引言
OpenClaw 作为一个功能强大的 AI 助手平台,其核心能力来自于两个关键系统:Tools(工具) 和 Skills(技能)。工具为 Agent 提供了与外部世界交互的能力,而技能则是教会 Agent 如何组合使用这些工具完成复杂任务的知识库。
本文将深入探讨:
- OpenClaw 设计了哪些工具及其功能
- Skills 的注入时机和完整流程
- Tools 与 Skills 的协作关系
第一部分:OpenClaw 工具体系全览
1.1 工具设计理念
OpenClaw 的工具系统遵循以下设计原则:
核心理念:
- 最小权限原则:工具按功能分组,支持细粒度权限控制
- 组合优于单体:小而专注的工具,通过组合实现复杂功能
- 安全第一:多层策略过滤,防止滥用
- 可扩展性:插件系统支持自定义工具
工具定义:
typescript
interface Tool {
name: string; // 唯一标识符(如 "read", "exec")
description: string; // 功能描述
parameters: JSONSchema; // TypeBox定义的参数schema
execute: (params) => Promise<Result>; // 执行函数
}
1.2 内置工具完整清单
OpenClaw 提供了 28 个核心工具,按 8 个功能类别组织:
📁 文件系统工具(File System)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| read | 读取文件内容 | 查看代码、读取配置、检查日志 | 基础 |
| write | 创建或覆盖文件 | 生成代码、保存数据、写入配置 | 中等 |
| edit | 精确字符串替换编辑 | 修改代码片段、更新配置项 | 中等 |
| apply_patch | 应用 OpenAI 风格补丁 | 批量代码修改、多文件编辑 | 高 |
设计考量:
read和write是最基础的操作edit提供精确的字符串替换(避免覆盖整个文件)apply_patch支持复杂的多文件编辑(仅限特定模型)
安全机制:
typescript
// 工作空间根目录保护
wrapToolWorkspaceRootGuard(tool)
// 防止修改:.git, .openclaw, node_modules 等
⚙️ 运行时工具(Runtime)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| exec | 执行 shell 命令 | 运行脚本、编译代码、系统操作 | 高 |
| process | 后台进程管理 | 启动服务、监控任务 | 高 |
关键特性:
exec支持超时、信号中断、输出捕获process支持后台守护进程- 默认拒绝:HTTP网关默认禁止这两个工具(安全考虑)
使用示例:
typescript
// 执行Python脚本
exec("python3 script.py --input data.json")
// 启动开发服务器
process({
command: "npm run dev",
background: true
})
🌐 网络工具(Web)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| web_search | Web 搜索 | 查找最新信息、技术文档 | 基础 |
| web_fetch | 获取网页内容 | 抓取文章、解析API文档 | 基础 |
多提供商支持:
web_search:支持 Tavily、Brave、Perplexity 等web_fetch:自动转换HTML为Markdown,提取主要内容
返回格式:
json
{
"results": [
{
"title": "OpenClaw Documentation",
"url": "https://docs.openclaw.ai",
"snippet": "OpenClaw is a self-hosted AI assistant...",
"content": "Full page content in markdown"
}
]
}
🧠 内存工具(Memory)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| memory_search | 语义搜索记忆文件 | 查找历史对话、检索知识 | 基础 |
| memory_get | 按路径读取记忆 | 精确获取特定记忆片段 | 基础 |
核心能力:
- 向量搜索 + 关键词搜索混合
- 支持 MEMORY.md、memory/.md、sessions/.jsonl
- RRF(Reciprocal Rank Fusion)融合算法
查询流程:
lua
memory_search("Discord配置")
↓
返回:
- path: MEMORY.md
- startLine: 10
- endLine: 15
- score: 0.85
↓
memory_get(path="MEMORY.md", from=10, lines=5)
↓
返回完整内容
详细机制参考 :openclaw-memory-system-blog-zh-CN.md
💬 会话工具(Sessions)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| sessions_list | 列出所有会话 | 查看活跃对话、管理会话 | 基础 |
| sessions_history | 读取会话历史 | 回顾对话、分析上下文 | 基础 |
| sessions_send | 发送消息到会话 | 跨会话通信、批量通知 | 中等 |
| sessions_spawn | 生成子代理 | 并行任务、委托工作 | 高 |
| subagents | 管理子代理 | 监控、停止子任务 | 高 |
| session_status | 查询会话状态 | 健康检查、负载监控 | 基础 |
会话架构:
scss
Parent Agent (主代理)
├─ Spawn → Child Agent 1 (子代理1)
│ └─ Task: 分析代码
├─ Spawn → Child Agent 2 (子代理2)
│ └─ Task: 写文档
└─ 汇总结果
子代理限制:
typescript
// 子代理被拒绝的工具(安全考虑)
const SUBAGENT_TOOL_DENY_ALWAYS = [
"memory_search", // 不能访问父代理记忆
"memory_get",
"gateway", // 不能控制系统
"cron", // 不能创建定时任务
];
🎨 UI工具(User Interface)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| browser | 浏览器自动化 | 网页截图、自动化测试 | 高 |
| canvas | Canvas/HTML渲染 | 生成图表、可视化 | 中等 |
browser 工具能力:
- Playwright 驱动
- 支持导航、点击、输入、截图
- JavaScript 执行
canvas 工具能力:
- HTML/SVG → PNG/JPEG
- 支持 Chart.js、D3.js 等库
- 自定义尺寸和样式
示例:
typescript
// 生成饼图
canvas({
html: `
<canvas id="chart"></canvas>
<script src="chart.js"></script>
<script>
new Chart(ctx, {
type: 'pie',
data: { labels: ['A', 'B'], datasets: [{ data: [30, 70] }] }
});
</script>
`,
width: 800,
height: 600
})
📧 消息工具(Messaging)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| message | 发送消息到当前渠道 | 主动通知、状态更新 | 基础 |
支持的渠道:
- Discord、Telegram、Slack
- WhatsApp、Signal、iMessage
- Line、GoogleChat 等
使用示例:
typescript
message({
text: "✅ 任务完成!共处理了 1000 条记录。",
channelId: "channel_123" // 可选,默认当前渠道
})
🤖 自动化工具(Automation)
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| cron | 创建定时任务 | 定期报告、自动备份 | 高(仅所有者) |
| gateway | 网关系统控制 | 重启服务、更新配置 | 高(仅所有者) |
cron 工具示例:
typescript
cron({
schedule: "0 9 * * *", // 每天早上9点
command: "node report-generator.js",
description: "Daily sales report"
})
gateway 工具功能:
- 重启网关
- 重新加载配置
- 查看系统状态
- 清理缓存
📦 其他工具
| 工具名 | 功能描述 | 典型用例 | 权限级别 |
|---|---|---|---|
| nodes | 节点和设备管理 | 列出连接的设备 | 中等 |
| agents_list | 列出所有代理 | 查看可用代理 | 基础 |
| image | 图像理解(多模态) | 分析图片、OCR | 基础 |
| tts | 文本转语音 | 生成语音消息 | 基础 |
1.3 工具配置文件(Tool Profiles)
OpenClaw 提供了 4 个预定义配置文件,简化工具权限配置:
1. minimal - 最小配置
json5
{
"tools": {
"profile": "minimal"
}
}
包含工具:
session_status(仅会话状态)
适用场景:
- 受限环境
- 只读监控
2. coding - 编程配置 ⭐
json5
{
"tools": {
"profile": "coding"
}
}
包含工具:
- 文件系统:read, write, edit, apply_patch
- 运行时:exec, process
- 内存:memory_search, memory_get
- 会话:sessions_list, sessions_send, sessions_spawn
- 自动化:cron
- 媒体:image
适用场景:
- 代码开发
- 自动化脚本
- 技术支持
3. messaging - 消息配置
json5
{
"tools": {
"profile": "messaging"
}
}
包含工具:
- sessions_list, sessions_send
- session_status
- message
适用场景:
- 客服机器人
- 通知助手
- 会话管理
4. full - 完整配置
json5
{
"tools": {
"profile": "full"
}
}
包含工具:所有工具(无限制)
适用场景:
- 开发环境
- 超级管理员
- 完全信任的环境
1.4 工具策略系统
OpenClaw 使用多层策略控制工具访问:
策略应用顺序
markdown
1. 配置文件策略 tools.profile
2. 提供商配置文件 tools.byProvider[provider].profile
3. 全局允许列表 tools.allow
4. 全局拒绝列表 tools.deny
5. 代理允许列表 agents[id].tools.allow
6. 代理拒绝列表 agents[id].tools.deny
7. 组策略 (从会话推导)
8. 子代理策略 (深度限制)
9. 网关HTTP拒绝列表 (硬编码: exec, process)
策略配置示例
json5
{
"tools": {
// 基础配置文件
"profile": "coding",
// 全局允许(可使用工具组)
"allow": [
"group:openclaw", // 所有核心工具
"group:fs", // 仅文件系统工具
"my_custom_tool" // 自定义工具
],
// 全局拒绝(覆盖允许列表)
"deny": ["exec", "gateway"],
// 基于提供商的策略
"byProvider": {
"anthropic": {
"profile": "full" // Claude模型允许所有工具
},
"openai": {
"allow": ["read", "write", "web_search"]
}
}
},
"agents": {
"list": [
{
"id": "coding-assistant",
"tools": {
"profile": "coding",
"alsoAllow": ["browser", "canvas"]
}
},
{
"id": "read-only",
"tools": {
"allow": ["read", "memory_search"]
}
}
]
}
}
工具组(Tool Groups)
typescript
const CORE_TOOL_GROUPS = {
"group:openclaw": [/* 所有主要工具 */],
"group:fs": ["read", "write", "edit", "apply_patch"],
"group:runtime": ["exec", "process"],
"group:web": ["web_search", "web_fetch"],
"group:memory": ["memory_search", "memory_get"],
"group:sessions": ["sessions_list", "sessions_send", ...],
"group:plugins": "<all-plugin-tools>",
};
使用示例:
json5
{
"tools": {
"allow": [
"group:fs", // 所有文件操作
"group:memory", // 所有内存操作
"web_search" // 单个工具
]
}
}
1.5 插件工具扩展
OpenClaw 支持通过插件注册自定义工具:
插件工具注册
typescript
// my-plugin/src/tools.ts
import { Type } from "@sinclair/typebox";
export default function registerTools(api) {
// 必需工具(始终可用)
api.registerTool({
name: "analytics_query",
description: "Query analytics data",
parameters: Type.Object({
metric: Type.String(),
timeRange: Type.Optional(Type.String()),
}),
async execute(toolCallId, params) {
const data = await fetchAnalytics(params.metric);
return {
content: [{ type: "text", text: JSON.stringify(data) }]
};
},
});
// 可选工具(需要白名单)
api.registerTool(
{
name: "advanced_analytics",
description: "Advanced analytics with ML",
parameters: Type.Object({ query: Type.String() }),
async execute(toolCallId, params) {
// 实现
},
},
{ optional: true } // 标记为可选
);
}
启用插件工具
json5
{
"agents": {
"list": [
{
"id": "main",
"tools": {
"allow": [
"analytics_query", // 启用特定工具
"my-analytics-plugin", // 或启用插件的所有工具
]
}
}
]
}
}
第二部分:Skills 注入机制详解
2.1 什么是 Skill?
定义:Skill 是 Markdown 格式的知识文档,教会 Agent 如何组合使用工具完成复杂任务。
核心组成:
ini
Skill = Prompt(教学内容)+ Tool References(工具引用)
示例:
markdown
---
name: git
description: Git version control operations
user-invocable: true
---
# Git Operations Skill
Use the `exec` tool for git commands: ← Tool引用
## Steps
1. Check status: `exec git status`
2. Stage changes: `exec git add .`
3. Commit: `exec git commit -m "message"`
4. Push: `exec git push`
## Best Practices
- Always check status first
- Write clear commit messages
与 Tool 的本质区别:
| 维度 | Tool(工具) | Skill(技能) |
|---|---|---|
| 形式 | 代码(TypeScript函数) | 文档(Markdown文本) |
| 作用 | 提供原子能力 | 教学如何使用工具 |
| 注册 | 代码注册(启动时) | 文件扫描(启动时) |
| 传递 | Function calling API | 系统提示注入 |
| 执行 | 直接调用 | LLM读取并遵循 |
2.2 Skill 注入的完整时间线
时间线概览
yaml
系统启动/配置变更
↓
┌─────────────────────────────────────────────────────┐
│ 阶段1:加载阶段(构建时) │
│ 时机:启动或配置变更 │
└─────────────────────────────────────────────────────┘
↓
1.1 扫描技能文件
位置:workspace/skills/, ~/.openclaw/skills/, ...
优先级:workspace > managed > bundled
1.2 解析 frontmatter
提取:name, description, user-invocable, ...
1.3 检查运行时资格
- OS匹配?(metadata.os)
- 二进制可用?(metadata.requires.bins)
- 环境变量存在?(metadata.requires.env)
1.4 应用配置过滤
- skills.entries[name].enabled !== false
- skills.allowBundled 白名单
- 代理级 skillFilter
1.5 生成技能快照(SkillSnapshot)
prompt: 格式化后的技能列表
skills: 技能元数据
version: 1
↓
┌─────────────────────────────────────────────────────┐
│ 阶段2:系统提示构建(每次会话开始) │
│ 时机:会话创建时 │
└─────────────────────────────────────────────────────┘
↓
2.1 创建工具列表(独立流程)
tools = createOpenClawTools()
filtered = applyToolPolicyPipeline(tools)
toolNames = filtered.map(t => t.name)
2.2 格式化技能块
过滤:disable-model-invocation !== true
限制:maxSkillsInPrompt=150, maxChars=30000
生成:
<available_skills>
- python: Execute Python code
Location: ~/.openclaw/skills/python/SKILL.md
- git: Git version control
Location: ~/.openclaw/skills/git/SKILL.md
</available_skills>
2.3 注入系统提示
buildAgentSystemPrompt({
toolNames, // ← 已过滤的工具列表
skillsPrompt, // ← 格式化的技能块
})
生成:
## Tooling
- read: Read file
- write: Write file
- exec: Execute command
...
## Skills (mandatory)
Before replying: scan <available_skills>...
<available_skills>
- python: ...
- git: ...
</available_skills>
↓
┌─────────────────────────────────────────────────────┐
│ 阶段3:LLM 处理(运行时) │
│ 时机:每次用户消息 │
└─────────────────────────────────────────────────────┘
↓
3.1 LLM接收系统提示
包含:工具列表 + 技能列表
3.2 LLM分析用户请求
判断:是否需要使用技能?
3.3 LLM选择技能(可选)
扫描:<available_skills>
选择:最相关的技能
3.4 LLM读取技能文档
Tool call: read("~/.openclaw/skills/python/SKILL.md")
返回:完整的Markdown说明
3.5 LLM遵循技能执行
按照技能步骤调用工具
2.3 阶段1:技能加载详解
加载位置和优先级
typescript
// 技能搜索路径(优先级从高到低)
const skillDirs = [
"workspace/skills/", // 1. 工作空间(最高优先级)
".agents/skills/", // 2. 项目级代理目录
"~/.agents/skills/", // 3. 个人代理目录
"~/.openclaw/skills/", // 4. 本地管理目录
"node_modules/.../skills/", // 5. 捆绑技能(最低优先级)
// + extraDirs from config
];
位置 :src/agents/skills/workspace.ts:221-406
typescript
export function loadSkillEntries(params: {
workspaceDir?: string;
extraDirs?: string[];
}): SkillEntry[] {
const dirs: string[] = [];
// 1. 额外目录(最低优先级)
if (params.extraDirs) {
dirs.push(...params.extraDirs);
}
// 2. 捆绑技能
const bundledDir = resolveBundledSkillsDir();
if (bundledDir) {
dirs.push(bundledDir);
}
// 3. 本地管理
dirs.push(path.join(os.homedir(), ".openclaw", "skills"));
// 4. 个人代理
dirs.push(path.join(os.homedir(), ".agents", "skills"));
// 5. 项目代理
if (params.workspaceDir) {
dirs.push(path.join(params.workspaceDir, ".agents", "skills"));
}
// 6. 工作空间(最高优先级)
if (params.workspaceDir) {
dirs.push(path.join(params.workspaceDir, "skills"));
}
// 从所有目录加载技能
const allEntries: SkillEntry[] = [];
for (const dir of dirs) {
const entries = loadSkillEntriesFromDir(dir);
allEntries.push(...entries);
}
// 按名称合并(后加载的覆盖先加载的)
const merged = mergeSkillsByName(allEntries);
return merged;
}
合并策略:
- 同名技能:后加载的覆盖先加载的
- 允许工作空间覆盖捆绑技能
- 实现自定义和扩展
运行时资格检查
位置 :src/agents/skills/config.ts:50-120
typescript
export function shouldIncludeSkill(params: {
entry: SkillEntry;
config?: OpenClawConfig;
eligibility?: SkillEligibilityContext;
}): boolean {
const { entry, config, eligibility } = params;
// 1. 检查配置启用
const skillConfig = resolveSkillConfig(config, entry.skill.name);
if (skillConfig?.enabled === false) {
return false; // 配置明确禁用
}
// 2. 检查捆绑技能白名单
if (entry.skill.source === "openclaw-bundled") {
const allowBundled = config?.skills?.allowBundled ?? [];
if (!allowBundled.includes(entry.skill.name)) {
return false; // 不在白名单中
}
}
// 3. 运行时资格评估
return evaluateRuntimeEligibility({
// 操作系统检查
os: entry.metadata?.os,
currentPlatform: process.platform,
// always标志(强制加载)
always: entry.metadata?.always,
// 二进制文件检查
requires: entry.metadata?.requires,
hasBin: (bin) => hasBinary(bin),
// 环境变量检查
hasEnv: (envName) =>
!!process.env[envName] ||
!!skillConfig?.env?.[envName],
// 配置路径检查
isConfigPathTruthy: (path) =>
isConfigPathTruthy(config, path),
});
}
资格检查示例:
yaml
# Python技能(需要python3二进制)
---
metadata:
openclaw:
requires:
bins: ["python3"]
---
检查逻辑:
typescript
if (!hasBinary("python3")) {
return false; // Python技能不加载
}
yaml
# OpenAI技能(需要API密钥)
---
metadata:
openclaw:
primaryEnv: "OPENAI_API_KEY"
requires:
env: ["OPENAI_API_KEY"]
---
检查逻辑:
typescript
if (!process.env.OPENAI_API_KEY && !skillConfig?.apiKey) {
return false; // OpenAI技能不加载
}
2.4 阶段2:系统提示注入详解
技能块格式化
位置 :src/agents/skills/format.ts
typescript
function formatSkillsForPrompt(entries: SkillEntry[]): string {
// 1. 过滤:排除 disable-model-invocation
const forPrompt = entries.filter(
e => e.invocation?.disableModelInvocation !== true
);
// 2. 应用大小限制
const limited = applySkillsPromptLimits(forPrompt, {
maxSkillsInPrompt: 150, // 最多150个技能
maxSkillsPromptChars: 30000, // 最多30000字符
});
// 3. 格式化为列表
const lines = limited.map(entry => {
const name = entry.skill.name;
const desc = entry.skill.description || "(no description)";
const location = entry.skill.filePath;
return `- ${name}: ${desc}\n Location: ${location}`;
});
return `
<available_skills>
${lines.join("\n")}
</available_skills>
`;
}
生成示例:
javascript
<available_skills>
- python: Execute Python code
Location: ~/.openclaw/skills/python/SKILL.md
- git: Git version control operations
Location: ~/.openclaw/skills/git/SKILL.md
- node: Node.js development
Location: ~/.openclaw/skills/node/SKILL.md
</available_skills>
系统提示构建
位置 :src/agents/system-prompt.ts:396-473
typescript
export function buildAgentSystemPrompt(params: {
toolNames: string[]; // 已过滤的工具名列表
skillsPrompt?: string; // 格式化的技能块
workspaceDir?: string;
// ... 其他参数
}): string {
const sections = [
buildModelSection(),
buildRoleSection(),
buildToolingSection(params.toolNames), // ← Tools部分
buildSafetySection(),
buildSkillsSection(params.skillsPrompt), // ← Skills部分
buildMemorySection(),
buildWorkspaceSection(params.workspaceDir),
buildOutputSection(),
];
return sections.flat().join("\n");
}
技能部分构建:
typescript
function buildSkillsSection(params: {
skillsPrompt?: string;
readToolName: string;
}): string[] {
const trimmed = params.skillsPrompt?.trim();
if (!trimmed) {
return []; // 没有技能,跳过
}
return [
"## Skills (mandatory)",
"Before replying: scan <available_skills> <description> entries.",
"- If exactly one skill clearly applies: read its SKILL.md at <location> with `read`, then follow it.",
"- If multiple could apply: choose the most specific one, then read/follow it.",
"- If none clearly apply: do not read any SKILL.md.",
"Constraints: never read more than one skill up front; only read after selecting.",
trimmed, // 注入技能块
"",
];
}
最终系统提示:
markdown
## Tooling
Available tools:
- read: Read file contents
- write: Create or overwrite file
- exec: Execute shell command
- memory_search: Search memory files
...
## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md at <location> with `read`, then follow it.
- If multiple could apply: choose the most specific one, then read/follow it.
- If none clearly apply: do not read any SKILL.md.
Constraints: never read more than one skill up front; only read after selecting.
<available_skills>
- python: Execute Python code
Location: ~/.openclaw/skills/python/SKILL.md
- git: Git version control operations
Location: ~/.openclaw/skills/git/SKILL.md
</available_skills>
## Memory
Search MEMORY.md before answering questions about history...
关键点:
- Skills部分在Tools部分之后
- 包含明确的选择指令("scan", "read", "follow")
- 使用 "mandatory" 强调重要性
- 限制只读取一个技能(避免过度加载)
2.5 阶段3:运行时执行详解
LLM 自主选择流程
perl
用户消息:"用Python写一个hello world程序"
↓
LLM接收系统提示:
## Tooling
- read, write, exec, ...
## Skills
- python: Execute Python code
- git: Git operations
↓
LLM推理:
1. 用户要求:写Python程序
2. 扫描技能列表
3. 判断:"python" skill clearly applies
4. 决定:read python/SKILL.md
↓
LLM调用工具:
Tool: read
Args: { path: "~/.openclaw/skills/python/SKILL.md" }
↓
返回技能文档:
# Python Skill
Use the `exec` tool to run Python code:
## Steps
1. Write Python code using `write` tool
2. Execute with `exec python3 script.py`
3. Handle errors and output
## Example
User: "write hello world"
→ write("hello.py", "print('hello world')")
→ exec("python3 hello.py")
↓
LLM遵循技能步骤:
1. Tool: write
Args: { path: "hello.py", content: "print('hello world')" }
2. Tool: exec
Args: { command: "python3 hello.py" }
↓
返回结果:
"✅ 已创建并执行 hello.py,输出:hello world"
关键点:
- LLM 自主判断是否需要技能
- LLM 自主选择使用哪个技能
- 技能文档通过
read工具动态获取 - 技能只是指导,最终执行仍依赖工具
用户显式调用(Slash Commands)
scss
用户消息:"/python print('hello')"
↓
解析命令:
commandName = "python"
args = "print('hello')"
↓
加载技能命令列表:
listSkillCommandsForWorkspace()
↓
返回:[ { name: "python", skillName: "python", dispatch: { kind: "tool", toolName: "exec" } }, ... ]
↓
匹配命令:
resolveSkillCommandInvocation("python", args)
↓
检查 dispatch 配置:
dispatch = { kind: "tool", toolName: "exec" }
↓
有 tool-dispatch → 直接调用工具
↓
查找工具:
tools = createOpenClawTools() // 应用Tool策略
tool = tools.find(t => t.name === "exec")
↓
执行工具(绕过LLM):
result = tool.execute({
command: "print('hello')",
commandName: "python",
skillName: "python"
})
↓
返回结果:
"hello"
关键点:
- 用户通过
/skillname显式调用 - 支持 tool-dispatch(直接调用工具)
- 不经过LLM推理(确定性执行)
- 仍受Tool策略约束
第三部分:Tools 与 Skills 的协作关系
3.1 依赖关系图
markdown
┌─────────────────────────────────────────────────────┐
│ Tools(工具层) │
│ - 提供原子能力 │
│ - 代码实现 │
│ - 启动时注册 │
│ - 独立于Skills │
└───────────────┬─────────────────────────────────────┘
│
│ 被引用
│
↓
┌─────────────────────────────────────────────────────┐
│ Skills(技能层) │
│ - 教学如何使用工具 │
│ - Markdown文档 │
│ - 启动时加载 │
│ - 引用工具名称(字符串) │
└───────────────┬─────────────────────────────────────┘
│
│ 指导
│
↓
┌─────────────────────────────────────────────────────┐
│ LLM(执行层) │
│ - 读取技能文档 │
│ - 理解步骤 │
│ - 调用工具执行 │
│ - 组合工具完成任务 │
└─────────────────────────────────────────────────────┘
3.2 执行时序图
bash
启动阶段(一次性):
├─ 注册所有Tools
│ ├─ 内置工具
│ └─ 插件工具
│
├─ 加载所有Skills
│ ├─ 扫描文件
│ └─ 检查资格
│
└─ 生成快照
├─ 工具列表
└─ 技能列表
会话开始:
├─ 应用Tool策略
│ └─ 过滤工具列表
│
├─ 构建系统提示
│ ├─ ## Tooling: [过滤后的工具]
│ └─ ## Skills: [所有技能]
│
└─ 发送给LLM
用户消息:
├─ LLM分析请求
│
├─ 决定:需要技能?
│ ├─ 需要 → 选择技能
│ │ ├─ read技能文档
│ │ ├─ 理解步骤
│ │ └─ 调用工具执行
│ │
│ └─ 不需要 → 直接调用工具
│
└─ 返回结果
3.3 关键约束
1. Tool 必须提前注册
❌ 错误理解:读取Skill时会加载Skill中提到的工具
✅ 正确理解:Skill只能引用已注册的工具
证明:
typescript
// read工具的执行函数
execute: async (toolCallId, params) => {
const content = fs.readFileSync(params.path, "utf-8");
return { content: [{ type: "text", text: content }] };
// ❌ 没有工具注册逻辑
// ❌ 没有解析技能中的工具引用
// ✅ 只是返回文本给LLM
}
2. Tool策略不影响Skill可见性
perl
配置:
tools.deny = ["exec"] // 拒绝exec工具
skills.allow = ["python"] // 但允许Python技能
结果:
系统提示包含:
## Tooling
- read ✅
- write ✅
- exec ❌(不在列表中)
## Skills
- python ✅(技能仍然可见)
执行:
LLM读取python技能 → 尝试调用exec → 失败 ❌
原因:
- Tool过滤和Skill过滤是独立的
- Skill可见性不受Tool策略影响
- 但Skill执行依赖Tool可用性
3. 配置一致性很重要
反例(会失败):
json5
{
"tools": { "deny": ["exec"] },
"skills": { "allowBundled": ["python", "git", "node"] }
}
所有这些技能都依赖 exec,会执行失败。
正例(一致):
json5
{
"tools": {
"allow": ["read", "write", "exec", "memory_search"]
},
"skills": {
"allowBundled": ["python", "git", "node"]
}
}
或者使用元数据声明依赖:
yaml
---
metadata:
openclaw:
requires:
config: ["tools.allow"] # 要求工具策略允许必要工具
---
3.4 最佳实践
1. 使用配置文件简化管理
json5
{
"tools": {
"profile": "coding" // 一次性允许所有编程工具
},
"skills": {
"allowBundled": ["python", "git", "node"]
}
}
2. 为Skill声明工具依赖
yaml
---
metadata:
openclaw:
requires:
bins: ["python3"] # 二进制要求
config: ["tools.allow"] # 配置要求
---
这样技能会在依赖不满足时自动不加载。
3. 使用 tool-dispatch 确保一致性
yaml
---
command-dispatch: tool
command-tool: exec
---
用户输入 /python code 时:
- 直接调用
exec工具 - 绕过LLM推理
- 确保行为一致
4. 监控技能执行失败
如果用户报告"技能不工作",检查清单:
scss
□ 技能是否在系统提示中?
→ 查看 buildWorkspaceSkillSnapshot() 输出
□ 技能依赖的工具是否可用?
→ 查看 createOpenClawTools() 输出
→ 查看 applyToolPolicyPipeline() 结果
□ Tool策略是否拒绝了必要工具?
→ 检查 config.tools.deny
→ 检查 config.agents[id].tools.deny
□ 运行时资格是否满足?
→ 检查 OS、二进制、环境变量
第四部分:实战场景
场景1:代码审查助手
目标:自动审查代码变更
配置:
json5
{
"tools": {
"profile": "coding",
"alsoAllow": ["web_search"] // 查找最佳实践
},
"skills": {
"allowBundled": ["git"]
}
}
工作流:
scss
用户:"审查最近的commit"
↓
LLM选择技能:git
↓
LLM读取git技能
↓
LLM执行步骤:
1. exec("git diff HEAD~1") ← 查看变更
2. read(...modified files...) ← 读取完整文件
3. web_search("best practices for ...") ← 查找规范
4. 生成审查报告
↓
返回:
"审查报告:
- ✅ 代码质量良好
- ⚠️ 建议添加错误处理
- 💡 参考资料:[链接]"
场景2:自动化报告生成
目标:每天生成销售报告
配置:
json5
{
"tools": {
"profile": "coding",
"alsoAllow": ["cron", "message"]
},
"skills": {
"entries": {
"daily-report": {
"enabled": true
}
}
}
}
自定义技能:
yaml
# workspace/skills/daily-report/SKILL.md
---
name: daily-report
user-invocable: true
command-dispatch: tool
command-tool: cron
---
# Daily Report Skill
Generate and send daily sales reports.
## Steps
1. Query database: `exec psql -c "SELECT ..."`
2. Generate chart: `canvas <chart html>`
3. Send to Slack: `message "Daily Report: ..."`
触发:
bash
方式1:手动触发
/daily-report
方式2:定时任务
cron("0 9 * * *", "/daily-report")
场景3:多语言开发助手
目标:支持Python、Node.js、Go开发
配置:
json5
{
"tools": {
"profile": "coding"
},
"skills": {
"allowBundled": ["python", "node", "go"],
"entries": {
"python": {
"env": { "PYTHON_BIN": "/usr/local/bin/python3.11" }
},
"node": {
"env": { "NODE_BIN": "/usr/local/bin/node" }
}
}
}
}
智能选择:
scss
用户:"写一个REST API"
↓
LLM扫描技能:
- python(Django/Flask)
- node(Express)
- go(Gin)
↓
LLM询问:
"您想使用哪种语言?Python (Flask)、Node.js (Express) 还是 Go (Gin)?"
↓
用户:"Node.js"
↓
LLM选择:node技能
↓
LLM执行:
1. write("package.json", ...)
2. write("server.js", ...)
3. exec("npm install")
4. exec("node server.js")
总结
核心要点
-
工具是能力,技能是智慧
- 工具:28个核心工具 + 插件扩展
- 技能:Markdown文档,教学如何使用工具
-
注入发生在启动和会话开始
- 启动时:加载技能和工具
- 会话时:构建系统提示
- 运行时:LLM读取和执行
-
工具和技能独立但关联
- 独立注册和加载
- 技能引用工具名称
- 技能执行依赖工具可用性
-
多层策略确保安全
- 工具策略:控制工具可用性
- 技能策略:控制技能可见性
- 两者配合保证系统安全
架构优势
- 灵活性:工具和技能分离,易于扩展
- 安全性:多层策略控制,细粒度权限
- 可组合性:小工具组合成复杂功能
- 可维护性:技能是文档,易于更新
设计哲学
"Give the agent tools to do things, and skills to know how to do them well."
"给代理工具去做事,给技能教它如何做好。"