ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenClaw 技能开发实战:从 SKILL.md 设计到 Function Calling 调优

OpenClaw 技能开发实战:从 SKILL.md 设计到 Function Calling 调优 1. 先想清楚什么样的功能值得做成 OpenClaw 技能先说结论OpenClaw 本质是一个智能体运行时框架它把“大模型的对话能力”和“外部工具的执行能力”粘在一起。技能Skill就是这层粘合剂里最灵活的单元——一段可以被模型按需调用的代码、脚本或命令集合。玩过几天 OpenClaw 的朋友应该都有同感内置技能虽然覆盖了文件操作、网页抓取、代码执行这些常见场景可真到自己工作流里总有那么几个需求是“差一步就够用但就差这一步”。这时候自定义技能就是唯一靠谱的解法。我在日常使用中总结过一套判断标准满足任意两条的功能就值得做成技能操作链路固定比如“查磁盘占用→找超过阈值的目录→生成报告”每周都要做。需要调用外部程序或 API比如查天气、发消息、调企业微信机器人。有明确的输入输出边界模型只需要给出几个参数就能得到结构化结果。自己不想每次都用自然语言重新描述一遍执行流程希望模型“一句话就懂”。反过来有些东西不建议做成技能。比如纯推理类问题、需要多轮交互的复杂流程、或者模型本身通过 Prompt 就能做好的文本处理。强行封装技能只会增加维护成本还会让模型在工具调用上绕远路。技能不是越多越好而是越精准越好。另外我说个容易被忽略的点技能描述Skill Description本身就是模型的路标。OpenClaw 里的模型不是“看到所有技能再挑一个”而是根据你的自然语言去匹配技能描述匹配到了才触发调用。所以写技能时花一半精力写代码逻辑另一半精力得花在“怎么写描述才能让模型一眼看中它”这件事上。这个我后文会专门展开它是新手和老手做技能的分水岭。2. 技能包的结构与触发机制2.1 标准技能包长什么样一个规范的 OpenClaw 技能通常是一个自包含的目录。基于我上手多个开源技能包的经验通用结构大致是这样my-skill/ ├── SKILL.md # 技能说明书模型读这份文件来理解技能用途 ├── src/ # 核心代码目录 │ └── main.py # 入口脚本通常暴露一个 run 函数 ├── pyproject.toml # Python 项目配置声明依赖和元信息 ├── requirements.txt # 依赖列表pip 安装用 └── assets/ # 可选的静态资源模板、配置文件等这个结构看着简单但每个文件的角色都要分清。SKILL.md是模型侧的文件它告诉模型“这个技能是干什么的、什么时候该用、怎么传参数”。src/main.py是执行侧的文件真正跑逻辑、出结果。pyproject.toml和requirements.txt是给运行环境看的保证代码能装依赖、能启动。我见过不少新手把代码逻辑全写在SKILL.md里让模型“自己看着办”。这种思路在实验里偶尔能跑通但稳定性极其糟糕——模型每次生成的代码都不一样执行结果也就不可控。正确的做法是代码老老实实写在src/里SKILL.md只负责“说明书”部分。让模型调用一个稳定的函数而不是让它即兴写脚本这是生产可用和玩具 Demo 的本质区别。2.2 SKILL.md 的写法直接决定技能“能不能被触发”SKILL.md前几行通常是 YAML 格式的 frontmatter用来登记技能名称、描述、参数结构。后面跟着的正文是给模型的“使用指南”要写清楚这个技能在什么场景下调用、输出的结果长什么样、有没有特殊注意事项。以我常用的一个“当前时间查询”技能为例它的SKILL.md开头长这样--- name: get_current_time description: 返回指定时区的当前日期和时间用于回答“现在几点”“今天几号”等问题。 parameters: type: object properties: timezone: type: string description: IANA 时区名称如 Asia/Shanghai default: Asia/Shanghai required: [] ---注意description那一行我特意写了“用于回答‘现在几点’‘今天几号’等问题”。这就是给模型看的路标。模型在对话里发现用户问时间和日期就会去匹配这个描述匹配上了才触发技能。如果描述只写“获取时间”模型理解起来就很模糊触发率会明显下降。这份文件写得好不好直接决定模型愿不愿意用你的技能。有人吐槽“技能写了半天模型根本不调用”十有八九是描述太抽象、没覆盖用户的实际问法。我自己踩过这个坑之后总结出一个模板技能简称用于明确的任务场景。当用户说出这类话术/提出这类需求时调用此技能。一句话把“名字、场景、触发条件”都框住模型就不容易迷路。2.3 入口函数与返回值设计代码侧的核心是入口函数。规范的做法是暴露一个名为run的函数接收parameters字典返回可序列化的结果。OpenClaw 会把这个返回值拼进上下文让模型基于结果继续和用户对话。所以返回值最好是结构化的、方便模型“读”的数据。最简单但很实用的返回格式是纯文本报告比如def run(parameters): tz parameters.get(timezone, Asia/Shanghai) now ... # 获取时间 return f当前北京时间{now}星期{...}也可以返回 JSON适合后续还要被模型进一步加工的场景。但有一点要注意返回给模型的内容不是给人看的而是让模型“读”的。所以结构清晰、重点前置非常关键。如果你想输出给人看的富文本那也要在返回结果里预留 markdown 格式的字段让模型转述给用户。3. 实战做一个“磁盘空间体检”技能光说不练假把式我直接用最近做的一个磁盘体检技能来演示完整流程。这个需求很典型我有一台轻度使用的机器上跑着 OpenClaw它既要处理日常问答又要兼职跑点本地脚本。结果有段时间磁盘被日志和缓存吃满了差点把系统搞挂。我就想着让助手能够帮我定期“体检”磁盘发现问题直接出报告。3.1 需求拆解与目录初始化技能需求定义如下查看指定目录的磁盘占用情况。往下扫描一层子目录找出超过指定阈值的“大户”。输出一段人话报告包含总量、可清理建议。划分到这个程度代码逻辑就很清晰了。我先建好目录mkdir -p disk-health/src cd disk-health touch SKILL.md touch requirements.txt然后初始化 Python 项目环境依赖用shutil、os这种标准库就够了连第三方包都不用装。可以说这是新手练手最友好的技能类型结构简单、依赖容易、逻辑直观。3.2 编写 SKILL.md给模型一份“使用说明书”--- name: disk_health_check description: 检查磁盘空间占用情况报告指定目录下的大文件和大目录用于回答“磁盘快满了”“空间去哪了”“C盘清理”等问题。 parameters: type: object properties: path: type: string description: 要检查的目录路径默认是当前用户主目录 default: warn_size_gb: type: number description: 超过该大小GB的目录才会被单独列出默认 1GB default: 1 required: [] ---这里有两个细节值得说。第一path给了默认空字符串代码里自动转为用户主目录。这很关键——用户说“帮我看看磁盘”没有指定路径时模型不一定知道你机器的家目录在哪给默认值能减少出错。第二warn_size_gb设了默认 1GB因为绝大多数情况下扫描所有子目录没必要只找超 1GB 的大块头就够了。聪明的默认值是让模型少犯错的最简单方式。3.3 核心逻辑实现src/main.py的核心代码如下import os import shutil def run(parameters: dict): path parameters.get(path) or os.path.expanduser(~) warn_size_gb float(parameters.get(warn_size_gb, 1)) warn_size_bytes warn_size_gb * 1024 * 1024 * 1024 if not os.path.exists(path): return f路径不存在{path} total_usage shutil.disk_usage(path) total_gb total_usage.used / (1024 ** 3) heavy_items [] try: for entry in os.scandir(path): if entry.is_dir(follow_symlinksFalse): try: dir_size 0 for root, dirs, files in os.walk(entry.path): for f in files: fp os.path.join(root, f) if os.path.exists(fp) and not os.path.islink(fp): dir_size os.path.getsize(fp) if dir_size warn_size_bytes: heavy_items.append({ path: entry.path, size_gb: round(dir_size / (1024 ** 3), 2) }) except PermissionError: pass except PermissionError: pass heavy_items.sort(keylambda x: x[size_gb], reverseTrue) lines [f磁盘总占用{total_gb:.2f}GB] if heavy_items: lines.append(重点目录) for item in heavy_items[:10]: lines.append(f- {item[path]}{item[size_gb]}GB) else: lines.append(f未发现超过 {warn_size_gb}GB 的大目录。) return \n.join(lines)这个实现有几个刻意为之的点。一用os.walk递归统计目录体积而不是直接调用du跨平台更稳。二PermissionError做了兜底权限不足的目录直接跳过不让整个技能崩溃。三结果按大小倒序排列只取前 10 个避免输出长到把模型上下文撑爆。这些都是实际跑过之后才加进去的细节初版没考虑权限问题在 Linux 上跑就被/proc、/sys这类虚拟目录整出过一堆杂音。3.4 注册到 OpenClaw 并调试把整个disk-health目录放进 OpenClaw 的技能目录通常是~/.openclaw/skills/或你配置的 skills 路径。然后重启或触发技能重载在对话里测试。我第一次测试时输入“磁盘是不是快满了”模型居然没触发技能而是直接用自己的知识敷衍我。排查后发现问题不在代码而在SKILL.md的 description 太像“系统功能”而非“用户问话”模型根本没意识到用户的需求和它有交集。我把描述里加上“用于回答‘磁盘快满了’‘空间去哪了’等口语化问题”再测试立刻触发成功。这个教训我记了挺久——技能触发的关键不是把功能做得多复杂而是让模型在“对话上下文”里认出这个需求。模型不是程序员它不会看代码它只看描述。所以描述写得越贴近用户真实问法触发越准。4. 模型选型与多模型适配技巧4.1 为什么模型能力决定技能体验同样一套技能在不同模型上的表现差距是肉眼可见的。核心原因在于 Function Calling工具调用能力。OpenClaw 生成参数是靠模型根据用户意图去理解并填充 JSON Schema这个动作本身对模型提出了不低的要求。我拿开源社区里比较常见的Qwen2.5-3B举例。这个模型体积小适合本地部署但 3B 参数在复杂工具调用上的稳定性和 70B 甚至云端的商用模型还是没法比。你让它识别“磁盘满了”这种口语化意图、再正确映射到path和warn_size_gb两个参数对它来说负担不小。我实测下来小模型更容易出现参数漏传、类型填错、把默认值当字符串传进去。所以在本地跑小模型时技能设计要适当妥协。参数能设默认值就设默认值能少就少描述里把可能的值提前写明白能给枚举就给枚举。这本质上是把“模型要做的推理”转移到“作者的代码逻辑”里降低模型出错的概率。4.2 配置 OpenAI 兼容接口关联大模型很多朋友用的不是 OpenClaw 内置的默认模型而是通过 OpenAI 兼容接口接第三方模型。比如把qwen2.5-3b关联到 OpenClaw需要在模型配置文件里填三样核心信息model: provider: openai-compatible base_url: http://localhost:8000/v1 api_key: dummy model_name: qwen2.5-3b这里base_url指向本地推理服务比如 vLLM 或 Ollama 的 OpenAI 兼容端点。api_key随便填一个本地服务一般不校验。model_name必须和推理服务里加载的模型名完全一致。我当时卡了半天原因是model_name填成了qwen2.5而服务里实际注册的名字带上了参数规模后缀。这种事属于典型“配置一分钟排查两小时”的坑列出来给大家提个醒。4.3 大小模型的调试策略差异我的调试习惯是技能开发阶段直接用大模型云端或本地大参数等技能的代码逻辑稳定了再切到小模型做兼容性测试。理由很简单大模型容错率高能自动处理参数里的一些小瑕疵方便你判断问题到底出在“代码”还是出在“模型”。小模型则会把问题放大——明明SKILL.md写得清清楚楚它还是会漏参数。如果必须用小模型还有个折中方案把参数简化到极致。比如上面的磁盘体检技能把path和warn_size_gb都设默认值再把 description 写成“不传参数即可执行可选参数为 path 和 warn_size_gb”。这样模型只需要识别意图不需要从对话里抽实体成功率会显著提升。5. 常见问题与排查技巧实录这节我把自己踩过的坑和社区里高频出现的问题做个汇总按“现象、原因、解法”的表格呈现方便照着排查。现象原因解法模型压根不调用技能SKILL.md 描述和用户问法匹配不上重写 description覆盖口语化问法参考“用于回答‘磁盘快满了’这类问题”句式技能被调用但报参数错误参数类型或必填项约束过严模型填不齐尽量给默认值减少 required 字段数量用 enum 限定可选值返回结果乱码或格式错乱代码输出里夹带了非 UTF-8 编码或多余日志统一 encode 设置入口函数只返回最终结果log 写到 stderr 或文件依赖装不上requirements.txt 版本冲突用虚拟环境venv隔离固定依赖版本号避免 float 版本范围技能目录不生效目录层级或文件命名不符合约定检查技能包是不是完整目录SKILL.md 和 src/ 是否在同一层Windows 下 WSL 环境提示“无法安全验证 SL2 环境”WSL 状态异常或未正确初始化在 PowerShell 中执行wsl --status检查状态必要时wsl --shutdown后重启再重新启动 OpenClaw最后一个 Windows 相关的坑值得单独展开。不少人把 OpenClaw 跑在 Windows 的 WSL 里但 WSL 版本和 Windows 侧的 Docker、代理环境经常互相干扰。我遇到过几次 OpenClaw 启动直接报错提示 SL2Second Level 2环境无法验证这时候排查顺序是先跑wsl --status看 WSL 内核状态再跑wsl --shutdown强制重置最后检查是否有杀毒软件拦截了 WSL 的进程通信。大部分情况下这一套组合拳下来环境就能恢复。这个坑属于典型的“不是你的代码有问题而是运行环境抽风”别急着改代码先排查基础设施。还有 OCR 类技能缺失的问题社区里经常有人问。OpenClaw 的技能市场里纯 OCR 技能确实不算多。但这个问题不一定非要靠现成技能解决两条路都走得通一条是把 OCR 能力封装成一个自定义技能内部调用外部的 OCR API 或本地库比如用 PaddleOCR另一条是把 OCR 服务独立部署成 HTTP 接口让技能代码只负责请求和解析返回结果。我自己偏好后者因为接口可以复用不只服务 OpenClaw其他脚本也都能接进来用。另外提一嘴 OBSIDIAN 集成。如果你用 Obsidian 管理笔记可以给 OpenClaw 加一个技能做到“自然语言提问返回笔记检索结果”。实现思路也不复杂核心代码就是把 Obsidian 的 vault 路径传进技能用关键词在 markdown 文件里做检索按文档名和段落相关度打分返回前几个结果。这类“个人知识库助手”的技能用途广、逻辑不复杂很适合作为第二个练手项目。6. 从内置技能到第三方技能包的扩展经验6.1 怎么引入现成的第三方技能实际工作中我们经常不是从零写技能而是引入社区已有的技能再改。OpenClaw 生态里已经有不少技能包安装方式一般是把技能目录克隆或下载到~/.openclaw/skills/下然后重启或热加载。引入第三方技能时有两点我强烈建议先做。第一先看SKILL.md的描述判定这个技能是否真的符合你的需求别被名字误导。第二看requirements.txt和入口代码确认它跑在你的运行环境里没有额外负担。比如有些技能依赖特定 GPU 或系统库本地环境不具备就别硬装。我曾想引入一个网盘相关的技能它的说明写得很诱人可以上传下载文件。但实际装完之后发现它有几个前置条件是本地没有的配置了老半天也没跑通。后来我改了思路不依赖现成的网盘技能而是把它拆分成两个更小的技能一个负责生成分享链接一个负责同步指定目录反而更贴合自己的需求。6.2 WorkBuddy 这类项目与 OpenClaw 的异同社区里最近讨论比较多的 WorkBuddy 这类 agent 工具集本质上也是把技能、工具、模型编排在一起的框架。不少人问“WorkBuddy 是不是参考了 OpenClaw”。老实说开源世界里相互借鉴太正常了技能包的设计理念其实高度相似用可插拔的技能模块扩展 agent 能力、用结构化的参数让模型理解工具边界。与其纠结谁先谁后不如直接把这套思路学到手——无论你用哪个框架通用的做法都是一样的定义清晰的技能边界、提供结构化的参数、让模型通过描述来发现和调用。6.3 从技能树拆解到技能设计我个人做技能时很喜欢用“技能树”的思路来规划尤其是复杂业务。比如想做一个“CTF 题目辅助”技能直接做成一个大技能既臃肿又难调试拆成技能树就清爽多了第一层题目信息解析提取题目类型、描述、附件第二层Web 方向辅助抓包分析、参数构造、SQL 注入探测第二层Reverse 方向辅助二进制特征识别、调试器调用第三层报告生成把分析过程整理成 writeup每层做成独立技能上层技能负责判断该调哪个下层技能。这样每个技能保持单一职责模型调用起来也更精准。这其实就是把“大任务的拆解”和“技能树的粒度”对齐了。技能不是越全越好而是要能拆分、能组合、能复用。7. 实战心得与扩展想法代码写完、技能能跑通只是第一步。真正让技能变得好用的是你在实际使用中反复打磨的那些细节。分享几个我沉淀下来的心得。第一日志先行。开发技能时先让代码把每个参数打印到日志里再一步步验证逻辑。不要上来就写完整实现否则模型传错参数时你根本看不出来。我写磁盘体检技能时第一步就是print(parameters)确认模型传来的参数到底是什么再去优化逻辑。第二把“人话”写进描述。技能的 description 要覆盖用户真实会说的口语。别写“获取文件系统空间使用情况的统计信息”要写“用于回答‘磁盘满了吗’‘空间去哪了’‘帮我清理一下磁盘’这类问题”。这两句话在模型眼里命中率差别极大。第三先稳定再花哨。技能刚能跑通时先在默认参数下连续测 10 次确认稳定了再去加复杂选项。很多人一上来就搞十来个参数结果模型根本填不对。好的技能是“默认好用进阶可调”。第四不要吝啬“给模型兜底”。代码里对各种异常情况都写好兜底逻辑模型传错路径就告诉它路径不存在权限不足就告诉它跳过。这些看似不起眼的容错处理恰恰是把实验级技能变成生产级技能的关键。这个项目后续还有很多可扩展的方向。比如磁盘体检技能可以加定时任务逻辑让它每天自动化检查一次并主动汇报技能包里可以集成模板文件让输出结果能直接对接周报也可以把技能发布到社区让更多有同样需求的人直接拿来用。我自己的体会是OpenClaw 的技能体系真正降低了我把想法变成工具的难度——不需要写完整的应用只要拆解成场景、写好说明书、封装好代码就能让模型顺手替你干活。这也是我越来越愿意把日常的小需求都往技能里塞的原因多积累几个精准好用的技能你的 OpenClaw 就会从一个实验玩具一步步变成真正顺手的生产力工具。
返回列表