
说实话第一次把 OpenClaw 跑起来的时候我心里是有点失望的。它确实能接上大模型 API能聊天能记住上下文——可这不就是套了一层壳的聊天机器人吗直到我花了一个下午把第一个 Skill 挂进它的技能目录然后亲眼看着它自动调起脚本、把一堆散乱的日志整理成表格、再按模板生成一份报告我才反应过来所谓“贾维斯”差的根本不是话术而是那双能干活的“手”。这篇文章我就从 OpenClaw 接入 Skills 这条线往下讲不绕弯子。会先讲清楚技能机制到底是怎么回事再给你一个能直接抄的实操流程最后把我自己踩过的坑、总结的经验一次性倒出来。适合两类人看一是刚搭好 OpenClaw 但不知道下一步怎么玩的新手二是已经装了 Skills 却老觉得“它怎么不按我想的调用”的折腾型玩家。1. 为什么大模型光会说话当不了贾维斯1.1 大模型是大脑Skills 是手脚先把这个关系掰开揉碎。大模型这东西本质上是一个“概率文本生成器”。你问它“今天天气怎么样”它能给你写出一段像模像样的天气描述但它不会真的去访问气象接口也没有办法打开浏览器、读写文件、发 HTTP 请求、操作数据库。它的一切“能力”都停留在生成文本这个层面。这就好比你让一个只会读书的学霸去帮你搬家。他能在纸上把搬家流程写得明明白白——“先打包、再搬运、最后归置”——但真要扛起箱子手是无能为力的。OpenClaw 里的 Skills就是给这个学霸安装了一双手脚。每个 Skill 包含一段可执行的脚本脚本能真正去调用系统命令、访问网络、处理数据然后把结果拿回来。很多人的误解在于以为把模型换大一点AI 助理就能自己干活了。但实际上从“听懂你的需求”到“真正办成事”中间隔着一条巨大的鸿沟。OpenClaw 恰恰是来填这条沟的框架而 Skills 是它最核心的执行单元。1.2 从“听懂”到“办成”中间发生了什么那 OpenClaw 是怎么把“大脑”和“手脚”接起来的这里得提到一项底层机制——函数调用Function Calling / Tool Use虽然你平时感知不到它。整个过程可以拆成四步你向 OpenClaw 发出指令比如“帮我把 /tmp/download 目录下的 Excel 汇总一下”。大模型收到这句话后并不会直接去扫描目录。它会先在 OpenClaw 提供的技能清单里“找”看哪个 Skill 的描述和当前任务匹配。一旦匹配上模型会输出一个结构化的调用请求里面带有技能名称和从你话里抽取的参数。OpenClaw 收到这个请求后在本地或容器里真正执行对应的脚本。脚本把执行结果返回给模型模型再把它组织成一段自然语言回复给你。这个循环跑通之后OpenClaw 就不再是“复读机”了。它像一个真正能办事的助手——眼睛在大模型这里手在脚本那里而 OpenClaw 是中间的神经系统。你所有关于“贾维斯”的想象其实都建立在这条闭环是否顺畅上。2. 接入 Skills 前先把这几个概念吃透2.1 技能清单大模型读的“招聘启事”每个 Skill 都必须附带一份描述文件。在 OpenClaw 的不同版本里它可能叫manifest.yaml、skill.json或SKILL.md但核心字段大同小异。这里我统一用manifest.yaml来说。这份文件里最重要的不是脚本代码本身而是“描述”和“参数说明”。因为大模型根本不看你脚本里写了什么它只看这段文本描述description告诉模型“这个技能是干什么的、什么时候该用、什么时候不该用”。参数parameters声明调用这个技能需要哪些输入比如文件路径、城市名称、日期范围相当于给脚本定了一个输入接口。执行方式command / runtime指定用什么语言、什么命令去跑脚本。你细品一下就会发现这本质上是一份“给大模型看的招聘启事”。模型像一个 HR根据你描述的内容决定“派哪个员工去办这件事”。如果你描述写得模棱两可模型就会乱点鸳鸯谱把查天气的技能拿去算账——这种事我遇到过不止一次后面会专门展开讲。2.2 脚本本体真正干活的执行体描述文件之下才是那个真正干活的脚本。OpenClaw 对脚本语言没有强制限制但社区里主流是 Python其次是 Node.js少数简单操作会写 Bash。选择 Python 的理由很朴素生态足够全。处理 Excel 有openpyxl发请求有requests分析数据有pandas。你不需要让大模型去生成一段复杂 Shell 指令只需要让它按参数调用你的函数。这也是 Skills 设计里很聪明的一点——把不可控的“模型自由发挥”压缩到最小把稳定可控的代码固化到脚本里。2.3 路由与沙箱OpenClaw 在背后做了什么OpenClaw 连接模型和脚本还有两个容易被忽略的环节一个叫“技能路由”一个叫“执行沙箱”。技能路由解决的是“选谁”的问题。当你有十几个 Skills 时模型不可能把每个脚本都读一遍它只读每个技能的描述。所以你的描述写得好不好直接决定了路由准不准。这里有个朴素但有效的原则描述里写清楚“使用场景”和“边界”甚至可以直接写“当用户聊到 XXX 话题时不要调用本技能”。沙箱则解决“安全”问题。官方推荐的做法是把技能脚本放进一个受限环境里执行比如容器或受限用户目录这样脚本即使写崩了、删错了文件也不会拖垮宿主系统。我的习惯是所有涉及文件写入和网络请求的技能一律在容器里跑只读类技能才直接放本机执行。2.4 我的建议先装现成的再自己造轮子很多人一上来就想写自己的技能结果卡在调参上大半天。我更推荐先把你需要的现成技能装上——OpenClaw 官方仓库和社区里维护着一批高质量技能从查天气、读 RSS、操作浏览器到发邮件都有。先跑通整套机制你才会知道“一次成功的调用”应该是什么手感然后再动笔写自己的。3. 实操给 OpenClaw 装上第一个 Skill让它学会查天气3.1 先把环境跑起来我这里假设你已经装好了 OpenClaw 主程序并且能正常对话。如果还没装官方的安装脚本一般就是一路默认它会在你的用户目录下创建.openclaw文件夹里面包含配置文件、日志目录和技能目录。可以打开终端验证一下openclaw --version能看到版本号说明主程序 OK。然后确定你的技能目录位置不同版本路径略有差异最常见的两个是~/.openclaw/skills/ ~/.config/openclaw/skills/用ls看一眼哪个存在就用哪个。找不到就直接创建一个OpenClaw 启动时也会自动扫描这个目录。3.2 把技能文件放进正确的位置假设你要装一个天气查询技能社区里通常给的是一个压缩包或 git 仓库。结构一般长这样weather-skill/ ├── manifest.yaml ├── script.py ├── requirements.txt └── README.md你要做的事情很简单把整个文件夹复制到技能目录下然后保证目录名和manifest.yaml里的name字段一致。我见过不少人栽在目录名不匹配上导致技能被扫描到但一直加载失败所以这个细节第一次就要做对。复制完之后manifest.yaml的样子大概是这样的name: weather_query description: | 根据城市名称查询实时天气和未来三天预报。 当用户询问“天气”、“气温”、“下雨”、“风力”等话题时使用。 如果用户提到“气温历史数据”或“气候统计”不要使用本技能。 parameters: type: object properties: city: type: string description: 城市名称例如北京、上海、广州 days: type: integer description: 预报天数默认为1最大为3 default: 1 required: - city runtime: command: python3 script.py注意那段 description。我特意写了“什么时候用”和“什么时候不要用”这能让模型在路由阶段少犯浑。required字段里只放必填参数其余给默认值因为模型从一句话里提取参数的能力虽然不差但如果你全是必填项它提取不到就很容易报错。3.3 验证技能是否被识别装好之后重启 OpenClaw或执行热加载命令然后查看技能列表openclaw skills list如果列表里出现了weather_query说明扫描没问题。接下来做一个最简单的直连测试——有些版本支持直接手动指定技能名来跑openclaw skills run weather_query {city: 上海, days: 2}这里手动传入 JSON 参数是为了跳过模型路由这一步直接验证脚本本身通不通。如果脚本有 bug你会在这里看到完整报错如果脚本正常返回的就是结构化 JSON 或纯文本。这一步很关键先确认“手是好的”再放心交给“大脑”去指挥。3.4 回到对话框体验完整闭环脚本验证通过后回到 OpenClaw 的正常对话界面发给它一句帮我看看上海明天需要带伞吗正常情况下你会看到 OpenClaw 先调用了weather_query技能日志里会打出来然后返回一段自然语言回复比如“上海明天有小雨出门建议带伞”。这短短几十秒就是一次完整的“模型理解→技能路由→脚本执行→结果回填”闭环。跑通这一个技能之后整套机制你就摸到门道了。剩下的就是批量安装、按需定制甚至自己动手写新技能。4. 从零写一个专属技能自动周报生成器4.1 动手前先设计输入和输出装别人的技能只是热身真正有意思的是按自己工作流写技能。我用一个最实用的例子来讲自动周报生成器。这个技能的诉求很典型系统里散落着各种操作日志和任务记录每周要人工汇总成一份周报格式固定内容还经常忘。如果能把“收集数据→整理格式→生成文档”固化成技能让模型只负责理解“我要哪一周的报告”那会省下大量重复劳动。先设计清楚输入输出输入起始日期、结束日期、项目代号可选。输出一份 Markdown 格式的周报文件保存到指定目录同时把内容发给指定的 Webhook 地址。输入越简单越好因为大模型从自然语言里提取参数的可靠性并不是 100%。日期范围、项目代号这种结构化信息够用再复杂的需求就不适合做成单个技能了。4.2 把 manifest 里的“话”说清楚接下来写描述文件。这是最容易敷衍、也最容易出事的地方。我的写法如下name: weekly_report_generator description: | 根据日期范围生成项目周报。支持按项目代号筛选。 当用户说“生成周报”、“写一下本周总结”、“汇总上周工作”时使用。 如果用户只是普通聊天、没有要求生成正式文档不要调用本技能。 parameters: type: object properties: start_date: type: string description: 起始日期格式 YYYY-MM-DD end_date: type: string description: 结束日期格式 YYYY-MM-DD默认与开始日期同年同周 project_code: type: string description: 项目代号例如 PROJ-01可选默认汇总所有项目 required: - start_date runtime: command: python3 generate_report.py这里我想强调两点经验。第一description 里“不做什么”比“做什么”更能减少误调用。模型是概率决策你给它划了边界它跑偏的概率会明显下降。第二参数描述里要写清楚格式比如YYYY-MM-DD因为模型如果把日期格式传错脚本再健壮也白搭。4.3 写执行脚本注意 stdout 的规矩脚本本身不复杂核心逻辑是从一个本地记录文件里读取活动条目按日期范围过滤再按项目分组生成 Markdown。#!/usr/bin/env python3 import argparse import json import urllib.request from datetime import datetime def load_records(pathrecords.jsonl): records [] with open(path, r, encodingutf-8) as f: for line in f: try: records.append(json.loads(line)) except json.JSONDecodeError: continue return records def filter_by_date(records, start, end): start_dt datetime.strptime(start, %Y-%m-%d) end_dt datetime.strptime(end, %Y-%m-%d) result [] for r in records: r_dt datetime.strptime(r[date], %Y-%m-%d) if start_dt r_dt end_dt: result.append(r) return result def build_markdown(records, project_codeNone): if project_code: records [r for r in records if r.get(project) project_code] lines [## 周报, ] for r in sorted(records, keylambda x: x[date]): lines.append(f- {r[date]}{r.get(project, N/A)}{r[content]}) return \n.join(lines) def send_to_webhook(url, content): data json.dumps({text: content}).encode(utf-8) req urllib.request.Request(url, datadata, headers{Content-Type: application/json}) with urllib.request.urlopen(req, timeout10) as resp: return resp.status if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--start-date, requiredTrue) parser.add_argument(--end-date, requiredTrue) parser.add_argument(--project-code, defaultNone) parser.add_argument(--output, defaultweekly_report.md) parser.add_argument(--webhook, defaultNone) args parser.parse_args() recs load_records() filtered filter_by_date(recs, args.start_date, args.end_date) md build_markdown(filtered, args.project_code) with open(args.output, w, encodingutf-8) as f: f.write(md) if args.webhook: status send_to_webhook(args.webhook, md) print(json.dumps({status: status, file: args.output}, ensure_asciiFalse)) else: print(json.dumps({file: args.output}, ensure_asciiFalse))写脚本时有一个铁律stdout 只输出给模型看的结果日志只进 stderr。因为 OpenClaw 会把脚本的标准输出回传到大模型如果脚本里写满了print(正在处理...)这类调试日志模型就会被噪音干扰甚至把日志当成结果回复给你。真正需要诊断时去看日志文件即可。4.4 调试技巧先手动喂参数再进对话新技能上线前我强烈建议先绕过模型手动执行一次python3 generate_report.py --start-date 2025-06-02 --end-date 2025-06-06确认文件生成、输出 JSON 正常后再回到 OpenClaw 里让模型来调用。这样可以隔离问题脚本出错还是模型乱路由一测便知。另一个技巧是给脚本加--timeout之类的参数如果有长任务因为 OpenClaw 默认对技能执行有时间上限我在下一章会专门说这个坑。5. 我这几个月踩过的坑整理成清单给你5.1 技能描述写得太泛模型把天气技能调去算账我第一次写技能时description 只写了一句话“查询天气信息”。结果有一天我让它“算一下这个月项目预算的结余”它居然给我调了天气接口。我把日志翻开发现模型认为“预算结余”也是“查询信息”于是命中了我那个描述极宽泛的技能。解决方法是把描述改成带边界条件的版本明确写“仅当涉及天气、气温、雨雪时才使用涉及财务、预算、账目时绝不使用”。模型虽然不懂天气和财务的区别但描述里的否定句能显著降低误命中概率。这个经验适用于所有技能别嫌啰嗦。5.2 脚本依赖装不进沙箱卡了整整一晚上有一次写处理 PDF 的技能脚本里用到pypdf在沙箱环境执行时直接报ModuleNotFoundError。原因是沙箱里根本不会自动安装requirements.txt里的依赖需要你手动构建镜像或预先安装。我当时用了最土但最有效的办法进入沙箱环境手动跑一遍pip install -r requirements.txt确认安装成功后再把依赖固化进镜像。后来学乖了凡是新技能首跑第一件事就是把依赖问题解决掉而不是急着调业务逻辑。这也提醒我技能的“最小可运行环境”必须自己负责OpenClaw 不会帮你装依赖。5.3 路径写死导致的“一键迁移失败”早期我写技能时经常图省事在脚本里直接写/home/myuser/records.jsonl。结果技能目录从一台机器复制到另一台时路径彻底失效所有调用秒挂。排查半天最后发现就是路径问题。现在我的做法是所有路径一律用相对当前脚本目录的方式或者读环境变量。比如import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) records_path os.path.join(BASE_DIR, records.jsonl)这样技能文件夹拷到哪都能跑。官方仓库里那些高质量技能基本都是这个写法这是个能省掉大量无谓时间的规范。5.4 超时与重试不是所有任务都能秒回OpenClaw 对技能执行一般有个超时限制默认值不长具体看你装的版本。我第一次跑一个批量抓取任务脚本要跑两分钟结果在 30 秒左右被强制掐断。当时我以为是脚本卡死反复调试浪费了一个晚上。后来查文档才明白超时时间是可以调的。但我也反思了一个更根本的问题动辄几分钟的长任务不适合做成同步技能。更好的方案是脚本后台异步执行先把任务标记为“已提交”再提供一个查询接口让技能返回任务 ID用户过一会儿再查结果。这套模式对付长任务非常管用我现在所有批量处理技能都按这个思路写。5.5 两个技能抢同一个外部服务再后来我装了自动化操作浏览器的技能又装了一个自动填表的技能。两个技能都依赖同一个后台服务结果同时被触发时互相抢资源页面状态被搞乱。这个问题的根子是技能职责边界不清。我的做法是在 manifest 里明确拆分使用场景让其中一个技能只管“打开/抓取”另一个只管“填写/提交”并在描述里互相标注“本技能不做 XXX请交给 YYY 技能”。OpenClaw 本身不限制技能之间的边界但设计者必须自己划清。职责清晰的技能库比技能数量多但互相打架的库好用十倍。6. 进阶玩法让多个 Skills 学会分工协作6.1 先查日历再订会议技能编排的雏形OpenClaw 真正迷人的地方在于技能可以被“编排”起来。比如我想让助理自动安排一场会议它不需要一个大而全的“会议技能”而是拆成两个小技能一个查日历空闲时间一个创建会议邀请。模型会先调用查询技能拿到空闲时段再把这个结果作为参数传给创建会议技能。这种“小技能拼装”的模式比一个大脚本硬扛全部逻辑稳健得多。每个脚本职责单一单独调试、单独升级都不影响其他部分。很多“智能体工作流”的玩法底层都是这种简单哲学。6.2 用命名规范和优先级管理技能库技能一多管理就变成大问题。我会给技能命名定一套规则领域-动作-对象。比如calendar_query_slots、mail_send_report、browser_fetch_page。这样技能列表一眼扫过去就知道谁负责什么不会出现两个语义相似的技能互相干扰。另外如果几个技能都能处理同类请求可以在描述里加上“优先级”暗示。比如你同时有“快速搜索”和“深度分析”两个技能就在前者的描述里写“当用户需要快速直接答案时优先使用本技能”后者写“当用户需要详尽分析报告时使用本技能”。模型读到这类语义路由会更符合预期。6.3 给外部接口留好“安全阀”技能一旦涉及发邮件、转账、删文件这些有副作用的操作我强烈建议做两道安全措施API Key、Token 一律放环境变量不要写死在脚本里。技能目录可能在多台机器间复制密钥一旦泄漏就是连锁事故。危险操作加确认环节。可以设计成技能先返回一个“待确认摘要”让模型向你复述“即将向 XXX 发送邮件主题为 YYY”得到你确认后再真正执行。这个模式表面上多了一步但对交易类、发送类操作来说是值得的保险。6.4 版本管理与回滚技能坏了别慌最后一个经验是给技能目录上 Git 管理。每改动一个技能脚本就提交一次写清 commit message。某次改坏了直接git revert就回到上一个能用的版本。这套操作不复杂但能救命——尤其是你同时维护十几个技能、又喜欢频繁微调描述词的时候。有一次我为了优化路由改了某个技能的 description结果这个技能连续三天没被触发过回滚后立刻恢复正常。从那以后我再也没有裸改过技能文件。回到开头那个问题OpenClaw 到底离“贾维斯”有多远我的答案是技术上差的很少真正差的是你往它的技能库里塞了多少“真本事”。每接入一个 Skill它就长出一只新的手每写清楚一段描述它就更懂你一分。从第一个天气技能跑通到给自己定制周报生成器再到设计出一套能互相协作的技能矩阵——这个过程不复杂但需要一点点耐心和把“让 AI 真正干活”这件事当真的态度。如果你也想让你那个只会聊天的助理动起来我建议就从今天装一个最小的技能开始。