☰
Agent-skills实战指南:从工具到技能,构建稳定可控的智能体能力体系
2026/10/7 17:36:52 网站建设 项目流程

如果让我用一个词总结过去半年做 agent 的实战感受,我会选 agent-skills。这个词在圈子里还没有被完全定义清楚,但它戳中了一个很多团队都绕不开的痛点:我们天天在调 prompt、接 function calling、堆各种 tool,却很少有人认真对待 Agent 到底"会什么"——它拥有的技能边界在哪里,技能之间怎么组织,被调用时如何保证稳定。这篇文章就围绕 agent-skills 展开,聊聊我对这个概念的理解,以及我在真实项目里把一套技能体系从零搭到上线时踩过的坑和最终沉淀出的做法。适合正在做 autonomous agent、深度使用 function calling、或者想把 agent 从 demo 推向生产环境的同学参考。我会尽量把每个设计背后的理由讲清楚,而不是只丢给你一套结论。

1. 先搞清楚:agent-skills 在系统里到底处于哪一层

1.1 技能不是工具,也不是插件

大家口头上经常把 tool、function、skill、capability 混着说,但它们在系统里的位置其实完全不同。工具(tool)是 Agent 可以调用的外部动作,比如一个查询天气的 API;插件(plugin)往往是一个打包好的、可插拔的扩展单元,可能包含多个工具和配置;而技能(skill)在我看来更接近"任务的执行能力"——它是模型通过观察、推理、调用工具并校验结果,最终完成某个具体目标的能力组合。

一个直观的例子:给 Agent 注册一个 send_email 函数,这是工具;如果这个函数附带了一套模板、登录态管理、收件人校验、失败重发逻辑,打包成邮件发送插件,这是插件;而当 Agent 面对"给客户写一封跟进邮件并发送"这个目标时,它知道要先生成正文、再调用发送工具、再检查返回结果、必要时重新措辞——这一整套行为模式,才是技能。

这三层的差异用表格看更清楚:

层次关注点谁来决定调用典型形态
工具单个动作的可用性模型直接选择函数声明
插件一组功能的打包与生命周期框架装载扩展包
技能完整任务的行为闭环模型规划描述+参数+实现+校验

所以在设计 agent-skills 的时候,我一开始就把它放在比 tool 高一层、比 workflow 低一层的抽象上。技能不是一个单纯的函数声明,它是"目标—计划—工具调用—校验"的完整闭环。如果你把技能当工具做,模型就只会把它当成一个孤立函数,根本不会围绕任务目标去编排它;如果你把技能当 workflow 做,又太刚性,业务一变化就得改代码。技能应该处于中间这个位置:任务边界由你定义,边界内怎么走,留给模型一定的自由度。

1.2 从"对话能力"到"任务能力"的关键跃迁

纯对话型的 LLM 应用,你只需要关注上下文和输出质量,模型回答得自然流畅,用户体验就不会太差。但是一旦进入任务型 agent 场景,衡量标准就变了:结果是否正确、过程中是否调用了正确的工具、失败之后是否能自我修正。这个跃迁意味着你不能再把所有希望寄托在 prompt 上,必须把一部分"行为逻辑"显式地建模为技能。

我举个例子。一个客服 agent,如果只是接在聊天框后面,用户说"帮我查订单",它把订单信息读出来回复,这还算简单。但当它要处理"用户退货"这种多步骤任务时,就需要一个完整的退货技能:先确认订单号、再检查退货政策、给出退货运单、记录客服备注、触发仓库系统。任何一个环节掉链子,整个任务就废了。这种技能如果不提前设计,模型在中间的每一步都可能产生随机性,结果就是十个用户十种体验。

我还见过一个特别典型的反例:有人把所有业务逻辑都塞进一个巨大的 prompt,让模型"自由发挥"。结果模型在查订单之后,下一轮直接开始编造退款金额,因为它根本不知道自己的技能边界在哪里,也不知道哪些信息是真实从系统里拿到的、哪些是它自己推理出来的。技能在这里的作用,就是给模型的每一步行为划定"可执行的锚点",让它始终在真实数据的地基上推理,而不是在幻觉里打转。

1.3 为什么技能设计比 prompt 工程更值得投入

很多人觉得 agent 效果不好就是 prompt 写得不够好,于是反复调 prompt。但我后来发现,prompt 是"面"上的东西,技能是"骨架"上的东西。如果骨架定义得清晰,prompt 只需要做风格的润色,稳定性会大幅提升;如果骨架混乱,prompt 再精致也救不回来。

用一个比喻:prompt 是一个优秀员工的沟通话术,而技能是这个员工的能力模型。话术可以训练,但能力边界不清楚,话术再漂亮也没法保证交付。过去半年我最大的体会是,把时间花在定义技能上,产出比花在调 prompt 上高得多。prompt 的修改是反馈式的,改十遍才能逼近一个点;而技能的设计是结构式的,改一次描述、收紧一个参数,可能同时改善几十个相关 case 的表现。

这里有一个很多人忽视的成本因素:prompt 越长,模型每轮推理的 token 消耗越大,延迟也越高。而技能化之后,描述是精简的、参数是结构化的、实现是代码,模型只需要做"选择"而不是"记忆"。从成本角度看,技能体系也远比堆 prompt 更经济。这也是我写这篇文章的初衷——希望大家把重心往技能设计上挪一挪,而不是继续在 prompt 的无底洞里消耗耐心。

2. 设计一套技能的核心拆解:粒度、边界与状态

2.1 技能的粒度:拆多细才算合理

这是第一个让我反复纠结的问题。技能拆得太大,模型拿到手反而不知道从哪里下手;拆得太小,Agent 为了完成一个简单任务要来回调十几次技能,延迟和 token 成本都爆炸,而且上下文里的工具列表也会越来越长。

我最终用的判断标准是:一个技能应该对应一个"可被用户感知的完整结果"。比如"创建工单"是一个技能,而不是"读取用户原始消息""解析用户邮箱""插入数据库"这样拆成三个技能。前者对用户来说是一个完整动作,后者是内部实现细节,应该交给代码脚本而不是模型。技能是给模型的任务入口,实现细节藏在技能内部,这个边界能让模型的决策负担大大降低。

但这里也有一个反方向的陷阱:有些任务看起来是一个完整结果,实际上内部有多个互相独立的决策点。比如"处理投诉",它既涉及判断投诉是否成立,又涉及选择补偿方案,还涉及是否升级给人工。如果把这些合并成一个技能,模型每次都要在同一个技能里做多次高风险的决策,一旦中间选择错误,后面就没有重试的机会。我在这种情况下会把它拆成"投诉预审"和"补偿执行"两步,让模型先做判断,拿到判断结果后再决定下一步。这个取舍没有绝对标准,我的经验是:如果技能内部存在超过两个以上需要依赖外部反馈才能做出的决策,就该考虑拆分。

2.2 技能描述:写给模型看的"使用说明书"

技能描述的质量直接影响模型能不能正确选择技能。我在早期犯过一个错误,就是把描述写成对开发人员友好的文档——"该函数用于检索数据库中的用户信息,返回 dict 类型"。模型读这种描述经常会误用,因为它不理解什么时候该用它、哪些情况不该用、边界条件是什么。

后来我把描述改成了"给一个聪明但对业务不熟的实习生写任务说明"的风格。比如:当用户询问自己的订单状态、物流进度、收货时间等信息时使用本技能;如果用户只是询问退货政策,请使用另一个技能;如果订单号为空,必须先用追问补充信息,不要尝试用空参数调用。实测下来,这种带触发条件、排除条件、前置要求的描述,能让工具选择的准确率明显上升。

我总结描述至少要包含四个部分:触发条件(什么情况下调用)、执行内容(调用了什么、做了什么)、输出形式(返回什么结构、什么字段)、排除场景(什么情况下不要调用)。尤其是排除场景,很多人会忽略。模型对"负面指令"的遵循能力其实不弱,你把不该用的场景写清楚,比只写该用的场景更能减少误触发。比如一个订单查询技能,明确写"如果用户尚未下单,只是询问商品信息,不要调用本技能,应该转给商品详情查询",这就能避免模型在售前阶段错误地进入售后链路。

2.3 参数 Schema:把自由度转化为约束

技能的参数定义,我建议尽量收紧而不是放开。很多框架允许你把参数定义得非常宽松,模型可以自由发挥,看起来灵活,实际上灾难。模型自由发挥参数的结果,往往是该填的没填、不该传的传了,或者字段类型完全对不上。

我习惯给每个参数写清楚类型、格式、允许值枚举,并且明确标注必填与可选。特别重要的一点是:把"调用方需要先知道什么"写进 schema 的描述里。比如一个 cancel_order 技能,参数 order_id 的描述里写清楚"需要先从订单查询技能获得有效的订单号,不能凭空猜测"。这样模型在编排多个技能时,就会自然地形成先查询后操作的正确链路。

参数 schema 设计还有一个很多人忽略的细节:给参数设置合理的默认值和边界校验。比如退款金额这个参数,如果允许模型自己填,它可能填出一个大于订单总额的数字。我建议在 schema 里直接写"金额必须小于等于订单实付总额,且为正数",同时在实现代码里再做一层兜底校验。模型侧的约束和代码侧的校验要同时存在:代码校验是最后一道防线,schema 约束是为了让模型尽量少犯错。两个都做,才能把异常率压到足够低。

2.4 状态与上下文:技能之间怎么交接

单个技能内部的逻辑容易控制,真正难的是技能之间的数据交接。Agent 在执行一个复杂任务时经常要调用多个技能,前一个技能的输出是后一个技能的输入。这一步如果没设计好,任务链就会在中间断裂。

我推荐的方式是:每个技能都定义清晰的"输入锚点"和"输出产物",并且把输出结构设计成可被后续技能直接消费的形式。比如查订单技能返回的 order 对象,包含 order_id、status、items 这些字段;后续的退款技能直接声明接受这个对象。不要把技能做成黑盒,只给一个"成功/失败"的布尔结果,那样下游技能需要重新推理,出错率极高。显式的上下文传递是 Agent 稳定性的基石。

这里我特别想强调"结构化产物的可复用性"。技能输出的每个字段最好都有明确的业务定义,而不是夹在一大段自然语言文本里让下游去解析。比如订单查询技能返回一个 JSON 对象,比返回一段"您的订单正在运输途中"要好得多。前者可以被任何下游技能直接引用,后者只能给人看。既然你的 Agent 是任务型的,那么技能之间的接口就应该像函数签名一样清晰,而不是像聊天记录一样模糊。

3. 实操:技能库的注册、加载与版本管理

3.1 技能清单的目录结构设计

当技能数量超过十个之后,你一定会遇到管理问题。我在项目里的做法是每个技能一个独立目录,包含描述文件、schema 文件、实现代码和测试用例。用 YAML 写描述和 schema,比在代码里硬编码更容易审查和维护。

skills/ order_query/ skill.yaml main.py test_cases.json refund_create/ skill.yaml main.py test_cases.json complaint_handle/ skill.yaml main.py test_cases.json

skill.yaml 里写技能的唯一 ID、版本号、名称、描述、参数定义和返回结构。这样做的好处是,所有技能都可以被自动扫描、加载、注册,而不用每次新增技能都去改主程序代码。我见过不少项目把所有技能堆在一个文件里,前期很爽,后期改动时牵一发动全身,迟早要重构。目录化的结构还带来一个隐藏收益:代码 review 的时候,每个技能的改动都是独立的 PR,不会出现"改了一个技能影响所有技能"的混乱局面。

3.2 一个技能注册器的实现思路

注册器的核心逻辑其实不复杂:启动时扫描技能目录,加载每个目录下的 yaml 和代码,把技能元信息送进模型可感知的工具列表,把实现函数挂到运行时环境。关键不在注册这个动作,而在注册之前的校验。

def load_skill(skill_dir): meta = read_yaml(skill_dir / "skill.yaml") module = import_module(skill_dir / "main.py") validate_schema(meta["parameters"]) run_smoke_test(module, meta["smoke_input"]) return Skill( skill_id=meta["id"], version=meta["version"], description=meta["description"], handler=module.run, output_schema=meta["outputs"], )

我通常会加一道"预热校验":启动时执行每个技能的最小冒烟测试,确认它的 schema 能被正确解析、最少参数的调用路径能跑通、返回的 top-level 字段和 schema 声明一致。任何一项失败,就直接把该技能从注册列表中摘除并报警。这一步在多人协作时尤其重要。有人改了公共库导致某个技能 import 失败,如果没有预热校验,这个技能会在线上静默消失,模型到时候就只会报"没有找到合适的工具"。等你从日志里发现,可能已经过去好几个小时了。

3.3 技能的版本演进与灰度策略

技能不是写完就不动的。业务规则在变,模型能力在变,技能实现也得跟着升级。我踩过的坑是:直接原地修改一个线上技能,结果新逻辑有问题,又没法快速回滚——第二天用户遇到问题,你只能眼巴巴看着代码仓库的历史记录翻找上一个版本。

现在的做法是给每个技能带上版本号,注册时保留 v1、v2 等多个版本,线上默认用最新版,配置中心支持按用户或按流量百分比灰度。灰度策略其实不用做得很复杂,关键是要有。哪怕只是先切 5% 的流量观察错误率,也比全量发布后发现问题要强得多。特别是涉及外部 API 的技能——第三方接口行为变了、限流策略变了,这些都不是你能提前预知的,没有回滚能力会很被动。

版本管理还有一个细节:技能描述的变化比实现变化更需要灰度。因为描述一变,模型理解就变,行为可能整体偏移。我见过一个团队只改了某个技能描述里的一句话,结果该技能的触发率从 60% 直接掉到 20%。所以描述级别的修改,哪怕是改一个标点,在正式环境里也要当一次完整发布来做,不要觉得这是小事。

3.4 技能路由与冲突消解

技能多起来之后,模型可能面临"多个技能都能处理当前请求"的情况。我见过两个查询技能描述相近,模型随机选一个,结果行为不一致。解决方式有两个:一是把描述写成互斥的,明确划分边界;二是引入一个显式的路由技能,先让模型判断意图,再分发给具体技能。

对于技能数量在几十个以内的场景,我更推荐第一种,靠描述互斥来消解冲突,省去一层路由的开销。只有技能数量大到描述已经无法天然区分时,才值得做独立的意图路由层。这个决策一定要在功能体量还没有失控前做,否则后期重构成本很高。我自己的经验是,当一个技能的总量超过四十个,描述互斥的维护成本会急剧上升,那时候再上路由层会比较稳妥。路由层可以简单理解成一个"技能选择器",它的输入是用户原始请求,输出是命中的技能 ID,在主流程之外单独跑一轮,把决策集中起来,而不是让模型在几十个选项里做长尾选择。

4. 真实项目中踩过的坑与修法

4.1 技能太"大"导致模型无从下手

最早我设计过一个"用户全生命周期处理"技能,想让它覆盖从注册引导、订单查询、优惠券到售后投诉的一切用户服务动作。结果可想而知——这个技能的描述长到模型根本处理不过来,实际触发率极低,而且常常在错误的情景中被启用,因为它和目标"太像了"但又有大量前置条件。

后来我把这个巨型技能拆成了五个独立技能:注册引导、订单服务、优惠券查询、售后处理、投诉升级。拆完之后,每个技能的描述短了一半,参数更明确,模型的技能选择准确率肉眼可见地上升。我的教训是:技能描述如果超过 500 字,大概率说明它的职责边界没有划清楚。与其强行让一个技能覆盖所有可能性,不如把它拆成几个边界清晰的小技能,让模型通过组合来完成复杂任务。组合带来的灵活性,远远大于单个技能内部的复杂性。

4.2 描述里的模糊词让模型一次次选错

有一个教训我印象特别深。我在某个技能描述里写了"如果用户情绪激动,请优先安抚",结果模型在正常咨询场景中也频繁触发安抚流程,把一个简单的查订单请求硬生生做成了客服关怀现场。问题就出在"情绪激动"这种词没有量化标准,模型认为任何一个带感叹号的句子都算情绪激动,于是技能误触发率飙升。

修复方式是把可观测的客观条件写进触发逻辑,比如"当用户消息连续包含三个以上感叹号,或明确出现'投诉''赔偿''曝光'等关键词时"。用客观的信号词代替主观判断,模型的调用稳定性立刻提升。给模型的规则越接近"可执行代码的语义",效果就越稳定。这个原则放在所有描述里都适用:能写具体数字就不要写"很多",能写关键词枚举就不要写"类似情况",能写明确动作就不要写"酌情处理"。模型不是一个能读懂潜台词的人,它需要你把规则翻译成它容易执行的指令。

4.3 多技能并行时的上下文污染

Agent 执行一个复杂任务时,有时会并行尝试多个技能,或者在中途切换技能。上一轮技能的中间输出如果没有及时清理,会残留在上下文里,污染下一轮的判断。我遇到过的情况是:模型在调用订单查询后,把查询返回的原始 JSON 本身当作用户的输入来解析,结果一路跑偏,明明是查订单,最后却变成了修改订单地址。

解决方法是给每个技能的执行过程设置"工作区"——技能执行时的中间产物放在隔离的变量空间里,只有显式声明的输出才进入对话上下文。此外我还养成了一个习惯,在关键技能调用完成后做一次上下文归档,把中间推理记录压缩成一行摘要,减少后面轮次的噪音。比如订单查询之后,归档成"已获取订单 12345 的状态,用户等待后续处理",而不是把整个 JSON 响应原样留在历史里。这样模型既不会丢失关键信息,也不会被大段原始数据干扰判断。

4.4 评估缺位:没有指标就没有迭代

技能设计得再用心,如果没有评估集,一切都只是感觉。我早期对技能效果的评价停留在"看起来还行",后来发现这是最大的坑——因为 Agent 有随机性,同一个 prompt 每次结果可能不同,一次肉眼观察根本说明不了问题。你可能刚调完一个描述,肉眼看到两个 case 变好了,就以为一切在往好的方向走,实际上回归集里可能已经有二十个 case 悄悄退化了。

后来我建立一个约两百条真实脱敏请求的回归集,每条请求都标注了"期望调用的技能链"和"期望的输出字段"。每次修改技能后,就在这个回归集上跑一遍,记录技能选择准确率、任务完成率、平均调用轮数。三个指标一出,哪些技能在退化、哪些描述在误导,一眼就能看出来。没有这套评估,我后面几个大的技能重构根本不敢动。

以下是我常用的评估维度表,供参考:

评估维度指标定义典型问题
技能选择准确率正确技能被选中的比例描述歧义、边界不清
任务完成率链路结束后产出符合预期的比例参数错误、状态交接失败
平均调用轮数完成任务消耗的技能调用次数技能粒度过细、路由不当
失败自修正率首次失败后能自行修正的比例错误信息不足、缺校验步骤

这四类指标不是选一个就够,而是需要同时看:选择准确率反映的是"模型知不知道有什么可用",完成率反映的是"选对了之后能不能干完",调用轮数反映的是"效率",自修正率反映的是"鲁棒性"。任何一个指标异常,都指向不同的设计缺陷。回归集也不必追求绝对的大,关键在于覆盖到每个技能的正向场景、反向场景和边界场景,并且标注清晰。哪怕只有一两百条,也比完全没有强得多。

5. 一些我沉淀下来的经验:从哪几个技能开始

如果让我重来一次,我会把 agent-skills 的设计当作一个独立工程来看待,而不是当作 prompt 的附属品。对刚开始做 agent 的团队,我建议从三个最核心的技能起步:查询类技能(信息获取)、操作类技能(写入/变更)、确认类技能(在下发不可逆操作前让用户复核)。这三类技能基本覆盖了绝大多数业务 agent 的主体骨架。

具体到执行顺序,我的建议是先写 schema 和描述,再写实现代码。先把"模型视角里这个技能长什么样"确定下来,比先写代码再补描述要高效得多。因为模型视角决定了触发率和成功率,这是 Agent 体验的根。代码实现反而是最容易的一环。你甚至可以先用一个假的 mock 实现跑通整个技能链路,确认描述和 schema 能引导模型走通任务流程,再回头把真实逻辑填进去。这样开发和验证可以完全解耦。

在这套体系落地之后,我最直观的感受是:Agent 的随机性下降了一个量级,不再是"时灵时不灵"。你可以说某个任务在特定条件下的完成率是 97%,而不是"大概可以吧"。这种确定性带来的信心,是后续所有 agent 能力扩展的底气。做技术的人都明白,一个无法预知结果的东西,你是不敢往上叠更多功能的。技能体系把不确定性框定在可控范围内,这个价值远远大于某个具体技能本身的收益。

最后一个小技巧:每个技能上线前,让一个完全不了解项目的人读一遍它的描述,然后问他"用户说什么话时应该用这个技能"。如果他能准确说出来,这个描述大概率没问题;如果他说得含糊,那模型也一定会含糊,趁早改掉。这个方法几乎不用成本,却是筛掉烂描述最有效的办法。我后来每次新技能上线前都会走这一道流程,也建议你试试。

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

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

立即咨询