
写 Agent 的开发者应该都有同感模型能力再强如果没有一套好用的手也只能是纸上谈兵。我第一次看到 agent-skills 这个命名时第一反应是这不就是把 Agent 工具又包装了一层吗实际深入了解后才发现它背后是一整套关于技能定义、注册、调度、执行的工程范式。一句话概括agent-skills 要解决的是智能体不只是会聊天而是能稳定、可控地干活这件事核心就是把每个能力封装成独立技能让 Agent 按需调用、自由组合。这篇文章我不聊虚的直接讲清楚技能层为什么必须单独存在、技能规范怎么定、从零搭一套可用系统需要做什么以及我实测过程中踩过的坑和最终沉淀下来的做法。1. 拆解 agent-skills设计定位与核心思路1.1 为什么技能必须从提示词里独立出来很多初学者写 Agent 时习惯把所有能力说明都塞进系统提示词比如当你需要查天气时调用天气服务的 API参数是城市名称……。这样写在小 Demo 里没问题但一旦能力增加到十个以上问题立刻暴露提示词越来越长模型注意力被稀释经常该调的工具不调不该调的乱调而且每次新增能力都要动提示词测试成本成倍上升。agent-skills 的核心思路恰恰是把这些能力描述从提示词里彻底剥离形成独立的技能层。每一个技能拥有自己的定义文件包括名称、描述、参数结构、执行逻辑。Agent 在运行时先看技能目录再决定调哪个技能。这样做最直接的好处有三个第一提示词变短模型行为更稳定第二技能可以独立测试、独立迭代不影响其他功能第三技能可以复用同一个技能在不同 Agent 里都能挂载。这个解耦思路其实和软件开发里的模块化插件化一脉相承。你写后端时会封装 Service 层不会把业务逻辑全堆在 Controller 里Agent 的技能层就是扮演这个 Service 的角色。理解了这一点后续所有设计决策都有了一条主线一切为了可复用、可观测、可独立演进。1.2 技能、工具、插件的边界在哪里很多人分不清 agent-skills 里的技能和市面上常说的工具调用插件有什么区别我在做方案评审时也反复被问过。其实不用把概念搞得太玄它们的关系可以理解为不同抽象层级。工具Tool/Function是最底层的能力单元通常对应一个具体函数或 API 调用比如计算两个日期差调用某个接口查快递。插件Plugin通常是工具的集合围绕某个场景打包比如电商助手插件内含商品查询、订单查询、物流追踪三个工具。而技能Skill在 agent-skills 体系里往往是更高一层的抽象它不只是单一调用还可以包含调用策略、前置条件、后置处理甚至可以在内部编排多个工具。举个例子天气查询是一个工具出门建议是一个技能。技能内部可能先调天气工具拿到温度和降水概率再结合时间规则生成穿衣建议最后把结果格式化返回。对 Agent 来说它只需要理解出门建议这一个入口而不需要关心内部调了哪几个工具。这就是技能层的价值把复杂度藏在内部把简单暴露给模型。1.3 技能设计的三条基本原则踩过几轮坑之后我总结出设计技能必须遵守的三条原则缺一不可。第一是单一职责。一个技能只做一件事不要把查天气写日报订会议室揉在一个技能里。技能粒度越细Agent 越容易理解什么时候该用哪个技能组合起来也更灵活。第二是描述即文档。在 LLM 场景下技能描述就是给模型看的说明书模型判断该不该用这个技能几乎完全依赖描述文本。所以描述必须写清楚适用场景、边界条件、典型示例而不是写实现细节。第三是失败要可观测。技能执行必然有失败场景比如参数错误、外部服务超时、结果格式异常。设计时就要约定好错误码和错误信息格式让 Agent 能根据错误信息自行修正也让开发者能在日志里快速定位问题。2. 技能定义规范与核心实现细节2.1 技能描述文件一份给模型看的说明书一个技能最核心的资产就是它的描述文件。我目前常用的结构是 YAML 格式因为可读性好非开发者也看得懂。描述文件主要包含几个部分技能 ID 和名称、用途描述、参数定义、内部执行逻辑或者工具调用序列、返回结果格式说明。下面的示例展示了一个会议室预订技能的定义skill: id: meeting_room_booking name: 预订会议室 description: 当用户需要预定会议室时使用本技能。 适用条件用户明确提到预订、预定、占坑、借用会议室等需求。 不适用条件用户只是询问会议室位置或设施此时应使用查询类技能。 parameters: - name: room_type type: string enum: [small, medium, large] required: true description: 会议室规格small 容纳 2-4 人medium 容纳 5-10 人large 容纳 10 人以上 - name: start_time type: string format: datetime required: true description: 会议开始时间格式 YYYY-MM-DD HH:mm - name: duration type: integer default: 60 description: 会议时长单位分钟 workflow: - step: query_free_rooms tool: room_service.search input: {room_type: $room_type, start_time: $start_time, duration: $duration} - step: book_room tool: room_service.book input: {room_id: $query_free_rooms.result.room_id} condition: $query_free_rooms.result.available true注意 description 字段的写法它不是写给开发者的而是写给模型看的所以要明确什么情况用什么情况不用这就是前面说的描述即文档。我在实际测试中发现把描述写得具体一些Agent 的决策准确率能提升一大截。2.2 参数声明和输入校验别让模型裸奔模型从用户的话里抽取参数天然会出错。比如用户说帮我订明天下午三点的会议室模型可能把明天下午三点解析成各种格式。所以技能参数设计一定要包含几类约束必填还是选填、类型是什么、取值范围或枚举、格式模板。最好还要提供默认值和缺参时的人工兜底策略。我的习惯是每个技能内部做两层校验第一层是结构校验参数类型、枚举值、必需项是否齐全第二层是语义校验比如开始时间不能晚于结束时间、预约人数不能超过会议室容量。校验失败时不要直接抛异常而是返回一个标准化的参数错误对象里面包含错误字段名、期望格式、示例值让 Agent 能根据提示重新询问用户或者自己修正参数。这里有个容易被忽视的细节技能层校验通过的输入不一定是模型真正需要的输入。比如日期时间这类参数模型可能会输出2026-03-15 15:00:00这种格式但业务系统只接受 Unix 时间戳那应该在技能层做一次转换而不是把格式责任推给模型。技能层作为翻译官负责把模型语言转换成系统语言这一步做扎实了后面的执行才能顺畅。2.3 技能组合与编排从小积木到大能力单个技能解决单点问题但真实场景往往是复合的。用户说帮我订一间能容纳十人的会议室顺便通知项目组所有人这里至少涉及两个技能订会议室、发通知。agent-skills 体系里技能组合有两种主要方式。第一种是顺序编排在技能的 workflow 字段里显式声明步骤每一步输出都可以变成下一步的输入我用上面的 YAML 示例就是这个模式典型的使用场景就是订会议室之后再同步日历。第二种是动态编排即让 Agent 自己决定调用顺序。这种模式适合开放式任务比如帮我安排一场项目启动会Agent 可能会先调用查询参会人空闲时间再调用预订会议室再调用创建会议邀请。但动态编排的可靠性弱一些必须给 Agent 提供清晰的技能描述和错误恢复机制否则一旦中间某一步失败整个链路就断了。我的建议是固定流程用顺序编排开放流程才用动态编排能写死的流程不要依赖模型临场发挥。3. 实操过程搭建一套可用的技能系统3.1 技能目录结构与注册中心动手搭建前先把目录结构定好。我用的结构如下agent-skills/ ├── registry.yaml # 技能注册表所有可用技能的索引 ├── skills/ │ ├── meeting_room_booking/ # 一个技能一个文件夹 │ │ ├── skill.yaml # 描述文件 │ │ └── executor.py # 执行逻辑 │ ├── weather_query/ │ │ ├── skill.yaml │ │ └── executor.py │ └── ... └── runtime/ ├── loader.py # 技能加载器 ├── executor.py # 技能执行器 └── context.py # 共享上下文管理registry.yaml 是大脑Agent 在运行时只读取这个文件来了解自己有哪些能力。它维护的是技能名 - 技能目录路径 - 版本号的映射关系加载器启动时会扫描这个注册表把所有技能的描述信息加载到内存中供模型调用。新技能上线不需要重启整个服务只要更新注册表并触发热加载即可。3.2 技能执行器与共享上下文执行器是技能的调度中心它负责接收模型传来的技能名称和参数找到对应的执行函数完成参数校验、执行逻辑、结果返回。我的执行器采用了一个统一的 AbstractExecutor 基类每个技能继承后实现自己的 run 方法。class AbstractExecutor: def validate(self, params: dict) - dict: 参数校验返回标准化错误或清洗后的参数 raise NotImplementedError def execute(self, params: dict, context: dict) - dict: 执行技能逻辑返回标准化结果 raise NotImplementedError共享上下文 context 是这个系统里比较关键的设计。很多技能在执行时需要公共信息比如当前用户身份、当前时间、会话历史摘要。这些信息如果每次都要模型传参既浪费 token 又容易出错。我的做法是上下文分为两层全局上下文在服务启动时初始化比如当前地区、默认语言、服务调用凭据会话上下文按用户会话隔离比如用户 ID、会话中已经提取到的偏好信息。执行器调用技能时会把这两层上下文合并传入技能内部只需要声明自己需要哪些上下文字段不需要关心数据从哪里来。3.3 一个完整技能从编写到上线的全流程纸上谈兵没意思我拿工资查询这个高频率技能为例完整走一遍开发流程。第一步明确需求。用户会问这个月发了多少钱十月的补贴是多少这类问题核心是查工资单和补贴明细。我拆成三个技能查基本工资、查补贴明细、查实发金额因为模型根据用户问法会选不同的技能入口。第二步写描述文件。这是关键描述决定了模型能不能在正确时机选中它skill: id: salary_query name: 查询工资明细 description: 当用户询问本月或历史月份工资、薪资、税后金额、补贴明细时使用。 用户问法示例我这个月工资多少十月发了多少钱补贴包含哪些项。 如果用户问的是考勤、休假、报销不要使用本技能。 parameters: - name: month type: string format: YYYY-MM required: false description: 查询月份默认为当前月份。注意用户说上月时需要向前推算一个月注意补贴包含哪些项这类问法它其实不是标准工资单查询但语义上是技能范畴我把它写进适用示例就是告诉模型这个技能也能回答补贴结构问题实测效果不错。第三步写执行逻辑。调用薪资系统的接口把模型传来的月份转换成业务系统需要的格式请求数据过滤敏感字段最后把结果包装成结构化返回。这里有一个重点返回给模型的内容不要包含内部字段名要用自然语言拼接让模型能直接念给用户听。第四步注册与测试。把技能加进 registry.yaml写单元测试覆盖正常参数、缺参、非法月份三种情况然后用几组真实用户问法跑一遍全链路确认模型能正确选中技能。上线前我还会跑一轮负样本测试故意用无关问题测试确认模型不会误调用。4. 常见问题与排查实录4.1 典型问题速查表系统跑久了一定会遇到各种奇怪问题我把频率最高的几类整理成了速查表遇到问题先按表排查。现象可能原因排查方法解决方案模型始终不调用某个技能技能描述与用户问法语义差距大查看模型决策日志看哪些技能参与了排序重写描述加入真实用户问法示例模型频繁误调用某个技能描述里的适用范围写得太宽检查触发日志统计命中场景收紧描述补充不适用条件参数总是抽取不完整参数缺少默认值或描述不清晰查看参数抽取阶段的日志为必填参数补充示例值增加默认值兜底技能执行成功但结果错误上下文传入错误或参数转换有误对比输入参数与业务系统实际接收值在技能层增加日志记录入参和出参多个技能互相冲突技能职责边界模糊多个技能都能响应用户问题分析技能决策热力图明确技能边界把冲突场景合并到单一技能有一个很有代表性的案例某次上线后用户问我在哪一层办公模型居然调用了会议室预订技能因为会议室技能描述里有地点这个词。后来我把技能描述里的地点相关表述全部删除并在会议室预订技能的不适用条件里明确写了用户询问办公位置时不要使用本技能问题立刻消失。这个教训说明模型是词面理解的高手但语义边界完全靠你给它圈定每一句描述都在画边界。4.2 实测心得与避坑建议最后分享几个我积累的实操心得这些不是文档里能查到的都是真金白银踩出来的。关于描述文件我强烈建议把用户可能怎么说写成示例放进描述里。纯抽象描述对模型不友好比如查询工资远不如用户问法示例这个月工资多少、十月发了多少钱、年终奖什么时候到账有效。模型见过相似的表达决策才会稳。关于参数校验不要相信模型第一次给的参数就正确。时间、日期、金额这类参数模型经常理解错比如下周三到底指哪天不同时候问结果可能不同。技能层必须做语义校正不能只做格式校验。我一般会把时间解析逻辑写清楚用当前日期作为基准推算而不是把下周三这样的字符串直接传给业务系统。关于上下文传递最隐蔽的坑是共享上下文被多个技能并发修改。某次我发现一个用户查工资时另一个会话的查询结果串进来了排查了半天才发现是全局上下文里存了上一个会话的查询结果。后来我强制规定会话级数据一律按会话 ID 隔离技能内部产生的临时数据不得写入全局上下文。关于技能数量一个常见误区是能力越多越好。实际上技能超过 20 个之后模型的决策准确率会明显下降因为每次任务都要从大量候选中挑选。我做过一组对比实验技能描述总体量相近的情况下10 个技能时的决策准确率比 25 个技能时高十个百分点以上。所以每隔一段时间要主动做技能精简合并低频率技能删除重复技能让目录保持精简。关于热加载技能更新频率高的时候一定要有版本概念。我踩过一版更新后跑了一半旧逻辑、一半新逻辑的坑因为 executor 文件被部分刷新了。现在所有技能都带版本号注册表里只记录最新版本加载器会对每次加载做完整校验校验不过就回滚到上一个版本宁可失败一次也不带病运行。我个人在实际操作中体会最深的一点是agent-skills 的工程难点不在写执行逻辑而在定义边界和做决策优化。技能描述写得好Agent 就像一个训练有素的员工你说一句话它就知道该动哪只手技能描述写得差再强的模型也会手忙脚乱。所以每次新增或修改技能我都要求自己至少花一半时间在描述上而不是急着写代码。这个方法我推荐你也试试先不管实现把这个技能解决什么问题、什么场景不该用它写清楚再动手整个系统会稳很多。