1. 先搞清楚Agent Skills到底要解决什么问题
过去大半年,我一直在折腾LLM应用开发,从简单的RAG问答、到带工具的function calling、再到多步推理的Agent,踩过不少坑。一个最直观的感受是:每次新开一个Agent项目,前几周都在重复造同一批轮子——让模型会查天气、会查数据库、会解析文档、会把结果整理成特定格式。这些能力本质上都是"技能",但我之前根本没有把它们当作可复用的工程资产来管理,结果就是同一段逻辑在A项目里写在prompt里、在B项目里写成tool、在C项目里干脆塞进了workflow,维护起来痛不欲生。
"agent-skills"这个词最近在社区里热度很高,它指的不是某一个具体框架,而是一种**把智能体的能力拆成独立、可复用、可组合的"技能单元"**的工程范式。简单说,技能就是一整套打包好的能力描述:模型该怎么理解用户意图、该调用哪些工具、该按什么顺序执行、输出必须满足什么结构。技能不是一句提示词,也不是一个孤立的函数,它介于两者之间,自带行为边界和验收标准。
这套思路解决的核心痛点是三个:
- 复用难:同样的能力换个项目就要重新写一遍,或者从旧项目里东拼西凑复制粘贴。
- 测试难:能力逻辑和业务提示词纠缠在一起,没法单独验证"这个技能本身好不好用"。
- 组合难:想让Agent先查日程再安排会议再写邮件,传统做法是写死编排逻辑,缺乏灵活性。
打个比方,如果Agent是一个人,那技能就是他的职业证书。一个厨师不会在每次做菜时重新发明切墩技术,他直接调用"刀工技能";遇到新菜式,他也不是从零学起,而是把已有的"翻炒技能""调味技能"组合起来。我们想让Agent具备的,就是这种能力沉淀、按需取用的状态。
这篇文章我不会讲某个特定平台怎么配置技能,而是分享我从零开始把Agent能力技能化的一套完整思路,包括技能和Tool、Prompt怎么区分,怎么设计一个能落地的技能包,怎么让Agent准确调用技能,以及实测中踩过的一堆坑和对应的评测方法。内容偏工程实践,适合已经在写Agent、并且开始觉得代码和提示词越来越乱的开发者。
2. 技能与Prompt、Tool、Workflow的分界:别再混为一谈
很多人一开始会有个疑惑:技能不就是把prompt写得好一点吗?或者干脆就是封装了一层工具调用?我在早期也是这样想的,直到真正动手拆分才发现这几样东西有天壤之别。
我列个表,方便对照看:
| 维度 | Prompt | Tool | Agent Skill | Workflow |
|---|---|---|---|---|
| 本质 | 文本指令 | 可执行函数 | 带行为边界的技能包 | 固定流程编排 |
| 是否自带状态 | 无 | 无 | 可以有私有状态和上下文 | 有显式状态 |
| 能否独立测试 | 很难 | 可以 | 可以,且必须有评测 | 可以 |
| 组合方式 | 拼接 | 函数调用 | 技能间可以互相调用 | 节点连线 |
| 失败处理 | 靠模型自觉 | 靠调用方兜底 | 技能内部自带错误处理策略 | 靠节点分支 |
| 复用粒度 | 段落级 | 函数级 | 能力级 | 任务级 |
Prompt是给模型看的说明书,它不保证结果正确,也不具备可测试性。你说"请用JSON输出",模型可能偶尔给你多个关键词,没法在工程层面约束它。
Tool是执行力,比如一个get_weather(city)函数。但它没有"何时该用、参数从哪里来、输出怎么整合"的决策逻辑。Tool只是技能的手脚,技能才是那个决定了"该不该动、怎么动"的大脑。
Workflow是固定流程,适合那些步骤完全确定的场景,比如"每天定时抓数据→清洗→入库"。一旦用户需求稍有变化,Workflow就僵住了。
而Agent Skill的核心特征是三个:自包含、可组合、可评测。
自包含的意思是,这个技能所需的全部信息——指令、参数模式、工具列表、输出规范、错误处理策略——都打包在一个单元里,不依赖外部prompt的配合。可组合的意思是,一个技能内部可以调用其他技能,比如"撰写周报技能"可以调用"获取本周工作记录技能"和"按模板排版技能"。可评测的意思是,技能可以被单独拿来跑测试集,量化它的调用准确率和输出合规率,而不是整个Agent一团黑盒。
我在实际项目里常用的判断标准很简单:如果一段能力逻辑没法单独写测试用例,那它就不够格叫技能,顶多算prompt片段。反过来,如果一段能力过于死板、参数和流程全固定,那它更适合做成Tool或Workflow,硬包成技能反而增加模型的理解负担。
总之,技能化真正的价值是让AI能力的开发从"写提示词"升级为"做工程":有定义、有边界、有测试、有版本。这也决定了后面所有设计方法的出发点。
3. 从一个实战技能说起:设计"网页内容结构化提取"技能的全过程
理论讲多了容易飘,我们直接来一个我实际做过的技能案例。我的需求是:让Agent能根据用户输入的一个URL,自动抓取网页正文,并转成指定格式的结构化数据,比如摘要、关键词列表、正文Markdown。这听起来不就是写个爬虫再调一下LLM嘛,但如果我要把它做成一个合格的"技能",事情没那么简单。
3.1 技能描述文件:长什么样
我先定义技能的基本信息,通常用YAML或JSON描述。实际项目中我用的是YAML,因为可读性好、注释方便。一个精简版本长这样:
name: web_content_extractor version: 1.2.0 description: >- 从指定URL提取网页正文并转换为结构化输出(摘要、关键词、正文Markdown)。 当用户提供网页链接并要求总结、提取信息、保存正文时使用。 不要用于需要登录或需要JavaScript重度渲染的动态页面。 input_schema: type: object required: - url properties: url: type: string description: 目标网页的完整URL,必须以http://或https://开头 language: type: string enum: [auto, zh, en] default: auto include_metadata: type: boolean default: true description: 是否返回页面标题、作者、发布日期等元信息 output_schema: type: object properties: title: { type: string } author: { type: [string, "null"] } publish_date: { type: [string, "null"] } summary: { type: string } keywords: type: array items: { type: string } content_markdown: { type: string } instructions: | [见下方3.2节]这里最关键的是description字段,不夸张地说,它决定了这个技能在Agent决策时会不会被正确选中。我最初的写法是"Extract web content from URL",结果Agent在用户问"这篇新闻讲了什么"的时候经常不调用它,因为描述里没有"新闻""总结"这类触发词。后来我改成上面这种带触发场景、带功能说明、带排除条件的写法,调用准确率立刻从60%出头涨到了85%以上。这个点后面第4节还会展开讲。
3.2 指令部分怎么组织才可执行
instructions是技能的大脑,我见过很多人把它写成一大段自由发挥的散文,结果模型执行起来五花八门。经过多轮测试,我认为最可靠的指令结构应该固定为四块:
- 目标:一句话说明这个技能要完成什么任务。
- 执行步骤:带编号的、顺序明确的操作流程,每一步必须可执行、无歧义。
- 输出规范:结果必须满足什么结构,字段缺失、字段为空时怎么处理。
- 异常处理:遇到抓取失败、编码错误、内容过短等情况的具体兜底策略。
我这边的实际指令片段示意如下:
目标: 抓取url指定的网页,提取正文并按要求输出JSON。 执行步骤: 1. 先用metadata提取器获取页面标题、作者和发布日期;若元信息缺失,跳过而不是报错。 2. 使用内置的正文提取器定位<article>或<p>标签区域;若正文区域字数少于200字,视为失败,转异常处理。 3. 将正文转换为Markdown格式,保留标题层级、粗体、列表结构,去掉导航、广告、版权尾巴。 4. 在正文基础上生成不超过80字的中文摘要和5-8个关键词。 5. 按output_schema组装JSON。 输出规范: - title为空时置为url本身。 - publish_date无法解析时输出null。 - keywords为空数组时不要省略该字段。 异常处理: - 请求超时或返回非200状态码:输出{"error":"fetch_failed","detail":"..."},不要编造内容。 - 正文提取失败:输出{"error":"no_content"}并附上页面标题。 - 如果页面语言与language参数不一致,按language参数处理,返回结果中增加实际语言字段。这套结构的价值在于,它把模型自由发挥的空间压缩到了可控范围,同时保留了必要的弹性。模型不需要自己琢磨"我该怎么提取正文",只需要跟着步骤执行;遇到意外情况也有明确的路可走,而不是靠猜。
3.3 技能被调用的完整链路
技能定义好了,真正被Agent调用的过程,在我项目里大致是这样的链路,用文字表述给你:
用户说"帮我看下这篇文章讲了啥,然后把摘要发我",Agent接收到这条消息后,先把意图解析成标准化的查询。然后Agent根据用户query和技能注册表里所有技能的description做匹配,选中最匹配的web_content_extractor。接下来,Agent从用户消息中抽出url参数填入input_schema,必要时会反问用户补齐缺失参数。参数填充完成后,技能内部指令开始执行:抓取网页、提取正文、生成摘要、组装JSON。最后,输出经过schema校验后返回给用户,Agent再根据用户原始意图决定要不要补充一句口头说明。
这条链路看起来简单,但每个环节都有隐藏问题。我在初期遇到的最大坑是:Agent经常不按照output_schema输出,而是自作主张加字段或改字段名。后来我查了原因,是因为我在instructions里没有明确强调"必须严格符合output_schema,不得增加或修改任何字段",补上这句话之后输出合规率提升了接近20个百分点。这类细节我在第5节会集中梳理。
4. 让Agent准确调用技能:注册、描述与上下文约束
技能写得再完整,如果Agent在决策时压根不调用它,或者拿一个八竿子打不着的技能来硬凑,那就全白费了。这一节讲我趟过最多水的部分——怎么让技能被"恰到好处"地选中。
4.1 技能注册表:别小看这个仓库
技能不是写个文件就算数,需要有一个可以被Agent查询的注册表。这个注册表需要记录的信息,远不止技能名称和描述。我用的注册表结构支持以下字段:
name:全局唯一,不能和已有技能冲突。aliases:别名,比如web_extractor、page_summarizer,提高匹配灵活性。trigger_keywords:触发关键词,如"总结网页""提取正文""网页摘要"。related_skills:相关技能,用于组合调用时的候选排序。conflict_group:冲突组,当多个技能可能匹配同一场景时,用于消解。cooldown:冷却时间,某些高频低价值技能可以限制调用频率。permission_level:权限级别,比如涉及写入数据库的技能必须高权限。
很多人做Agent技能化时只维护一个name+description的列表,这在技能超过5个之后就会开始乱套。技能一多,description之间的边界模糊、触发词互相覆盖的问题就会爆发,到时候再回头补注册表结构就晚了。我建议你在设计技能的第一天就把注册表字段规范好,宁可多几个暂时用不上的字段,也不要事后重构。
4.2 description写作的黄金比例
description是Agent做技能选择时最主要的信息来源,它会和用户query一起被丢进模型进行意图匹配。写这个字段时,我的经验是分成三块:
- 功能定位:这个技能做什么,一句话,动词开头。
- 触发场景:什么情况下用户大概率需要它,列举3到4个典型句式或关键词。
- 排除条件:什么情况下绝对不要使用它。
拿前文那个网页提取技能来举例,排除条件就是"不要用于需要登录的页面、不要用于JS重度渲染的页面"。这很重要,因为如果用户只是甩了个链接说"看看这个页面",Agent可能不知道这个页面其实是个需要登录的后台,盲目调用技能只会浪费一堆token然后失败。
我还有一个习惯:每次换模型版本或更新技能后,都要用一批真实用户query回放一遍,挨个检查技能选择是否正确。这不是可做可不做的优化,而是必须做的回归测试。我曾经升级LLM之后,Agent突然从一个技能切到另一个更宽泛的技能,原因只是新模型的偏好发生了变化,而我的description没跟着适配。
4.3 上下文约束:技能不该是万金油
技能描述文件里,我专门设计了when_not_to_use区块,用自然语言约束技能的适用范围。很多初学者的误区是想让一个技能覆盖尽可能多的场景,觉得这样"通用",实际上恰恰相反。
社区里有个经典案例:一个开发者做了一堆技能,其中有个"database_query"技能,description里写着"查询各种数据",结果Agent在用户问"你能帮我查下天气吗"时也调用了这个技能,参数schema当然完全对不上,最后整个会话崩掉。这就是没写好排除条件的典型例子。
我给每个技能都要求必须写清三条负向约束:
- 哪些意图看似相关但实际上不该用本技能(比如查天气不该调用数据库技能)。
- 哪些输入条件不满足时拒绝调用(比如缺少必填参数且无法推断)。
- 哪些边界情况需要转交其他技能(比如网页提取遇到登录页,应转交"登录页处理技能"或直接告知用户)。
负向约束的价值在于它能把Agent从"盲目自信"拉回到"审慎决策"。LLM天然倾向于选择一个技能去执行,即使拿不准。你如果不给它明确的拒绝出口,它就会硬着头皮瞎调。
4.4 技能之间的组合与冲突消解
当Agent能力变多、技能超过十几个之后,会出现两个新问题:组合时机和冲突消解。
组合时机是指:一次用户请求可能需要多个技能协作。我现在的做法是允许技能在instructions里声明"本技能在步骤3会调用技能X"。从工程上,这相当于技能内部有一个子调用接口。但要注意,技能的嵌套层级不宜超过两层,否则Agent的上下文会变得混乱,token消耗也急剧上升。
冲突消解是指:两个技能都能解释用户需求时怎么选。举我实际遇到过的例子:我有一个"weekly_report_generator"和一个"daily_summary_generator",两者description里都包含"总结工作"这一触发词,于是Agent经常选错。我的解决方法是给这两个技能设置一个conflict_group,在注册表里额外加一条优先级规则:如果用户提到"本周""周报""这周"等时间词,优先选weekly;如果只提到"今天""总结一下"且没有明确周期,默认选daily。类似规则写起来不复杂,但很有效。
5. 技能落地的踩坑清单与评测方法
最后这部分,我把实际运行一个包含十几个技能的Agent大半年以来踩过的坑做一个集中汇总。每个坑我都按"症状 → 根因 → 解法"的结构来说,方便你直接对照排查。
5.1 坑一:技能描述太泛,被当成万能技能
- 症状:某技能经常在完全不相关的场景下被调用,调用率奇高但成功率奇低。
- 根因:description里用了"各种""各类""处理数据"这种大词,模型把它的适用范围理解得过于宽泛。
- 解法:重写description,用触发句式+排除条件双重限制。同时缩短功能定位的描述,让"这个技能不是干什么的"和"是干什么的"同样清晰。
这条我之前提过,但忍不住再说一次,因为它是所有技能化项目里出现频率最高的坑,没有之一。
5.2 坑二:输入输出schema得不到严格遵守
- 症状:技能执行成功,但返回的JSON字段和定义对不上,要么缺字段,要么多出意外字段,要么参数类型不对。
- 根因:只在前端让模型"看着schema输出",但指令里没有明确"不得修改schema"的强制性要求。对LLM来说,可选的限制就等于没限制。
- 解法:在指令的输出规范中逐字写明"必须严格符合output_schema,不得新增字段,不得修改字段类型,不得省略字段(除非明确允许null)"。必要时在代码层增加一次schema校验,不通过就重试一次,这比靠模型自觉可靠得多。
5.3 坑三:技能状态在多轮对话中被污染
- 症状:一个技能在前一轮执行时产生的中间结果,下一轮被另一个技能错误引用,导致输出错乱。
- 根因:技能没有良好的状态隔离。我早期把所有技能的中间数据都存在同一个全局上下文里,不同技能读到了彼此的数据。
- 解法:每个技能实例维护独立的局部上下文,任务结束后清空或归档;跨技能传递数据必须通过显式的参数传递,而不是共享状态。这跟写后端服务时的"请求上下文隔离"是同一个思路。
提示:判断技能状态是否隔离得好,有个简单的测试办法——连续运行同一个技能三次,分别输入不同参数,观察结果是否互相串扰。如果第二次运行的结果里出现了第一次运行的输入残留,那隔离就是有问题的。
5.4 坑四:评测只看成功率,忽略误用率
很多团队评测Agent时只统计"任务完成率",比如10次请求里8次成功,就认为系统80分。但真正要命的是误用率:在这10次请求里,有多少次调用了错误的技能、使用了错误的参数、或者在不该调用时盲目调用。误用的代价往往比失败更大——失败至少是明着出错,误用是悄悄把事办砸。
我用的评测方案分为三个层次:
- 技能级评测:对每个技能单独构造测试集,每条测试数据包含用户query、期望调用的技能名、期望参数、期望输出规范。跑一轮Agent,统计调用准确率、参数正确率、输出合规率。
- 场景级评测:把技能放进完整的对话流程里模拟多轮,看技能是否在正确的时机被调用、切换是否自然、上下文是否串扰。
- 回归评测:每次更新技能描述、升级模型版本之后,全量跑前面的测试集,对比指标波动。这个环节最容易被偷懒省略,但它恰恰是技能系统能持续演进的保障。
我目前维护了一个大约300条query的技能评测集,每次改动技能文件之后,花半小时跑一轮,能避免绝大多数线上翻车。最开始我也觉得这事很烦,但有一次我在改weekly_report技能的description时,因为措辞变化导致daily_summary技能调用率下降了12%,测试集一跑立刻就发现了,那次之后我就再也没跳过回归测试。
5.5 实操心得:先做深,再做广
最后分享三条我在实际项目中的经验总结,供你参考。
第一,技能化改造的起点不要贪多。先把1到2个最高频、最痛苦的能力打磨成技能,跑通设计、调用、测试的闭环,再逐步扩展。我见过有团队一口气定义了30多个技能,结果一半处于"有定义但永远不被调用"的僵尸状态,还拖慢了每次Agent决策的速度。
第二,给技能写测试用例要优先于写实现。这跟我之前做TDD(测试驱动开发)的习惯一脉相承。先想清楚"这个技能应该在什么场景被调用、输出必须长什么样",再动手写指令和逻辑,能避免大部分返工。技能的本质是工程资产,不是prompt素材,工程资产就得有验收标准。
第三,版本管理必须跟上。技能文件的改动会直接改变Agent行为,所以我用Git管理技能仓库,每个技能文件头部都带版本号,重要变更在commit message里标明。一次线上事故之后,我能准确回溯到"就是这次版本更新改坏了技能选择行为",这个能力在技能数量增长之后会越来越值钱。
Agent Skills这条路,走下来最深刻的感受是:它改变的不仅是一个Agent的能力边界,更是整个开发团队协同的方式。当一个Agent系统的能力像乐高积木一样被拆分、维护、独立升级时,迭代速度会快得惊人。但这些红利的前置条件是基本功——描述写得清楚、边界划得明白、测试做得扎实。希望这篇文章能把你在技能化路上最常踩的坑垫平一点。