☰
OpenClaw Skills开发实战:从机制到网页摘要助手
2026/10/19 6:27:28 网站建设 项目流程

最近 OpenClaw 在开源社区里热度一天比一天高,大家聊它的时候三句话离不开 Skills。很多人看到别人的智能体能够查天气、读网页、写周报、管日历,觉得“也就那样”,真到自己上手开发时,却又不知道从哪里开始。实际上 Skills 这个机制门槛远没有想象中高,它本质上就是把一个高频需求打包成一个能被智能体识别并调用的小工具包。你要是想把自己的工作流自动化,或者单纯想给 OpenClaw 加一个专属能力,这篇文章应该能给你一份可以直接落地的参考路径。

我会从机制讲起,再带你把一个完整的“网页摘要助手”写出来,整个过程包括目录结构、清单文件、核心函数、提示词注入和调试排错。说人话就是:你只要会一点 Python 基础,甚至不熟悉复杂的智能体原理,也能照着这套思路做出自己的第一个 Skill。

1. 先搞懂:OpenClaw 的 Skills 到底是个什么机制

1.1 Skill 到底是什么

用大白话讲,Skill 就是一个“可插拔的能力包”。你给智能体装一个 Skill,它就多学会一件事。就像手机里的 App,系统本身只提供底层的通话、短信、联网能力,你装一个翻译 App,手机才会翻译;装一个地图 App,手机才会导航。OpenClaw 本身是一套智能体运行框架,负责接收消息、理解意图、调度任务,而 Skills 就是往上挂的“能力模块”。每个 Skill 通常由三块内容组成:一份描述自身用途和触发条件的清单文件、一段负责实际干活的代码、以及一段告诉大模型“什么时候用、怎么用、参数怎么填”的提示词配置。

这里要分清一个容易混淆的概念:Skill 不是普通的一段函数,它必须同时具备“可被大模型理解”和“可被系统执行”两个属性。普通函数写得再好,如果大模型不知道它存在,或者不知道怎么调它,那它也只是一个躺在仓库里的死代码。反过来说,只有花里胡哨的提示词描述,却没有实际可执行的处理逻辑,那也只是纸上谈兵。一个设计合格的 Skill,关键在于让两者严丝合缝地配合。

1.2 OpenClaw 的核心运转逻辑

要开发 Skill,得先理解 OpenClaw 处理一条消息时的完整链路。用户发来一句“帮我整理下这个网页的核心观点”,系统会先把消息送给大模型,大模型看到 Skill 清单里有一个“网页摘要助手”的描述和参数表格,判断出“这个请求可以交给它”,于是按约定的格式输出一个调用请求,比如执行一个名为summarize_url的动作,并且附带url参数。框架拿到这个动作名,去 Skills 目录里找到对应函数,传入参数执行,再把返回结果交给大模型。大模型拿到结果后,会结合上下文组织成一段自然语言回复给用户。

理解这条链路有什么用呢?它决定了你开发 Skill 时的思考方式。你的核心任务不是“写代码”,而是“让大模型知道在什么条件下、用什么参数格式来调你的代码”。代码本身再简单都没有关系,只要能在被调用时稳定输出结构化的结果就好。很多时候,我们写的 Skill 出问题,并不是代码跑不起来,而是大模型压根没识别到它,或者识别到了却填错了参数。

1.3 为什么非要自己开发 Skills

官方仓库和社区里确实有现成的 Skill 可以拿来即用,但随着你用得越来越深入,你会发现通用 Skill 往往摸不准你自己的使用场景。比如我自己需要定期分析一段本地日志文件里高频出现的错误码,官方没有现成的能力;又比如你想让智能体在回复时主动带上你团队的术语表,这些个性化需求没有一个通用包能满足。

自己开发 Skills 还有一个隐藏好处:你会被迫梳理自己的需求边界。不知道你发现没有,很多人用智能体时提需求很模糊,效果自然不稳定。当你以“开发 Skill”的角度去拆解时,你会把“帮我分析日志”变成“读取指定目录下的最近一个日志文件,提取 ERROR 级别以上的日志,按错误码聚合统计并输出 Top 10”,这种明确边界的过程,本身就能让你的工作流质量上升一大截。

2. 动手前的准备:先想清楚 Skill 的边界

2.1 工具型 Skill 与流程型 Skill

第一次写 Skill 的人容易犯一个毛病:什么功能都想塞进去。结果一个 Skill 里又有网页请求,又有数据库操作,又有文件读写,最后不仅体积巨大,而且大模型根本搞不清楚什么时候该调它。

我建议你先把自己的需求简单分成两类。

类型特点典型例子复杂度
工具型 Skill输入明确,输出明确,动作单一查询天气、计算汇率、解析时间低
流程型 Skill涉及多个步骤,需要中间状态生成周报、整理会议纪要、批量处理文件高

第一次入门,我强烈建议从“工具型 Skill”起步。因为它的逻辑链路短,出问题时好排查。等你跑通一整个闭环之后,再尝试把多个步骤编排到一个流程型 Skill 里,或者干脆用多个工具型 Skill 组合出一个大流程,这样维护起来也轻松很多。

2.2 一个 Skill 的标准目录结构

先别急着写代码,看一眼一个常规 Skill 在 OpenClaw 里该长什么样。虽然不同版本的框架目录细节可能略有差异,但整体约定大致如下:

skills/ └── web-summarizer/ ├── SKILL.md ├── requirements.txt ├── main.py └── assets/ └── prompt_templates/ └── summary_prompt.txt

各部分的职责可以这样理解:SKILL.md是给大模型看的说明书,main.py是给机器执行的功能实现,requirements.txt记录依赖,assets目录放一些模板文件,方便多语言、多风格场景复用。目录和文件命名最好都用小写字母加连字符,不要用空格和中文,否则后面路径处理会有一堆小麻烦。

2.3 触发机制与命名规则

Skill 怎么被触发,是新手最容易忽略又最重要的问题。触发一般分两种:一种是“显式触发”,用户消息里提到某个特定动作词,比如“调用网页摘要工具”;另一种是“隐式触发”,大模型根据上下文内容自行判断并调用。

在实际开发中,绝大多数调用是大模型隐式完成的。所以你在SKILL.md里写的“何时使用”就格外关键。不少人的第一个 Skill 不生效,原因就是描述写得太含糊。比如你写“用于总结网页”,大模型可能根本不知道这个工具是干嘛的;但如果你写“当用户提供一个以 http 开头的链接并希望概括其内容、解析其观点、整理其要点时使用”,模型就能非常准确地匹配。

还有命名规则,函数名尽量不要起得太宽泛,比如run()或者process()。这种名字在多个 Skill 并存时很容易冲突。我习惯用“动词_对象”的方式,比如summarize_url、extract_reading_time、fetch_page_title,一眼就能看出这个函数做什么。在清单文件里声明这些动作时,也尽量保持和你代码里的函数名一致,减少不必要的映射错误。

3. 实操:开发一个“网页摘要助手”Skill

3.1 搭建项目骨架

这一节我直接带你写一个可以跑起来的例子。假设我们想做一个 Skill:用户发来一个网址,它能自动抓取网页正文、过滤掉导航栏和广告噪音、提取核心内容,并生成一段简洁摘要。

第一步,在 OpenClaw 的 skills 目录下新建一个项目文件夹:

mkdir skills/web-summarizer cd skills/web-summarizer touch SKILL.md touch main.py touch requirements.txt

这里要注意:SKILL.md文件名必须严格一致,大小写不要写错,因为框架扫描时是精确匹配的。目录名web-summarizer可以自定义,但尽量一看便知用途。我见过有人图省事把目录命名为test1,结果项目一多根本分不清是谁。

3.2 编写 SKILL.md 清单文件

这是整个 Skill 里最核心的文件。它决定了你的 Skill 能不能被正确识别和调用。不要小瞧这份“描述文档”,我甚至认为它比功能实现本身更重要。下面是一份可以直接修改使用的模板:

--- name: web-summarizer description: 抓取网页正文内容并生成简洁摘要,适用于用户提供链接并要求概括、提炼要点、快速了解文章内容的场景。 version: 1.0.0 author: some-developer license: MIT actions: - name: summarize_url description: 抓取指定 URL 的网页内容,提取正文并生成中文摘要。 input: - name: url type: string required: true description: 需要摘要的完整网页地址,必须包含 http:// 或 https:// 前缀。 - name: max_length type: integer required: false default: 200 description: 生成摘要的最大字数,默认 200,范围限制在 50 到 500 之间。 output: - name: summary type: string description: 网页内容的摘要文本。 - name: title type: string description: 网页标题。

写这份文件有四个关键点值得展开说说。

一是actions里的参数要详细描述清楚,尤其是url这种业务参数,你得告诉大模型“必须包含协议头”。否则它很可能把用户直接输入的example.com原样传进来,你的代码如果没做兼容就直接报错。二是description里一定要写明“什么情况不能用”,比如“只支持公开网页,不处理需要登录的内容”。负向描述能显著减少误调用。三是max_length设置默认值和范围是很有用的技巧,它相当于给大模型一个约束,免得用户说“概括一下”时,模型塞给你一个 3000 字的“摘要”。四是version字段不是摆设,你后期改接口格式时靠它做兼容判断。

3.3 核心逻辑:从网络请求到干净正文

接下来是main.py。这部分逻辑并不复杂,但有几个网页抓取领域的经验值得提前说。直接用requests拿 HTML 只是第一步,真正麻烦的是把“正文”从一堆标签、脚本、样式中剥离出来。社区里有个老牌的trafilatura库就是专门干这个的,实测效果比用正则自己清洗稳定得多。我以它为例写一个版本。

import os import trafilatura import requests import json from urllib.parse import urlparse def summarize_url(url: str, max_length: int = 200): parsed = urlparse(url) if parsed.scheme not in ("http", "https"): return { "ok": False, "error": "invalid url, must start with http:// or https://" } headers = { "User-Agent": "Mozilla/5.0 (compatible; OpenClawSkill/1.0)" } try: downloaded = trafilatura.fetch_url(url, headers=headers) if not downloaded: return {"ok": False, "error": "fetch failed or empty body"} content = trafilatura.extract( downloaded, include_links=False, include_images=False, include_tables=False, favor_precision=True ) if not content: return {"ok": False, "error": "no extractable text content"} title = extract_title(downloaded) summary = make_summary(content, max_length) return { "ok": True, "title": title, "summary": summary, "content_length": len(content) } except requests.RequestException as e: return {"ok": False, "error": f"network error: {str(e)[:200]}"} def extract_title(html_text: str) -> str: if "<title>" not in html_text: return "untitled" start = html_text.find("<title>") + len("<title>") end = html_text.find("</title>", start) if end == -1: return "untitled" return html_text[start:end].strip()[:80] def make_summary(content: str, max_length: int): max_length = max(50, min(500, max_length)) if len(content) <= max_length: return content return content[:max_length] + "…"

这段代码里,make_summary目前只是简单截断,真实场景一般会让大模型来生成摘要,我在后面会讲到更优雅的方案。这里先保证功能闭环。注意我在代码里对max_length做了50到500的钳制处理,这对应SKILL.md里的范围定义。这种“双方共同约束”的设计,能避免模型传一个负数或者超大值进来,把系统拖垮。

headers里的User-Agent也是很值得留意的细节。不少网站会拦截默认 Python 请求的 UA,你带一个常规浏览器的 UA 能减少被拒概率。还有favor_precision=True这个参数,它让抽取结果更保守,宁缺毋滥,适合摘要场景;如果后面你要分析全量正文,可以改成favor_recall=True提高召回。

3.4 系统提示词的注入方式

如果你用过智能体平台,会发现这类 Skill 的最佳实践通常是“代码做粗加工,模型做精加工”。上面make_summary的简单截断方案只能作为兜底,真正的摘要应该由大模型来生成。但你不可能把整篇正文直接塞给模型,因为上下文窗口有限,而且费用也受不了。

常见做法是:先用代码抽取正文,截取前一段核心文本,然后把它作为上下文传给后续的大模型调用。这里的“传法”通常有两种。一种是在main.py里直接调用模型接口,把抽取好的文本发给模型并约定输出格式;另一种更符合 OpenClaw 架构习惯的做法是:把清洗后的内容和用户原始问题一起返回给框架,让框架负责调度大模型完成最终的摘要生成。我建议第二种方式,因为它让 Skill 保持“工具”的纯粹性,模型层面的对话策略统一由上层管理。

无论选哪种方式,你都要在SKILL.md或者提示词模板中声明输出偏好。一个标准的提示词可以是:

你是一名资深内容编辑。根据用户提供的网页正文,用不超过 200 字的中文概括其核心观点。要求: 1. 保留关键数据、结论和行动建议。 2. 不要添加原文中不存在的信息。 3. 使用简洁的段落输出,不要使用列表。

这段提示词放在assets/prompt_templates/summary_prompt.txt里,代码里读取它并拼接正文内容。这样做的好处是后续你想调整摘要风格,只需要改模板文件,不用碰代码逻辑。

3.5 本地测试与联调

写完代码先别急着塞进 OpenClaw,我建议先在命令行做一次冒烟测试。这一步能帮你把“代码问题”和“框架集成问题”分隔开。创建一个临时测试脚本或者直接在 Python 交互环境里调用:

from main import summarize_url result = summarize_url("https://example.com/article", max_length=150) print(json.dumps(result, ensure_ascii=False, indent=2))

如果返回值里ok为True,说明抓取和提取流程没问题。接着再测几个边界情况。第一个是传一个不完整的链接,比如example.com,期望返回协议错误;第二个是传一个根本不存在的域名,期望返回网络错误;第三个是传一个需要登录的网页,这时候fetch_url可能返回空,你要确认自己的返回信息能引导大模型向用户解释“该页面无法公开访问”。

联调的时候,我在 OpenClaw 里输入“帮我总结下 https://example.com 这篇文章”,然后打开框架日志,确认它是怎么识别动作的。如果它直接回了话而没有调用函数,多半是SKILL.md的描述写得不够清晰;如果它调用了但传参不对,检查参数描述里的type和required。这种分步排查法,能省下大把的试错时间。

4. 排错实录:开发中容易被咬的坑

4.1 Skill 压根没有被触发

这是我在社区里看到最多的求助帖。排查的时候先问一个问题:你的 Action 名称在SKILL.md里的声明和main.py里的函数名是否严格对应?你可能会觉得这不是废话吗,但真实项目里大小写不一致、下划线写错的情况实在太多了。OpenClaw 按声明扫描时是精确匹配的,summarize_url和summarizeUrl就是两个完全不同的东西。

第二个常见原因是“描述写得太抽象”。你写“处理网页”和写“当用户提供 URL 并要求总结或提取要点时使用”,在模型眼里完全不是一回事。你要站在大模型的角度思考:我看到的是一堆 Skill 的描述,我凭什么选你的?描述越具体、越明确“何时用、何时不用”,被选中的概率越高。第三个可能的原因是用户消息里就根本没提到链接,大模型当然不会去调一个需要 URL 参数的工具。

4.2 参数解析鲁棒性不足

就算你的 Skill 被触发了,参数解析也经常出幺蛾子。比较典型的有几类:时间类参数,用户说“帮我查一下最近一周的天气”,那传入的起止时间到底是按用户时区还是按服务器时区?这个必须在描述里写清楚,最好要求大模型把相对时间解析成具体日期再传入。再比如 URL 参数,用户发来的是“这个链接 ”并附带了一个打了红框的截图,大模型可能就把链接提取错了;更隐蔽的是用户发的带中文查询参数的长链接,urlparse解析时编码有问题,导致抓取返回 404。

我自己的习惯是能不用自由文本尽量不用,能用枚举就用枚举。比如language参数,我会在描述里写清楚“只接受 zh / en / ja 三个值,如果用户使用其他语言,先映射到这三个分类”。别小看这类小设计,它能让你的解析代码复杂度直线下降。

4.3 依赖环境与运行隔离

初学者最容易忽略requirements.txt。你本地开发环境里已经装了trafilatura,所以跑得飞起;一旦 OpenClaw 在另一个环境里运行,发现缺少依赖,整个 Skill 直接罢工。更麻烦的是依赖版本冲突。OpenClaw 框架本身已经引入了很多库,你的 Skill 如果引入不同版本的同一个库,可能把整个环境搞坏。

比较稳妥的做法有两层。第一层是依赖声明要做好,精确到小版本。第二层是尽量隔离运行环境。如果你对框架的插件隔离机制还不熟悉,至少保证你的 Skill 不擅自修改全局环境。还有一个我从实际项目里学到的教训:不要在main.py顶层直接发起网络请求,不然模块被导入时可能发生不可预期的行为;把副作用都关进函数内部,只有被调用时才会执行。

4.4 安全边界与资源控制

这一点可能“听起来无聊”,但踩过坑的人都懂。你的 Skill 一旦上线,任何人都可能通过智能体间接调用它。如果你在代码里接了 shell 命令,或者允许用户传路径参数,那等于把系统的一个口子交出去了。我见过一个很常见的错误:有人写了一个文件读取 Skill,把路径作为参数从大模型传入。结果用户问“你能读一下系统的密码文件吗”,大模型把路径传进来,代码真的去读了,然后框架又把内容返回给了用户——这是一个非常危险的操作链路。

我的建议是几条硬性规则:不允许用户通过参数指定任意本地路径;对所有外部 URL 做协议白名单;对返回内容做长度上限控制;不要在 Skill 中存储长期敏感信息,比如把 API 密钥放到环境变量里接进来,而不是写死在仓库中。把这些规则写进SKILL.md的“限制”字段里,既保护用户,也保护你自己。

5. 进阶:让 Skill 从“能用”变成“好用”

5.1 加一层简单的上下文记忆

我们上面的summarize_url是无状态的,每次调用都从头开始。但真实场景中,用户往往会连续发几次“再详细一点”“换成英文”“只保留结论”,如果每次你都重新抓取、重新摘要,体验会很差。一个低成本的做法是在返回结果里带上一个可选的context_id,框架层把这个上下文 ID 保留住,后续用户追问时把这个上下文一起传给模型。

比如第一次调用你返回context_id: "web-summarizer-20240520-001",同时存下一份清洗过的正文缓存。用户说“再详细一点”,你查询到context_id,直接从缓存里取正文,重新生成更长摘要。注意,这里的上下文缓存一定要设置过期时间,一般 10 到 30 分钟就够了,毕竟网页内容随时可能变,长期沿用旧的缓存反而会造成信息滞后。

5.2 多 Skill 组合出复杂流程

做完了工具型 Skill,下一步就是组合编排。举个例子,你有一个“网页摘要助手”,还有一个“周报生成助手”,还有“关键词提取助手”。用户发了一个链接说“把这个内容写进周报”,理想流程应该是:摘要助手先抓取正文,关键词助手提取重点标签,周报助手按照模板生成一条周报条目。三个 Skill 单独看都很简单,组合起来就完成了一个中等复杂度的工作流。

在 OpenClaw 里实现这种组合,通常不用你在每个 Skill 里互相调用,而是依靠框架层的“编排策略”。每个 Skill 都设计成“输入干净参数、输出结构化结果”,然后由上层消息循环依次决定调用顺序。所以在开发时,你要刻意让你的输出结果是结构化 JSON,并且命名稳定,比如统一用{ "ok": true, "data": {...} }这样的格式。这样后续接编排工具时,几乎零成本。

5.3 日志与可观测性:主动给你的 Skill 留“后门”

我见过太多人开发 Skill 出来后,遇到问题只能靠猜。你完全可以在代码里埋几条logger.info,把关键路径上的变量打出来。比如收到请求时记录url和参数信息,请求失败时记录异常摘要,返回结果时记录耗时。这些日志在你排错时就是救命稻草。

更高级一点的做法是在返回结果里带上debug字段,默认不启用,只有检测到环境变量里OPENCLAW_DEBUG=true时才输出详细内部信息。这样,你既不会在生产环境里打扰正常用户,又能在出问题时快速打开调试模式定位根因。这套“后门”思路不复杂,但确实能省下你不少和框架日志搏斗的时间。

5.4 版本管理与发布姿势

写到最后忽然想起一个很多人忽视的问题:Skill 更新之后,旧版本的缓存怎么办?我自己踩过一次坑,把一个 Skill 的函数参数从两个字段改成了三个字段,旧的消息记录里还保留着旧的调用格式,结果框架在处理历史任务时传参报错。从那以后我习惯在SKILL.md里把version升上去,并且在主函数开头做一个参数兼容层,比如:

def summarize_url(url: str = None, url_source: str = None, max_length: int = 200): if url is None and url_source is not None: url = url_source

这个兼容层的代码可能永远不会被触发,但一旦遇到历史任务,它就能兜底。做开源爱好者,多考虑一步兼容性,真的能让你的 Skill 活得比别人久很多。

我个人在实际操作中的体会是:开发 OpenClaw 的 Skills,最大的收获往往不是那个功能本身,而是逼着自己把需求想得前所未有的清楚。本来模糊的“帮我处理一下这个网页”,你会拆成“抓取正文、去除噪音、截断长度、清洗排版”这么一串可执行动作。以后你用任何智能体工具,这种拆解能力都能直接迁移。如果你也想动手试试,建议就从“网页摘要助手”起步,跑通一个最小闭环后再往上加花样。种一棵树最好的时间是十年前,其次是现在——写人生第一个 Skill 最好的时间,也就是今天下午。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询