刚过去的一年里,AI圈最热的关键词早就不再是“聊天”“写诗”“画图”了。各大模型厂商和开发者社区都在拼命解决同一件事:怎么让大模型不只是“动嘴”,而是真正“动手”。如果你一直在关注 Claude 的新功能,一定绕不开一个概念——Skills。简单说,它是一套让 AI 按需加载“技能包”的机制:大模型不再是那个只会按提示词生成文本的对话机器,而是可以临时拥有一个工具集、一段脚本、一套流程,去完成文件整理、数据分析、系统巡检、批量处理这类真实任务。
我在实际项目中重度使用了这套机制,体验很直接:它把“让 AI 干活”这件事的成本打到了极低。过去要写一堆 prompt 让模型记住你的工作流,或者硬啃 Function Calling 的框架对接,现在只需把一个技能文件夹摆到指定位置,模型自己就知道什么时候该调用、怎么调用、用什么参数。这篇文章我把我从零搭建、调试、优化 Skills 的完整过程写出来,包括设计思路、配置细节、踩坑实录和排查技巧。适合刚开始接触 AI Agent 开发,或者已经用过 Claude 但还没深入折腾过 Skills 的读者。
1. 什么是 Skills:理解 AI 的“外挂技能包”
1.1 从“会聊天”到“会干活”的一步之遥
先厘清一个底层问题。传统的对话式 AI,本质是“文本进、文本出”:你给它一段指令,它基于训练数据和你提供的上下文,生成一段回答。它很聪明,但它没有手,也没有脚。你让它“帮我把这个目录下所有文件名里的日期格式统一一下”,它只能给你一段 Python 代码,然后你自己去跑;你让它“检查一下这台服务器上 Nginx 的配置有没有问题”,它只能说个大概思路,没法真的登进去看。
Skills 把这层墙拆了。它的设计哲学很简单:给模型配一副“可穿戴设备”。平时模型还是那个模型,一旦任务环境需要,它可以从技能库里选出一个合适的技能,读取技能的使用说明,加载技能附带的脚本或工具,然后真正去执行。
打个比方,这就像你请了一个万能助理,他不会修水管,但你给了他一本水管工手册和一套工具箱,他接到修水管的活儿时会自己翻手册、拿工具上手。Skills 就是那本手册加工具箱。
1.2 一个 Skill 的组成:说明文件 + 可执行代码
从技术实现上看,一个 Skill 不是一个魔法开关,它就是一个普通文件夹,里面放两样东西:
- SKILL.md:技能说明文件,用 Markdown 写的。作用是告诉模型这个技能能干什么、什么时候用、怎么用、有哪些限制。这就是给模型看的“使用手册”。
- 可执行脚本或其他资源:通常是 Python 脚本、Shell 脚本,也可以是一个本地服务的接口定义。这是真正干活的“手”。
模型会在对话过程中,根据用户请求判断“该不该用这个技能”,然后读取 SKILL.md 理解用法,再调用脚本执行。整个过程对使用者来说几乎是透明的——你不需要自己写代码去调用工具,模型负责协调。
1.3 它和 Function Calling、MCP 有什么区别
很多读者会想到 OpenAI 的 Function Calling,或者当下很火的 MCP(Model Context Protocol)。这几个概念确实容易混淆,但定位完全不同:
| 对比维度 | Function Calling | MCP | Skills |
|---|---|---|---|
| 核心思路 | 让模型输出结构化调用指令 | 统一工具调用协议,标准化对接 | 模型自主决定何时加载何种能力 |
| 谁主导调用 | 开发者预设函数,模型选择 | 客户端按协议分发请求 | 模型读取文档后自主使用 |
| 对开发者的要求 | 需要写 API 封装,定义 schema | 需要搭建 MCP Server,遵循协议 | 只需写一个 Markdown 文档 + 脚本 |
| 灵活性 | 中,函数列表固定 | 高,可动态发现工具 | 极高,按任务场景动态加载 |
| 使用门槛 | 中偏高 | 高 | 低 |
我做过的项目里,如果只是给模型暴露一两个固定的数据查询接口,Function Calling 够用;如果要对接很多外部系统,MCP 是正确的选择;但如果你的目标是让模型具备“一系列可复用的做事能力”,Skills 是最自然、最轻量的方式。三者不是互斥关系,实际项目里甚至可以混合用。
2. 设计 Skills 前的思考:什么样的任务才值得做成技能
2.1 任务拆解:不是所有需求都适合塞进技能包
一开始容易犯的错,是恨不得把所有操作都做成 Skill。我试过把一个包含十几步判断逻辑的运营流程硬塞进一个技能里,结果模型在“先用哪个子步骤”上反复纠结,效果很差。后来我学乖了:做技能前,先把任务拆到单个“动作”的粒度。
一个合适的 Skill 任务,通常满足这几个条件:
- 目标明确:一个技能只做好一件事,输出结果可验证。
- 流程固定:输入相似,执行路径相似,不需要模型临时发挥太多。
- 有真实执行价值:模型靠文本回答解决不了,必须调用脚本或工具操作。
- 可以自动化验收:跑完能看得到结果文件、日志或者状态变化。
举个例子,“把照片按拍摄日期归档到文件夹”是一个好技能;“帮我策划一场市场营销活动”不是一个好技能。后者需要大量创意判断,模型直接对话完成度更高,硬做成技能反而画蛇添足。
2.2 定义“接口”:输入、输出、边界
所有好的工程实践都是先定义接口,写 Skill 也一样。动手写 SKILL.md 之前,我会先在脑子里或草稿纸上明确三件事:
- 输入是什么:这个技能接收什么信息?是文件路径、URL、一段原始文本,还是几个参数?用什么方式传给脚本?环境变量、命令行参数还是标准输入?
- 输出是什么:执行完返回什么?是写一个文件、打印一段结构化 JSON,还是直接对系统做了修改?返回的结果要能被模型读懂并继续处理。
- 边界在哪里:哪些事绝对不能做?比如不能删除文件、不能执行危险命令、不能访问外网。这些边界必须白纸黑字写进 SKILL.md,模型才会约束自己。
我见过不少失败的技能,问题都出在接口定义模糊。脚本读取参数的方式没写明白,模型凭感觉传参,脚本直接报错;输出格式没有约定,模型不知道结果成不成功,只能猜。接口定义清晰了,这个技能就成功了一半。
2.3 选择脚本语言:Python 依然是首选,但不是唯一
目前社区里大部分 Skill 示例都是用 Python 写的,我也默认用 Python,原因很现实:生态全、跨平台、AI 训练语料里 Python 代码最多,模型写 Python 脚本的准确率明显高于其他语言。不过如果你处理的是纯系统运维场景,Shell 脚本更直接,不用带着 Python 环境的依赖到处跑;如果是 Windows 环境下的自动化,PowerShell 也有它的优势。
我的建议是按场景选语言,但优先保证脚本能在目标机器上一键运行。为了让技能可移植性更强,我通常会在脚本里做依赖检查,缺什么库时输出明确的提示而不是直接崩溃,这样模型看到报错也知道怎么处理。
3. 实战:手把手实现一个 Skill——自动批量重命名文件
讲完理论,我们直接上手。下面我用一个真实可用的例子走一遍完整流程:做一个技能,让模型能够自动把指定目录下的所有文件,按“创建时间_原始文件名”的格式批量重命名。这个场景非常典型,既涉及文件系统操作,又适合展示 Skills 的核心机制。
3.1 项目结构规划
先规划目录结构。假设我们把这个 Skill 放在~/.claude/skills/下面(具体位置取决于你在用的客户端,建议先查自己客户端的文档,各个工具的实现可能不同。以 Claude Desktop 和 Claude Code 为例,都支持通过配置指定技能目录)。
skills/ └── batch-rename/ ├── SKILL.md └── rename_script.pybatch-rename是技能目录名,SKILL.md 和脚本都放在里面。技能目录名建议用小写加连字符,可读性好,也不容易在跨平台时出问题。
3.2 编写 SKILL.md:写给模型看的“使用手册”
这是整个技能里最关键的文件。我见过很多人把 SKILL.md 写成给人类看的 README,这完全跑偏了——它的目标读者是 AI 模型,所以要写得让模型一看就懂、一读就能照着执行。
我的 SKILL.md 全文如下,可以直接参考:
--- name: batch-rename description: 按创建时间批量重命名指定目录下的所有文件,格式为“YYYYMMDD_HHMMSS_原文件名”。 --- # Batch Rename Skill ## 适用场景 - 用户要求整理某个目录下的文件,希望文件名带上创建时间。 - 用户说“把这个文件夹里的文件按时间重命名”或类似的表达。 - 目录下文件较多,手工重命名费时且容易出错。 ## 不适用场景 - 用户只需要重命名单个文件。 - 用户要求按文件大小、类型等其他规则命名。 - 用户指定的目录不存在或没有读取权限。 ## 使用前必须确认 1. 向用户确认目标目录的完整路径。 2. 确认重命名规则是否需要保留原始文件名主体部分。 3. 确认是否需要对子目录中的文件递归操作(默认不递归)。 ## 执行步骤 1. 使用 Python 脚本 `rename_script.py`,传入目标目录路径作为第一个位置参数。 2. 脚本会扫描目录下所有普通文件(跳过子目录和隐藏文件)。 3. 对每个文件,读取其创建时间(Linux 下为 birth time,macOS 下为 birth time,Windows 下为创建时间),如果没有创建时间则回退到修改时间。 4. 生成新文件名:前缀时间戳 + 原文件名。如果文件名已符合格式,跳过。 5. 重命名时如果目标文件已存在,自动追加序号,避免覆盖。 6. 执行完成后,脚本会输出 JSON 格式的结果,包含重命名成功、跳过、失败的文件列表。 7. 将执行结果摘要反馈给用户。 ## 重要限制 - 不要删除任何文件。 - 不要重命名目录。 - 不要修改文件内容。 - 如果目录路径中包含空格或特殊字符,调用脚本时务必将路径用引号包住。写好之后,我必须给你画个重点:SKILL.md 不是写给人看的,是写给模型看的。所以不要炫技,不要写废话,不要用含糊的形容词。模型读取文档时是在“学习规则”,规则越清晰,它的执行就越可靠。上面我用“适用场景”“不适用场景”“使用前必须确认”“执行步骤”四个模块,本质上是在做流程拆解,降低模型自主决策时的随机性。
3.3 实现核心逻辑脚本
SKILL.md 是说明书,脚本才是真正干活的“手”。我来写一个健壮性比较高的版本:
#!/usr/bin/env python3 """ batch-rename 技能的核心执行脚本 用法: python rename_script.py /path/to/target_directory """ import sys import os import json from datetime import datetime def get_file_timestamp(filepath): """获取文件时间戳,优先创建时间,回退到修改时间""" stat_info = os.stat(filepath) # 优先创建时间,不同平台字段不同 if hasattr(stat_info, "st_birthtime"): ts = stat_info.st_birthtime else: ts = stat_info.st_ctime return datetime.fromtimestamp(ts) def build_new_name(original_name, timestamp_str): """构建新文件名:时间戳_原始名,如果已符合格式就直接返回原名称""" if original_name.startswith(timestamp_str): return original_name, False new_name = f"{timestamp_str}_{original_name}" return new_name, True def rename_file(directory, filename): """执行重命名,返回结果状态""" old_path = os.path.join(directory, filename) # 跳过目录、隐藏文件和符号链接 if os.path.isdir(old_path) or filename.startswith(".") or os.path.islink(old_path): return {"file": filename, "status": "skipped"} try: ts_obj = get_file_timestamp(old_path) timestamp_str = ts_obj.strftime("%Y%m%d_%H%M%S") new_name, should_rename = build_new_name(filename, timestamp_str) if not should_rename: return {"file": filename, "status": "skipped"} new_path = os.path.join(directory, new_name) # 如果目标存在,追加序号 counter = 1 final_new_path = new_path while os.path.exists(final_new_path): name, ext = os.path.splitext(new_name) final_new_path = os.path.join(directory, f"{name}_{counter}{ext}") counter += 1 os.rename(old_path, final_new_path) return {"file": filename, "new_name": os.path.basename(final_new_path), "status": "success"} except Exception as e: return {"file": filename, "status": "error", "message": str(e)} def main(): if len(sys.argv) < 2: print(json.dumps({"error": "缺少目标目录参数"}, ensure_ascii=False)) sys.exit(1) directory = sys.argv[1] if not os.path.isdir(directory): print(json.dumps({"error": f"目录不存在: {directory}"}, ensure_ascii=False)) sys.exit(1) results = [] success_count = 0 for filename in sorted(os.listdir(directory)): result = rename_file(directory, filename) results.append(result) if result["status"] == "success": success_count += 1 summary = { "directory": directory, "total": len(results), "success": success_count, "skipped": sum(1 for r in results if r["status"] == "skipped"), "failed": sum(1 for r in results if r["status"] == "error"), "details": results, } print(json.dumps(summary, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()这个脚本有几个值得留意的设计:
- 我用了
st_birthtime拿创建时间,但 Linux 上 Python 的os.stat默认是没有这个字段的(不同平台行为不一样),所以做了回退到st_ctime(在 Linux 上更接近元数据变更时间)的处理。技能执行跨平台时,这类兼容逻辑能减少很多莫名奇妙的报错。 - 输出统一用 JSON,并且打印在 stdout 上。这么做的原因是,模型的“眼睛”就是那段标准输出文本,它需要从输出里判断任务成没成、哪些文件有问题。JSON 结构化输出比纯文本更容易让模型解析。
- 重名处理我用了“追加序号”而不是直接覆盖。文件操作最怕数据丢失,宁可多生成几个带序号的副本,也不要互相覆盖。
3.4 本地测试与调试:先让脚本自己跑通
脚本写完别急着丢给模型,先在终端里手动测几轮。这是我最强调的步骤——技能出问题,九成是脚本本身的问题,不是模型的问题。
测试几步:
- 找一个测试目录,塞几个测试文件,运行:
python rename_script.py /tmp/test_files- 看看输出的 JSON 里 total、success、skipped 是否符合预期。
- 检查目录里文件是否真的重命名了,时间戳格式对不对。
- 再次运行脚本,确认文件被跳过而不是重复改名。
- 测试错误路径:传入一个不存在的目录,看脚本是否输出友好报错。
跑通之后,再接进客户端里做集成测试。你会逐渐发现最耗时的地方不是我预想里的界面或配置,而是“把模型的调用习惯调顺”——它有时候不按 SKILL.md 里写的参数传递,路径里带空格时不加引号,这些都需要靠调整描述文件和测试对话来纠正。
4. 进阶配置与运行机制:让技能更聪明、更安全
4.1 技能描述里的“触发词”:教你如何在文档里提高命中率
模型的技能调用是基于语义匹配的,它读到“批量重命名”相关请求时,会自动调出 batch-rename 技能。但这个匹配不是 100% 准确的,特别是用户表述很模糊的时候。这时候,SKILL.md 里的description字段就承担了“路标”的作用。
我在描述里会刻意埋入用户可能使用的各种说法,比如“整理文件”“按时间重命名”“给文件加日期前缀”“批量改文件名”,确保用户的真实表达能撞上技能关键词。这跟搜索引擎做 SEO 的核心思路一模一样——你优化的是 AI 对文档的检索命中率。
4.2 权限控制与安全边界:防止 AI “手滑”
任何一个让 AI 直接操作系统的功能,都必须正面回答安全问题。模型不是不会犯错,它在参数传递、路径拼接这种细节上,比人类更容易产生离奇操作。为了让技能在实际使用中“出不了大事”,我给自己定了几条铁律:
- 绝不删除文件:脚本里不提供任何删除接口。如需清理,可以先移动到回收站目录,人肉确认后再删。
- 只在用户指定的目录里活动:脚本强制锁定目标目录,禁止使用
..跳转到上级目录。如果用户传了绝对路径,必须校验它在允许范围内。 - 敏感操作前置确认:如果技能需要修改大量文件(比如超过 100 个),在 SKILL.md 里要求模型先向用户发确认提示,等用户点了同意再执行。
安全不能只靠模型自觉,脚本层面就要把危险操作堵死。我在实际项目里,甚至会把关键脚本里的os.remove直接物理删除,防止模型在生成新调用时绕过限制。
4.3 上下文与状态管理:技能执行完了,然后呢
一个很常见的坑是:技能执行完,模型就把结果丢了,用户问“刚才改到第几个文件了?”,模型一脸懵。原因在于技能脚本是无状态的,它跑完就结束,不保留任何信息。
解决方式我推荐两个:
- 脚本每次输出完整的 JSON 结果,让模型基于输出继续对话,这样结果就存在于对话上下文里。
- 如果任务跨多次对话,脚本可以把执行记录追加到一个日志文件里,模型需要时读取日志。
状态管理这层,本质上是给模型搭记忆脚手架。技能的“事后处理”有没有做扎实,决定了用户的体验是“AI 帮我搞定了”还是“AI 做了但我不确定它做了什么”。
5. 常见问题与排查实录:我把踩过的坑都列在这里
5.1 技能没有被加载,模型无视技能库
这是新手最容易碰到的问题。排查顺序我建议这么来:
- 确认技能目录路径配置正确。不同客户端读的技能目录不一样,用前先查文档。
- 确认 SKILL.md 文件名大小写。有些系统严格区分大小写,写
skill.md和SKILL.md可能是两回事。 - 确认描述文件里有没有
name和description字段,缺少任何一个都可能导致解析失败。 - 换个更直白的表达方式测试,比如直接说“用批量改名技能处理这个目录”。如果这样能触发,说明语义匹配没问题,是用户表达离技能关键词太远了。
5.2 技能调用了,但脚本报错
脚本报错有几种典型情况:
| 报错类型 | 可能原因 | 解决方式 |
|---|---|---|
No such file or directory | 目录路径传错,或路径里有空格没加引号 | 在 SKILL.md 里反复强调路径必须用引号包裹 |
Permission denied | 当前用户对目标目录没有写权限 | 给脚本加权限检查,提前给出清晰提示 |
ModuleNotFoundError | 脚本依赖第三方库但环境里没装 | 脚本开头做依赖检测,缺库时输出安装指引 |
| 中文文件名乱码 | 不同系统的编码差异 | 脚本里统一用 UTF-8,并在描述里要求模型不要改动原始文件名编码 |
5.3 模型对技能的理解有偏差
SKILL.md 写得再细,模型也有跑偏的时候。比如用户明明只要求重命名一个文件,它却调用了批量技能把整目录都改了。这种问题要从两个方向同时修:
- 在 SKILL.md 的“不适用场景”里写清楚哪些情况不要调用这个技能。
- 在“使用前必须确认”部分添加“确认用户是否要求处理整个目录,如果不是,拒绝执行”。
5.4 技能执行速度太慢,或者文件太多超时
批量处理几千个文件时,如果脚本还是同步等待所有文件跑完,很容易触发客户端的超时限制。我的做法是给脚本加一个--limit参数,分批次处理,每批处理完输出一次进度,给模型一个可以持续反馈的过程。同时,处理大目录之前先和用户确认“本次要处理 5000 个文件,可能耗时较长,是否继续”。
6. 写在最后的经验和建议
Skills 这套机制,最打动我的地方是——它把“AI 能力扩展”的门槛从“工程问题”降到了“文档问题”。任何一个能写清楚使用说明的人,都可以给模型武装一项新能力,不一定要精通后端开发或 API 设计。但是门槛低不代表不需要用心,我在反复调试中最大的体会是:技能的可靠性不取决于脚本写得多炫,而取决于边界画得多清楚。
几个具体的建议,送给准备入坑的读者:
- 从一个小而具体的场景开始,不要一上来就做“全能工作流”。一个能稳定完成文件重命名的技能,比一个什么都想管但经常出错的技能有价值得多。
- SKILL.md 要持续迭代。每当你发现模型在某类表达下用错了技能,就去描述文件里补一句限制或规则,它就会越来越“懂事”。
- 脚本的输出一定要结构化、可被模型解析。JSON 是默认选项,别用一堆无格式的 print 文本。
- 安全底线要提前画好。删除操作、危险命令、外网访问这类高风险行为,最好从一开始就不出现在技能能力范围内。
最后分享一个我在实践中养成的小习惯:每次新增一个技能,我都会准备一个“测试对话”模板,里面包含正常请求、边界请求、错误请求三类问题,每轮改完配置就先跑一遍模板,确保任何场景下模型的行为都可预期。这套流程虽然简单,却帮我省下了大量线上出问题的补救时间。