乐于分享
好东西不私藏

OpenClaw 插件与自定义 Tool 开发:给 AI 装上“手脚”

OpenClaw 插件与自定义 Tool 开发:给 AI 装上“手脚”

大家好,我是阿木木。

很多同学在部署完 OpenClaw 后,最常问我的一个问题是:“阿木木,AI 除了能跟我聊天,能不能帮我干点实事?”

我一般会把“实事”拆成三类,你一对照就知道自己需要的是哪种能力:

  • 查本地数据
    :SQLite/MySQL 最新订单、今天的错误日志、Nginx 访问量、某个目录下最近改动的文件
  • 调外部系统
    :Home Assistant 开关灯、飞书/Slack 发消息、Jira 拉任务、GitHub 查 PR 状态
  • 执行动作
    :跑脚本生成报表、批量重命名/清理文件、拉代码并跑测试

如果你只是把 OpenClaw 当作一个网页版 ChatGPT 的替代品,那就真的大材小用了。OpenClaw 真正的价值是:把 LLM 从“会说话”升级成“能做事”——靠的就是 插件系统(Plugins/Extensions) 和 自定义 Tool(工具函数)

这篇我不讲虚的,直接给你一套能落地的套路:从 0 到 1 写一个可安装插件,注册一个稳定可控的 Tool,并把调试与安全边界一次讲透。

提醒一句:不同版本的 OpenClaw 插件 API 命名可能略有差异,但下面的“工程结构 + Tool 四要素 + 受控执行模板”是通用的,你按同样思路替换到你当前版本即可。


1. 先把概念讲透:插件 vs Tool(别一上来就写 shell

很多人一听“插件”,脑子里想到的是“装个包”。在 OpenClaw 里,你可以把它理解成两层:

  • 插件(Plugin/Extension)
    :一个可安装、可配置、可分发的能力包。它负责声明元信息、依赖、配置项,并把能力注册到网关/Agent。
  • Tool(工具)
    :插件暴露给 Agent 的“可调用函数”。模型会以结构化参数调用 Tool,你在 handler 里真正去查库/调接口/执行动作,然后把结果返回。

所以“给 AI 装手脚”的正确姿势是:用插件承载工程化(安装/配置/版本/日志),用 Tool 承载行动能力(输入/输出/执行)。


2. 从 0 到 1:一个最小可用插件骨架(可复制)

先把目录搭起来(最小骨架就够用):

my-first-extension/
 openclaw.plugin.json
 package.json
 src/
 index.ts

2.1 openclaw.plugin.json:插件身份证(别写得“随便”)

你至少要交代清楚:插件是谁、入口在哪、有哪些配置、约束是什么。给你一个可直接改的模板(字段名以你当前版本为准,重点是表达的信息):

{
"name":"my-first-extension",
"version":"0.1.0",
"displayName":"My First Extension",
"description":"给 Agent 提供本地查询与自动化能力",
"main":"dist/index.js",
"configSchema":{
"type":"object",
"properties":{
"allowCommands":{
"type":"array",
"items":{"type":"string"},
"description":"允许执行的命令白名单(前缀匹配)"
},
"defaultTimeoutMs":{
"type":"number",
"description":"默认超时时间(毫秒)"
}
},
"required":["allowCommands"]
}
}

这里最关键的是 configSchema:它决定你的插件是不是“可维护”。没有 schema 的插件,后面基本就是靠口头约定,越用越乱。

2.2 插件放哪儿:本地优先级带来的“开发爽点”

OpenClaw 一般会按类似这样的优先级加载扩展:

  1. 项目私有扩展
    .openclaw/extensions/(强烈建议:项目相关的 Tool 放这里)
  2. 全局扩展
    ~/.openclaw/extensions/(通用工具放这里)
  3. 内置扩展
    :随核心包提供

这意味着你在调试时,把插件放在项目目录下改完重载就能生效,效率非常高。


3. 核心干货:注册一个“稳定好用”的 Tool(结构化参数 + 受控执行)

你原稿里给了一个 local_exec(command: string) 的例子,能跑,但很快会踩三类坑:

  • 不稳定
    :模型会把自然语言塞进 command,命令报错、输出不可解析
  • 不安全
    :一句“清理一下临时目录”就可能变成灾难
  • 不可维护
    :所有需求都往一个 command 里塞,最后变成垃圾桶

更靠谱的方式是:把“自由文本”变成“结构化参数”,把“任意执行”变成“受控动作”。

一个 Tool,我建议你按四要素来写(写得越具体,模型越稳定):

  • Name
    :短、稳定、可读(例如 safe_execorders_latest
  • Description
    :明确“什么时候用 / 什么时候别用”
  • Schema
    :把参数约束死(让模型更容易填对)
  • Handler
    :执行逻辑(加超时、截断、返回结构)

3.1 可直接抄的模板:白名单 + 超时 + 结构化返回

下面给你一个“可长期迭代”的 Tool 模板。它的目标不是“万能执行”,而是把风险和不确定性压到最低:

import { execFile } from"node:child_process";
import { promisify } from"node:util";

const execFileAsync = promisify(execFile);

exportdefaultfunctionregister(api: any) {
 api.registerTool({
name"safe_exec",
description:
"执行受控的本地只读命令(必须匹配 allowCommands 白名单)。适用于:查看状态、读取日志、运行诊断命令。禁止用于删除/写入/下载等高危操作。",
schema: {
type"object",
properties: {
program: { type"string"description"可执行程序名,如 git/node/ls" },
args: {
type"array",
items: { type"string" },
description"参数数组(不要拼接成一整段字符串)"
 },
timeoutMs: { type"number"description"超时毫秒数(可选)" }
 },
required: ["program""args"]
 },
handlerasync ({ program, args, timeoutMs }: any) => {
const cfg = api.getConfig?.() ?? {};
constallowstring[] = cfg.allowCommands ?? [];
const timeout = timeoutMs ?? cfg.defaultTimeoutMs ?? 10_000;

const commandLine = [program, ...args].join(" ");
const allowed = allow.some((prefix) => commandLine.startsWith(prefix));
if (!allowed) return { okfalseerror`command not allowed: ${commandLine}` };

try {
const { stdout, stderr } = awaitexecFileAsync(program, args, {
 timeout,
windowsHidetrue,
maxBuffer1024 * 1024
 });
return {
oktrue,
stdout: (stdout ?? "").slice(020_000),
stderr: (stderr ?? "").slice(020_000)
 };
 } catch (eany) {
return { okfalseerror: e?.message ?? String(e) };
 }
 }
 });
}

这段模板背后的“硬干货”是这四条:

  1. 不要用一整段 command 字符串
    ,拆成 program + args[],模型更容易填对,也更安全。
  2. 一定要做白名单
    (前缀匹配足够实用),别把“权限控制”交给提示词。
  3. 输出必须结构化
    ok/stdout/stderr/error),Agent 才能基于结果继续推理与行动。
  4. 一定要有超时 + 输出截断
    ,避免一个卡住的命令拖垮整条链路。

3.2 Schema 设计小抄:让模型“更愿意用、且用得对”

你想让 Tool 被稳定触发,Schema 和 Description 的写法决定了 80%:

  • 把自由输入改成枚举/选项
    :比如 range: "1h" | "24h" | "7d",模型更稳定
  • 尽量扁平化参数
    :少嵌套(模型更少填错层级)
  • 给字段写格式示例
    :比如 date 要求 YYYY-MM-DD
  • 返回“可继续行动的信息”
    :比如 countrowsfilePath,而不是一句“成功了”

4. 不止是 Tool:把插件做成“能长期用”的工程件

除了 Agent Tool,插件通常还能承载这些能力(你不一定全用,但要知道它的边界):

  • 快速命令(不走 LLM)
    :类似 /status 这种确定性逻辑,建议直接走命令路由,速度快、成本低、结果稳定
  • 网关侧常驻能力
    :定时任务、缓存、队列、消息推送、指标上报
  • CLI 子命令
    :把运维式动作做成命令,Agent 负责触发,CLI 负责执行与打印(可控性更强)

如果你打算让它在团队里长期用,我建议你至少补齐三件事:

  1. 配置可视化
    :用 configSchema 声明类型、必填项、默认值,减少“填错参数”的无效排查
  2. 可观测性
    :记录 Tool 入参摘要、耗时、错误原因、截断后的输出(不要在日志里泄露敏感信息)
  3. 失败可恢复
    :把常见错误变成可解释的返回(例如 ok=false + error),而不是一堆堆栈

5. 实战建议:安装/状态/排障,一张 checklist 够了

如果你已经写好了插件,或者想安装别人分享的插件,可以用 OpenClaw 的 CLI(命令可能随版本略有差异,但思路一致):

  • 安装插件
    openclaw plugins install <pkg-name>
  • 查看状态
    openclaw plugins list
  • 排查故障
    openclaw plugins doctor

我自己排障基本按这张清单走(非常省时间):

  • 插件没生效
  • 放置目录是否在加载路径里(项目私有 vs 全局)
  • main
     指向的入口文件是否存在(dist/index.js 是否真的生成了)
  • 构建是否更新(很多人改了 src,但忘了编译到 dist
  • Tool 不被调用
  • name/description
     是否足够具体(写清触发条件与禁用场景)
  • schema 是否太宽泛(只有一个 string 时,模型往往更倾向“继续聊天”)
  • handler 返回是否结构化(否则 Agent 很难做下一步)
  • 调用了但结果错
  • 打印入参摘要与耗时(你会很快发现是参数填错还是执行逻辑慢)
  • 外部系统(数据库/API)要做超时与重试(别把随机失败放大给用户)

6. 安全边界:别让“装手脚”变成“给自己挖坑”

这一段我说得直白一点:插件通常和网关跑在同一个权限域里。你给 Agent 开的能力,就是给本机开口子。

我的建议很简单,四条就够:

  1. 默认最小权限
    :先只读(查数据/查状态),再逐步开放写入动作
  2. 白名单 + 结构化参数
    :把“能做什么”锁死在 schema 与配置里,不要靠提示词约束
  3. 不要让 Tool 直接接触密钥原文
    :能统一凭证管理就别把 token 暴露给模型上下文
  4. 高危动作强制二次确认
    :删除/写入/发外部消息等动作,建议必须带 confirm: true 或走人工确认

总结

插件系统是 OpenClaw 真正“从聊天走向自动化”的核心。关键不是“注册一个函数”,而是把它做成 可安装、可配置、可调试、可控且安全 的能力包。

你可以从一个很小的 Tool 开始(例如:只读地查日志/查订单/查运行状态),按本文模板把 schema、白名单、超时、结构化输出都做好,再慢慢扩展到更复杂的自动化。

在下一篇文章中,我们聊聊 OpenClaw 的 多模态感知与媒体处理管道:图片/音频/视频是怎么进来、怎么处理、怎么变成可用信息的。

关注我,每天解锁一个 AI 技能。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » OpenClaw 插件与自定义 Tool 开发:给 AI 装上“手脚”

评论 抢沙发

9 + 7 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮