OpenClaw 技能开发指南:从入门到精通

技能是 OpenClaw 的核心能力扩展机制。通过开发自定义技能,你可以让 AI 助手执行各种特定的任务。本文将从基础概念到高级技巧,全面讲解 OpenClaw 技能开发。

技能开发基础

什么是技能?

技能是可插拔的功能模块,定义了: - 触发条件:什么时候执行 - 输入参数:需要什么数据 - 处理逻辑:如何处理数据 - 输出格式:返回什么结果

技能生命周期

初始化 → 触发 → 执行 → 返回结果 → 清理

创建第一个技能

步骤 1:创建技能目录

mkdir -p skills/hello
cd skills/hello

步骤 2:编写技能配置

创建 skill.yaml

name: "hello"
version: "1.0.0"
description: "简单的打招呼技能"

# 触发关键词
triggers:
  - "你好"
  - "hello"
  - "打招呼"

# 无需参数
parameters: []

# 作者信息
author:
  name: "Your Name"
  email: "you@example.com"

步骤 3:编写技能逻辑

创建 index.js

module.exports = {
  // 技能名称
  name: 'hello',

  // 技能描述
  description: '打招呼技能',

  // 执行函数(必需)
  async execute(params, context) {
    const username = context.user?.name || '朋友';
    const hour = new Date().getHours();

    let greeting;
    if (hour < 12) {
      greeting = '早上好';
    } else if (hour < 18) {
      greeting = '下午好';
    } else {
      greeting = '晚上好';
    }

    return {
      type: 'text',
      content: `${greeting}${username}!很高兴见到你!`
    };
  }
};

步骤 4:注册技能

config/skills.yaml 中:

skills:
  - path: "./skills/hello"
    enabled: true

步骤 5:测试技能

# 重启服务
npm run dev

# 在 Telegram/Discord 中发送 "你好"
# 应该收到问候消息

技能配置详解

触发器配置

triggers:
  # 关键词触发
  - "关键词1"
  - "关键词2"

  # 正则触发
  patterns:
    - "^查天气\\s+(.+)$"
    - "^天气\\s+(.+)$"

  # 命令触发
  commands:
    - name: "hello"
      description: "打招呼"

  # 事件触发
  events:
    - "message.receive"
    - "user.join"

参数配置

parameters:
  # 字符串参数
  - name: "city"
    type: "string"
    required: true
    description: "城市名称"
    default: "北京"

  # 数字参数
  - name: "count"
    type: "number"
    required: false
    default: 10
    min: 1
    max: 100

  # 布尔参数
  - name: "detailed"
    type: "boolean"
    required: false
    default: false

  # 枚举参数
  - name: "unit"
    type: "string"
    required: false
    default: "celsius"
    options:
      - value: "celsius"
        label: "摄氏度"
      - value: "fahrenheit"
        label: "华氏度"

  # 数组参数
  - name: "tags"
    type: "array"
    required: false
    items:
      type: "string"

高级技能开发

1. 调用外部 API

const axios = require('axios');

module.exports = {
  name: 'weather',

  async execute(params, context) {
    const { city } = params;

    try {
      // 调用天气 API
      const response = await axios.get('https://api.weather.example.com/v1/current', {
        params: {
          city,
          apiKey: process.env.WEATHER_API_KEY
        },
        timeout: 10000  // 10秒超时
      });

      const { condition, temperature, humidity } = response.data;

      return {
        type: 'text',
        content: `【${city}天气】\n天气:${condition}\n温度:${temperature}°C\n湿度:${humidity}%`
      };
    } catch (error) {
      // 错误处理
      context.logger.error('Weather API error', error);

      return {
        type: 'text',
        content: `抱歉,无法获取 ${city} 的天气信息。请稍后重试。`
      };
    }
  }
};

2. 使用记忆功能

module.exports = {
  name: 'preference',

  async execute(params, context) {
    const { action, key, value } = params;
    const userId = context.user?.id;

    switch (action) {
      case 'set':
        // 存储偏好
        await context.memory.set(`pref:${userId}:${key}`, value);
        return {
          type: 'text',
          content: `已记住您的偏好:${key} = ${value}`
        };

      case 'get':
        // 读取偏好
        const savedValue = await context.memory.get(`pref:${userId}:${key}`);
        return {
          type: 'text',
          content: savedValue 
            ? `您的 ${key} 偏好是:${savedValue}` 
            : `您还没有设置 ${key} 偏好`
        };

      case 'list':
        // 列出所有偏好
        const prefs = await context.memory.keys(`pref:${userId}:*`);
        const prefList = await Promise.all(
          prefs.map(async (k) => {
            const v = await context.memory.get(k);
            return `${k.split(':').pop()}: ${v}`;
          })
        );
        return {
          type: 'text',
          content: `您的偏好设置:\n${prefList.join('\n')}`
        };
    }
  }
};

3. 调用其他技能

module.exports = {
  name: 'research',

  async execute(params, context) {
    const { topic, language = 'zh' } = params;

    // 步骤 1:搜索
    const searchResult = await context.callSkill('search', {
      query: topic,
      limit: 5
    });

    // 步骤 2:摘要
    const summaryResult = await context.callSkill('summarize', {
      content: searchResult.content,
      maxLength: 500
    });

    // 步骤 3:翻译(如果需要)
    if (language !== 'en') {
      const translateResult = await context.callSkill('translate', {
        text: summaryResult.content,
        to: language
      });
      return translateResult;
    }

    return summaryResult;
  }
};

4. 异步长时间任务

module.exports = {
  name: 'long-task',

  async execute(params, context) {
    const { taskId } = params;

    // 立即返回,任务在后台执行
    setImmediate(async () => {
      try {
        // 执行耗时任务
        for (let i = 0; i < 10; i++) {
          await new Promise(r => setTimeout(r, 1000));

          // 发送进度通知
          await context.notifier.notify({
            userId: context.user.id,
            content: `任务进度:${(i + 1) * 10}%`
          });
        }

        // 任务完成通知
        await context.notifier.notify({
          userId: context.user.id,
          content: '✅ 任务完成!'
        });
      } catch (error) {
        // 错误通知
        await context.notifier.notify({
          userId: context.user.id,
          content: `❌ 任务失败:${error.message}`
        });
      }
    });

    return {
      type: 'text',
      content: `任务 ${taskId} 已启动,我会通知你进度。`
    };
  }
};

5. 处理文件

const fs = require('fs').promises;
const path = require('path');

module.exports = {
  name: 'file-handler',

  async execute(params, context) {
    const { action, filename, content } = params;
    const dataDir = './data/skill-data';

    switch (action) {
      case 'read':
        const filePath = path.join(dataDir, filename);
        const fileContent = await fs.readFile(filePath, 'utf-8');
        return {
          type: 'text',
          content: fileContent
        };

      case 'write':
        await fs.mkdir(dataDir, { recursive: true });
        await fs.writeFile(path.join(dataDir, filename), content);
        return {
          type: 'text',
          content: `文件 ${filename} 已保存`
        };

      case 'list':
        const files = await fs.readdir(dataDir);
        return {
          type: 'text',
          content: `文件列表:\n${files.join('\n')}`
        };
    }
  }
};

技能测试

单元测试

创建 skills/hello/index.test.js

const { execute } = require('./index');

describe('hello skill', () => {
  test('should return greeting', async () => {
    const context = {
      user: { name: 'Test' }
    };

    const result = await execute({}, context);

    expect(result.type).toBe('text');
    expect(result.content).toContain('Test');
  });
});

运行测试:

npm test skills/hello/index.test.js

集成测试

describe('hello skill integration', () => {
  test('should respond to trigger', async () => {
    // 模拟触发
    const message = {
      content: '你好',
      user: { id: 'test-user' }
    };

    const result = await skillManager.trigger(message);

    expect(result).toBeDefined();
    expect(result.type).toBe('text');
  });
});

技能发布

打包技能

// package.json
{
  "name": "openclaw-skill-hello",
  "version": "1.0.0",
  "main": "index.js",
  "keywords": ["openclaw", "skill", "hello"],
  "openclaw": {
    "skill": {
      "name": "hello",
      "description": "打招呼技能",
      "author": "Your Name"
    }
  }
}

发布到 NPM

npm login
npm publish

使用发布的技能

# config/skills.yaml
skills:
  - package: "openclaw-skill-hello"
    version: "^1.0.0"
    enabled: true

最佳实践

1. 错误处理

async execute(params, context) {
  try {
    // 业务逻辑
  } catch (error) {
    // 记录日志
    context.logger.error('Skill execution failed', {
      skill: 'my-skill',
      error: error.message,
      params
    });

    // 返回友好错误
    return {
      type: 'text',
      content: '抱歉,处理您的请求时出现问题。请稍后重试。'
    };
  }
}

2. 参数验证

async execute(params, context) {
  // 验证必填参数
  if (!params.required) {
    return {
      type: 'text',
      content: '请提供必要的参数'
    };
  }

  // 验证参数类型
  if (typeof params.count !== 'number') {
    return {
      type: 'text',
      content: 'count 必须是数字'
    };
  }

  // 验证参数范围
  if (params.count < 1 || params.count > 100) {
    return {
      type: 'text',
      content: 'count 必须在 1-100 之间'
    };
  }

  // 继续处理...
}

3. 日志记录

async execute(params, context) {
  const startTime = Date.now();

  context.logger.info('Skill started', {
    skill: 'my-skill',
    userId: context.user?.id,
    params
  });

  // 业务逻辑...

  context.logger.info('Skill completed', {
    skill: 'my-skill',
    duration: Date.now() - startTime
  });
}

4. 性能优化

async execute(params, context) {
  // 使用缓存
  const cacheKey = `skill:${params.query}`;
  const cached = await context.cache.get(cacheKey);

  if (cached) {
    return cached;
  }

  // 计算结果
  const result = await expensiveOperation(params);

  // 缓存结果
  await context.cache.set(cacheKey, result, 3600); // 1小时

  return result;
}

调试技巧

1. 开启调试日志

logging:
  level: "debug"

2. 使用调试器

node --inspect dist/index.js

然后在 Chrome DevTools 中连接调试。

3. 输出调试信息

async execute(params, context) {
  context.logger.debug('Debug info', {
    params,
    user: context.user,
    timestamp: Date.now()
  });

  // ...
}

总结

技能开发是 OpenClaw 的核心能力,通过本文你应该掌握了:

  • ✅ 技能的基本概念
  • ✅ 创建和注册技能
  • ✅ 参数配置和验证
  • ✅ 高级功能(API、记忆、异步)
  • ✅ 测试和发布技能
  • ✅ 最佳实践

开始开发你自己的技能吧!


相关阅读: - OpenClaw 技能系统 - OpenClaw 自动化工作流 - OpenClaw 最佳实践