最近大模型圈子里冒出一个高频词:agent-skills。我上周刚好在公司内部把一个半残废的内部工具从“一堆零散prompt”重构成了“一套技能库”,效果立竿见影——同样一个任务,以前Agent经常跑一半犯迷糊,现在稳定得多,而且新同事接手也不用再读那十几页没人维护的操作文档。
如果你正在做AI应用开发,或者你手头已经接了Function Calling、MCP,却总觉得这些“工具”没被Agent用好,那这篇东西就是写给你的。我尽量不讲抽象概念,直接把agent-skills是什么、怎么搭、怎么调、怎么避免翻车讲透,所有代码和目录结构都是可以直接抄走用的。
1. 从“会聊天”到“能干活”:Agent Skills到底解决了什么问题
1.1 大模型不缺工具,缺的是使用说明书
先说我踩过的那个坑。之前我在项目里给Agent接了一个内部查询接口,想着模型只要会调接口就行。结果用户问“帮我看下上个月华东区的退货率”,Agent第一轮调错参数,第二轮把时间范围理解错,第三轮直接开始编数据。表面上看是模型“笨”,实际上问题出在:我给了它一把扳手,但没告诉它这扳手是用来拧水管还是拧螺丝。
这就是agent-skills要解决的核心问题——它不只是一个工具,而是一份“会随着上下文被模型读取”的说明书。每个技能本质上是一个文件夹,里面放一段结构化文档(SKILL.md)和可选的辅助脚本。当用户的需求命中技能的描述时,模型会把这份说明书加载进来,照着里面的步骤、参数约定和边界条件去执行任务。
说白了,工具是“手”,技能是“大脑里那本操作手册”。之前的问题是手已经有了,大脑没有手册。
1.2 Skills、MCP、Function Calling三者根本不是一回事
很多朋友第一次接触agent-skills时都会懵:“这不就是MCP Server吗?或者不就是Function Calling?”还真不是。
我把这三者的关系用一个比喻讲清楚:
- Function Calling:是模型输出“结构化调用意图”的能力,比如它决定“我要调用get_weather(city=北京, date=今天)”。这是底层机制,相当于OS提供的系统调用。
- MCP:是一个“外设接口标准”。你按这个标准写一个服务,模型就能以统一格式调用外部工具、读取外部数据,相当于USB-C协议,谁都可以插。
- Agent Skills:是“外设驱动+使用教程”。技能里不仅包含怎么触发底层函数,更包含“什么场景下才该用”“参数该怎么填最稳妥”“一系列步骤应按什么顺序做”“做了之后结果怎么解读”,这些纯靠MCP和Function Calling是表达不出来的。
我实测下来最直观的感受是:只挂MCP工具,模型经常处于“有工具但不知道该不该用、怎么用”的状态;加了技能层之后,模型会先判断“这是不是技能管辖的活儿”,再决定要不要调度外部工具。这层“判断+流程编排”恰恰是技能的核心价值。
1.3 技能库让Agent有了“成长档案”
还有一个非常实际的好处:技能是可复用、可积累的。以前做一个Prompt调优项目,经验和教训都存在某个人的脑子里;现在每总结出一个稳定可靠的做事流程,就可以固化成一个Skill文件夹,放进团队共享的技能库。
我亲眼看着我们团队从“每个Agent临时拼Prompt”进化成“公共技能库+业务技能库”两层结构。上个月做的合同审核Skill,这月另一个项目做招投标文件审查时直接拉过去复用,稍微改了改边界描述就上线了。这种积累效应,比多写几个Prompt模板有价值得多。
2. 一张SKILL.md撑起一个技能:规范与加载机制拆解
2.1 文件夹即技能,SKILL.md即说明书
不同Agent框架对skills的实现细节略有差异,但主流方案的目录结构高度一致。下面这套是我目前在项目中直接使用的,兼容性和可读性都不错:
skills/ ├── daily-report/ │ ├── SKILL.md │ └── scripts/ │ └── collect_commits.py ├── contract-review/ │ ├── SKILL.md │ ├── rules/ │ │ └── clause_dict.txt │ └── scripts/ │ └── parse_pdf.py └──>--- name: daily-report description: 用于生成日报 ---结果是什么?用户问“帮我汇总一下这周的工作进展”,模型不调用它;用户问“把这几个commit整理一下”,模型也不调用它。原因很简单:描述太模糊,模型无法把它和用户的真实意图关联起来。
后来我改成这样:
--- name: daily-report description: 适用于用户要求生成当日或指定日期的日报、工作汇报、进展摘要时。技能会读取用户指定的Git仓库提交记录、仓库目录下的项目日志或用户粘贴的工作记录,按“今日完成/进行中/风险与阻塞/明日计划”四个模块输出结构化Markdown日报。不适用于生成周报、月报或对大量历史数据的统计分析。 ---改完之后召回率立刻上来了。总结成一句话:描述要回答三个问题——什么情况下用、它会做什么、它不做什么。“不做什么”尤其重要,能显著减少模型的误调用。比如我明确写“不适用于生成周报”,用户要周报时模型就会自动跳过这个技能,不会乱来。
3. 手把手做好一个日报技能:从描述到脚本的一次完整落地
3.1 目标拆解:先给技能划好能力边界
现在我用一个高频场景——自动生成日报——完整演示一遍从设计到落地的全过程。这个技能不挑领域,你只要把后端换成自己公司的数据源就行。
第一步不是写代码,而是想清楚这技能管什么、不管什么。我的设计思路是:
- 管:读取今日git提交记录、合并的PR、用户粘贴的零散工作记录,汇总成日报。
- 不管:分析历史趋势、做绩效评估、生成周报月报。这些交给别的技能或直接由模型处理。
这个边界会在SKILL.md里用很直白的话写清楚。很多人写技能喜欢大包大揽,最后模型什么都想干,什么都干不好。一个技能专注一件事,才是正确姿势。
3.2 SKILL.md正文:给模型可执行的操作步骤
接下来是SKILL.md的核心正文。我把这块当成“给刚入职的实习生写操作手册”来写,不看别的文档,光看这份说明就能把事情做对。
--- name: daily-report description: 适用于用户要求生成当日或指定日期的日报、工作汇报、进展摘要时。技能会读取用户指定的Git仓库提交记录、仓库目录下的项目日志或用户粘贴的工作记录,按“今日完成/进行中/风险与阻塞/明日计划”四个模块输出结构化Markdown日报。不适用于生成周报、月报或对大量历史数据的统计分析。 --- # 日报生成 ## 目标 生成一份简洁、结构化、不夸大事实的工作日报。 ## 数据来源优先级 1. 用户明确指定的仓库路径或粘贴的工作记录。 2. 当前工作目录及递归子目录中名称包含“log”“memo”“todo”的文件。 3. 如果以上两类数据都不存在,直接向用户说明缺少数据,禁止编造工作内容。 ## 执行步骤 1. 确定日期范围。用户没指定时,默认取今天(必须换算成项目所在时区)。 2. 若存在git仓库,运行 `git log --since="YYYY-MM-DD 00:00" --until="YYYY-MM-DD 23:59:59" --pretty=format:"%h %s"` 拉取提交记录。注意使用 `--date=iso` 确保时间准确。 3. 借助 `scripts/collect_commits.py` 拉取当日PR合并情况,若脚本因网络或权限失败,记录失败原因并继续处理其余数据源。 4. 汇总以上内容,按四模块整理。没有内容的模块写“无”,不要用“暂无”之类的模糊词。 5. 确保每条事项有具体信息——涉及哪个项目、做了什么、结果如何。拒绝“优化了部分功能”这类空话。注意正文里的几个设计。步骤1强调了时区,因为Agent运行在服务器上默认UTC,日报按UTC切分日期会直接错一天。步骤2给出了具体命令,不给模型发挥空间。步骤4和5约束了输出格式,明确告诉模型“无就写无”,省得它瞎编。
3.3 collect_commits.py:让模型有“手”可用
SKILL.md写清楚了,但模型光读说明还拉不了PR数据,这时候需要辅助脚本。我的习惯是:凡是模型体面完成不了的事(多步git操作、调第三方API、解析结构化数据),都写成脚本;凡是模型擅长的事(归纳总结、判断信息价值),都留在SKILL.md里让它自己干。
这个日报技能的辅助脚本写得相对简单:
#!/usr/bin/env python3 """收集指定日期范围内合并的PR。用法: python collect_commits.py --repo <路径> --since <date> --until <date>""" import argparse import subprocess import json def main(): parser = argparse.ArgumentParser() parser.add_argument("--repo", required=True) parser.add_argument("--since", required=True) parser.add_argument("--until", required=True) args = parser.parse_args() try: # 此处根据实际托管平台的CLI或REST API进行调用 result = subprocess.run( ["gh", "pr", "list", "--repo", args.repo, "--state", "merged", "--search", f"merged:{args.since}..{args.until}"], capture_output=True, text=True, check=True ) # 输出JSON,方便模型解析 print(json.dumps({"success": True, "prs": result.stdout})) except Exception as e: print(json.dumps({"success": False, "error": str(e)})) raise if __name__ == "__main__": main()脚本的约定我说明一下:stdout输出要能直接作为模型上下文的输入,不是给人看的log,而是给模型看的JSON。出错时也要输出结构化信息,这样模型能判断是重试还是放弃。
还有一个细节:脚本开头有完整的usage注释。因为Skill的执行流程是“模型读SKILL.md → 模型决定跑脚本 → 模型可能需要给脚本传参数”,脚本参数怎么传必须写清楚,否则模型会瞎猜参数名。
3.4 注册进技能库并跑通全链路
技能建好后,只需要把整个文件夹放进Agent配置的技能目录,框架会自动扫描注册。在我的项目里是改agent_config.yaml:
agent: name: daily-helper model: gpt-4o skills: - name: daily-report path: ./skills/daily-report重启框架后,技能索引里已经能看到daily-report。接下来就是测试。我先用一条最典型的请求验证全链路:
“帮我把今天的日报生成了,仓库在 /data/projects/billing-service”
让我来还原模型实际执行时的思考链路:
- 用户要求“生成日报”,命中了daily-report技能的description。加载SKILL.md全文。
- 按“确定日期范围”步骤,计算出今天的起止时间。
- 按“执行步骤2”,先跑git log看提交记录。
- 决定调用辅助脚本collect_commits.py,传入repo、since、until三个参数。
- 脚本返回了当日合并的PR列表和对应的标题、作者。
- 模型把所有数据汇总,按四个模块输出日报Markdown。
全程无需人工干预,一次跑通。第一版能跑通就算成功了一半,剩下的一半是后面的调优和避坑。
4. 技能选型与编排:该自己写Skill还是接MCP工具
4.1 三条选型原则,帮你少走弯路
很多人在“要不要把功能做成Skill”这个问题上纠结。我自己的判断标准就三条:
原则一:看处理对象是“数据加工”还是“工具调用”。MCP更适合纯粹的工具暴露——比如给模型一个查询天气、查数据库的通道,通道本身没有复杂的业务逻辑。Skill则适合带“流程编排+判断规则”的任务——比如合同审核要分五步走,每步有不同判断标准。一句话:MCP管“能调什么”,Skill管“该怎么调、按什么顺序调、调完之后怎么判断”。
原则二:看逻辑是否会跨多次调用、依赖中间状态。如果一个任务需要“先查A,A的结果决定要不要查B,B失败后要回退到A”,这种多步状态流转用纯工具配置很难表达,交给Skill写清楚最合适。
原则三:看是否有现成可复用的标准工具。如果你要做的功能,市场上已经有人写好了标准MCP Server,那就直接接,别重复造轮子。只有当你需要的是“一套独特的做事流程”而不是“一个通用能力”时,才值得写Skill。
4.2 技能组合与调用顺序:把复杂任务拆成技能流水线
建了五六个技能之后,你会遇到一个更复杂的问题:用户的任务可能需要多个技能配合。比如用户说“基于这几个合同数据,给我出个季度经营分析,并顺便生成日报”。
我目前的做法是维护一个“路由清单”,在Agent的主系统提示词里写清楚技能之间的组合关系:
# 技能组合约定 - daily-report: 用于每日例行汇报。当日数据已由其他技能生成时,优先复用已有中间结果。 - contract-review: 用于合同文件审查。输出为结构化条款摘要。 -># eval_set.yaml test_cases: - input: "帮我把今天的日报生成一下,仓库在 /data/projects/billing-service" expect_skill: daily-report expect_output_contains: ["今日完成", "明日计划"] - input: "这两个星期的工作总结一下" expect_skill: weekly-report expect_not_skill: daily-report - input: "把这份合同的关键条款提取出来,重点是金额、违约金和终止条款" expect_skill: contract-review expect_output_contains: ["违约金", "合同金额"]跑回归时,我会用脚本一次性把评估集里的请求发给Agent,然后检查两个层面:意图对不对(该调的技能是否调用了、不该调的是否没调)、输出好不好(关键内容是否覆盖)。这套方法帮我抓出过好几次“改坏”的情况,已经成了团队Agent质量保障的标准动作。
5.3 一次典型的描述迭代:从召回失败到稳定命中
拿我最开始提到的日报技能举个实例。第一版上线后回归集里有个场景挂了:“今天没啥进展,但明天有个上线”,这是用户真实会说的话,模型的意图判断却是“不应该调日报技能”——它认为用户说“没进展”就不需要汇报了。
这个问题不是技术能解决的,得靠语义理解。我在description里补了一句:“即使当日进展为空或用户表示没有进展,也应当调用本技能并生成'无进展'日报,以便保留工作记录。”这就等于把边界条件的说明写进了技能元数据。改完后,该场景立刻通过回归测试。
这类“语义边界盲区”不测不知道,一测全暴露。所以我强烈建议:把你在日常交流中见过的所有用户真实说法都沉淀进技能评估集,这是技能越用越准的根本保证。
最后分享一个自己换来的教训
技能不是越“厚”越好。我早期有个技能SKILL.md写了四千多字,流程图、背景知识、参考案例全塞进去,结果模型执行时反而决策迟缓,还经常抓不住重点。后来我把正文狠狠压缩到以“可直接执行的指令”为主,原则、背景、判断逻辑能省则省,只围绕“干什么、按什么顺序、遇到什么情况怎么办”来写,执行成功率反而提升了。
现在我的团队里有一条不成文规则:SKILL.md正文超过800字,必须做减法。技能是给模型看的“行动指南”,不是给人类看的“产品文档”。祝各位都能早日攒出自己的技能库,让Agent真正从“嘴替”变成“手替”。