
Agent 圈子里聊得最多的一个词最近一定是 agent-skills。我自己在项目里反复打磨这个方向小半年了从最开始把技能写成一大坨 prompt 塞进系统消息到后来拆成独立、可复用、可测试的技能包中间踩了不少坑也总结了一套相对顺手的做法。这篇就围绕 agent-skills 这个概念把我在实操中积累的设计思路、落地步骤和排查经验完整梳理一遍希望能给正在做 Agent 应用的同学一些参考。1. Agent Skills 到底是什么为什么突然这么火1.1 一个技能和一段提示词的本质区别先说清楚我理解的 agent-skills 是什么。简单讲它是给 Agent 预定义的一组可调用、可编排、可复用的能力单元。每个技能单元通常包含触发条件、执行逻辑、输入输出协议、工具调用规则以及最小化的上下文说明。很多人觉得这不就是 prompt 里的工具描述吗还真不是。工具描述只是技能的一个对外接口而一个完整的技能包要解决的是Agent 知道什么时候该用、怎么用、用完怎么收尾这一整条链路的问题。举一个例子我之前做一个内部的知识库问答 Agent早期版本把所有处理逻辑写在一个巨型 system prompt 里问它帮我查一下上季度的报表明细它能回答但换一种问法Q3 的销售数据汇总下表现就开始不稳定经常答非所问或者自己瞎编格式。后来我把报表查询拆成一个独立技能里面明确规定触发场景是用户提到季度、报表、销售数据等关键词执行流程是先确认时间范围再走 API 查询最后按固定模板生成结果异常情况是数据缺失时怎么反馈。改成这套之后同样的提问识别准确率和输出稳定性提升非常明显。1.2 为什么要把技能从 Prompt 里拆出来把技能从 prompt 里拆出来核心动机有三个稳定、复用、演进。稳定方面prompt 越写越长模型对指令的遵循率会下降尤其是多个任务混在一起的时候互相干扰特别严重。技能拆出来后每个技能的指令上下文可以单独拼装需要时才注入模型每轮实际看到的指令量是可控的。复用方面同样的技能可以在不同 Agent 之间共享。比如我写过一个人力资源场景的请假审批技能后来做另一个行政类的 Agent直接复制过去改个 API 地址就能用不用重新调 prompt。演进方面更有意思。Prompt 是活文档改一次就可能影响全局你很难做版本管理。技能包可以像代码一样走 Git、走测试、走评审每次改动影响面可控出了问题能快速回滚。这些特性决定了 agent-skills 不是一个锦上添花的概念而是 Agent 走向工程化的必经之路。我见过太多项目死在Agent 能力看着都有但一交到用户手上就失控这个阶段本质上就是没有把能力做结构化拆分。2. 技能体系的整体设计与拆解思路2.1 先确定能力边界再动手拆技能我在设计技能体系时第一件事不是写代码也不是写 prompt而是先列能力清单。做法是把业务上一段时间内用户真正会问到、用到的高频需求全部罗列出来然后逐个判断这个需求是一次性的对话能力还是可以沉淀成反复使用的技能。判断标准有三条一是使用频率高不高二是流程是否足够标准化三是是否涉及外部系统调用。三个条件至少满足两个才值得拆成一个独立技能。举例来说帮用户查快递这种需求高频、流程固定、要调外部接口适合做技能但是陪用户闲聊这种虽然高频但流程完全没有标准可言做成技能反而是束缚应该保留在通用对话能力里。这条标准帮我砍掉了不少伪需求。最初我列的技能清单有二十多项按这个标准筛完只剩九项后续开发和维护成本大幅下降。2.2 技能包的目录结构与分层设计技能包我会按功能域分层组织而不是平铺一堆技能文件。这样做的原因是 Agent 在运行时需要快速定位技能清晰的分层目录可以显著减少检索和匹配的耗时。我常用的目录结构大致是这个形态skills/ common/ # 通用基础技能 web_search/ # 网络搜索 pdf_parser/ # 文档解析 datetime_utils/ # 日期处理 business/ # 业务领域技能 order_query/ # 订单查询 refund_process/ # 退款处理 report_generate/ # 报表生成 expert/ # 专家级技能 risk_analyze/ # 风险分析 legal_review/ # 法务审核每个技能目录内部再放三个核心文件skill.yaml技能元信息、instructions.md执行指令、参考代码或 API 配置。skill.yaml 里记录技能名称、描述、适用场景、触发关键词、输入输出协议这部分主要给 Agent 的技能调度层读取。instructions.md 是给模型看的自然语言指令写清楚执行步骤和边界规则。API 配置则定义实际调用外部系统时的接口细节。这种分层设计的价值在后期维护时体现得最明显。通用技能几乎不用动业务技能随着需求迭代频繁更新专家技能看规则变化。分层之后每次改动只影响自己那一层回归测试范围很小。2.3 技能调度的核心机制该用哪个技能谁来决定技能多了以后最考验设计的地方其实是调度环节。Agent 面对用户的一句话怎么知道自己该激活哪个技能我试过三种方案效果差异很大。第一种是把所有技能描述直接塞给模型让模型自己选。这种方法实现简单但技能数量一多描述互相干扰选择准确率明显下滑。实测八个技能以内还行超过十二个就开始频繁选错。第二种是加一层分类路由。先用一个轻量分类模型判断用户意图属于哪个功能域再只把该域内的技能描述注入给主模型。这种方式准确率提升不少但增加了链路复杂度和延迟。第三种是我目前采用的混合方式常用技能走规则关键词匹配快速激活模糊场景交给模型判断最后加一道确认机制。比如用户说查一下我的订单先做关键词匹配命中订单查询技能就直接进入对应流程如果用户表达很模糊比如看看我上次买的东西到哪了就交给模型在具体场景的候选技能里做选择。确认机制是指如果技能匹配置信度不高Agent 先向用户复述一遍理解结果得到确认后再执行。这个设计能明显降低误操作率。这三层配合下来准确率和响应速度达成一个比较理想的平衡。我后来的经历证明一上来就追求纯模型路由并不划算规则兜底加模型判断的组合才是工程上的稳妥选择。3. 核心细节解析与实操要点3.1 技能描述怎么写模型才看得懂、选得准这是 agent-skills 里最容易被低估的环节。技能描述写得不好再强的调度机制也救不回来。先说一个常见的反面例子很多人写技能描述就一句话查询用户的订单信息。这种描述太泛模型不知道订单的具体边界是什么也不知道什么时候该用不该用。我踩过这个坑之后总结出一套相对成熟的描述写法。技能描述必须包含三块内容。第一块是触发场景要写清楚哪些用户表达应该落到这个技能上最好附两三个典型问句示例。第二块是能力范围明确这个技能能做什么、不能做什么比如只支持查询近一年内的订单这种边界一定要写死。第三块是行为约定比如用户询问多笔订单时是逐条展示还是汇总展示这些细节直接影响输出质量。我整理过一个技能描述模板大致长这样name: order_query description: 当用户查询订单状态、物流进度、购买记录时使用。 典型提问示例我的订单到哪了帮我看看最近买的手机发货没查一下上周的订单情况。 本技能仅支持查询当前账号近一年内的订单不支持修改订单、申请退款等操作。 若用户同时询问多笔订单默认按时间倒序展示最近三笔并提示可展开查看全部。把描述写到这个粒度模型在选择技能时基本不会跑偏。有一个细节值得注意描述不要写得太抽象模型对典型示例的感知远远强于对功能特点的感知。所以我在每个技能描述里都会塞一到三个具体的用户问句示例效果非常直接。3.2 技能内部执行指令的编写规范instructions.md 是技能执行时的操作手册直接决定 Agent 干活的靠谱程度。这部分我踩过的坑最多主要的经验可以归纳成四条。第一条执行步骤要清单化不要写成散文。模型对第一步做什么、第二步做什么的序列指令遵循率很高但对根据实际情况灵活处理这种开放式指令会把控不住。一个技能的执行步骤控制在五到八步比较合适超过十步就考虑拆成子技能。第二条每个技能要点明前置条件检查。比如查询订单前必须确认用户已经登录、会话里已经拿到用户 ID如果没有就走统一的鉴权流程。不写这一步Agent 很可能拿着空参数就发起查询然后报一堆看不懂的错误。第三条输出格式要提前锁定。我会在指令里直接定义输出模板比如结果必须用表格呈现列名固定为订单号、商品名称、下单时间、物流状态然后要求模型严格按照模板输出。模板锁死以后下游的页面渲染和用户阅读体验都稳定了。第四条必须写清楚失败处理路径。技能执行失败是常态关键是失败后怎么办。我的约定是API 调用失败时先做一次重试重试仍失败则向用户输出一句标准话术暂时查不到您的订单信息请稍后再试同时把错误日志记录下来。很多 Agent 产品给用户的体验差就是死在技能失败后模型开始自由发挥编出一些不存在的错误原因。3.3 技能复用与联动技能拆小了怎么拼起来技能拆得越细复用性越好但同时也带来一个新问题——单个技能解决不了复杂任务需要多个技能联动。这个矛盾我花了不少时间才理顺。我的做法是引入一个编排层允许技能通过显式声明来互相调用。比如生成季度销售报表这个复合技能内部定义了一条执行链路先调用数据查询技能拉取原始数据再调用数据清洗技能处理异常值最后调用报表渲染技能生成最终文档。每个子技能保持独立随时可以被其他复合技能复用。实现这种联动的方式不复杂本质上是在技能指令里写明本技能执行过程中会调用以下子技能以及子技能之间的数据传输格式。但有一个关键点数据格式必须统一。我踩过的一个典型坑是数据查询技能输出的是一个 JSON 数组而数据清洗技能期望的入参是 CSV 文本中间没有做转换导致整条链路在联动时反复报错。后来我在技能目录里单独加了一个 data_protocols 文件集中定义所有技能间传递数据的标准格式这个问题就彻底解决了。3.4 技能安全边界权限、敏感操作与回归控制技能一旦接上真实业务系统安全问题就不容回避。我在这方面坚持几条铁律宁可功能少一点不能出安全事故。第一条铁律是最小权限原则。每个技能只配它确实需要的那点权限绝不给 Agent 一个拥有全部操作权限的通用接口。给订单查询技能就只配只读权限给退款处理技能才单独配写权限并且要额外加一层二次授权流程。第二条是敏感操作必须走确认流程。涉及资金、隐私、删除类操作的技能我在执行链路上固定插一个确认节点Agent 不能直接执行必须先生成一个操作摘要让用户确认。这个机制很土但非常有效它能拦住大模型随机抽风导致的误操作。第三条是建立技能操作的审计日志。每次技能被调用就记录触发时间、输入参数、调用结果、消耗 token 数。这些日志一方面是排查线上问题的线索另一方面也可以用来分析用户真实需求反哺技能迭代。我后来做技能效果评估时靠的全是这些积累下来的日志数据而不是凭感觉。4. 实操过程从零实现一个完整技能包4.1 场景选择与需求定义用哪个技能当例子讲实操比较合适我选请假审批这个吧因为它覆盖了一个典型技能的完整链路关键词触发、表单信息收集、外部系统调用、规则判断、结果反馈整个流程足够代表性又不会因为业务复杂度太高把读者绕晕。这个技能的需求定义是这样的用户向 Agent 发起请假请求Agent 需要收集请假类型、开始时间、结束时间、请假事由然后调用 OA 系统的接口提交申请根据接口返回结果组织反馈话术。如果用户给的信息不完整Agent 需要主动追问而不是带着残缺参数硬调接口。这个定义实际上就是技能的需求说明书。我会强调团队成员在这个环节多花时间对齐因为后面所有编码、调优都是围绕这个定义展开的定义不清后面全是返工。4.2 编写技能元信息与执行指令定义清楚后第一步是写 skill.yaml。请假审批技能的元信息大概长这样name: leave_apply version: 1.2.0 domain: hr triggers: - 请假 - 申请休假 - 年假 - 调休 capabilities: - 收集请假信息并提交OA审批 constraints: - 仅支持当前登录用户本人发起 - 不支持的请假类型需明确告知用户 input_schema: leave_type: type: enum values: [年假, 事假, 病假, 调休] start_time: type: date end_time: type: date reason: type: string max_length: 200 output_schema: status: type: enum values: [pending, approved, rejected] leave_id: type: string这个文件的核心作用是给调度层和模型做技能画像。其中 triggers 字段特别重要它决定了这个技能能不能被快速命中input_schema 和 output_schema 则给模型提供了处理入参和出参的完整约束。接着写 instructions.md。这个文件才是真正指导模型干活的核心。我在里面明确写了执行流程第一步核对用户是否已登录第二步逐项确认用户提供的请假信息缺哪项补问哪项用户提供的信息直接填入参数第三步把参数转成 OA 接口要求的 payload 并发起请求第四步根据接口返回结果组织用户回复。同时在里面加了两个重要的规则提示请假时间跨度不能超过可选范围否则要提示用户分次申请发起提交前必须把最终确认信息回显给用户得到回复确认后再真正调接口。4.3 接入外部接口与数据校验写到这里就进入真正动手接接口的环节了。我没有把接口调用逻辑写成一段固定代码塞给 Agent 执行而是把它封装成一个可被模型调用的工具函数这样模型只负责填充参数、发起调用、接收结果具体 HTTP 细节由工具层处理。def create_leave_request(leave_type, start_time, end_time, reason, user_token): payload { type: leave_type, start: start_time, end: end_time, reason: reason, applicant: get_user_id(user_token), } # 这里做一层防呆校验 if not validate_dates(start_time, end_time): return {code: 4001, message: 日期格式不正确或开始晚于结束} if get_remaining_leave_days(user_token, leave_type) 0: return {code: 4002, message: 可用假期余额不足} resp requests.post(f{OA_BASE_URL}/api/v1/leave, jsonpayload, headersauth_headers(user_token), timeout10) if resp.status_code ! 200: return {code: 4003, message: OA系统暂时不可用请稍后重试} return {code: 0, data: resp.json()[data], message: success}值得说一下这个防呆校验的作用。模型在填充参数时什么离谱的情况都可能发生比如给一个2024-02-30这种不存在的日期或者结束时间早于开始时间如果直接把这些参数打到上游 OA 系统迟早被人投诉。工具层做一层基础校验等于给不可控的模型行为加了一道保险。接口报错时的处理逻辑也写在工具函数里了。我规定所有错误必须以标准结构返回错误码分三类参数类错误返回 4001业务规则类错误返回 4002系统异常类错误返回 4003。模型拿到这个结构化错误后可以根据指令里的失败处理路径组织话术而不是自己瞎解释服务器可能有问题。4.4 调试与联调的关键技巧技能写完真正的战斗才开始。我把这段调试经验单独拉出来说是因为太多人在这里栽跟头。第一件要做的事是单技能单测。我整理了一份包含十多个测试用例的清单覆盖正常请求、缺少参数、非法日期、余额不足、接口超时、用户未登录等场景。每跑一个用例都看模型在这一轮里是否选择了正确技能、是否正确追问缺失参数、是否在特殊场景下给出符合预期的话术。这里推荐一个做法不要直接看最终回复而是把模型每一步的中间推理过程和工具调用日志拉出来看才能定位问题到底出在技能选择、参数填充还是输出编排。第二件要做的事是跨技能串联测试。比如用户说下周我想休个年假帮我走个流程这句话其实先触发了日历查询技能确认日期是否可行再触发请假审批技能提交申请。这种串联场景我最开始没测结果上线第一天就出问题模型在前一个技能结束时把上下文搞丢了后面那个技能拿不到完整的请假信息。后来我在技能指令里强制约定上一个技能得到的关键信息必须保留在会话上下文中并明确标记字段名串联问题才算根治。第三件是建立回归测试机制。技能迭代时老功能不能退化。我每次改完一个技能就把之前积累的测试用例全套跑一遍看有没有哪个用例的表现在改完之后变差了。人工跑这套测试很累我后来写了一个简单的自动化脚本把测试用例作为输入循环调用 Agent 并对比输出关键字段效率提升明显。5. 常见问题与排查技巧实录5.1 技能总是不被触发先查描述里的触发信号最常遇到的问题就是技能写好了但用户怎么问都不触发。我排查这种问题时有一套稳定的排查顺序。第一步查 skill.yaml 里的 triggers 是否覆盖了真实用户的表达习惯。我吃过一个亏写的触发词是请假休假年假但实际用户最常说的一句话是我想歇几天。后来我把用户访谈里收集到的真实问法整理成一份话术清单然后逐条过了一遍 triggers把覆盖不到的统统补上。第二步查技能描述里是否写清了什么时候不能用。模型在选择技能时负向约束和正向约束同样重要。如果描述里只写了用户问请假相关问题时使用没说用户只是闲聊提了一句上周请假出去玩不算申请场景那模型就可能在非触发场景被误导给用户推送一个是否要提交请假申请的卡片体验极差。第三步查技能是不是被其他技能抢走了。多个技能描述如果有重叠模型很容易选错。我的解决方法是给每个技能定义足够差异化的触发场景并且在技能描述里显式声明如果用户意图属于XX技能的范畴不要使用本技能。5.2 输出内容不稳定用模板和校验双重锁死模型输出不稳定是 Agent 应用的老大难问题了。我的处理思路是凡是可以模板化的输出一律模板化凡是模板化不了的就在指令里给出正反例。以请假审批技能为例我要求成功提交申请后的回复必须包含申请单号于是我在输出模板里明确写了一句回复用户时必须包含 OA 系统返回的申请单号单号字段名为 leave_id。实测下来加上这个约束之后包含正确单号的回复率直接从八成提升到几乎百分百。原因很简单模型对必须包含某个具体字段这种硬约束的遵循能力远强于对回复要全面一些这种模糊期望的遵循能力。另一个有效手段是给输出加一层后置校验。我在工具层写完提交逻辑后会检查模型生成的最终回复里是否包含必要字段如果不包含就自动触发一次重写把缺失字段的信息重新传给模型并要求补上。这种做法相当于在一个可控的点上把模型犯的错兜住。5.3 技能之间状态混乱统一上下文协议多个技能联动时最常见的故障是信息在技能切换间丢失。用户跟 Agent 说帮我查下最近的订单顺便看下能不能退这里先走了订单查询技能再切退款处理技能退款技能需要知道上一个技能查出来的订单号但如果上下文没有按约定传递退款技能就会拿不到参数。这个问题我在前面提了一句解决办法这里详细展开。我在项目里定了一个统一的上下文协议规定所有技能输入参数中的关键实体在技能执行完成后必须回写到会话上下文的固定字段中比如 order_id、user_name、leave_id。字段命名全局统一后续任何技能都能直接读取。同时技能指令里明确写出执行本技能后必须将以下字段回写到上下文...。执行完一轮联调后技能间状态传递的稳定性显著上升。这个协议看起来简单但需要项目早期就定下来并且代码评审时严格把关。我见过不少项目做到一半才引入这个规范结果历史技能全部要返工代价相当大。5.4 技能效果怎么评估不只看答没答对最后聊一个容易被忽视的问题技能上线后怎么判断它真的有用我见过太多人只统计一个指标——用户的问题有没有被成功回答。这个指标太粗糙了很难指导迭代。我自己在评估技能时至少看四个维度一是触发准确率该触发时是否触发不该触发时是否瞎触发二是过程合规率技能执行时是否严格按照指令中的步骤来有没有跳过前置检查或遗漏必要字段三是输出规范率生成的回复是否符合约定的模板和格式四是用户反馈率用户对回复的点赞点踩数据以及是否出现了要求转人工的行为。这四个维度各有侧重触发准确率衡量调度层的效果过程合规率衡量执行层的稳定性输出规范率衡量产出质量用户反馈率衡量真实体验。配上前面提到的日志系统每周拉一张表出来看趋势哪个技能要优化、优化哪里一目了然。我印象最深的一次优化某周报表显示一个技能的触发准确率只有六成排查日志后发现是用户表达变化太快新增了一波帮我看看额度还剩多少的表达触发词里完全没有覆盖。这种问题光看答没答对是永远发现不了的还得靠分维度指标和日志来定位。6. 几条踩坑后的真心话我自己做 agent-skills 这一年多下来最大的体会是技能体系的复杂度不会消失只会转移。你不做技能拆分复杂度就堆积在 prompt 里每次修改都是一次渡劫做了技能拆分复杂度转移到了调度、上下文、安全和测试上但每一块都能用工程手段去解决这是本质区别。如果你正准备在项目里引入 agent-skills我的建议是从小处起步先挑三个最高频、流程最标准的场景做成技能包跑通整个开发、测试、上线的流程再逐步扩展。一上来就想把十几个技能一次做齐大概率会在调度和调试环节被拖垮。最后再分享一个小技巧技能描述里的典型示例问句一定要从真实用户对话里收集不要自己编。自己编的问句往往偏书面、偏规范真实用户说话随心所欲示例越贴近真实表达模型命中技能的准确率就越高。这一条看起来不起眼对整体效果的贡献却比很多花哨的架构设计都大。