1. 为什么我建议你从 SKILL.md 开始动手OpenClaw 技能开发这件事很多人卡在第一步不知道一个技能到底由哪些文件组成SKILL.md 里该写什么脚本又该怎么被调用。我试过直接照搬别人的技能目录结果 AI 完全不触发排查半天才发现是 description 写得太模糊。所以这篇内容不绕弯子直接带你从零搭一个能跑起来的技能目录结构、SKILL.md 模板、脚本示例、本地加载验证全部给到可复制的版本。先说清楚 OpenClaw 技能是什么。你可以把它理解成给 AI Agent 装的一个“插件包”一个文件夹里面有一个 SKILL.md 负责告诉模型“我是谁、我能干什么、什么时候该用我”再配一个 scripts 目录放实际执行的脚本。模型读到 SKILL.md 的描述后判断用户的问题是否匹配匹配就调用对应脚本脚本返回结构化结果模型再把结果组织成自然语言回复。适合谁适合已经用过 OpenClaw、想让 Agent 接入自己业务逻辑的开发者也适合刚接触 AI Agent 技能体系、想跑通第一个 demo 的新手。为什么强调“从 SKILL.md 开始”因为技能开发里最容易出错的不是脚本逻辑而是技能描述。脚本写错了会报错你能看到堆栈但 SKILL.md 写得不清楚模型压根不会调用你的技能你连报错都看不到。所以正确的顺序是先想清楚技能的能力边界把它写成 SKILL.md再让脚本去实现这个边界。下面我会按“目录结构 → SKILL.md 模板 → 脚本实现 → 本地加载验证 → 排错”的顺序走一遍。整个过程不需要联网调外部 API 也能验证我会用一个本地可跑的文件统计技能做演示避免你因为网络问题卡住。等你跑通这个最小闭环再换成天气查询、数据库查询这类真实技能就是替换脚本逻辑的事。2. OpenClaw 技能目录结构与 SKILL.md 模板详解2.1 一个标准技能目录长什么样先看目录结构这是所有技能的地基file-stats/ ├── SKILL.md # 技能定义文件必需 ├── scripts/ │ ├── main.py # 主脚本必需 │ └── utils.py # 辅助模块可选 ├── tests/ │ └── test_main.py # 测试可选 └── README.md # 说明文档可选关键点只有两个SKILL.md 必须在技能根目录脚本必须放在 scripts 目录下。OpenClaw 启动时会扫描技能目录识别每个子目录里的 SKILL.md把技能元数据加载进内存。目录名建议用英文小写加连字符比如file-stats、weather-query避免中文和空格减少路径解析问题。2.2 SKILL.md 的 frontmatter 是核心SKILL.md 分两部分顶部的 YAML frontmatter 和下面的 Markdown 正文。frontmatter 是给模型看的元数据正文是给人看的详细说明。模板如下--- name: file-stats description: 统计指定目录下的文件数量、总大小和类型分布。Use when: 用户询问某个目录有多少文件、占用多少空间、文件类型构成。NOT for: 文件内容搜索、文件删除、权限修改。 --- # File Stats Skill 统计目录文件信息返回文件数量、总大小和扩展名分布。 ## When to Use - 用户问“这个目录有多少文件” - 用户问“某个文件夹占多大空间” - 用户想了解文件类型分布 ## When NOT to Use - 需要读取文件内容 - 需要修改或删除文件 - 需要递归搜索特定文件名 ## Parameters | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | path | string | 是 | - | 目标目录路径 | | recursive | boolean | 否 | false | 是否递归统计子目录 | | top | number | 否 | 5 | 返回前 N 个文件类型 | ## Commands bash python3 scripts/main.py --path /tmp/demo --recursive true --top 5Response{ code: 0, message: success, data: { path: /tmp/demo, total_files: 12, total_size: 204800, types: [ {ext: .py, count: 5, size: 102400} ] } }这里有几个细节值得展开。description 里的 “Use when” 和 “NOT for” 是模型判断是否调用的主要依据写得越具体误触发越少。参数表用 Markdown 表格模型能直接解析出参数名、类型和默认值。Commands 部分给出可复制的命令行示例模型在生成调用参数时会参考这个格式。Response 部分给出返回结构方便模型理解脚本输出。 ### 2.3 参数设计的三条原则 第一参数最小化。只让用户提供必要信息其余给默认值。比如 recursive 默认 falsetop 默认 5用户不传也能跑。 第二类型明确。path 是字符串recursive 是布尔值top 是数字。类型不明确模型可能把 true 传成字符串 true脚本解析就会出错。 第三命名语义化。用 path 而不是 p用 recursive 而不是 r。模型靠参数名理解含义缩写会降低准确率。 ### 2.4 技能生命周期与调用链路 技能从创建到执行走的是这条链路创建目录 → 编写 SKILL.md → 开发脚本 → 本地测试 → 部署到 OpenClaw → 模型发现技能 → 用户请求触发 → 模型匹配技能 → 脚本执行 → 返回结果 → 模型整合回复。 其中“模型匹配技能”这一步完全依赖 SKILL.md 的 description。如果模型没匹配上后面所有环节都不会发生。所以每次改完 SKILL.md都要重新验证一次触发是否正常。 ## 3. 可复制配置脚本实现与 settings 片段 ### 3.1 主脚本 main.py 完整实现 这个脚本不依赖任何外部 API纯本地文件统计方便你直接跑通 python #!/usr/bin/env python3 File Stats Skill - 统计目录文件信息 import argparse import json import logging import os import sys from collections import defaultdict from pathlib import Path logging.basicConfig( levellogging.INFO, format%(asctime)s | %(levelname)s | %(message)s ) logger logging.getLogger(__name__) def parse_args(): parser argparse.ArgumentParser(description统计目录文件信息) parser.add_argument(--path, requiredTrue, help目标目录路径) parser.add_argument(--recursive, defaultfalse, help是否递归统计) parser.add_argument(--top, typeint, default5, help返回前 N 个类型) return parser.parse_args() def to_bool(value): return str(value).lower() in (true, 1, yes) def collect_files(root: Path, recursive: bool): files [] if recursive: for dirpath, _, filenames in os.walk(root): for name in filenames: files.append(Path(dirpath) / name) else: for entry in root.iterdir(): if entry.is_file(): files.append(entry) return files def build_stats(root: Path, files, top: int): total_size 0 type_count defaultdict(int) type_size defaultdict(int) for f in files: try: size f.stat().st_size except OSError: continue total_size size ext f.suffix.lower() or (no ext) type_count[ext] 1 type_size[ext] size types sorted( [ {ext: ext, count: type_count[ext], size: type_size[ext]} for ext in type_count ], keylambda x: x[size], reverseTrue )[:top] return { path: str(root), total_files: len(files), total_size: total_size, types: types } def output(code, message, dataNone): print(json.dumps( {code: code, message: message, data: data}, ensure_asciiFalse )) def main(): args parse_args() root Path(args.path).expanduser().resolve() if not root.exists(): output(1001, f路径不存在: {root}) sys.exit(1) if not root.is_dir(): output(1001, f不是目录: {root}) sys.exit(1) recursive to_bool(args.recursive) logger.info(f统计目录: {root}, recursive{recursive}) try: files collect_files(root, recursive) stats build_stats(root, files, args.top) output(0, success, stats) logger.info(f统计完成: {stats[total_files]} 个文件) except Exception as e: logger.exception(统计失败) output(9999, f内部错误: {e}) sys.exit(1) if __name__ __main__: main()脚本结构很清晰parse_args解析参数collect_files收集文件build_stats计算统计output统一输出 JSON。所有输出都是{code, message, data}三段式模型解析起来很稳定。3.2 本地 settings 配置片段如果你用 OpenClaw 的本地配置来加载技能目录settings 里需要指定技能根路径。以 JSON 格式为例{ skills: { enabled: true, paths: [ /Users/yourname/openclaw-skills ], auto_reload: true, log_level: info } }如果你用的是 TOML 格式[skills] enabled true paths [/Users/yourname/openclaw-skills] auto_reload true log_level info把file-stats整个目录放到paths指向的目录下重启 OpenClaw 或触发 reload技能就会被扫描到。auto_reload打开后改完 SKILL.md 不用重启也能生效调试阶段很方便。3.3 三件套Base URL、Key、Model ID如果你是通过 API 方式接入模型来驱动技能调用配置里需要写全三件套。以环境变量为例export OPENAI_BASE_URLhttps://taotoken.net/api export OPENAI_API_KEYsk-你的密钥 export OPENAI_MODELclaude-sonnet-4-20250514Base URL 指向https://taotoken.net/apiKey 在控制台的 API Keys 页面生成Model ID 按你实际使用的模型填写。这三项缺一不可少一个就会在请求阶段报错。生成 Key 的入口在 API Keys接入细节可以对照 接入文档。4. 验证请求本地加载与调用成功结果4.1 先单独跑脚本在接入 OpenClaw 之前先确认脚本本身能跑。准备一个测试目录mkdir -p /tmp/demo/sub echo hello /tmp/demo/a.txt echo world /tmp/demo/b.txt echo print(1) /tmp/demo/c.py echo print(2) /tmp/demo/sub/d.py然后执行python3 scripts/main.py --path /tmp/demo --recursive true --top 5预期输出{ code: 0, message: success, data: { path: /tmp/demo, total_files: 4, total_size: 24, types: [ {ext: .txt, count: 2, size: 12}, {ext: .py, count: 2, size: 12} ] } }看到code: 0就说明脚本逻辑没问题。如果这里就报错先别急着接 OpenClaw把脚本调通再说。4.2 再验证技能加载把file-stats目录放到 settings 里配置的paths下重启 OpenClaw。查看日志应该能看到类似[INFO] skill loaded: file-stats [INFO] skill registry: 1 skill(s) available如果日志里没有file-stats说明目录结构或 SKILL.md 有问题回到第 5 节排查。4.3 触发技能调用在对话里输入“帮我统计一下 /tmp/demo 目录有多少文件占多大空间。”模型应该会匹配到file-stats技能生成调用参数path/tmp/demo执行脚本然后返回类似/tmp/demo 目录下共有 4 个文件总大小 24 字节。 其中 .txt 文件 2 个.py 文件 2 个。如果模型没有调用技能而是直接回答“我无法访问你的文件系统”那就是 description 没写清楚或者技能没加载成功。这时候去看 OpenClaw 的日志确认技能是否在 registry 里。4.4 用模型对话验证触发如果你想单独验证模型对技能描述的理解可以在 模型对话 里把 SKILL.md 的 description 贴进去问它“用户说‘这个目录有多少文件’时你会调用这个技能吗”看模型的判断是否符合预期。这是快速迭代 description 的好办法不用每次都重启 OpenClaw。5. 本篇常见错误排查5.1 技能没被加载日志无 skill loaded最常见的原因是目录层级不对。OpenClaw 扫描的是paths下的一级子目录如果你的结构是paths/file-stats/file-stats/SKILL.md就会扫不到。正确结构是paths/file-stats/SKILL.md。第二个原因是 SKILL.md 的 frontmatter 格式错误。YAML 对缩进敏感name和description必须顶格冒号后面要有空格。可以用在线 YAML 校验工具过一遍。5.2 报错 401Key 无效或未配置如果你在接入模型时看到Error: 401 Unauthorized说明 API Key 没配或配错了。检查环境变量OPENAI_API_KEY是否设置Key 是否从控制台正确复制。注意 Key 只在创建时显示一次如果丢了就重新生成一个。生成入口在 API Keys。5.3 报错 local proxy failedError: local proxy failed to connect这个报错通常出现在本地代理配置和实际网络环境不匹配时。检查你的 Base URL 是否写成了https://taotoken.net/api末尾不要多加斜杠。如果你在 settings 里同时配了多个 endpoint确认没有冲突项。把配置精简到只剩一个 Base URL 再试。5.4 报错 reading choices返回结构不匹配Error: reading choices field failed这个报错说明模型返回的 JSON 结构和你代码里解析的字段对不上。常见原因是 Model ID 填错了或者请求体里model字段和实际可用模型不一致。检查三件套里的 Model ID确认它和你在控制台看到的模型名完全一致。另外确认请求头Content-Type: application/json没有漏。5.5 脚本执行失败参数类型不匹配如果日志里出现ValueError: invalid literal for int() with base 10: true说明模型把布尔参数传成了字符串而脚本按整数解析。解决办法是在脚本里做类型兼容比如to_bool函数同时接受true、1、yes。或者在 SKILL.md 的参数表里把类型写得更明确减少模型误传。5.6 OAuth 相关报错如果你用的是需要 OAuth 的接入方式看到Error: OAuth token expired说明 token 过期了需要重新走授权流程。检查你的 token 刷新逻辑或者改用 API Key 方式接入配置更简单。OAuth 场景下还要注意回调地址和实际部署地址一致否则授权会失败。5.7 技能触发了但结果不对模型调用了技能但返回结果和预期不符。先看脚本单独执行的结果是否正确如果脚本没问题那就是参数传递错了。在脚本里加一行日志把收到的参数原样打印出来logger.info(freceived args: path{args.path}, recursive{args.recursive}, top{args.top})对比模型生成的参数和你预期的参数差异通常就在类型转换或默认值上。6. 从最小技能到可复用 Agent 能力跑通file-stats之后你已经掌握了 OpenClaw 技能开发的完整闭环目录结构、SKILL.md 定义、脚本实现、本地加载、触发验证、错误排查。接下来要做的是把这个模式复制到真实业务场景。比如你要做一个天气查询技能只需要把collect_files换成调用天气 API 的函数把 SKILL.md 的 description 改成天气相关触发词参数表改成city、days、units。脚本骨架、错误处理、输出格式都可以复用。这就是技能系统的价值能力边界用 SKILL.md 声明具体实现用脚本替换两者解耦。如果你打算长期做技能开发和 Agent 编排建议把常用技能沉淀成一套自己的技能库统一命名规范和返回结构。需要跑批量任务或长时间运行的 Agent 时可以了解 Coding Plan它更适合持续性的编码和 Agent 场景。配置过程中遇到接入问题对照 接入文档 排查比在群里问快得多。最后一个实用建议每次改完 SKILL.md先用模型对话单独验证 description 的触发准确率再重启 OpenClaw 做端到端测试。这样能把“描述问题”和“脚本问题”分开定位省掉大量来回折腾的时间。