开头先聊个现象:最近 Agent 类项目满天飞,但绝大多数团队卡在同一个坎上——模型能力明明够,任务就是交付不了。你问它"能不能做",它什么都能做;你真让它做,它要么中途跑偏,要么把简单事搞复杂。问题的根源往往不在模型,而在技能体系。agent-skills这个话题,说白了就是研究怎么把"模型会"变成"Agent 能交付",今天这篇文章,我把这套东西从设计思路到落地实现完整拆一遍,分享给正在做 Agent 应用开发,或者准备入场的团队做个参考。
1. 为什么"技能"成了 Agent 项目的新瓶颈
1.1 从模型能力到任务交付,中间缺的正是技能层
先理清一个概念:大模型本身具备的是"知识"和"推理能力",但它不具备"稳定完成某类任务"的能力。你可以让模型写一段 Python 代码,但你不能指望它稳定地"爬取某网站并结构化存储数据"——后者涉及目标解析、反爬策略、数据清洗、异常恢复、结果校验,这一整条链路由一个稳定流程串起来,才是技能。
传统做法是把这套流程写死在代码里,或者塞进一段超长 System Prompt。前者的问题是:每换一个场景就要改代码,Agent 的灵活性全没了。后者的问题是:Prompt 一长,模型注意力被稀释,表现反而变差。
技能体系要解决的,就是在这两者之间找一个中间态——把"完成某类任务的方法论"沉淀成可复用、可组合的模块,Agent 运行时动态调用,既保留灵活性,又保证稳定性。
1.2 agent-skills 与普通提示词的本质区别
很多人误以为技能就是"写得好一点的提示词",这个理解差了十万八千里。
提示词是静态的,它描述的是"你应该怎么做",但不管执行结果。技能是动态的,它包含触发条件、执行步骤、验收标准、异常处理策略,甚至会根据执行反馈自动调整。举个例子,一个"网页信息抽取"技能,提示词里你只能说"提取页面的主要内容",但当页面结构异常、需要调整选择器、需要处理动态加载内容时,提示词就无能为力了;而技能会内置多级降级策略,页面渲染不出来就换无头浏览器,动态内容加载慢就调大等待阈值,每一步都有对应的条件和动作。
再打个更生活化的比方:提示词是菜谱,技能是厨师。菜谱告诉你红烧肉怎么做,但火候大了该关小火、糖色不够该补老抽,这些临场判断菜谱不会写,厨师会。
2. 技能体系的核心设计思路
2.1 技能的粒度划分:不是越细越好
设计技能体系第一步,是决定技能的"颗粒度"。颗粒度太粗,技能变成一个什么都能干但什么都干不精的"万金油",复用性差;颗粒度太细,技能库膨胀到几千个,Agent 光选技能就选半天,上下文消耗也受不了。
我自己的实践体会是,按"任务类型"划分是比较好用的标准,而不是按"业务场景"。比如"网页内容提取"是一个技能,而不是"提取新闻标题""提取商品价格""提取招聘信息"各建一个技能。业务差异通过参数传入,技能本身保持通用。这样做的原因是:底层方法高度一致,差异只在选择器和解析规则上,完全可以参数化。
一个技能内部的步骤数,我建议控制在 3 到 8 步。少于 3 步说明这个技能拆得太碎,多于 8 步说明它应该继续往下拆,否则可维护性会急剧下降。
2.2 技能描述规范:让 Agent 一眼看懂
技能除了要被人类维护,更重要的是被 Agent 理解和使用。Agent 靠什么理解技能?靠技能的元数据描述。这部分写得不好,技能写得再漂亮也是废的。
我总结了一套比较成熟的技能描述模板,分为四块:
第一块是触发条件,明确什么情况下 Agent 应该调用这个技能。第二块是输入输出契约,定义入参的格式、类型、必填项,以及输出数据的结构。第三块是执行流程,用简短的自然语言描述核心步骤。第四块是限制条件,说明哪些情况技能不适用、哪些场景需要降级。
这里有个细节:描述一定要用"Agent 视角",而不是"人类视角"。人类看"爬取页面并提取正文"就理解了,Agent 需要的是"当用户请求包含 URL 且意图为获取页面内容时,使用本技能。入参 url 为完整的 HTTP/HTTPS 地址,输出 markdown 格式正文。页面加载失败时,等待 5 秒后重试一次。"——这种精确到触发条件和异常处理路径的描述,Agent 才能真正"用"起来。
2.3 技能与工具调用的边界划分
技能和工具(Tool/Function)是 Agent 体系里最容易混淆的两个概念。我的理解是:工具是原子操作,技能是对原子操作的编排。读文件、发请求、调 API、执行 SQL,这些是工具。而"分析某份数据报告并生成摘要"是技能,它内部会依次调用文件读取工具、数据解析工具、文本生成能力,并且每一步之间有逻辑依赖和容错处理。
这个区分决定了你的代码结构:工具层保持薄和通用,技能层才是业务逻辑的主要载体。实际开发中,我看到不少团队把工具写得特别厚,一个工具函数里塞了十几步业务逻辑,Agent 调一次就好几分钟,调试时根本分不清是哪一步出了问题。正确的做法是:工具只做一件事,且做到极致——判断复杂度的时候,你可以试一下用一个反问来检验:这个工具输出能不能被另一个技能直接复用?如果不能,它可能就太"厚"了。
3. 从零搭建 Agent 技能库的完整步骤
3.1 第一步:定义技能描述文件的结构
在实践里,我推荐用 JSON 或 YAML 格式给技能定义一个集中的描述文件,这样人类可读,Agent 解析起来也方便。下面给出一个实际在用的 schema 做参考:
name: web_content_extractor version: 1.2.0 description: > 当用户提供 URL 并要求获取页面内容或提炼信息时使用此技能。 支持静态页面和动态渲染页面,输出为结构化 Markdown。 triggers: - "用户请求中包含 URL,且意图为获取页面内容" - "用户说'帮我看看这个链接里有什么'/‘总结一下这个页面’" - "批量任务中需要对一组 URL 做内容提取" input: url: type: string required: true description: "完整的 HTTP/HTTPS 链接地址" output_format: type: string required: false default: "markdown" enum: ["markdown", "json"] output: content: type: string description: "提取到的页面正文内容" title: type: string description: "页面标题" meta: type: object description: "页面元信息,包括作者、发布时间等" steps: - "用 requests 库请求目标 URL,设置 10 秒超时" - "若返回状态码非 200,等待 5 秒后重试,最多重试 2 次" - "用 BeautifulSoup 解析 HTML,定位 article 或 main 标签" - "若目标标签不存在,降级为提取 body 内最大文本块" - "将提取结果转换为指定输出格式" error_handling: - "请求超时: 返回错误信息'页面加载超时,请确认 URL 可达'" - "内容为空: 提示'页面可能为动态渲染,尝试使用 js 渲染模式'" - "URL 无效: 返回提示'链接格式有误,请检查地址'"别嫌写描述文件麻烦,这恰恰是整个技能体系里投入产出比最高的一步。描述文件写清楚了,Agent 才能正确决策;写不清楚,技能库再大也是给 Agent 增加噪声。
3.2 第二步:搭建技能注册与检索机制
技能描述写好之后,需要一个注册中心来管理所有的技能。这个注册中心在 Agent 内部主要负责两件事:一是技能索引,让 Agent 能快速知道当前环境里有哪些技能可用;二是输入校验,Agent 调用技能之前,在这里统一检查参数合法性。
技能索引的做法有一个比较关键的细节:把技能的"核心功能"和"典型用法"做成 embedding 向量存起来。Agent 拿到用户请求后,先把请求做向量化。计算向量相似度,选 top-k 个技能返回,再结合描述文件做最终决策。这样做的好处是技能多了以后能保持检索效率。如果技能总数少于 20 个,直接用关键词匹配或干脆全量丢给模型选都可以;但超过 50 个,就必须上向量检索了——不用把它想得多复杂,类似一个搜索的过程,以用户请求为"搜索词",在索引里找最匹配的技能说明文本。
我自己的经验是,这个检索模块不要做得太重,基于一个轻量的向量库服务就足够了。真正的重头戏是技能描述本身,描述写得好,简单检索就能命中;描述写得烂,再先进的检索算法也救不回来。
3.3 第三步:写好技能的"状态机"
技能执行不能是一段"从头到尾"的线性代码。现实任务里随时可能出意外:网页结构变了、第三方接口限流、数据处理到一半内存爆了。技能必须有状态管理。
我习惯把技能执行拆成四个状态:idle(待命)、running(执行中)、degraded(降级中)、completed/exception(完成或异常)。每个状态转移都要有明确的条件。比如从running转移到degraded,条件是某一步执行失败且该步骤有降级方案;转移到exception,条件是降级方案也失败。
这样做有两个实实在在的好处。第一,可观测性大幅提升——每个技能的执行进度、当前处于哪个环节、有没有降级,一目了然。第二,异常恢复有迹可循——用户说"刚才跑失败了",你能明确知道是在哪一步失败的,而不是看到一堆日志干瞪眼。
4. 技能调优与真实场景避坑指南
4.1 上下文消耗控制:别让技能库吃掉你大半的上下文窗口
所有做过 Agent 应用的人,都会遇到同一个问题:技能描述文件一多,请求还没发出去,上下文先被技能说明占满了。以gpt-4o这类模型的上下文窗口来算,技能说明所占用的 token 很可能直接挤占掉真正对话内容的空间。
解决方法有两个方向。一是激进裁剪:只保留技能描述里 Agent 做决策必需的部分(触发条件 + 输入输出契约),把执行细节从描述文件里挪到技能内部代码里。二是在调用时按需拼接:检索命中哪些技能,就只把哪些技能的描述注入上下文,而不是一股脑全塞进去。
我们测过,单技能描述文件控制在 500 token 以内比较合适。超出这个量,Agent 对这个技能的"理解准确率"会有肉眼可见的下降——它开始频繁忽略描述里的限制条件,凭自己的"直觉"乱来。
这里补充一个实验心得:我试过把描述压到 300 token 以内,准确率最优。原因是描述越短,模型越容易"抓住核心";描述一长,它反而会从中抽取出自己觉得重要的那部分信息,而你精心设计的条款它反而注意不到。你把这个想成给一个人快速介绍另一个人的性格——你不可能一句不落地说完对方的所有细节,挑最关键的说,反而更容易让对方在关键时刻做出符合预期的判断。
4.2 技能冲突问题:多个技能同时"觉得自己该上场"
技能库上规模以后,一定会遇到技能间的"抢活"问题。比如你既有"网页信息抽取"又有"新闻聚合",用户给了一个新闻网站的 URL,这俩技能都触发了,Agent 到底该用哪个?
这里需要给技能设计一个"优先级"概念,并且要在描述文件里明确写出来。我一般会在技能描述里加一个priority字段,数值范围 1 到 10。数值越高,表示当多个技能同时匹配时,Agent 越应该优先考虑它。同时,在检索阶段做一个简单的冲突消解:如果 top-k 里有两个技能的triggers高度重叠,就只返回优先级高的那个。
但靠优先级只是治标,更根本的解法是在技能设计阶段就规避重叠。怎么规避?简单:让技能的"职责边界"尽可能清晰。IP 归属查询的技能,就只管查归属地;新闻聚合的技能,就只管多源信息集合汇总。如果一个技能能拆成两个更聚焦的技能,不要犹豫,拆。
4.3 可观测性:技能执行过程要能被"看见"
技能是一个黑盒还是白盒,直接决定了上线后你的头发能保住多少。我强烈建议从第一天起就做好技能执行的日志埋点。每个技能执行的关键节点都要记录:技能名称、版本号、入参、出参、执行耗时、每一步的中间结果、降级/异常路径。
很多同学会问:这有什么难的,打印日志不就行了吗?但实际做起来会发现,Agent 的调用链路比传统程序复杂得多。一个任务可能先后调用了四五个技能,每个技能内部又有多个工具调用,日志分散在不同模块里。你需要的是一个集中的链路追踪视图,把所有技能调用按时间线和调用关系串联起来。
简单实现可以用一个trace_id贯穿整个任务生命周期,每个技能执行时带上这个trace_id把日志打到统一收集服务里,再按trace_id聚合展示。做完了这个,你会发现调试 Agent 的效率翻倍不止。
4.4 常见问题速查表
最后把这些年见过的高频问题整理成一张速查表,直接对照排查:
| 问题现象 | 根因分析 | 排查方案 |
|---|---|---|
| Agent 总是忽略某个技能 | 描述不清晰或检索埋没 | 检查触发条件描述,看和用户请求的语义距离是否过远;降低描述文件复杂度 |
| 技能执行到一半就停了 | 状态机缺失或异常处理不完整 | 检查步骤间是否有状态转移,为每步加 "超时/失败" 处理分支 |
| Agent 反复调用同一个技能 | 没有做结果缓存 | 引入结果缓存,相同入参直接返回上次结果 |
| 技能输出格式不稳定 | 输出契约定义太粗 | 在描述文件里给出输出示例,要求 Agent 严格按示例结构输出 |
| 技能越用越不准 | 技能版本未管理 | 引入版本号管理,定期回归测试不同版本的技能表现,留出降级路径 |
5. 关于技能复用与长期积累的个人体会
最后分享一点我的真实感受。技能体系的搭建,本质上是一个"资产积累"的过程,和写代码不太一样。写代码是一次性的投入产出,技能是一次投入、长期收益——只要你的技能描述文件写得够好,每一次 Agent 调用它,都在以比较低边际成本复用它背后那套沉淀过的执行逻辑和踩坑经历。
我的建议是:如果团队还在 Agent 应用的早期,完全可以先不追求技能体系的完备度,用两三个核心技能快速跑通业务流程,验证价值。等跑通了,再逐步把业务和数据积累的流程,沉淀成新的技能。关键是要形成"每次踩坑都反哺技能"的机制——线上出了问题,不要只修代码,把修复经验写回技能描述和异常处理分支里。这样技能会越用越"聪明",Agent 的表现也会越来越接近一个"资深工程师"的水平,而不是永远停留在"应届生"层面。这个过程没法一蹴而就,但只要走对方向,后面的复利效应会很明显。