
最近这半年“skills”在 Agent 项目里的出现频率高得离谱。不管你是做企业内部自动化还是在捣鼓个人助理开会、看 PR、翻技术群绕不开这个词。我一开始也以为它就是“给大模型写 prompt”的另一种说法后来把一个遗留系统里的重复流程真正改成 skills 结构之后才发现这玩意儿不是概念包装它是真的能把模型的能力边界往外推一层。这篇文章就围绕 skills 的实际落地来写适合已经在用或者打算用 Agent 处理真实任务的开发者。我会从最基础的结构讲起给一个可以直接抄的完整示例再把部署、调用、踩坑和组合用法都过一遍。1. 为什么“skills”突然成了 Agent 开发里的高频词先说结论skills 解决的痛点是“模型什么都会一点但什么都不熟”。你在 system prompt 里写一百条规则模型确实会听但每次对话都要把这些规则重新读一遍token 在烧效果还不见得好。更麻烦的是一旦规则多了它们之间会互相干扰你改一条另外几条就变得模棱两可。传统做法大概有三条路。第一条是纯 prompt 工程。把操作手册、业务规则、输出格式全部堆进 system prompt。优点是简单缺点是上下文越来越胖推理速度变慢维护成本直线上升。而且模型对长文本中段内容的注意力会衰减你精心写的第 80 条规则它可能真的“看不见”。第二条是 function calling。让模型调用你定义好的函数适合获取实时数据、操作外部 API。但它解决的问题是“连接”不负责“教会模型怎么把一个多步骤的活儿干完”。你还是要自己在函数外面写清楚什么时候该调、参数怎么传、返回结果怎么处理。第三条就是 skills。它把一整套流程、说明、脚本打包成一个独立单元平时完全不占对话上下文模型判断“现在需要这个技能”的时候才把对应文件加载进来按里面的说明一步步执行。我举个具体场景。我们之前有个数据处理任务需要把客户发来的 Excel 文件清洗、转换、合并再生成一份 PDF 报告。用 function calling 做也行但每个环节都要单独写函数定义和调用逻辑模型在函数之间跳来跳去稍微复杂一点就乱。后来我把整套流程写成一个 skill里面就三样东西一份操作说明、一个 Python 脚本、一个输出模板。模型拿到新文件后自己读 skill 说明按步骤执行一步到位。所以 skills 本质上不是替代 prompt 或 function calling而是把这两者能力的“可复用部分”沉淀下来。它最核心的价值不是“让模型做什么”而是“让模型知道你这里有一整套干这个活的方法并且随时可以调用”。这一点想通之后后面看结构、写配置、排故障就顺了。2. 一个 skill 的基本盘目录结构、SKILL.md 与加载逻辑先看目录一个 skill 就是一个文件夹官方推荐的命名是短横线小写比如log-archiver、weekly-report-builder。里面最关键的文件是SKILL.md这是模型的“说明书”也是唯一的入口。其余脚本和资源都放在子目录里按需加载。my-skill/ ├── SKILL.md ├── scripts/ │ ├── main.py │ └── config.json └── assets/ └── templates/ └── report.md结构看上去简单但这里有一个容易被忽略的设计逻辑模型默认只读SKILL.md不会主动去翻scripts/和assets/。你必须在SKILL.md里明确告诉它脚本在哪里、怎么运行、什么时候该读 assets 里的模板。换句话说SKILL.md是写给模型看的工作手册而不是写给人类看的说明文档。SKILL.md的第一部分是 YAML frontmatter用来声明元信息--- name: log-archiver description: 用于归档和压缩日志文件。当用户提到日志整理、磁盘空间清理、旧日志归档时使用。 ---name是唯一标识description是触发条件。description特别重要因为模型就是靠它来判断“什么时候该亮出这个技能”。写得太窄模型想不起来用写得太宽模型在任何不相关的对话里都会强行套用。SKILL.md的正文部分直接是 Markdown写给模型看的操作步骤。你可以写得直白甚至可以像给实习生交代任务一样分步骤、给命令、写注意事项。以官方 skills 仓库为例里面 docx、pdf、pptx、xlsx 这些技能都是这么组织的先说明目标再给出脚本调用方式最后说明对输出结果的预期。我自己的习惯是正文里控制在一屏以内能读完步骤不超过五条。如果流程特别长把它拆成多个 skill 或者让脚本内部自己处理复杂分支而不是让模型在 Markdown 里做大量条件判断。模型读说明是为了知道“什么时候运行什么命令、结果怎么检查”不是来模拟一个微型程序员的。3. 手写一个真实技能日志归档 skill 从需求拆解到可用配置理论说多了容易飘直接来一个能落地的例子。假设你的服务器上有一堆应用日志按天增长磁盘空间告急手工归档太费劲。这个需求适合做成 skill因为它的流程固定、可复用、而且不同环境下都能用。先拆解需求给定日志目录把超过 30 天的.log文件按月份归档到归档目录并压缩最后输出本次归档了哪些文件。就这三件事。第一步建目录和SKILL.md--- name: log-archiver description: 将指定目录下的日志文件按月份归档并压缩用于磁盘空间清理和日志轮转。当提到日志归档、清理旧日志、日志文件太多、磁盘空间不足时使用。 --- # 日志归档 目标把 source 参数指定目录下的 .log 文件按最后修改时间归档到 archive 参数指定目录压缩为 .gz 格式并删除原文件。 执行步骤 1. 运行以下命令 python3 scripts/archive_logs.py --source /var/log/app --archive /data/log-archive --keep-days 30 2. 脚本会输出每一条归档记录格式为 archived: 文件路径。 3. 最后一行输出 done, total N files。N 为 0 表示没有需要归档的文件。 4. 如果脚本报错直接向用户展示错误信息并根据错误提示排查路径是否可读、目录是否存在。第二步写脚本。我用 Python 标准库不引入任何第三方依赖这样在任何带 Python 3 的机器上都能直接跑#!/usr/bin/env python3 归档指定目录下超过保留期限的日志文件。 import argparse import gzip import shutil from datetime import datetime from pathlib import Path def archive_logs(source_dir: Path, archive_dir: Path, keep_days: int) - list[str]: cutoff datetime.now().timestamp() - keep_days * 86400 archive_dir.mkdir(parentsTrue, exist_okTrue) archived [] for log_file in sorted(source_dir.glob(*.log)): mtime log_file.stat().st_mtime if mtime cutoff: date_str datetime.fromtimestamp(mtime).strftime(%Y-%m) dest_dir archive_dir / date_str dest_dir.mkdir(parentsTrue, exist_okTrue) dest_path dest_dir / (log_file.stem f_{date_str} log_file.suffix .gz) with log_file.open(rb) as f_in, gzip.open(dest_path, wb) as f_out: shutil.copyfileobj(f_in, f_out) log_file.unlink() archived.append(str(dest_path)) return archived if __name__ __main__: parser argparse.ArgumentParser(description归档日志文件) parser.add_argument(--source, typePath, requiredTrue, help日志目录) parser.add_argument(--archive, typePath, requiredTrue, help归档目录) parser.add_argument(--keep-days, typeint, default30, help保留天数) args parser.parse_args() for item in archive_logs(args.source, args.archive, args.keep_days): print(farchived: {item}) print(fdone, total {len(archived)} files)这段脚本有一个值得注意的点删除原文件前先完成了压缩写入并确认没有异常。这不是随手写的而是考虑到如果压缩中途失败原文件又被删了数据就没了。归档类操作优先保证数据安全。第三步本地手动测试。先不用模型自己跑一遍脚本python3 scripts/archive_logs.py --source ./test-logs --archive ./test-archive --keep-days 30看到输出正常再把它交给模型用。这样后面排查问题的时候你就知道脚本本身没问题问题出在调用方式或环境上。4. 部署到项目里加载路径与让模型“想起来用”写好 skill 之后要放进模型能读到的地方。我常用的方式有两种项目级和用户级。项目级是把 skill 放到当前项目的.claude/skills/目录下your-project/ ├── .claude/ │ └── skills/ │ └── log-archiver/ │ ├── SKILL.md │ └── scripts/ │ └── archive_logs.py这样整个项目共享这个 skill团队克隆仓库之后自带技能适合跟业务强相关的技能。用户级是放到用户目录下比如~/.claude/skills/对所有项目生效。适合放一些通用的、跨项目的技能比如文档格式转换、代码仓库整理、文件批处理之类。两者可以共存同名时项目级优先。把 skill 放好之后怎么确认它能被调用最简单的办法是主动触发一次。比如直接跟模型说“帮我把 /var/log/app 下的旧日志归档一下”。如果它真的去执行了说明加载没问题。如果它回你一段“你可以运行以下命令”这种话说明它没识别出这个技能或者description没写好。我见过很多次这种问题最后都出在description上。模型匹配技能靠的是把用户当前请求跟description做语义对齐。你写“用于归档和压缩日志文件”用户说“磁盘快满了帮我清理一下”模型不一定能反应过来。更好的写法是把用户可能说的话也放进去“当用户提到日志归档、磁盘空间不足、旧日志清理、log archive 时使用”。这相当于给模型几个“钩子”让它更容易命中。如果模型调用了 skill但它执行时没有按SKILL.md里的步骤走比如自己改了脚本路径或者跳过了某一步那就要检查正文是不是有歧义。每一条步骤最好都给出明确的命令和预期的输出不要让模型自己“发挥”。模型在没有明确指令时倾向于脑补而脑补在生产环境里就是灾难。5. 实测中踩过的坑路径、依赖、权限与调试链路技能写多了踩坑也踩多了。这里把最有代表性的几个问题拎出来每个都带排查思路而不是直接甩答案。第一个坑是路径问题而且是最隐蔽的。我把SKILL.md里的命令写成了python3 scripts/archive_logs.py ...但模型执行的时候工作目录不一定在 skill 所在目录。它在项目根目录下跑就找不到scripts/。当时我排查了很久因为手动在 skill 目录里跑脚本一切正常但模型一调用就报错“No such file or directory”。解决办法有两个一是命令里写相对当前工作目录的路径比较脆弱二是脚本开头通过Path(__file__).parent定位自身位置再基于这个位置拼接路径。推荐第二种因为不管从哪个目录调用都不会出错。第二个坑是第三方依赖。我早期写过一个处理 Excel 的 skill脚本里import openpyxl结果模型环境里没装。报错那一刻我才意识到skill 脚本不能默认环境里什么都有。现在我的原则是优先用标准库实现如果必须用第三方库在SKILL.md里明确写安装命令并在脚本开头做 import 异常提示。你可以在技能说明里加一句“运行前先执行 pip install openpyxl”也可以让脚本在缺依赖时打印安装提示让模型看到之后自己装。第三个坑是权限和文件安全。模型运行脚本时通常没有 sudo 权限脚本里一旦有写入 system 目录、修改 root 权限文件的操作必然失败。更危险的是删除操作日志归档脚本里有log_file.unlink()如果参数校验不严模型随手传入一个根目录或误把非日志文件传进来后果不可控。我现在会在脚本里做两层防护限定只处理*.log后缀文件并且要求--source路径存在且是目录。删除操作之前先打印警告让模型在输出里明确“我准备删除以下文件”再执行。排查问题的基本链路是这样的先手动跑脚本确认脚本本身没问题再看SKILL.md里的命令路径是否可靠接着让模型执行时把完整输出打出来看它实际运行了什么命令最后根据错误信息逐层定位。我一般会让模型把“我准备怎么做、执行了哪些命令、输出是什么”完整展示出来。这三点看起来基础但很多问题其实就在其中一环。调试时还有一个技巧把SKILL.md里的步骤拆得足够细让模型每一步都能输出中间结果。比如归档脚本最好每归档一个文件就打印一行archived: xxx而不是最后只给一句“归档完成”。模型能根据中间输出判断哪里出了问题你自己排查的时候也有据可循。6. 更进一步多个 skill 组合成真实工作流单技能的威力有限真正好用的是把多个 skill 串起来完成一条完整的工作流。比如“每周自动生成运维周报”这个任务就可以拆成三个 skilllog-archiver负责归档日志并统计各应用日志量、metric-collector负责从监控接口拉取指标、docx-builder负责把前面两步的结果生成 Word 文档。三个技能各自独立但通过SKILL.md里的描述串成一个流程。关键是怎么让模型知道要按顺序调用。我的做法是在每个SKILL.md里都写一段“关联技能”例如在metric-collector的描述里写“当用户要求生成周报时先运行 log-archiver 获取日志统计再运行 metric-collector 获取指标最后使用 docx-builder 生成文档。”模型的执行路径就清晰了。技能拆分的粒度也很讲究。太细会导致模型频繁做“决策”每一步都要读一个 skill效率低太粗又会导致一个技能里堆了太多逻辑复用性差。我现在遵循的原则是一个 skill 只解决一个明确的问题但这个问题本身是完整闭环。日志归档就是“清理压缩输出统计”不包含发送通知发送通知单独拆出去。这样任何一个环节要替换或调整都不影响其他部分。再补充一点如果已经在用 MCP 或类似的外部工具连接机制不要慌skills 和它可以共存。MCP 适合连接外部系统拿数据skills 适合把数据处理逻辑沉淀下来。两者的边界很简单数据从哪来靠 MCP数据怎么处理靠 skills。配合起来之后Agent 才能从“会说话”进化到“会干活”。最后分享一点个人体会技能化改造不是把旧的 prompt 搬个家那么简单。我做过好几次“重构式”技能化就是把系统里一段长 prompt 直接塞进SKILL.md结果模型调用时效果并不好。后来想明白了prompt 是给人看的指令而SKILL.md是要执行的工作手册——它的表达方式要更像“操作 SOP”更短、更明确、每一条都能被验证。把这个转变做完之后技能的成功率才是真正稳定下来。