☰
agent-skills实战:从工具函数到结构化技能库的Agent工程化改造
2026/10/14 9:19:39 网站建设 项目流程

“agent-skills”这个项目名,我是在一次Agent应用大规模重构时真正意识其价值的。当时我们的智能助手已经在生产环境跑了三个月,功能不断叠加,但效果却越来越差——不是模型变笨了,而是我们把太多逻辑硬塞进了System Prompt和工具函数里。每次新增一个能力,就要在提示词里加一段说明、在代码里加一个分支,最后提示词膨胀到八千多字,模型开始频繁漏调用、错调用。就在那时候,我把目光转向了agent-skills,也就是把Agent的能力从“零散的函数列表”升级成“结构化的技能库”。这篇文章就围绕这个主题,整理我搭建技能库的完整思路、目录设计、路由机制、实测数据和踩坑记录,给正在做Agent工程化落地的朋友一个可以直接参考的实施方案。

1. 当“工具调用”变成“技能体系”:agent-skills到底解决了什么问题

1.1 工具函数越来越多,Agent反而越来越笨

早期做Agent,大家普遍的做法是给模型暴露一堆Python函数,每个函数代表一个操作,然后在提示词里写清楚这些函数怎么用。比如我们的客服助手,一开始只有四个工具:查订单、查物流、退换货申请、转人工。模型表现还不错,调用准确率能到九成以上。

后来业务部门要求陆续加功能,工具数量从四个膨胀到了三十七个。按惯例,我们继续往提示词里堆函数签名和说明,结果模型开始出现几种古怪的行为:本来该调用“查物流”的时候,它去调了“查订单”;用户在问退款政策,它却调用了“创建退款工单”;更离谱的是,有些高频工具明明能解决问题,模型就是不调用,非要自己凭着训练知识瞎答。

问题出在哪?工具函数本身是无状态的、平铺的、缺乏语义上下文的。函数签名和描述只能告诉模型“这个函数能做什么”,但没法告诉它“什么情况下应该用它”“用了之后会产生什么副作用”“和相邻的几个工具是什么关系”。模型在几十个平铺的函数里做选择,本质上就是在做一次高难度的意图分类,出错率自然会随着选项增多而上升。

1.2 agent-skills的核心转变:从“函数清单”到“能力单元”

agent-skills的思路,是把Agent的每个能力封装成一个自包含、声明式、可路由、可组合的技能单元。一个技能不再只是一段可执行代码,而是把触发条件、参数Schema、执行逻辑、副作用声明、验证用例全部打包在一起,成为一个完整的“能力模块”。

我喜欢的类比是:工具函数像散落在工具箱里的螺丝刀、扳手、电钻,模型得自己辨认哪个是哪个、该怎么用;而技能库像是一个贴好标签的工具墙,每个工具都挂在固定的位置,旁边写着适用场景、使用方法和注意事项。模型要做的不再是“辨认”,而是“匹配”。

这个转变的实际效果非常明显。技能库上线后,我们把原来的三十七个工具重组成了十九个技能,模型的调用准确率从84%回升到了96%,而且在新能力接入时,再也不用无限堆提示词了。

1.3 什么样的项目适合引入agent-skills

不是所有Agent都需要技能库。我接手的那些项目,规模小、工具少、交互简单,硬上技能库反而增加维护成本。但现在我判断一个项目是否适合引入agent-skills,主要看四个信号:

第一,工具数量超过十五个,并且还在持续增长。第二,多个工具之间存在连锁调用关系,比如“查订单”后面经常跟“申请售后”。第三,同一个工具在不同业务场景下的触发条件差异很大。第四,你需要对Agent的能力做版本管理,而不是每次改动都靠改提示词来硬撑。

如果你的项目出现了这四种情况中的两到三种,agent-skills这套模式就能给你带来质的改善。下面我从头到尾讲一遍我是怎么落地的。

2. 从零搭建一个技能库:目录设计、Schema模型与注册机制

2.1 技能目录的物理组织方式

我最初踩的第一个坑,就是把所有技能文件堆在一个目录里,结果三个月后自己都找不到哪个技能是哪个。后来参考后端的模块化规范,把技能目录按“领域+动作”的维度拆开,每个技能一个独立目录,结构如下:

skills/ ├── order/ # 订单域 │ ├── query_status/ # 技能:查询订单状态 │ │ ├── SKILL.md # 技能描述(模型读这个) │ │ ├── schema.json # 参数Schema │ │ └── action.py # 执行逻辑 │ ├── apply_refund/ │ │ ├── SKILL.md │ │ ├── schema.json │ │ └── action.py ├── logistics/ │ ├── track_package/ │ ├── modify_address/ └── common/ # 跨域共享技能 ├── get_customer_info/ └── verify_identity/

每个技能目录里的SKILL.md是核心,它就是给模型看的“使用说明书”。schema.json负责约束参数的格式和必填项,action.py是真正执行的代码。这样分完之后,有三个好处:新增技能只需要加一个目录;测试可以按目录批量跑;查看某个技能时,描述、约束和实现都在一个地方,不需要跨文件跳转。

2.2 SKILL.md的写法:决定了模型能不能正确调用

SKILL.md写得好不好,直接决定了模型在复杂场景下能不能正确触发这个技能。我经历过几个版本的迭代,最后总结出一套比较稳定的模板,核心字段包含以下几个方面。

技能名称要简短唯一,建议用“动词_对象”的形式,比如查询订单状态、申请退款审核,不要用“订单处理”这种模糊的名称。技能描述是整个文件的重中之重,不能只说“查询订单状态”,必须写清楚“什么情况下会用到这个技能”,包括正面触发条件和反面排除条件。参数声明要非常具体,列出每个参数的类型、必填性、取值范围、默认值,以及参数之间的依赖关系。副作用声明是很多人容易忽略的,任何会产生外部影响的技能,比如发消息、创建工单、修改数据库,都必须说清楚副作用,避免模型在不确定时乱调。执行前置条件,比如需要用户身份验证,也要在描述中写清楚。

我拿“申请退款”这个技能举例,最初的描述只写了一句话:“申请退款”。模型经常在用户咨询退款政策时就触发它,导致误创建工单。加了详细描述之后,误触发率明显下降,描述大致是这样的:

技能:申请退款审核 触发条件:用户明确表示要对某个订单发起退款/退货请求,且该订单已送达或处于可退款状态。 排除条件:用户仅询问退款政策、退款时效、能否退款,并未明确要求发起退款时,不得触发本技能。 前置条件:必须已完成用户身份验证,且提供了有效订单号。 副作用:该操作会在售后系统创建一张退款工单,且会发送短信通知用户。 输出:返回工单编号和预计审核时效。

这段描述看起来啰嗦,却正是模型做正确路由的关键信息。最需要记住的一点是——描述要面向“何时用”和“何时不用”,而不是只写“能干什么”。

2.3 Schema模型与类型校验:别让模型随机填参数

聊完描述再来看参数约束。早期我们的action.py里全靠模型自己生成参数,结果经常出现订单号传错、日期格式传成自然语言、枚举值超出范围之类的问题。后来我在每个技能目录里放了一个schema.json,用JSON Schema的规范来约束参数结构。

以“查询订单状态”技能为例,schema如下:

{ "name": "query_status", "description": "查询订单状态", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "pattern": "^ORD\\d{10}$", "description": "订单号,以ORD开头,后接10位数字" }, "customer_level": { "type": "string", "enum": ["普通", "VIP", "SVIP"], "default": "普通" } }, "required": ["order_id"] } }

有了这个schema之后,模型在调用前会先按约束“思考”参数的合法性。如果模型生成的参数过不了类型校验,我们的执行层会返回一个结构化错误,里面包含“期望的格式”和“实际收到的格式”,模型看到错误后会自动修正重试。这套机制把参数错误率从12%压到了2%以内。

2.4 技能注册机制:静态注册和动态扫描哪个更可靠

技能写好了总要被系统发现。我在两种注册方式之间都做过尝试。第一种是静态注册,在配置里手动列出所有技能,好处是路径清晰、启动快、依赖关系明确,缺点是新增技能要改两处配置,漏了就会导致技能“迷失”。第二种是动态扫描,程序启动时自动遍历skills目录,读取每个SKILL.md和schema.json完成注册,好处是零配置、扩展方便,缺点是路径出错时问题暴露到运行时。

我的建议是线上环境用“动态扫描+静态锁”的组合拳:目录自动扫描生成技能清单,同时维护一个已注册清单做比对,启动时如果发现某个技能没被识别,直接报警。这样既保证了扩展性,又能在第一时间发现清单不一致的问题。

3. 技能编排的三层管线:意图路由、参数补齐与执行回退

3.1 第一层:语义路由,不靠if-else匹配技能

有了技能库还不够,Agent的核心问题是如何在合适的时机选出合适的技能。很多第一次做技能库的人会在这里犯一个惯性错误——写一个路由函数,用关键词匹配去决定调用哪个技能。

比如用户说“我的快递到哪了”,路由函数匹配“快递”就去查物流;用户说“订单状态”,匹配“订单”就去查订单。这看起来直接,实际一上线就会崩。用户的话术千变万化,“我的包裹显示签收但没收到”“物流信息三天没更新”“退款什么时候到账”这些说法,用关键词匹配根本覆盖不全。

正确做法是让路由判断回归模型本身。我们把所有技能的SKILL.md描述汇总起来,每次用户请求进来时,先把用户意图描述和候选技能描述交给模型做语义召回,让模型输出最匹配的技能编号和参数。这里不做排名的话效果会差很多,所以我的设计里是这样一个流程。

第一步,召回阶段。把技能描述和用户请求一起发给轻量模型,让它返回可能相关的前三个技能编号,这一步快速缩小候选范围。第二步,排序阶段。将候选技能完整信息,包括参数Schema和副产品说明,和用户对话上下文一起发给主模型,由主模型做最终决策,判断到底用哪个技能、要不要调、参数是什么。第三步,调用阶段。主模型返回结构化的调用指令,进入参数校验和执行层。

第三层的设计很关键,它把“要不要调用”的决策权完全还给模型,而不是预设“用户提到物流就一定要调物流”。在实际效果上,加入语义召回后,误调用率下降了约七成。

3.2 第二层:参数补齐策略,缺什么就问什么、能推断的就推断

参数丢失是技能调用的高频故障。用户说“帮我查一下订单”,但没说订单号,模型直接调用就会因为缺参而失败。我早期做法是直接返回错误,让用户重说一遍,体验很差。

后来我在技能执行层加了一个参数补齐组件,逻辑分为三档。第一档,能从上下文推断的参数直接填上,比如用户前一秒刚报了订单号,可以默认延用。第二档,能从系统侧获取的参数静默补全,比如用户ID、当前时间、用户等级,这些不需要问用户。第三档,真正缺失且无法推断的参数,才进入“缺参追问流程”,由模型生成一句自然语言的追问,而不是直接报错。

追问也有讲究。模型问用户“请提供订单号”是对的,但如果用户已经在上一句话里给了订单号,只是格式不标准,应该先尝试标准化,比如把“ORD123”补成“ORD0000000123”,再确认给用户看,而不是机械地反问。这套补齐策略落地后,技能的首次调用成功率从58%提升到了81%,用户感知到的“对话流畅度”提升非常明显。

3.3 第三层:执行回退,一次调用失败不等于整个任务失败

技能执行过程中总有意外,接口超时、数据缺失、权限不足,这都是常态。最初我们的Agent一遇到执行错误就直接把错误信息抛给用户,说是“系统开小差”,这明显不合格。

我后来给执行层设计了三段式回退策略。第一段是重试,针对瞬时性错误,比如网络超时和限流,延迟重试一到两次。这里要特别注意,重试必须安排在副作用发生之前,比如一个技能已经发送了短信,再重试就会造成重复发送。第二段是替换技能,当主技能失败且能确认失败原因时,尝试调用一个功能相似的备选技能。比如“查询物流”服务的接口挂了,但“查询订单备注”里可能带着物流信息,就能作为兜底路径。这里需要在SKILL.md里预先声明fallback技能,允许一级跳转,不允许无限递归。第三段是询问用户。如果重试和替换都失败,就如实告诉用户“这个操作暂时无法完成”,并提供人工替代入口,而不是让模型硬编一个结果出来骗用户。

回退策略的价值,不只是提高了成功率,更重要的是让Agent在面对失败时有了一套稳定的行为规范,不会在异常路径上越走越偏。

4. 实测对比:纯工具调用与技能库在三种典型场景下的差异

为了看得更直观,我把记录的实际对比数据整理在这张表里,场景都是在接近生产的仿真环境里用同一套模型、同一套上下文跑的,每次场景测了两百条用户话术,统计准确率用的是“正确完成任务的比例”。

场景纯工具函数方案agent-skills技能库提升幅度
售后客服:涉及查单、退款、物流三门以上能力的综合对话准确率84%,平均需要Model重试2.3次才能收敛准确率96%,大多情况下1次调用直接成功准确率+12%,重试次数-60%
数据运营助理:让Agent用自然语言完成数据筛选、汇总、生成图表经常混淆“筛选”和“汇总”两个工具,图表生成时机偶尔错乱准确率91%,技能描述里写清了执行顺序和互斥关系准确率+9%
办公自动化:读取邮件附件、解析CSV、写入表单系统附件解析经常选错解析器,CSV格式判断不准准确率93%,解析格式被统一收敛到“文件解析技能”的参数枚举里准确率+11%

表格里的“重试次数”很能说明问题。纯工具方案下,模型要把一句话反复“猜”两三次才能正确对齐工具和参数,这种不稳定在用户侧表现为“反应慢、答非所问”。技能库方案给每个技能提供了边界清晰的描述和参数约束,模型一次就能锁定目标。那正确的思路和结论你应该已经很清楚了——决定Agent质量的,不只是模型能力,还有技能体系的组织方式。

5. 技能演进中的维护难题:版本管理、技能冲突与回归测试

5.1 技能的版本管理与升级策略

技能不是写一次就不动的。业务规则变了,技能逻辑就要跟着变;模型能力升级了,旧技能描述里的一些约束可能不再需要;甚至连其他技能的改动都可能影响当前技能的触发条件。这里最怕出现的问题,就是技能体量增大之后某个技能行为变了,却没法快速定位是哪次改动引起的。

我参照软件版本号的思路,给每个技能目录额外放了一个version文件,内容格式类似“1.4.0”。版本号的三段各有含义:主版本号变动,说明技能的执行逻辑或副作用发生了破坏性变化,这会影响所有依赖它的上层技能;次版本号变动,说明增加了新的可选参数或优化了描述,不影响既有调用方式;补丁号变动,说明修正了Bug或调整了描述里的措辞,行为保持不变。

版本升级也要讲究节奏。大版本升级不能整个技能库一起上,要做灰度切换。我在执行层里加了一个简单的路由规则,按用户ID哈希的百分比走新版本技能,剩下的走旧版本,观察两天数据没问题再全量切换。这个习惯帮我拦下过两个隐蔽的问题,有一次新版本把“查订单”技能的order_id校验规则改严了,老用户用旧格式单号全部报错,靠灰度及时发现,回滚后业务损失很小。

5.2 技能冲突:描述太像导致的误路由

技能数量多了之后,一个非常隐蔽的问题开始出现——两个技能之间“语义重叠”。举我们实际遇到的例子:有一个技能叫“查询退款进度”,另一个叫“查询售后工单进度”。业务上这两个东西是不同的,但在用户的语境里,“我的退款到哪一步了”和“我的售后处理到哪一步了”几乎是一回事。

模型在路由时经常在两者之间犹豫,甚至随机选一个,导致用户拿到的答案经常对不上。解决方向不是靠调提示词,而是主动做技能交叉评审。我在技能库迭代流程里加了一个“冲突检测”环节:每次有新技能注册时,系统会把新技能的描述和已有技能描述做一次向量相似度计算,相似度超过阈值的自动进入人工评审列表,由我去看这两个技能是应该合并、还是应该把边界描述说清楚。

“查询退款进度”和“查询售后工单进度”最后合并成了一个技能,内部根据工单类型分流,误路由问题直接清零。经验是——技能设计阶段多花点时间做语义隔离,比上线后反复调优要划算得多。

5.3 回归测试:防止改一个技能带崩一片能力

技能库到了一定规模之后,最怕的不是新技能不工作,而是旧技能被悄悄改坏。我自己经历过一次:修改了“获取用户信息”技能的参数校验规则,结果“创建工单”技能因为依赖用户信息字段,在调用链路上直接报错,而且报错信息不明显,排查了大半天。

正是因为这种教训,技能库必须配套一套回归测试机制。不用多复杂,但一定要有。我现在维护的是三个层次的测试用例集:

层级覆盖范围示例
单技能测试单个技能的正常路径和异常路径向“查订单”技能传入合法单号、非法单号、缺失单号,验证返回分别是什么
调用链测试两个以上技能串联的完整流程“查订单”成功后再调“申请退款”,验证参数能否正确传递
全链路回归集模拟真实用户对话片段,验证技能路由是否稳定用户说“我昨天买的那个东西怎么还没到”,确认最终落到“查物流”技能

每个技能目录里放一个test_cases.json,记录该技能的典型输入和期望输出。CI上挂着轻量的定时任务,每次改动代码后跑一遍,几分钟就能出结果。这套机制上线后,我记忆中再没出现过“改A坏B”却事后才发现的情况。

6. 技能规模扩大后的架构演进:分层与组合

6.1 为什么技能到五十个之后,平铺结构就不够用了

技能数量超过五十个之后,哪怕每个技能描述都写得很规范,模型在候选列表里的检索效率也会开始下降。描述再清晰,面对五十个甚至八十个平铺技能,模型依然容易出现“选择困难症”。这时候要做的事情,不是压缩描述,而是调整结构。

我采用的策略是给技能增加“层级”。最简单的分层方式是把技能划分成“原子技能”和“组合技能”两层。原子技能是真正执行操作的单元,比如“发送消息”“查询订单”“计算金额”,体积小,职责单一,描述清晰。组合技能则是通过编排多个原子技能来完成一个复杂的业务目标,比如“处理退款申请”,内部可能调用了校验身份、查询订单、计算退款金额、创建工单、发送通知这五个原子技能。

这种拆分设计有几个明显的好处。第一,模型在选择组合技能时,只需要在几个业务目标之间做判断,候选列表大幅缩小。第二,原子技能可以被多个组合技能复用,不会出现“同一个逻辑在多处各写一套”的重复。第三,当业务逻辑发生变化时,只需要调整组合技能的编排,原子技能基本不用动。

6.2 编排文件:把组合技能的流程显式声明出来

组合技能内部怎么做编排?我尝试过在action.py里用Python代码写编排逻辑,也尝试过让模型自己临时决定调用顺序,两种方式都有明显的问题。前者太死板,很难覆盖用户表达的灵活性;后者太随机,顺序混乱时结果不可靠。

最终采用的方式是“声明式编排文件”。每个组合技能额外维护一个flow.yaml,描述执行步骤和依赖关系,类似这样:

skill: process_refund steps: - call: verify_identity on_fail: abort - call: query_order_status params: order_id: $context.order_id - if: $output.status == "已签收" then: - call: calc_refund_amount else: - call: notify_user params: message: "订单未签收,无法发起退款"

flow.yaml的执行由统一编排引擎解释执行,而不是把逻辑塞死在action.py里。这样改起来特别快,我改的是“声明”,不是“代码”,评审和变更记录都清晰得多。模型在这种模式下只负责“决定走哪条路径”,每一步具体怎么执行由编排引擎按flow.yaml驱动。

6.3 不要过度设计:组合层级太深同样会反噬

分层和组合也不是越深越好。我见过有人把技能拆成四级:业务域-场景-组合技能-原子技能,结果模型在路由时多跳了两层,每次调用延迟增加,而且中间层的决策失误会顺着链路放大。我现在的原则是尽量控制在两级到三级,业务简单的时候就用一级平铺,业务复杂了只增加“组合技能”这一层,绝不再继续往下拆。

判断“该不该加一层”的标准很简单:如果拆完层级之后,模型路由准确率没有提升,反而因为链路变长导致失败点变多,那就是过度设计了,果断退回上一版结构。

7. 我踩过的几个坑和现在推荐的工程清单

聊了这么多架构和机制,最后把这些年实际踩过的坑汇总一下,都是线上环境真实遇到过的,按“问题-原因-解法”列清楚,方便你对照自查。

7.1 技能描述过于文学化导致的误触发

第一版SKILL.md里,我为了让描述“更自然”,写了不少口语化表达,比如“当用户拖拖拉拉不付款时,你可以帮他催一下”。效果很糟糕,模型经常在用户正常问价时也会触发“催付”技能。后来我把描述里的所有修辞和情绪词全部删掉,只保留“触发条件-排除条件-副作用”三要素,误触发率立刻降下来了。描述是工程技术文档,不是营销文案,越直白越好。

7.2 把多余的业务逻辑塞进action.py

刚开始做的时候,我为了省事,把很多和技能本身无关的业务判断直接写进了action.py。比如“查订单”技能里塞了一段“如果是VIP用户就顺便拉取专属客服电话”的逻辑。这个技巧看似顺手,实际上让技能变得不可复用,其他技能想复用“查订单”基础能力时,会被这段多余逻辑污染。现在我的原则是:每个原子技能只做一件事,业务策略一律放到组合技能的flow里。

7.3 忽略了技能输出的结构化

早期技能执行完直接返回自然语言文本,比如“订单状态:已发货,物流公司:某公司,单号:123456”,结果下一环节想提取物流单号时还要再做一遍解析,稍微复杂一点的场景就到处卡壳。后来我要求所有技能返回结构化内容,以JSON为主,字段名和类型在schema.json里声明清楚。自然语言文本只在最后面向用户输出时才生成。这个改动让技能之间的衔接顺畅了很多。

7.4 当前推荐的技能库搭建清单

如果你准备在自己的项目里引入agent-skills,可以从下面这个清单开始着手,每一条都是我在实际链路中验证过有价值的步骤。

第一,先梳理现有能力清单,把零散工具函数按业务域归类,确定哪些是原子技能,哪些天然应该成为组合技能。第二,为每个技能补齐文档三件套——SKILL.md、schema.json、test_cases.json,其中描述部分坚持“触发条件+排除条件+副作用+前置条件”。第三,在路由层引入语义召回机制,跳过关键词匹配的诱惑,直接让模型做候选排序。第四,实现参数校验和补齐流程,不要等着模型传参一次到位,校验失败要有结构化错误反馈,支持模型自纠。第五,搭建发布与回滚机制,大版本灰度、小版本自动生效,配合线上埋点监控调用成功率。第六,把回归测试挂在CI里,单测覆盖每个技能的合法与非法路径,至少保证每次改动后旧技能不会静默损坏。

我自己在经历了这一整套改造之后,最大的体感变化是:Agent的定义、调优和维护终于从“玄学”变成了“工程”。过去版本上线总带着莫名的不确定感,现在每次改动技能库,我都能在一分钟内说清楚这次影响范围是什么、测试覆盖在哪里、回滚点在哪里。如果你也正被工具越加越多、效果越调越乱的问题困扰,希望这篇关于agent-skills的实践拆解,能帮你把Agent的能力底座重新理清楚。技能库这个东西,不需要一开始就设计得面面俱到,但方向一定要选对——把每一个技能当作一个严肃的软件模块来对待,Agent的整体表现一定会给你正向的回报。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询