搞 Agent 开发的朋友,最近应该经常听到 agent-skills 这个词,各种框架和社区里都在聊。我自己的理解很简单:它就是把一个智能体能做的事,拆成一个个可以单独定义、单独维护、单独复用的“技能单元”,比如网页检索、代码执行、数据分析、文件整理,每个技能就像工具箱里的一把专用工具,Agent 接到任务时按需调用,而不是每次开工都从零攒一堆提示词。
这篇文章不聊概念包装,就讲我实际搭建 agent-skills 体系时踩过的坑、摸索出来的落地方法。从设计思路、技能包结构、路由匹配到评估和排查,完整过一遍。适合正在做 Agent 应用、想给智能体做能力扩展的工程师参考,也适合刚接触这个方向、想搞明白它到底解决什么问题的朋友。我会尽量把每个决策背后的理由讲清楚,让看完的人能直接照着搭一套。
1. 先搞清楚 Agent Skills 到底在解决什么问题
1.1 从“会聊天”到“会干活”的能力跃迁
先往回看一步。早期的 LLM 应用基本就是聊天机器人,模型只负责生成文本,能力边界在对话窗口里。后来出现了函数调用(function calling),模型可以决定“我需要执行一个外部操作”,于是系统里有了工具的概念。再到 Agent 循环,模型不再只是调一次工具,而是可以多轮规划、执行、观察结果、调整计划,直到完成任务。
这一步跃迁带来的变化,很多人低估了:系统的复杂度从“提示词管理”转移到了“能力管理”。
以前你维护的是一个 prompt 文件,现在你要维护的是几十个工具、几十段工具描述、不同任务场景下的调用策略。提示词膨胀的问题还没解决,工具膨胀的问题就来了。我见过一个项目,工具列表加到四十多个,模型每次选择工具时,光是把工具描述读完就已经消耗大量上下文,而且经常选错。agent-skills 就是在这个背景下被反复提及的:它把“工具”从单纯的执行函数,升级成带有教学说明、使用示例、校验规则、输出规范的能力包,让模型在调用时不仅知道“有这个工具”,还知道“这个工具应该在什么场景用、怎么用、输出长什么样”。
1.2 Skill、Tool、Workflow 三者的边界
很多刚开始接触的人会把 skill、tool、workflow 混为一谈,这三者的边界如果不清,后面设计一定会乱。
- **Tool(工具)**是系统提供给模型的最小可执行单元,通常是一个函数、一个 API、一段命令。它只负责“执行动作”,不负责“教模型怎么用”。比如
search_web(query)就是一个工具,输入关键词,返回搜索结果。 - **Skill(技能)**是一个能力包,包含一个或多个工具,同时携带自然语言的使用说明、典型示例、边界约束、输出格式要求。可以理解为“教模型正确使用工具的说明书 + 工具本体”。同一个
search_web工具,放在“学术文献调研技能”里和放在“电商竞品分析技能”里,用法完全不同,skill 就是解决这个“用法”问题的。 - **Workflow(工作流)**是固定化的流程编排,定义的是步骤之间的前后顺序和依赖关系,适合那些“每次都要按照同样流程走”的场景。
用一个类比来区分:Tool 是给厨师准备的炉子和锅,Skill 是菜谱(告诉厨师这道菜该用什么火、先放什么、做到什么程度算好了),Workflow 是餐厅的后厨动线(冷菜间、热菜间、出菜口的顺序不能乱)。
这三者可以组合使用:一个 skill 内部可能调用多个 tool,一个 workflow 可能编排多个 skill。我在实际设计时有一条原则:凡是“模型需要学习才能用好”的能力,就放进 skill 里;凡是“固定不变、不需要模型决策”的步骤,就放进 workflow 里。
1.3 我总结的 agent-skills 真正解决的问题
做了几轮之后,我发现 agent-skills 解决的痛点非常具体,不是概念炒作:
痛点一:提示词膨胀。过去我们为了让模型正确使用工具,会在 system prompt 里写大段说明,工具一多,说明越来越长,模型注意力被稀释。skills 把说明拆分到每个技能包里,模型只在实际选中某个技能时才读取对应的教学文档,system prompt 可以保持精简。
痛点二:能力难以测试。工具是扁平的,测试时只能测“输入参数对不对、返回值合不合法”。skill 是带上下文的,你可以针对一个完整场景做测试,比如“用户让你调研某个行业,模型是否选择了正确的调研技能、是否按技能要求执行了检索、摘要和分析三步”。这种测试粒度,直接决定了系统能不能持续迭代。
痛点三:上下文失控。工具数量多时,工具描述本身就会吃掉上下文。skill 采用按需加载,模型先根据简短描述决定用哪个技能,再注入完整的技能说明,上下文消耗大大降低。
2. agent-skills 设计的核心思路
2.1 技能包采用“教学文档 + 可执行体”的双轨结构
我见过不少实现方案,最后稳定下来用的是双轨结构:每个技能由两部分组成,一部分是给人(和给模型)读的说明文档,一部分是真正可执行的代码或脚本。
说明文档我通常叫SKILL.md,它承担“教学”职责。内容包括技能的目标、适用场景、不适用场景、使用步骤、每个步骤的关键细节、输出格式要求、常见错误规避。这份文档不是摆设,它会被注入到模型上下文中,所以必须写得极其结构化。模型自己也是靠这份文档学会“什么时候用、怎么用”这个技能的。
可执行体则是具体的代码文件,比如search.py、summarize.py。它承载真正的逻辑,负责调 API、处理数据、返回结构化结果。
为什么要这么设计?因为模型本身不是一个稳定的程序执行器,它擅长的是“理解意图、制定计划、解析结果”,而不是“稳定地跑完一段复杂逻辑”。把不稳定的人类语言教学和稳定的程序逻辑分开,各取所长。文档负责让模型做对决策,代码负责让执行不出错。这个分离是 agent-skills 最核心的思想,后面所有的设计都是围绕它展开的。
2.2 技能路由:Agent 怎么知道该用哪个技能
技能多了之后,最现实的问题就是:模型怎么在几十个技能里挑出正确的那一个?
答案藏在技能包的元信息里。每个技能都需要一个name(技能名)和description(技能描述),这两个字段就是模型做路由选择的检索依据。模型其实是在做一次“根据用户意图匹配技能描述”的语义匹配。所以描述写得干不干净,直接决定命中率。
我自己写技能描述时有三条硬性要求:
- 开头写清楚“这个技能做什么”,一句话,不超过 15 个字。
- 写明白触发条件:用户提出什么类型的问题、处于什么场景时应该用我。
- 写明白非触发条件(反例):什么情况下不要用我。这个很多人会忽略,但反例对消除误选非常有效。
举例,一个“周报生成技能”的描述可以这样写:
name: weekly_report description: 根据用户提供的本周工作记录生成周报。 当用户提到"周报""本周总结""本周工作汇报"时使用。 若用户只是询问上周内容,或尚未提供任何工作记录,不应使用本技能。注意最后一句“不应使用”,这就在模型脑子里划了一道边界。实测下来,加了反例之后,技能误选率能下降不少。
2.3 一个最小可用技能包长什么样
我习惯用一个轻量的 YAML 作为技能包的元信息文件,再配一个 Markdown 教学文档和一个 Python 实现脚本。骨架大概是这样的:
skills/ └── weekly_report/ ├── SKILL.md # 教学文档 ├── generate.py # 可执行脚本 └── skill.yaml # 元信息:name、description、dependencies其中skill.yaml只负责描述“这个技能是什么”,不写长篇大论:
name: weekly_report description: > 根据用户提供的工作记录生成结构化周报。 触发条件:用户提到"周报""本周总结""工作汇报"。 不触发:用户没有提供工作记录,或询问往年数据。 dependencies: - python3 - jinja2SKILL.md才是给模型看的核心教学文档:
# 周报生成技能 ## 目标 将用户散乱的工作记录整理为结构清晰的周报,包含本周完成、未完成、风险、下周计划四个部分。 ## 使用步骤 1. 解析用户提供的工作记录,识别每一条的完成状态。 2. 将记录归类到"本周完成"或"未完成",提炼关键成果和数字。 3. 对照项目风险清单,识别潜在风险。 4. 生成 markdown 格式周报。 ## 输出要求 - 使用 markdown 二级标题分节。 - 每个"本周完成"条目必须包含:做了什么 + 结果量化(如有)+ 对应项目。 - 不要虚构用户未提供的细节。 ## 常见错误 - 不要把"进行中"的工作写成"已完成"。 - 不要在周报中加入个人评价和情绪表达。这种结构的好处是:模型在需要时读一次文档就能学会,而代码实现可以专注在“字符串格式化、模板渲染”这类稳定逻辑上。
3. 实操:从零搭建一套可用的 agent-skills
3.1 选型:什么时候自研,什么时候用现成框架
动手之前先回答选型问题。现在社区里已经有几类现成的实现,比如 LangChain 的 Tool 体系、OpenAI 的 function calling、部分框架内置的 Agent Skill 机制。我的建议是分场景看:
| 方案 | 适合场景 | 局限 |
|---|---|---|
| 现成框架的 Tool 体系 | 快速验证、依赖框架已有生态 | 往往只有工具层,缺少教学文档和技能生命周期管理 |
| 直接使用大模型平台提供的 Agent 能力 | 不想维护底层链路,接受平台绑定 | 技能格式和调度策略受平台限制 |
| 自研轻量 skill 层 | 需要精细控制上下文注入、技能路由、评估体系 | 初期工作量较大,需要自己设计文档结构和调度逻辑 |
我自己最终选择了“自研轻量 skill 层 + 底层调用框架工具”的组合。原因很简单:我需要精细控制 SKILL.md 的注入时机和上下文长度,也希望技能包是纯文本、纯文件、可 Git 管理的,这样测试和 CI 都好做。自研的部分其实不重,核心就是一个技能注册表和一段调度逻辑,大概几百行代码的事,但灵活性提升非常大。
如果你只是做原型验证,我强烈建议先用现成框架跑通端到端,再决定要不要抽出自己的 skill 层。先看到效果,再谈优化,这个顺序别反。
3.2 手把手实现第一个技能:合同核心条款提取
我拿一个实际会高频用到的场景来演示完整流程:从合同 PDF 里提取核心条款。这个技能在企业内部场景非常常见,而且它足够复杂,能展示出 skill 设计的完整思路。
第一步:明确技能边界。这个技能只负责“从用户提供的合同文件中提取指定条款并输出结构化结果”,它不做合同审核、不做风险评分,也不做多文件对比。边界越清晰,SKILL.md 越好写,模型越不容易在调用时跑偏。
第二步:写 SKILL.md 教学文档。核心要写清楚提取的条款清单、每一条的判定标准、输出格式。比如“付款条款”要提取哪些字段、遇到“预付款 30%,货到付 70%”这种表达如何处理。
第三步:写可执行脚本。我会用 PDF 解析库提取文本,然后调用大模型做结构化抽取。注意这里有一个重要设计:不要把“解析 PDF”和“条款抽取”耦合在同一个脚本里,拆成两个脚本或者两个函数,这样单独测试解析、单独测试抽取逻辑,都好排查问题。
第四步:注册技能并配置路由。在技能注册表里登记 name 和 description,确保模型能通过简短描述命中它。
第五步:端到端测试。给模型一个模糊的用户请求,比如“帮我看看这份合同付款怎么安排的”,看它是否能自动选中该技能、是否按 SKILL.md 的步骤执行、输出是否合规。
我实际写技能的时候,会把脚本设计成从标准输入读入文件路径,从标准输出吐出 JSON,这样既方便手动测试,也方便被上层执行框架调用。下面是一个脚本骨架示例:
#!/usr/bin/env python3 """合同条款提取技能的执行体""" import json import sys from pathlib import Path def extract_text_from_pdf(path: Path) -> str: # 这里调用 PDF 解析库提取全文 # 注意处理扫描件:需要 OCR,否则结果为空 pass def extract_clauses(text: str, clause_names: list[str]) -> dict: # 调用 LLM 做结构化抽取 # 把 clause_names 和 text 一起交给模型,返回 JSON pass def main(): pdf_path = Path(sys.argv[1]) raw_text = extract_text_from_pdf(pdf_path) clauses = extract_clauses(raw_text, ["payment", "liability", "termination"]) print(json.dumps(clauses, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()这里有个容易踩的坑:PDF 解析出来的文本经常是带着换行符和表格碎片化的,直接扔给模型抽取时,模型可能把跨页的表格内容搞混。我后来在脚本里加了一个文本清洗函数,把行内换行替换成空格,再按段落重新组织,抽取准确率明显上去了。
3.3 技能的完整生命周期管理
技能不是写完就结束了。随着业务变化,技能需要更新、下架、灰度。我实践下来,技能生命周期至少要关注下面几件事:
版本管理。技能包整个目录纳入 Git,每次改动都留 commit。我给每个技能包维护一个version字段,修改教学文档或实现脚本时都要提升版本。为什么连文档都要管版本?因为模型行为直接受 SKILL.md 内容影响,文档一旦改了,技能的表现就可能变化。如果你的系统有线上会话在跑,改文档之前一定要想清楚。
依赖隔离。每个技能尽量使用独立的运行环境或独立的依赖声明。我见过最难受的场景:两个技能都需要用 Python 的 HTTP 库,一个要求 requests 2.x,一个要求 3.x,装完互相打架。我的处理办法是,技能包声明自己的依赖,执行时按技能包维度做环境隔离,依赖冲突问题就彻底没有了。
灰度发布。对于会直接影响业务的技能,我不会直接全量更新。先把新版本技能跑在一部分测试请求上,和旧版本的结果做对比,确认没有回归再放量。
下架和废弃。技能废弃时不要直接删目录,我习惯在描述里加一行“该技能已废弃,请勿使用”,同时把路由权重降为 0。直接删除可能导致正在运行的 agent 因为引用了不存在的技能而报错。
4. 常见问题与排查技巧实录
4.1 一份能救命的排查速查表
我把实操中遇到的问题和排查思路整理成一张表,遇到类似现象可以直接对着查:
| 现象 | 可能原因 | 排查方法与解决方案 |
|---|---|---|
| 模型死活不选某个技能 | 描述写得模糊,或与用户意图不对齐 | 检查 skill 的 description,简化触发条件,补上反例 |
| 模型选错技能(选了 A 但应该用 B) | 两个技能描述太相似,路由区分度不够 | 重写描述,制造差异:A 强调场景 X,B 强调场景 Y |
| 技能执行成功但结果不对 | SKILL.md 的教学步骤和实现脚本逻辑不一致 | 对比文档与脚本,确认文档描述的每一步都对应脚本的真实行为 |
| 调用技能后上下文迅速膨胀 | 技能输出太长,或 SKILL.md 被重复注入 | 限制技能输出长度,要求结构化摘要;检查是否因技能内部步骤循环导致多次读取文档 |
| 同一技能在不同会话表现差异大 | SKILL.md 中示例太少,模型没有足够参照 | 增加典型示例和反例,并在示例中标出易错点 |
| 技能更新后效果反而变差 | 新版文档破坏了原有指令结构 | 回滚版本,用 diff 分析文档改动点,小步迭代而非大改 |
这张表是真实反复被问到的场景,我自己几乎每条都踩过一遍,尤其是“描述相似导致误选”,几乎每个技能库膨胀到一定程度都会遇到。
4.2 “说会了但做不对”才是最大的坑
很多人在技能上遇到的不是“模型不会选”,而是“模型选了也做了,但结果根本不对”。我称这个问题为“说会了但做不对”,它的根因多半在 SKILL.md 的教学质量上。
SKILL.md 写得太抽象,模型就只会“照着感觉”执行;写得太笼统,没有具体的判定标准和输出样例,模型就会自由发挥。我自己的经验是,教学文档里要提供至少一个“完整示例”,包含输入、过程、输出三部分。而且要明确给出“什么算对”的判定标准,比如“当合同中金额同时出现小写数字和大写中文数字时,以大写中文数字为准,并在结果中保留两者原始值”。这种具体规则,模型是可以稳定遵循的。
还有一点容易被忽略:SKILL.md 的措辞会影响模型的执行严谨度。如果你写“请尽力提取所有条款”,模型就会表现得比较松散;如果你写“提取合同中出现的全部付款相关句子,按条款出现顺序输出”,模型就会更严格地检索。指令里每个限定词都有分量。
4.3 上下文失控:技能输出把对话窗口撑爆
技能返回的东西如果又长又杂,一轮下来整个对话上下文就爆了。这个坑是我在并行处理多合同对比时遇到的:三个合同分别调用技能,每个技能输出几千字的完整条款原文,第二轮回合还没开始,上下文已经要用完了。
后来我做了两个调整,问题基本解决:
第一,技能输出改为两级结构。第一级是极简摘要,只有关键字段和结论,用于 agent 的后续推理;第二级才是详细内容,默认情况下保留在文件或外部存储中,只有用户明确要求时才被拿出来展示。二级结构参数化设计如下:
{ "summary": { "payment_terms": "预付30%,货到后30天内付70%", "liability_cap": "不超过合同总额的20%" }, "details": "完整条款文本(相当长,默认不放入上下文)" }第二,技能支持“只返回摘要”的调用模式。在 SKILL.md 里写明调用参数detail_level=summary时只输出摘要,detail_level=full时才输出全文。上层 agent 默认用 summary 模式,只有用户追问细节时再重新调用技能取详情。这套设计让我的长链路任务稳定了很多。
4.4 安全与副作用控制:技能权限不可忽视
技能是有副作用的。一个搜索技能会消耗外部 API 额度,一个文件操作技能可能直接改动服务器上的文件,一个数据库技能可能执行带风险的查询。我在技能层做了两件事来控制风险:
一是技能描述中明确声明副作用。比如“本技能会调用第三方搜索服务,消耗 API 额度”“本技能会修改指定目录下的文件,执行前请与用户确认”。模型在规划时会读到这些说明,遇到高风险的技能组合时会更谨慎,必要时会停下来询问用户。
二是执行层的权限控制。我在技能执行接口里加了权限校验,按技能类型分类为“只读”“可控写入”“高危险操作”,高危险的技能默认要求二次确认。这个确认动作是由上层 agent 完成的,比如在调用 DELETE 类型操作前,agent 要先输出一段确认文案给用户,用户批准后才真正执行。这层保护在真实业务里非常必要。
5. 技能评估与持续迭代的实操建议
5.1 给每个技能建一套专属测试集
技能能不能用、改了之后有没有退化,不能靠感觉判断,要有测试集。我的做法是为每个技能准备一个“黄金样本集”,包含以下几类:
- 典型样本:最常见的用户请求和对应的理想输出。
- 边界样本:比如输入数据缺失、格式异常、内容超长的情况。
- 混淆样本:容易被误路由到其他技能的相似请求,用来检验路由区分度。
- 反例样本:明确不该触发该技能的用户请求,检验是否会误触发。
每次技能更新后,我会把这批样本跑一遍,对比输出是否符合预期。这个测试集不但能保住技能质量,还能作为新同学接手时的参考材料。
5.2 用“失败复盘”驱动迭代
我养成了一个习惯:让每个技能在返回结果时附带一个trace字段,记录自己是如何决策的,包括模型读取的 SKILL.md 片段、走的步骤、在哪一步结果不符合预期。当技能表现差时,第一件事不是改代码,而是看 trace 找问题出在文档还是实现。
有一段我印象很深:一个数据分析技能老是漏掉部分关键指标,看 trace 发现模型在读取 SKILL.md 时,把“指标清单”和“输出格式”两节混在一起理解了,导致步骤顺序错乱。后来我把 SKILL.md 的章节顺序调整,并在指标清单前加了一行“这是输入指标,不是输出格式”,问题立刻消失。
5.3 控制技能库规模,避免“能力过载”
技能不是越多越好。我见过一个团队把技能做到八十多个,结果模型几乎每次路由都要纠结,而且技能描述之间的互相干扰越来越严重。技能库规模的控制,和代码库一样:优先考虑复用和抽象,而不是堆数量。
我给自己定了几个量化标准:同一领域内,技能之间描述相似度不能过高;如果两个技能的触发场景高度重叠,就该合并成一个技能并用参数区分;技能总数超过二十个时,开始做分组和路由分层。分组很重要,我可以先按领域分大类,模型先在大类里选,再在类内选技能,路由准确率会显著提升。
6. 最后分享一点我的个人体会
做 agent-skills 这段时间,我最大的感受是:Agent 工程的复杂度,正在从“模型能力”转移到“能力组织和能力治理”。你在 skill 上花的心思,其实是在给模型搭建一套越来越完善的“职业培训体系”——技能包是教案,路由是分诊台,测试集是考核标准。这套体系搭得越扎实,后面加再多的能力都不会乱。
如果你正准备开始,我的建议是别一上来就铺十几个技能。先挑两三个业务价值最高、使用频率最高的场景,把 SKILL.md 和测试集打磨透,再逐步扩展。技能包的文件结构从一开始就按“教学文档 + 可执行体 + 元信息 + 测试集”的方式组织,后面维护成本会低很多。再提醒一句:SKILL.md 的每一次改动,都要像改生产代码一样对待,因为它直接决定了模型的行为边界。