1. 项目概述与核心需求解析
1.1 为什么我会去折腾 OpenClaw
先交代一下背景。我平时会跑大量的自动化流程,包括定时抓取数据、文件整理、跨平台消息推送、还有一些简单的 LLM 调用链路。之前用的是各种脚本拼起来的组合方案,维护成本说实话有点高。后来接触到 OpenClaw,发现它把 Agent 的编排、技能的注册调用、多通道接入都整合到了一起,确实比我之前“手工缝合”的方式要顺不少。
但用了一阵子之后,我发现一个问题:内置的技能虽然覆盖了常见的 Web 搜索、文件读写、HTTP 请求这些场景,可真到了我自己的业务里,总有那么几个需求是内置能力满足不了的。比如我要把内部系统的数据拉下来做一轮自定义清洗,再按特定格式推到某个 Webhook;再比如我要让 Agent 根据某个规则自动重试失败的请求。这些逻辑不复杂,但 OpenClaw 默认不带,我必须得自己写。
于是就有了“为自动化流程构建自定义 OpenClaw 技能”这个需求。这里说的“技能(Skill)”,在 OpenClaw 的体系里就是给 Agent 扩展能力的一种单元,你可以把它理解成给 Agent 加了个“外挂工具”。你不需要改 Agent 的核心代码,只要按约定的方式写好技能描述和实现逻辑,注册之后 Agent 就能在合适的场景下自动调用。
这篇东西适合谁看?主要是已经在用 OpenClaw、但对自定义扩展还不太熟悉的人;以及准备把 OpenClaw 引入自己项目、但担心内置能力不够用的朋友。我会从技能的基本结构讲起,再给一个完整的示例,最后把我踩过的坑也都列出来,希望你能少走一点弯路。
1.2 OpenClaw 技能体系的基本认知
在开始写代码之前,我觉得有必要先把 OpenClaw 的技能体系捋清楚。OpenClaw 里的 Agent 不是一台只会按固定剧本执行的机器,它更像是一个“能理解意图、能调用工具、能根据反馈调整策略”的执行者。而技能,就是它用来完成具体动作的那双手。
从架构上看,一个自定义技能通常包含三部分:
- 技能声明:告诉 Agent “我是谁”“我能做什么”“我适合在什么场景下被使用”。
- 执行逻辑:真正干活的代码,可能是一个脚本、一个函数、或者一个可执行文件的调用。
- 输入输出契约:定义 Agent 调用你时需要传什么参数、你返回什么数据格式,让 Agent 能正确理解和处理结果。
这三部分缺一不可。如果你只写执行逻辑而不写声明,Agent 压根不知道有你这个技能存在;如果你写了声明但参数定义不清楚,Agent 调用时就会频繁传错参数,甚至直接报错。
另外有一点要特别提醒:OpenClaw 的技能系统是“由 Agent 自主决策调用”的。也就是说,技能本身不决定“什么时候被调用”,而是由 Agent 根据对话上下文、任务目标、技能描述综合判断。这意味着,你的技能描述写得是否清晰、关键词是否准确,直接影响 Agent 会不会在正确的时机拉出你这个技能来用。我见过不少新手朋友,技能实现很完美,但描述写得太含糊,结果 Agent 宁可去翻内置技能也不碰自建的,问题就出在这里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 自定义技能的定位与方案选型
2.1 要先想清楚:你这个技能到底要不要做成技能
这句话听起来很像废话,但我在实际项目里确实反复纠结过。OpenClaw 的技能机制虽然有弹性,但它毕竟是一套定制化的模块体系,不是所有需求都适合做成技能。
我自己判断一个需求是否适合技能化的标准有三个:
- 是否有明确的输入输出边界:技能的本质是“给 Agent 一个黑盒工具”,你输入参数,它返回结果。如果你的流程需要依赖大量上下文、中间状态难以量化的,那更适合直接写在 Agent 的 workflow 编排里,而不是单独抽成一个技能。
- 是否会被复用:如果你只是临时跑一次的需求,写个一次性脚本反而更快;技能适合那些“以后可能反复出现”的场景。
- 是否需要“被理解”:如果这个能力你希望 Agent 在合适的场景下主动想起并使用,那技能化是合适的;如果你只是希望某个动作被固定执行,那直接写死在流程里更简单。
我一开始犯过的错误是,把一些很业务化、很临时的逻辑也包装成技能,结果除了白白增加了工作量之外,还让 Agent 在决策时多了一堆“选项”,反而降低了准确率。后来我删掉了那些无关技能,系统一下就清爽了。
2.2 实操中的三种构建路径对比
当你确认需求适合做技能之后,就要面对一个现实问题:技能怎么做?是写纯 Python 脚本、封装成命令行工具,还是直接把逻辑塞进一个 HTTP 服务里让 Agent 远程调用?
我把最常见的三种路径列了一张表,供你参考:
| 构建方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 直接写 Python 脚本,通过本地执行 | 数据处理、文件操作、简单计算 | 开发速度快、依赖少、便于调试 | 无法跨机器复用,且技能运行环境与 Agent 强耦合 |
| 封装为命令行工具(CLI) | 已有现成 CLI 工具,需要让 Agent 调用 | 覆盖面广、组合性强 | 需要对参数解析做额外处理,交互体验不如脚本直调 |
| 部署为 HTTP 微服务 | 团队共享、跨语言调用、技能逻辑较重 | 解耦程度高、支持远程调用、便于横向扩展 | 需要额外维护服务,Agent 调用时网络异常处理更复杂 |
我个人在早期阶段优先选择第一种,就是直接写 Python 脚本。原因很简单:OpenClaw 本身对 Python 环境的支持最成熟,而且脚本方式调试起来最直观。等你跑顺了、技能数量多了、或者需要多人协作的时候,再逐步迁移到 CLI 或者微服务也不迟。
这里要补充一个经验:技能的构建路径最好和你的自动化流程架构保持一致。 如果你的自动化流程本身已经是微服务化的,技能用 HTTP 服务封装会更顺手;如果整个流程还是单体脚本为主,强行拆成微服务反而增加维护负担。架构的演进应该跟着实际痛点走,而不是为了“高级”而高级。
3. 自定义技能的完整设计思路与工程结构
3.1 技能声明里的门道
技能声明是 OpenClaw Agent 理解技能的窗口,也是最容易被忽略的部分。它通常包含技能名称、描述、参数定义、返回说明这几个字段。很多人以为这部分只是写给人看的注释,其实不是,这是 Agent 决策时的“说明书”。
我强烈建议你在技能描述里写清楚这几件事:
- 这个技能解决什么问题:用一句平实的话说清楚,别用太多修饰词。
- 在什么场景下使用:比如“当用户需要将 CSV 数据转换为 JSON 时”“当需要调用内部订单接口时”。
- 有哪些关键限制:比如“仅支持 UTF-8 编码”“仅接受 HTTPS 地址”。
- 典型调用示例:给 Agent 一个具体的输入样例,能显著降低它的试错成本。
举个例子,你写一个“按关键词过滤网页正文”的技能,描述可以是:
从给定 URL 中提取网页正文内容,并按用户提供的关键词列表进行过滤,返回匹配的段落。适合在用户需要快速判断网页是否包含特定主题内容时调用。输入应为完整 URL 和关键词列表。
这个描述比“网页过滤工具”要具体得多。Agent 看到之后,能更准确地判断在什么情况下调用它、怎么传参、期望拿到什么结果。
参数定义也要尽量严谨。OpenClaw 支持多种参数类型,常见的包括字符串、整数、布尔值、数组、对象等。每个参数都要给出清晰的说明和默认值(如果有的话)。参数说明别嫌啰嗦,这是给 Agent 看的“接口文档”,写清楚了,调用成功率直线上升。
3.2 执行逻辑的组织方式
执行逻辑是技能的核心。我通常建议把“逻辑实现”和“参数解析”分开。参数解析负责把 Agent 传来的参数转换成内部数据结构;逻辑实现负责真正干事情;最后再把结果统一返回。
这样做的好处是,后期你换参数格式、或者调整逻辑时,可以动的范围最小化。而且分开之后,逻辑部分更容易做单元测试。
在具体实现上,有几个细节值得注意:
- 异常处理必须有:Agent 调用技能时,传参可能不符合预期,执行环境也可能有各种意外。技能里的异常捕获要覆盖常见错误类型,尽量返回结构化的错误信息,而不是直接抛出堆栈。
- 返回格式要稳定:OpenClaw 需要根据技能返回内容决定下一步动作。如果你的返回格式忽而 JSON、忽而纯文本,Agent 在解析时容易出问题。建议统一用 JSON 结构。
- 执行速度要考虑:Agent 在编排流程时通常有超时控制。如果技能内部有较重的计算或网络请求,要有对应的超时机制,避免长时间挂起。
3.3 技能目录与文件组织
OpenClaw 对技能的文件目录通常有一套默认约定。以常见部署方式为例,你需要在技能目录下创建对应文件夹,里面包含技能定义文件和实现脚本。目录结构大概长这样:
code复制skills/
my_custom_skill/
SKILL.md
script.py
requirements.txt
SKILL.md是技能声明文件,给 Agent 看的。script.py是执行逻辑。requirements.txt用于声明依赖。
这个结构不是死的,但遵循约定能让 OpenClaw 的加载过程更顺畅。如果你是 Linux 部署,注意文件权限;如果技能里有第三方依赖,记得在 requirements.txt 里列全,别让 Agent 运行到一半才报缺少模块。
我个人建议,即使暂时不需要复杂结构,也把技能目录独立出来,不要和主配置混在一起。这样后续技能多了,管理起来一目了然。
4. 实操过程:从零构建一个“网页内容抓取与关键词过滤”技能
4.1 需求场景与功能定义
光讲理论容易飘,我拿一个实际做过的技能来走一遍全流程。这个技能的功能是:输入一个 URL 和一组关键词,程序抓取网页正文,筛选出包含关键词的段落,并按出现次数排序返回。
为什么会做这个技能?我之前有一个自动化流程,需要每天监测几个竞品官网的新闻页,看有没有出现指定的产品词或行业词。如果每次都要我手动去翻,效率太低;直接用现成的爬虫脚本,又不能和 OpenClaw 的 Agent 决策能力结合。所以干脆做成技能,让 Agent 在我给它一组链接时,自动逐个抓取并汇总。
功能边界我控制得很窄:
- 只处理静态网页;遇到动态渲染页,不在这个技能里死磕。
- 只返回包含关键词的段落,不返回全文。
- 关键词支持多个,用逗号分隔。
- 结果最多返回 10 个段落,避免内容过长。
边界窄的好处是,技能逻辑简单清晰,Agent 调用时不容易产生歧义。后续真有动态渲染的需求,我再单独做别的技能来处理,而不是把所有抓取逻辑都塞进一个技能里。
4.2 核心代码实现与参数设计
技能声明文件 SKILL.md 我写成了这样:
markdown复制# 网页内容抓取与关键词过滤
## 描述
抓取指定网页的正文内容,根据用户提供的关键词列表进行匹配过滤,并返回匹配段落及出现次数。
## 适用场景
- 当用户需要判断某个页面是否涉及特定主题时
- 当用户需要批量检查多个页面上的关键词命中情况时
## 参数说明
- url: 必填,字符串,网页完整地址(需包含协议头)
- keywords: 必填,字符串,逗号分隔的关键词列表
## 返回格式
JSON 对象,包含:
- status: success 或 error
- matches: 数组,元素包含 text 和 count 两个字段
- error_msg: 当 status 为 error 时返回
## 示例
输入:url: "https://example.com/news/1", keywords: "AI, 自动化"
输出:{"status": "success", "matches": [{"text": "。。。", "count": 2}]}
实现脚本 script.py 我也放出来,你可以直接作为模板改:
python复制import sys
import json
import re
import requests
from bs4 import BeautifulSoup
def fetch_and_filter(url, keywords):
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
except Exception as e:
return {"status": "error", "error_msg": f"请求失败: {str(e)}"}
soup = BeautifulSoup(resp.text, "html.parser")
# 简单提取正文,实际场景可以根据网页结构调整
paragraphs = soup.find_all("p")
texts = [p.get_text(strip=True) for p in paragraphs if p.get_text(strip=True)]
keyword_list = [k.strip() for k in keywords.split(",") if k.strip()]
matches = []
for text in texts:
count = 0
for kw in keyword_list:
count += len(re.findall(re.escape(kw), text))
if count > 0:
matches.append({"text": text, "count": count})
matches.sort(key=lambda x: x["count"], reverse=True)
return {"status": "success", "matches": matches[:10]}
if __name__ == "__main__":
url = sys.argv[1]
keywords = sys.argv[2]
result = fetch_and_filter(url, keywords)
print(json.dumps(result, ensure_ascii=False))
注意几个细节:
- 超时时间我设了 10 秒。这个值不能太小,网络波动容易误报;也不能太大,否则 Agent 等太久会超时。
- 关键词匹配用
re.escape转义,避免用户输入正则元字符导致报错。 - 返回结果统一 JSON 化,并且中文用
ensure_ascii=False,Agent 读取时更友好。
4.3 注册技能并配置到 OpenClaw
脚本写完之后,不是丢到目录里就完事了,还需要确认 OpenClaw 能不能加载到。以我用的部署方式为例,配置完技能目录后,记得检查目录路径是否正确出现在 OpenClaw 的配置项里。
这一步看似简单,但我第一次部署时确实栽过跟头。技能文件放好了,Agent 却始终找不到,检查了半天发现是路径写错了。所以建议你注册完技能后,先通过管理接口或日志确认技能加载成功,再进入正式流程。
另外,如果你的技能有第三方依赖,比如上面的 requests 和 beautifulsoup4,需要在 OpenClaw 运行环境里安装好。我建议你在 requirements.txt 里写明版本号,方便环境重建。
4.4 运行效果与参数调整实录
技能注册好之后,我做了一轮实际测试。测试目标是某个静态新闻页,关键词填“AI”和“机器人”。Agent 收到任务后,自动调用了技能,返回的结果包含了两条匹配段落,其中一段同时命中了两个关键词,排在前面。
这里有个经验:关键词的粒度要控制好。 太宽泛的关键词(比如“技术”)会命中太多无关段落,输出结果缺乏价值;太狭窄的关键词(比如某个完整的品牌名加产品名)又可能漏报文。我的做法是,在技能描述里提示 Agent 优先使用“主题词+具体业务词”的组合,这样效果最好。
5. 常见问题与排查技巧
5.1 “Agent failed before reply: session file locked”这类问题
在围绕 OpenClaw 做开发时,很多人都会碰到 agent failed before reply: session file locked (timeout 60000ms) 这个报错。我第一次遇到时也一脸懵。
这个报错的意思,简单来说就是 Agent 在处理请求时,发现会话文件被锁住了,等待了 60 秒还没拿到锁,于是放弃了本次回复。通常发生在多个并发请求同时操作同一个会话、或者上一次任务没有正常结束时。
排查思路我建议按三步走:
- 看是不是有多个进程或请求同时访问同一个会话 ID。
- 看日志里有没有未正常结束的前序任务。
- 如果是消息堆积导致的,把并发的会话拆开,别都往一个会话上压。
这个报错和你的自定义技能本身没有直接关系,但如果你在技能里写了比较耗时的逻辑,可能导致 Agent 处理时间变长,更容易卷入这种锁冲突。所以技能里尽量控制单次执行时间,能分批处理的别一次全跑完。
5.2 技能明明存在,但 Agent 就是不用
这个问题比报错更隐蔽,也更让人郁闷。你辛辛苦苦写了个技能,测试也通过了,结果 Agent 在真实对话中就是不调用。我排查过几轮之后,发现常见原因有三个:
- 描述写得不够“语义化”:你的技能描述里没有出现 Agent 期望的关键词或语义关联。比如你的技能其实是做“文本摘要”的,但描述里写的是“句子的简明扼要输出”,Agent 就不容易关联到“摘要”这个意图。
- 内置技能优先:OpenClaw 可能内置了同类技能,Agent 根据优先级选择了内置方案。
- 参数说明让 Agent 犹豫:如果参数说明写得模糊,Agent 不确定自己能不能满足参数要求,就不敢调用。
解决方案也很直接:描述改得直白一点、功能边界写清楚一点、调用示例给具体一点。改完之后,重新发起一轮对话测试,基本上都能解决。
5.3 飞书等消息端输出被截断
还有一个在对接飞书这类消息平台时常见的问题:技能返回的内容太长,导致在飞书里输出时被截断。我在做关键词监控技能时就遇到过,一次性返回几十个段落,飞书窗口直接给你截掉一部分,看起来就像技能“没跑完”。
解决思路不是让技能强行压缩结果,而是让 Agent 在展示前做一轮摘要或分页。你可以在技能描述里加一句“返回结果过多时,请优先展示命中次数最高的前几项”,这样 Agent 会自己在回复里做取舍,而不是傻乎乎地全量输出。
如果你希望技能内部就做好限制,那就在 script.py 里像上面例子一样,限定只返回前 10 条。牺牲一点信息量,换来整体的可用性,是值得的。
6. 一些进阶玩法与后续扩展
6.1 多技能组合实现复杂流程
单个技能只能做单一动作,但 OpenClaw 的价值在于 Agent 可以串联多个技能。比如我现在的自动化流程里,网页抓取技能和内部系统的数据上报技能是分开写的,但 Agent 在一个任务中会先调用抓取技能拿数据,再做一轮简单的清洗,然后调用上报技能把结果推给 Webhook。
这种“技能组合”的设计思路,和写程序时“函数组合”其实是一样的。每个技能尽量保持单一职责,然后在 Agent 的编排层去组合。这样既便于维护,也方便替换某个环节的实现。
6.2 技能参数的进阶处理
如果你对技能调用频率有要求,可以在技能内部增加缓存机制。比如同一个 URL 在 10 分钟内抓取过,就直接返回缓存结果,避免频繁请求目标网站。
另外,如果技能的输入依赖复杂的上下文,比如“根据用户刚才提到的某个日期范围”,光靠单个参数传值可能不够。这时可以考虑让技能接收结构化 JSON 参数,然后内部解析。OpenClaw 本身是支持这个玩法的。
6.3 配置模型时的注意事项
网上有人问 OpenClaw 怎么配置千问,我拿通用的“配置模型”习惯说一下。模型配置的核心是让 OpenClaw 正确使用某个模型的 API 地址、API Key、模型名称等参数。你不一定要用千问,但配置逻辑是通用的:先在模型服务商那边拿到 API 信息,然后填入 OpenClaw 的模型配置项里。
不过这里要特别提醒一句:同一套技能代码,在不同模型下表现可能有差异。有些模型理解能力弱,技能描述稍微绕一点它就懵了;有些模型则能在复杂描述中准确抓住调用时机。所以如果你换了模型之后发现技能调用率下降,不用急着改代码,先调整技能描述,让描述更贴合当前模型的理解习惯。
7. 踩坑总结与个人体会
我在做自定义技能的前期,最大的坑还不是技术问题,而是“想太多”。总想着一个技能把一套完整流程都包进去,结果代码复杂、调试困难、Agent 还经常调不对。后来把大需求拆成小技能,一个技能只做一件事,整体反而顺了。
另一个体会是,技能描述和参数说明的质量,直接影响你整个自动化流程的上限。代码写得再漂亮,Agent 看不懂你的意图,一切都是白搭。所以如果你发现技能经常不被调用,先别急着怀疑 OpenClaw 的 bug,回头把描述读一遍,把自己想象成 Agent,看看能不能准确理解“这个技能是什么时候用的”。
还有一点就是版本管理。技能多了之后,改动一个技能可能会影响其他流程。我的建议是给每个技能建立独立的版本记录,改动之前测试一轮,改完之后再做一轮冒烟测试,避免“静默破坏”的情况。
最后再分享一个小技巧:如果你希望某个技能在特定任务里“必须被使用”,可以在任务提示词中明确要求,比如告诉 Agent“必须先调用网页抓取技能,再执行过滤”。这种做法相当于给 Agent 预设了执行路径,对于某些强流程的任务特别有用。
