“agent-skills”这个标题,拆开看就两个词:agent 和 skills。但市面上聊 agent 的文章太多了,多数都在讲“怎么让 agent 更像人”,真正落到“怎么定义、怎么组织、怎么打磨 agent 会用的技能”这层的内容,反而稀稀拉拉。我最近在一批真实项目里反复折腾了这个东西——从最初随手丢给 agent 几十个函数,到后面整理成一套能稳定复用、能评估、能迭代的技能体系,中间踩了不少坑。这篇就把我对 agent-skills 的完整理解、实操过程和避坑经验写下来,给同样在啃这个方向的人一个能直接参考的样例。
先说清楚,这里的“技能”不是某个大模型的新概念,而是我们自己定义给 agent 的能力单元。你可以把它理解成 agent 世界的“应用商店”:每一种技能就是一个封装好的能力,比如查天气、调数据库、生成报表、发起审批。agent 在接任务时不是把所有代码都背在身上,而是根据任务描述去“调用”合适的技能。这里面的关键问题有三个:技能怎么设计才不容易撞车?技能库存放在哪、怎么管理才不混乱?以及 agent 怎么高效地找到它需要的那个技能,而不是在一堆模糊描述里瞎猜。
1. 先搞清楚 agent 的“技能”到底长什么样
1.1 技能不是 API 也不是提示词
很多人一开始会把“技能”等同于 API 接口或者一段提示词模板,这是最容易踩的坑。API 只是一个函数入口,它本身不包含“什么时候该用、用了会有什么副作用、返回结果该怎么解读”这些决策信息;提示词模板则更偏内容生成,它不具备真实执行能力。
我在实际项目里更倾向于这样定义:一个技能 = 清晰的意图描述 + 可执行逻辑 + 参数规范 + 运行约束 + 失败处理策略。这五个缺一不可。举个例子,“查天气”这个技能不能只写一个getWeather(city)函数,它还要告诉 agent:这个技能适合什么场景、需要什么参数、如果城市不存在怎么反馈、超时了怎么办、数据权限是否满足。这种元信息越完整,agent 在真实使用中的准确率就越高。
1.2 我整理出的技能定义骨架
用一个查看员工信息的技能来演示我会怎么写:
技能ID: employee_info_query 描述: 查询员工基本信息(姓名、部门、职级、入职日期) 适用场景: 人事相关问答、组织架构查看 禁止场景: 薪资明细查询、绩效评分查询 参数: employee_id: 类型: string 必填: true 说明: 员工唯一标识 返回字段: 类型: list 必填: false 默认: ["name", "department"] 权限要求: 普通员工仅可查本人信息,HR角色可查全量 执行入口: backend/actions/employee_info/run.py 超时设置: 3秒 失败处理: 查无此人时回复“未找到该员工,请核对编号”,不做模糊匹配这个骨架在真实运行时的价值非常直接。agent 决策模块拿到任务“帮我看看市场部有谁”时,会扫描技能库里所有技能的描述、适用场景和禁止场景,最终定位到这个技能。如果没有那两行“禁止场景”,agent 很可能会拿这个技能去回答“市场部绩效如何”——后果就是权限漏洞。
2. 技能怎么组织才不会变成一锅粥
一个 agent 项目只要跑两三个月,技能数量就会膨胀到几十甚至上百个。如果不提前设计组织方式,后期基本处于“想加不敢加、想删找不到”的状态。
2.1 按“意图-能力-执行”分组
我习惯把技能分成三层来管理。
第一层是意图层,对应的是用户可能会问的问题类型,比如“人员查询类”“数据统计类”“流程审批类”。这一层不需要写代码,它就是一个索引目录,方便 agent 做初步的意图识别。
第二层是能力层,对应的是具体能执行的动作。比如“读取员工表”“计算部门平均值”。一个意图可以对应多个能力,一个能力也可以支撑多个意图,是多对多关系。
第三层才是执行层,也就是真正跑起来的函数、脚本或者远程服务调用。这里要保证每个执行体都是幂等或可重试的,否则 agent 在故障恢复时重复执行会搞出脏数据。
我在实际维护时发现,只要这三层分清楚,新增技能就变成一个很顺的操作:先挂意图,再映射能力,最后写执行体。如果一上来就埋头写执行体,后续找 bug 会非常痛苦。
2.2 版本与标签管理的具体做法
技能会持续迭代。上个月查员工信息的逻辑和下个月可能完全不同。为了不让 agent 在运行中调用到旧版本,我给每个技能都加了两类元信息:一类是版本号(v1.0.0~v2.1.3),另一类是生命周期标签(draft、stable、deprecated、archived)。
具体规则是这样的:处于draft状态的技能只允许在测试环境使用;stable状态才允许进入生产;一旦有更好的替代方案上线,旧技能打上deprecated,保留 30 天观察期,确认没有流量后转archived并下线。这样整个技能库看起来像一个严谨的应用商店,agent 永远不会在候选列表里看到一堆废弃项目。
另外标签不要只放功能标签,建议加上权限标签和数据敏感度标签。比如“salary_view”这个技能会涉及敏感字段,标签里一定要写permission: hr_only和data_level: high_restricted。我之前就因为漏了这个,导致普通用户通过 agent 的间接提问拿到了不该看的数据。
3. 从零搭一个技能库的实操过程
空谈理论没有意义,下面把我在一个内部运营系统里搭技能库的完整流程复述一遍,如果你也要做类似的事,可以直接对照着走。
3.1 第一步:先盘点 agent 的“刚需能力”
不要一上来就写代码。第一步是拿出纸笔,把所有用户可能提问的类型全部列出来。我当时的做法是拉取历史工单、客服聊天记录和内部论坛提问,做了简单的词频聚类,最终得到六个大类:人员查询、流程指引、数据报表、系统状态、权限变更、知识库检索。
这一步看起来很原始,但它的价值怎么强调都不过分。因为后面技能库里每一个技能,都必须能回溯到某个真实需求。没有需求背书的技能,一律属于过度设计,应该砍掉。
3.2 第二步:定义技能描述与参数规范
以“流程指引”大类里的“请假流程查询”为例,我写技能定义时的核心逻辑如下:
技能ID: leave_process_guide 描述: 返回当前员工的请假审批流程步骤、需要材料、审批时限 适用场景: 用户问“请假怎么走流程”“请假需要什么材料” 禁止场景: 用户问“请假时长怎么算”“请假扣多少钱”,这些归薪酬技能 参数: user_id: string, 必填 leave_type: enum[annual, sick, personal, marriage], 必填 返回示例: - step: 发起申请 handler: 直属主管 timeout: 48小时这里有个关键技巧:描述和适用场景要用“用户会问的原话”来写,不要用开发者的语言。很多技能之所以被 agent 漏掉,就是因为写描述的人太“技术化”,比如写成“本技能用于处理 LeaveRequest 实体的 CRUD 操作”,这种描述 agent 根本匹配不上真实提问。
3.3 第三步:分类、评分与淘汰机制
技能库建好之后,我开始做阶段性的质量评分。评分维度包括调用成功率、用户反馈满意度、平均响应延迟、误用次数。每两周出一次评分表,低于 60 分的技能进入整改或者下架流程。
这个淘汰机制非常有用。比如我最初做了一个“节假日查询”技能,理论上没问题,但实际调用率极低,因为模型自身知识已经能回答大部分节假日问题,技能反而增加了决策延迟。后来我评估后直接把它下架了。这就是很典型的“能不加就不加”原则:每多一个技能,agent 做意图识别时就要多算一次,技能规模越大,决策精度反而下降。所以技能库的重心不光是“能做”,更是“少而精”。
4. 用技能让 agent 真正“叫得动”的经验
技能库建好只是第一步。真正难的部分在于,怎么让 agent 在复杂对话里准确地把用户请求映射到某个具体技能上。我在这里做对了三件事。
4.1 “路由优先级”和技能冲突的取舍
当技能数量超过 50 个时,光靠大模型做意图分类已经不够稳。我引入了一套基于规则的预过滤层:先用关键词和实体命中做一轮粗筛,比如用户提到“薪资”“扣款”就直接锁进薪酬相关技能集合,把候选数量压缩到个位数,再交给模型做精细判定。
这样做的最大好处是大大降低技能“串台”的概率。两个技能描述有交叉时,比如“查假期余额”和“查考勤记录”,它们都和休假有关,但如果靠预过滤先锁定“假期余额”这个唯一候选,agent 就不会犹豫不决了。
碰到不可避免的冲突时,我踩出来的经验是:在技能定义里增加一个priority字段,数值高的优先。但这只是兜底,更重要的是把描述写得足够互斥。两个技能边界清晰,agent 自然不容易选错。
4.2 失败后的自动降级设计
技能执行不可能永远成功。第三方的接口会挂了,数据库会超时,权限会失效。最让我意外的设计点是:很多 agent 项目只写了“成功路径”,没人管“挂了之后怎么办”。结果就是某一个技能报错时,agent 会直接甩一句“当前无法处理”,非常粗暴。
我的处理方式是给每个技能增加降级策略。比如“查员工联系方式”技能如果查不到手机号,可以降级成显示企业邮箱,再不行就返回“请联系行政部”。降级不是简单的 try-catch,而是把“备选动作”作为技能定义的显式组成部分。
这段代码是降级逻辑的骨架:
def employee_contact_query(employee_id): # 主路径:查内部通讯录 result = query_internal_directory(employee_id) if result.phone: return build_response(result) # 第一层降级:查公共邮箱 email = query_public_email(employee_id) if email: return build_response({"email": email}) # 第二层降级:反馈到人工 return build_response({"fallback": "请联系HR共享服务中心"})在设计降级时还要注意一个点:降级路径必须显式告诉 agent 发生了什么。比如返回内容里加一个execution_note字段,写明“主路径失败,已使用备选邮箱结果”,这样 agent 在回答用户时不会表现得前后矛盾。
5. 调试与评估:别靠感觉判断技能好不好
5.1 如何验证技能质量,而不是靠“感觉还行”
我见过很多团队评估技能效果的方式是让几个测试同学跑几个固定问题,然后凭感觉打分。这种方式无法发现问题。一个技能从能跑到好用的距离,比想象中要大得多。
我自己在用的评估方法是三件套:
第一,离线回归测试集。准备 200 条真实历史用户问题,标记好期望命中的技能 ID,每次改动技能库后批量跑一遍路由准确率。这个数字低于 90% 就说明改动有问题,需要回退检查。
第二,线上日志分析。我每天看一次技能调用日志,重点看三个指标:无命中占比、错误命中占比和超时占比。无命中高说明技能描述覆盖不足,错误命中高说明描述有歧义,超时高说明执行链路有性能瓶颈。
第三,人工盲评。随机抽取 50 条实际会话,隐去路由方式,由两个人分别判断“agent 在该场景下选用的技能是否合理”,分岐的地方逐条讨论。这个动作能把模型决策的“隐性错误”挖出来。
这三个方法组合起来,能在问题爆发前兜住大部分意外。
5.2 实战中遇到的高频问题与排查清单
我整理了这段时间出现频率最高的几个问题,基本上每一类都有比较明确的解法。
第一类:agent 有技能不用,非要自己编。通常原因是技能描述和真实用户说法对不上。排查方法很简单,直接把这条用户原话放进测试集,然后看模型能不能检索到对应技能。如果检索不到,说明描述需要重写,要加更多“用户可能说的说法”。
第二类:多个技能同时被命中,agent 选错。这说明技能边界有重叠,我会去修改描述里的“禁止场景”字段,让两个技能明确划清界限。极端情况下会引入一个技能合并的候选方案,比如两个查询类技能干脆合成一个。
第三类:技能执行成功,但 agent 给用户的回复内容错误。这通常不是技能本身的问题,而是执行结果解析环节的问题。比如技能返回的是一个嵌套 JSON,模型取错了字段。我的做法是调整返回结构,让最核心的信息永远处于最外层,并且加上response_text这样的预制文案字段,让模型优先引用。
第四类:技能调用时延很高,用户体验差。最常见的原因是执行链路里串了太多无关的预处理。比如一个查数据库的技能,前面还得先调一次权限校验、一次审计日志写入、一次埋点统计,全串行执行,耗时直接翻倍。解决方式是把这些旁路操作改成异步,或者用本地缓存先挡住 80% 的重复查询。
下面是我整理的一个速查表:
| 现象判断 | 可能的根因 | 优先排查方向 |
|---|---|---|
| 技能未被调用 | 描述与用户表述不匹配 | 重写适用场景,加入真实问法 |
| 技能选错 | 边界与其他技能重叠 | 增加禁止场景,明确边界 |
| 返回内容不对 | 执行结果解析出错 | 简化返回结构,增加预制回复文案 |
| 调用超时 | 旁路操作串行过多 | 旁路异步化,增加缓存层 |
| 权限拦截 | 标签缺少敏感级别 | 补充 permission 与 data_level 标签 |
5.3 让技能库具备自我迭代能力的两个习惯
最后分享两个我坚持了比较久的习惯,它们对技能库长期健康程度影响很大。
第一个习惯是:每次用户反馈“答错了”,不管是产品经理转过来的,还是日志里发现的,都先归类到技能问题而不是模型问题。绝大多数“答错”背后其实是技能缺失、技能描述有误导、技能返回数据结构不合理。模型本身反而很少出错。
第二个习惯是:建立一个“技能灵感本”。任何人在工作里遇到重复性信息查询,都可以登记为候选技能。包括“查会议室空闲情况”“查项目里程碑”“查客户合同截止日”。这些单看事件本身不复杂,但积累起来就是 agent 长期价值的来源。每两周从灵感本里挑一两条落地成真实技能,比一次性规划一个大而全的技能库要靠谱得多。
我自己做下来最大的心得体会就是:agent-skills 本质上不是写代码的问题,而是知识工程和产品设计问题的综合体。技能技能,先得有“技”,也就是能稳定执行的逻辑,更要有“能”,也就是在正确的时间用正确的方式被唤醒的能力。把这两条都照顾好,agent 才会真正从一个聊天玩具变成可靠的工作伙伴。