前阵子维护一个内部知识库问答 Agent,工具函数从最初的 8 个一路涨到了 40 多个。prompt 里塞满了 function schema 的 JSON 定义,模型开始频繁选错工具——明明该查订单状态的,它去调了库存接口;明明该走退款流程的,它直接调了最高权限的审批接口。更头疼的是,业务同事想新增一个能力,还得等我改代码、发版、重新跑一遍回归。那段时间我基本每天都在当"工具保姆"。
后来我把项目里那套"工具调用"重新梳理了一遍,换成了Skill 调用机制——把每个能力打包成"触发条件 + 操作流程 + 知识约束 + 可选脚本"的独立技能单元,让模型按需加载、按说明执行。这个改造做完之后,工具选错率降了大半,新增能力的流程也从"改代码发版"变成了"往 skill 目录里加一个文件夹"。这篇文章就把这套架构设计的思路、运行时链路、以及落地时那些文档里不写的坑完整梳理一遍,给正在做 LLM 工具调用、Agent 工程化的朋友做个参考。
1. Skill 调用机制到底是什么:从一场"工具爆炸"事故说起
1.1 我为什么弃用纯 Function Calling
先说清楚我最初遇到的问题。Function Calling(也叫 Tool Use)是现在大多数 LLM 平台都支持的基础能力:你把工具的 JSON Schema 告诉模型,模型根据用户请求决定"要不要调、调哪个、传什么参数"。这个机制在小规模场景里很好用,十几个工具以内,模型基本不会选错。
但当工具数量冲到 30 个以上,问题就来了。
第一是上下文被 Schema 占满。每个 function schema 平均要消耗 200~400 token,30 个工具就是近一万 token 常驻在每次请求里。模型在这个噪音环境下处理用户问题,注意力被稀释得很厉害,回答质量和选工具准确率一起下滑。
第二是相似工具难以区分。当你有get_order_status、get_order_detail、query_order_logistics三个工具,描述写得不仔细,模型根本分不清该调哪个。我踩过的真实案例是:用户问"我的快递到哪了",模型去调了get_order_status,返回的是订单状态而不是物流轨迹,前端展示出来的物流信息全是错的。
第三,也是最本质的:Function Calling 只描述了"有哪些操作",没说"什么时候用、按什么顺序用、用之前要做什么检查"。比如退款这个能力,业务上要求先校验订单是否已发货、再计算应退金额、最后判断是否超过免审批额度。这套流程逻辑如果只靠模型自己"悟",它大概率会跳过某个环节。
第四是维护成本。每加一个工具都要改代码、发版、更新 Schema。业务方提的需求排着队,我一个后端被工具定义和 prompt 调优占满了时间。
1.2 Skill 与函数调用的本质差异
Skill 解决的不是"能不能调用"的问题,而是"会不会按规范办事"的问题。两者的差异可以用一张表说清楚:
| 维度 | Function Calling | Skill 调用机制 |
|---|---|---|
| 描述单位 | 单个操作(如查询订单状态) | 完整能力流程(如处理退款) |
| 内容形态 | JSON Schema(参数、类型、必填) | 自然语言说明 + 步骤 + 约束 + 脚本 |
| 注入时机 | 全部常驻上下文 | 按需动态加载 |
| 是否包含业务知识 | 不包含 | 包含流程、规则、反例、边界条件 |
| 维护方式 | 改代码、发版 | 新增/修改 Skill 目录,独立版本 |
| 执行方式 | 模型发起一次调用 | 模型按说明逐步执行,中间可调脚本 |
打一个生活化的比方:Function Calling 像是给用户一个自动售货机,上面每个按钮对应一种饮料,按一下就掉出来。Skill 则像是给一个"新员工"发了一套标准作业手册——手册里写着"客户说要退货,你先核对小票、再看商品是否拆封、最后填写退款单",同时旁边还配了计算器和验钞机(也就是辅助脚本)。售货机永远只处理单次动作,新员工才懂得完整流程。
这个区别决定了架构设计的方向:Skill 是把"流程知识"这种原本只能靠 prompt 硬塞的内容,变成了可管理、可复用、可按需加载的工程制品。
1.3 一个 Skill 的最小构成:不只是"提示词"
很多第一次接触 Skill 的人以为它就是个升级版 prompt,把一段 System Prompt 写规范点就算 Skill 了。实际上,一个工程上可用的 Skill 至少包含三个部分。
skills/ └── order_refund/ ├── SKILL.md # 技能说明书,模型照着执行的主文档 ├── scripts/ # 可选:辅助可执行脚本,处理确定性逻辑 │ ├── check_refund_eligibility.py │ └── refund_calculator.py └── assets/ # 可选:参考资料,如退款政策、错误码映射 └── refund_policy.md- SKILL.md 是灵魂,它用自然语言描述了这个技能什么时候启用、分哪几步执行、有哪些硬性约束、输出格式是什么。模型实际上是在"读说明书 + 照做"。
- scripts/ 是手脚,专门放那些确定性强的逻辑:金额计算、状态校验、数据格式转换。这些事交给代码做远比让模型"心算"可靠,脚本执行结果会回传给模型继续判断。
- assets/ 是资料库,放一些不该全部写进 SKILL.md 的大段参考信息,比如完整的退换货政策文本。模型在执行到相关步骤时,可以像查手册一样去读取。
这个结构最大的好处是职责分离:判断力和临场应变交给模型,确定性的计算交给代码,参考知识交给资料。三者各干各的,哪一层出了问题就单独修哪一层,不会像"巨型 prompt"那样改一处崩全局。
2. 运行时四大环节:注册、发现、注入、执行
2.1 注册与清单解析
Skill 调用机制的运行时链路,我把它拆成四个环节:注册(Register)、发现(Discover)、注入(Inject)、执行(Execute)。第一个环节是注册。
系统启动时,Skill 管理器会扫描技能目录,读取每个 Skill 的SKILL.md,解析其 YAML frontmatter,构建一份全局技能索引。这一步必须做严格的字段校验,缺一个必填字段就拒绝加载并报错,而不是等到运行时才发现技能坏了。
--- name: order_refund description: 当用户申请退款、取消订单,或询问退款到账时间时,完成资格校验、金额计算与审批流转 version: 1.2.0 tags: [order, refund, finance] requires_tools: [get_order_info, query_wallet] ---我建议至少校验这几个字段:name(唯一标识)、description(用于后续匹配)、version(版本管理)、requires_tools(声明依赖的底层工具)。这里有一个容易忽略的点:Skill 清单解析必须是幂等的,服务重启、热更新、多副本部署时,同一个技能不能重复注册。我们当时因为热加载实现不严谨,出现过技能被索引了两次、匹配时重复注入双份说明的情况,模型执行步骤就乱套了。
2.2 发现与匹配:描述文本是命中的关键
注册完成后,系统面对一个用户请求,要先决定"该加载哪些技能"。这一步我称之为发现与匹配。目前主流做法有两种。
一是向量检索式匹配:把技能描述向量化,用户请求也向量化,算余弦相似度,取 Top-K。这种方式成本低、延迟小,缺点是纯粹靠语义相似度,碰到描述不够具体的技能会漏召回。我个人习惯把相似度阈值设在 0.75 左右,低于阈值的技能不加载——宁可不到位,不要乱加载。
二是模型判断式匹配:系统把所有技能的 name + description 浓缩成一个候选清单,让 LLM 从中选最合适的技能。这种方式更准确,但多一次 LLM 调用,延迟和成本都更高,而且候选清单本身如果太长,模型还是会选错。
实际项目里建议混用:先用向量检索粗筛 Top-5,再用 LLM 精排选出真正需要的 1~2 个。
这里我要反复强调一个经验:description 写得好不好,直接决定命中率。好的 description 要写清触发场景、对象、动作和预期结果,坏的 description 则含糊其辞。对比一下:
# 坏描述 负责订单相关操作 # 好描述 当用户申请退款、取消订单、或询问退款到账时间时,使用本技能完成资格校验、 金额计算与审批流转;当用户仅查询物流轨迹时,不要使用本技能。好描述里不仅写了"什么时候用",还写了"什么时候不用"。这个负向触发条件特别重要,它能挡掉一大批"看似相关实则无关"的误匹配。
2.3 上下文注入:预算控制与注入位置
技能匹配完成后,系统要把 SKILL.md 的内容注入到模型上下文里。这一步有三个决策点:注入哪些、注入多少、注入到哪。
注入策略很直接:技能少就全量注入,技能多就按匹配结果选择性注入。我建议给上下文做一次显式预算。以 128K 上下文窗口为例,我的分配逻辑是:
- 系统提示词与全局规则:约 8K token,常驻
- 对话历史:按滚动窗口保留最近 20 轮,约 20K token
- 技能与工具区:预算上限 16K token
- 用户当前输入:剩余空间
在这个预算下,每个 SKILL.md 平均 2K token,那么一次请求最多注入 6~8 个技能。超出预算的技能按匹配分排序截断,并在日志里记录"哪些技能因预算被截断",方便后续复盘。
注入位置也有讲究。常驻技能(比如对话规范、安全红线)放系统提示词;动态加载的技能放用户消息之前、历史对话之后的独立区块,并用明显的分隔符标记。我实测下来,放中间区域比放最前面更容易被模型准确遵循,因为模型在读取用户最新问题时,技能说明还停留在"工作记忆"里,不会被长历史对话冲淡。当然这个结论依赖具体模型,你们需要在自己的场景里 A/B 测一下。
2.4 执行闭环:模型不是在"调用",而是在"照章办事"
最后一个环节是执行。很多人会混淆"执行 Skill"和"调用 Function",实际上这是两种完全不同的执行模式。
Function Calling 的模式是:模型说"我要调这个函数,参数是这些",系统执行函数,把结果返回给模型。整个过程模型只负责"决定",代码负责"执行"。
Skill 的执行模式是:模型先"读"SKILL.md 的完整步骤说明,然后逐步执行——每一步都可能调用脚本、查资料、做判断,最后产出一个符合预期的结构化结果。也就是说,模型同时扮演了"流程执行者"和"判断决策者"。
以"退款处理"Skill 为例,SKILL.md 里会要求模型:
- 第一步:调用
check_refund_eligibility.py校验订单是否符合退款条件; - 第二步:若符合,用
refund_calculator.py计算应退金额; - 第三步:判断金额是否超阈值,超阈值则生成人工审批单,否则提交自动退款;
- 最后:输出固定格式的结构化结果,比如
{"status": "success", "refund_amount": 128.00, "next_action": "auto_refund"}。
每一步的中间结果都会回到模型手里,模型再决定下一步怎么走。这和执行一个"函数"有本质区别——它是一整个带分支判断的流程在模型层面跑通。为了保证收尾干净,每个 Skill 必须在末尾声明"输出格式规范",并且运行时要做 JSON 校验,格式不对就重新生成一次(最多重试两次,再失败就走降级逻辑,这一点后面详说)。
3. Skill 的边界感:和代码、RAG、Agent 怎么分工
3.1 Skill 封装"怎么做",代码封装"做什么"
我见过不少团队在做 Skill 化改造时犯同一个错误:把本该写在代码里的确定性逻辑,硬塞进了 SKILL.md 的自然语言里。比如退款金额计算公式,用三段话跟模型讲清楚阶梯折扣怎么算,结果模型每次算出来的金额都不太一样,还振振有词地给出不同的"理解"。
我的原则很简单:凡是能确定执行的事情,一律放进 scripts/ 用代码写死;凡是需要判断和灵活处理的事情,才写进 SKILL.md 给模型发挥。
怎么区分两者?问一个问题:这件事的结果是否唯一?如果输入相同、输出必须完全一致,那就是确定性逻辑,交给代码;如果同一个场景在不同上下文里有不同最优解,那就是判断性逻辑,交给模型。
拿退款举例,"计算应退金额"是确定性逻辑,必须用脚本算;而"这笔退款是否属于特殊善意补偿"是判断性逻辑,因为它依赖用户历史、客服记录、业务弹性的综合权衡,就让模型根据 SKILL.md 里的原则来做判断。
这样分工还有一个附带好处:确定性逻辑可以被单元测试覆盖。refund_calculator.py可以直接跑 pytest 验证正确性,而如果用自然语言描述公式,你根本没法测试"模型是否理解对了"。
3.2 粒度控制:一个 Skill 该多大
Skill 的粒度是个反复踩坑的地方。太粗,SKILL.md 里塞了十几条"如果……那么……"的复杂分支,模型读着读着就迷失方向,执行时经常跳过关键分支;太细,技能数量爆炸,匹配阶段就容易选错,维护成本也高。
我总结了一个实用的粒度经验,满足这三条就是一个合适的 Skill:
- 一个明确的触发场景:技能描述里能一句话说清"什么情况用它";
- 3 到 7 个执行步骤:少于 3 步说明它可能只是底层工具,不需要包一层;多于 7 步说明它可能该拆成多个子技能了;
- 一种核心输出类型:要么是"结构化数据",要么是"一段文本回复",要么是"一个文件产物",别混。
如果某个 Skill 的 SKILL.md 正文开始超过 2000 token,或者步骤列表里的 "如果……那么" 分支超过三条,我就开始考虑拆分。我在实际操作中的做法是:把原来一个大 Skill 拆成"主流程 Skill + 子步骤 Skill",主流程 Skill 在其某一步中显示地要求模型"调用payment_approval技能来处理审批环节"。这种嵌套调用在模型能力较强的模型上工作得很好。
3.3 Skill 与 RAG、Workflow、Agent 的关系
很多读者会问:Skill 和 RAG、Agent、Workflow 这些概念到底什么关系?我用一句话概括各自的职责:
- RAG 管知识:解决"模型不知道"的问题,注入的是事实性内容;
- Skill 管流程:解决"模型不会按规范做"的问题,注入的是操作流程;
- Agent 管决策:决定"现在该调用什么能力",是运行时本体;
- Workflow 管编排:在多技能协同场景下,固定技能之间的衔接顺序和数据流转。
它们在同一个系统里是协同关系,不是替代关系。我当前项目的架构大致是:用户请求进来,Agent 负责意图理解和技能选择;选中的 Skill 负责流程执行;执行过程中如果需要事实数据,Skill 内部可以调用 RAG 检索接口;如果用户意图需要跨多个技能协作(比如"整理订单数据并生成周报"),则由 Workflow 层把"订单导出 Skill"和"周报生成 Skill"串起来。
一个容易犯的错误是把 Skill 当成 RAG 的平替。Skill 里虽然可以引用外部资料文件,但它存在的意义是"规范动作",不是"补充知识"。如果用户问"退款政策是什么",正确做法是走 RAG 检索政策文档;如果用户说"我要退款",正确做法是走退款 Skill。这两类需求的处理逻辑完全不同,混在一起会让匹配和注入都变形。
4. 工程落地:从零搭一套可复用的 Skill 调用系统
4.1 目录规范与版本管理
理论说再多,不如给一套可以直接抄的工程规范。这是我当前项目中使用的 Skill 目录标准:
skills/ <skill_id>/ SKILL.md # 必填,技能说明书 scripts/ # 可选,确定性逻辑脚本 main.py requirements.txt assets/ # 可选,参考数据与模板 tests/ # 可选,技能级测试用例 CHANGELOG.md # 可选,变更记录我把 Skill 单独放在一个 Git 仓库里,与应用主仓库解耦。这样做有三个好处:
- 业务同学可以在不影响主服务代码的前提下,通过提 PR 来维护技能内容——这是 Skill 化改造带来的最大解放;
- 每个 Skill 可以独立版本化。UI 采用 semantic version(如
1.2.0),应用运行到哪个 Skill 版本由注册表里的锁版本机制决定; - CI 可以做针对性的校验。每次 PR 都跑一遍 frontmatter 校验 + 测试集,不合格就不合入。
这里特别提醒一点:Skill 仓库和应用仓库解耦之后,必须增加"版本兼容性"检查。Skill 声明的requires_tools如果依赖某个底层工具接口,而应用端已经删掉或改了那个工具,Skill 跑起来就会报错。我们的做法是在 CI 里加一个静态检查:把 PR 中 Skill 声明的工具依赖,与当前主服务的工具注册清单做 diff,发现不匹配直接置为"需人工确认"状态。
4.2 SKILL.md 写作的五个要点
SKILL.md 是整个体系的"人机界面",它的质量直接决定了模型执行的好坏。我总结了五个写作要点,按优先级排序。
第一,description 是命门,必须写在 frontmatter 里且反复打磨。我之前说过了,这里再强调一次:它是模型/检索器决定"要不要加载这个技能"的唯一依据。写完描述后,拿 20 条真实用户问题测一下命中率,低于 80% 就要改描述。
第二,正文先写"适用场景"和"不适用场景"。让模型第一眼看到边界,而不是一头扎进细节。不适用场景的价值在于挡误用,比如"用户仅查询物流轨迹时,不要使用本技能"。
第三,执行步骤必须编号,且每步写明"预期结果"。编号方便模型跟踪进度,预期结果让模型知道自己有没有跑偏。例如:"1. 校验订单状态,预期结果为 REFUNDABLE 或 NOT_REFUNDABLE"。
第四,硬性约束单独成节,用"禁止"句式写清红线。比如"禁止在未获得用户二次确认时直接执行退款""金额超过 5000 元必须转人工审批"。红线内容不能散落在步骤里,不然模型容易漏读。
第五,至少给两个输入输出示例。一个常规场景,一个边界场景。示例能极大降低模型对步骤的理解偏差,尤其是边界场景的示例,效果比在步骤里反复解释好得多。
一个标准模板大概长这样:
--- name: order_refund description: 当用户申请退款、取消订单或询问退款到账时间时,完成资格校验、金额计算与审批流转 version: 1.2.0 --- ## 适用场景 - 用户明确要求退款/退货 - 用户询问"钱什么时候退回来"或退款进度 ## 不适用场景 - 仅查询订单物流轨迹 - 投诉卖家产品质量但不提退款 ## 执行步骤 1. 调用 check_refund_eligibility.py 校验订单状态(预期输出:REFUNDABLE / NOT_REFUNDABLE) 2. 若 REFUNDABLE,调用 refund_calculator.py 计算应退金额 3. 判断金额是否超过 5000 元,超阈值则生成人工审批单,否则进入自动退款 4. 输出结构化结果 {"status": "...", "refund_amount": ..., "next_action": "..."} ## 约束 - 禁止在用户未二次确认前执行退款 - 金额超过 5000 元必须转人工 - 禁止对已退款订单重复发起退款 ## 示例 输入:用户说"这双鞋我不要了,退了吧" 输出:{"status": "need_confirm", "refund_amount": 299.00, "next_action": "await_user_confirmation"}4.3 多 Skill 共存时的选择策略与冲突仲裁
当系统里技能数量超过 20 个,"选哪个"就变成一个实际问题。除了前面说的向量粗筛 + 模型精排,还要处理两类冲突。
第一类是多个 Skill 同时命中。比如用户说"我要退掉这个订单,然后重新下一个同样规格的",退款和下单两个 Skill 都可能被选中。这时候需要仲裁规则:我用的策略是给每个 Skill 声明priority字段(默认 100,数值越大优先级越高),同时限定"一次请求最多激活 2 个技能"。如果两个技能有明确的先后依赖关系,比如"先退再下",就在 SKILL.md 里写明。更稳妥的做法是让模型在精排阶段输出"选择理由 + 执行顺序",我再校验顺序是否符合预设的依赖约束。
第二类是没有 Skill 命中或分数都很低。此时必须有一个兜底行为,我建议分两级:如果最大相似度在 0.6~0.75 之间,让模型用"通用问答模式"直接回答,并允许它尝试调用少量全局常驻工具;如果低于 0.6,直接向用户澄清意图,不要强行套用技能。强行让低相关的技能去处理请求,得到的通常不是帮助,而是流程错乱后的负面体验。
4.4 容错与降级:自主容错控制设计
Skill 调用机制上线后,最大的工程挑战是可靠性——模型毕竟是概率系统,同一个 Skill 这轮执行对了,下轮可能就在某个步骤上犯糊涂。所以我在系统里设计了一套容错机制,核心思想是"每层都设卡、每卡都留后路"。
- 前置校验:Skill 执行前先做入参校验。比如退款 Skill 要求订单号存在且格式正确,不满足就返回
REJECT并附上原因,而不是硬着头皮执行。这一步能挡住大量幻觉调用(模型编造一个不存在的订单号)。 - 脚本超时与隔离:所有 scripts/ 下的可执行文件默认 10 秒超时,超过即终止,并在回传结果里标记
TIMEOUT。脚本运行环境放入隔离容器,按最小权限原则授权,只给必要的文件系统与网络访问权限。Skill 可能被恶意诱导去执行危险操作的问题,靠这层隔离兜底。 - 输出校验与重试:模型按 SKILL.md 要求输出结构化 JSON,系统先做 schema 校验,失败就带着校验错误信息重试一次。注意重试时要把"上一次错误"原样回传给模型,而不是让它凭空重来。
- 降级链:每个 Skill 可以配置一个降级链。主 Skill 失败后,依次尝试替代 Skill,再不行就回落到通用回复,最后是人工接管。比如自动退款失败,降级为生成退款工单,再由人工处理。容错设计的目标不是"永不失败",而是"失败在可控的层级,且用户感知最小"。
- 副作用审计:涉及资金、权限、外发消息等高频高风险操作的 Skill,执行前必须先过"人工确认关卡"。这一步会显著降低自动化率,但能保住系统底线。我一般是按操作风险等级分档:低风险全自动,中风险模型自判+事后审计,高风险必须人工确认。
5. 实测中的坑与效果评估
5.1 我踩过的五个真实翻车场景
这里多说几句,因为这些坑是我在实际运行中一个个踩出来的,网上文档几乎不写。
翻车一:description 写得太泛,技能根本没被选中。我有个"订单处理"Skill,description 写的是"负责订单相关操作"。结果用户问"收货地址填错了能改吗"时,匹配系统完全没有召回这个技能,模型只能靠通用知识硬答。后来我把 description 改成"当用户咨询或操作订单修改、地址变更、配送偏好调整时使用本技能",命中率从 55% 涨到了 92%。这一个改动,比调任何 prompt 都管用。
翻车二:SKILL.md 约束与系统提示词冲突。系统提示词里写"语气要热情亲切",SKILL.md 里要求"回复必须严格以 JSON 输出"。结果模型有时输出带问候语的 JSON,导致解析失败。解决方式是给优先级规则:具体 Skill 的输出格式要求 > 系统提示词的一般风格要求。我在系统提示词里显式加了这一条"优先级声明",问题才稳定下来。
翻车三:全量注入导致模型"失忆"。早期技能只有 8 个时,图省事把所有 SKILL.md 全量塞进上下文,没做选择注入。技能到 20 个之后,模型开始频繁忽略用户问题里的关键信息,像是被一大段技能说明"淹没"了指令。后来强制改成 Top-K 选择注入 + 预算上限,效果立刻反弹。上下文里每多一个无关技能,都在拉低主任务的完成度。
翻车四:模型跳过固定步骤。SKILL.md 明确写了"第一步校验资格,第二步计算金额",但模型在用户催促下直接跳到"确认退款"。后来我把第一步变成脚本强约束:模型必须先调用check_refund_eligibility.py,如果它没调,脚本层会拦下请求并提示"未完成资格校验,请先执行第一步"。把关键步骤从"提醒"变成"硬依赖",是治这个坑的正解。
翻车五:热更新后版本不一致。有一次我们改了某个 Skill 的 SKILL.md,但运行中的服务节点缓存了旧版,导致不同节点回复口径不一致。后来在注册表里加了版本号比对,并在技能索引里记录每个节点的"已加载版本",发布新版本时做全节点刷新校验。
5.2 评估指标:别只看"看起来对了"
Skill 系统的评估和普通 LLM 应用的评估不太一样,光看"回答好不好"远远不够。我给内部定了一套五个指标的打分体系,每次技能变更都要跑一遍。
- 技能命中率(Recall):针对 100~200 条标注好"该用哪个技能"的黄金测试集,统计正确技能被选中的比例。这个指标必须保持在 85% 以上,低于这个数先回去改 description。
- 执行成功率:技能被选中后,按 SKILL.md 步骤完整走完、且每步预期结果匹配的比例。反映 SKILL.md 指令的清晰度。
- 输出规范率:结构化输出通过 schema 校验的比例。这个指标容易拉满,但一旦拉满也说明容错机制没被触发过,需要留意是不是测试集覆盖太窄。
- 端到端任务完成率:最核心的指标,人工按"用户诉求是否被正确解决"打标。技能选对了不代表任务完成了。
- 副作用率:执行过程中是否有误调用其他工具、越权操作、重复操作等。这个指标是安全底线,建议零容忍,一旦出现要立即复盘走查。
测试集的构造有几个技巧:每条用例不仅要标"正确技能",还要标"该技能的错误用法";刻意构造相近技能之间的对抗用例,比如"查物流"和"改地址"都跟订单有关,确保匹配系统能分得清;每条真实事故案例都必须沉淀回测试集,防止复发。
5.3 回归测试与迭代节奏
Skill 是持续演进的资产,不能当一次性配置写完就放手。我的迭代节奏是:
每次 SKILL.md 有改动,CI 会自动跑一遍完整评估集,输出五个指标的 diff。指标下降 2 个百分点以上就阻断合入,要求作者说明原因或回滚。每个季度对整个技能库做一次专项审查,重点看三件事:还有没有技能长期命中率低于 80%;有没有两个技能的 description 语义过于接近;有没有 SKILL.md 内容膨胀到该拆分的程度。
我个人的经验是:让测试集跟着事故走。线上每次出现技能相关事故,第一件事不是去补 prompt 或改代码,而是先把出事的输入放进测试集,确认它能稳定复现,再动手修。修复的标准就是能让这条用例通过。这样做到三个月后,测试集会越来越厚实,技能的稳定性也会肉眼可见地提升——因为每一条用例,都是你踩过的坑留下的"疫苗"。
最后说点个人体会。Skill 调用机制不是银弹,它本质上是把"模型的临场发挥"往"受控执行"方向上掰了一把。它能解决的问题是流程错乱、工具误选、知识分散,但它没法解决模型本身能力不足的问题。我改造时没贪多求快,第一个月只挑了 5 个最痛、最容易选错工具、流程感最强的场景做试点,跑通并建立起完整的评估流程之后,才逐步推广到全量业务。如果你现在正被纯 Function Calling 的大规模工具列表折磨,不妨先挑两三个流程性强的场景,把 Skill 这套机制跑起来试试。等看到命中率和执行成功率的变化,你自然就知道下一步该怎么推了。