Skills设计:业务SOP拆成按需加载的手册
上一篇开头那句“刚买的手机我不想要了,还没发货的话帮我取消”,当时是拿来讲确认规则的示例。这次在本机把它真跑了一遍。2026-10-08,用admin账号对比特严选Agent说:
那台iPhone 18 Pro Max我不想要了,还没发货吧?帮我取消
一轮跑完,会话上下文里是六条消息:
| 序号 | 角色 | 内容 |
|---|---|---|
| 1 | USER | 用户那句话 |
| 2 | ASSISTANT | 思考 + 调用load_skill,参数skill_code: order_change |
| 3 | TOOL | load_skill的结果:一份“订单变更”办理手册 |
| 4 | ASSISTANT | 思考 + 调用query_order,不带参数 |
| 5 | TOOL | 最近5笔订单,其中88239是iPhone 18 Pro Max 512GB,已支付待发货 |
| 6 | ASSISTANT | 思考 + 调用cancel_order,参数orderNo: 88239,停在确认卡 |
要注意的是第2条。模型第一步没有去查订单,而是先取了一份手册。它的思考原话是英文的:
The rule: 用户要取消订单 → load order_change skill. Let me load the skill first, then follow it.
拿到手册之后,它才按手册第1步查订单,从五笔里认出88239,再按第5步发起取消。确认卡上我点了取消,订单现在还是已支付待发货。
“先查订单拿状态”“四个状态四条路”“取消前说清会发生什么”,这些规则不在人设里,也不在工具描述里。它们写在一份只有办取消改址这件事时才会被读到的文本里。这一篇讲的就是这份文本:它为什么不直接写进人设,它和工具的区别在哪,一份手册怎么写模型才用得起来,清单和正文各从哪来,以及它进了上下文之后会怎样。

上一篇定的是cancel_order执行前要不要用户点头。点头之前,模型怎么知道该先查什么、什么状态下能办,是本篇的事。两件事各管一段,谁也替代不了谁。
办事规则全写进人设会怎样
1. 比特严选有五件要按流程办的事
序章分过级:一星任务一次工具调用就完事,二星以上才用得着循环。其中三星和四星的任务,每一类都有一套固定的办理顺序:
- 退换货按签收天数分三档,第8到15天只收质量问题
- 取消和改地址按订单状态分四条路,已发货的不能取消只能拒收
- 故障诊断一次只给一步,等用户反馈再定下一步
- 下单先试算券、逐项列清,用户确认后才建单,建单和付款是两步
- 全家桶先定主设备再配周边,手表必须配iPhone,表带按表壳尺寸分档
这些就是业务SOP(Standard Operating Procedure,标准作业程序)。比特严选数据集里把它们写成了五份手册,正文是Markdown,初始化器从resources/initializer/bit-selection/skills/播进库里:
| 技能标识 | 名称 | 对应任务 | 正文字符数 |
|---|---|---|---|
after_sale_apply | 退换货办理 | T3 | 1,012 |
order_change | 订单变更 | T10 | 822 |
fault_diagnosis | 故障诊断 | T8 | 823 |
order_checkout | 选购下单 | T11 | 1,386 |
iot_bundle | 多设备搭配 | T7 | 1,184 |
字符数按库里t_agent_skill.content的长度数的,五份合计5,227。后续可能会继续扩展其他的Skill。
2. 都写进人设,每次调用都带着用不上的流程
最直接的放法是把五段流程全写进人设。人设是管理端配的那份系统提示词,每次模型调用都排在消息列表最前面,第一章讲提示词那篇展开过。比特严选的人设现在约2,200字符,五份手册塞进去,长度翻三倍还多,而且手册只会越写越多。
长本身不是主要问题,前缀缓存能把每轮重复的那段按折扣计价。问题出在三处:
- 每次调用都带着四份和这一轮无关的流程。排查手册写“一次只让用户做一步”,下单手册写“下单前逐项列清”,退换货手册写“照样提交,让工具给判定”。五套规则摆在同一份提示词里,模型每轮要自己从里面挑出当前适用的那一段。办取消的时候旁边躺着排查手册的“一次只给一步”,模型容易把别的场景的规则套到当前任务上。
- 改一条退货步骤要改人设。
ReActAgentProvider每次取Agent时会比对人设文本,不相等就重建Agent实例。手册拆出去之后改正文不重建,下文讲指纹时会看到为什么。 - 人设是所有场景共用的一份。“身份”“工具选择”“结果处理”这些真正每轮都要看的规则,被五类事的细节稀释了。
手册只有一两份、每份几行的时候,写进人设也能用。拆出去的收益随手册数量涨。
Ragent的做法是把每类事的流程单独存成一份手册,人设里只留“什么时候该去取手册”的判据,模型判断当前任务对得上哪份手册,才把那一份取进上下文。
手册和工具各管什么
1. 手册告诉模型怎么办,工具替模型办
工具是序章定义过的东西:入参出参固定,替模型做一件事。cancel_order收到订单号,真的去改订单状态、回补库存、退券。
手册只是一段文本。它进了上下文,模型照着做,手册自己不碰任何数据。两者摆在一起:
| 工具 | 手册 | |
|---|---|---|
| 谁执行 | MCP Server上的代码 | 模型自己 |
| 改不改业务数据 | 写工具会改 | 不改 |
| 以什么形式进模型输入 | 工具schema:名字、描述、参数定义 | 一段文本 |
| 什么时候在模型输入里 | 每次调用都在 | 加载之后才在 |
| 模型不照办怎么办 | 服务端校验、执行前确认 | 手册自己没有兜底,靠的还是工具那一列 |
最后一行要单独说。手册管不住执行,所以手册里一再把判断权往工具那边推。订单变更手册“系统的硬约束”第一条:
能不能取消、能不能改,由工具按库里的实时状态判定,不要拿几轮之前查到的状态下结论
这和序章定退货三档时的口径是同一件事:模型算出来的天数、模型记得的状态,一律不作数,判据在服务端现算。
开头那次实跑正好给了一个“模型不照办”的样本。当时手册第3步写的是“取消前把这单会一起发生的事说清楚:库存回补、用掉的券退回、已支付的原路退款”。模型的思考里看见了这一条:
Per manual step 3: explain what will happen — 库存回补、用掉的券退回、已支付原路退款. Then cancel. … But user explicitly asked to cancel, so I should just do it and report.
它判断用户已经说了要取消,跳过第3步直接提交。拦下它的不是手册,是前面讲执行前确认时那张确认卡;我点了取消之后,它的下一条回复才把三件事补上。
这件事划出了手册的边界。手册能提高模型按流程办的概率,不能保证。有代码把关的两道关卡也管不到这一步:确认卡只保证提交前要用户点头,服务端只按实时状态判断能不能办,谁都不看模型有没有先把退款、退券、回补库存讲给用户。所以“取消前先讲清楚”能不能做到,取决于手册和人设的文字写得够不够明确。出问题之后把这两处文字的提示词都改了,改成什么样,下面讲一份手册怎么写的时候说。
当然上面这个模型思考和模型会有关系,项目跑测试时用的deepseek-flash,使用GPT时就没有问题。
2. 两份不解锁任何工具的手册
看到手册和cancel_order成对出现,容易把手册理解成工具的使用说明。五份手册里有两份没有绑任何工具。故障诊断手册的声明文件是这样的:
# tool-ids 故意留空:这份手册解锁的不是工具而是流程——没有它模型会一次抛五条排查建议,多轮诊断塌成一轮
skill-code=fault_diagnosis
name=故障诊断
description=用户报告设备故障、异常或错误码,需要一步步排查时用这个技能;只问功能用法与参数的,不用加载
content-file=skills/fault-diagnosis.md
tool-ids=
sort-order=2
enabled=true
多设备搭配手册同样留空,理由也写在注释里:“搭配流程(先主设备后周边、手表必须配手机)提示词里一个字都没有,手册的价值在流程本身,不在解锁工具”。
效果在第二章讲裁剪那篇的八轮会话里能看到。用户说AirPods Pro左耳没声,第一轮模型加载了fault_diagnosis,给第一步之前的思考是“按手册,一次只让用户做一步”,之后两轮用户回来说没好,模型没有再查资料,接着往下排查。手册在那个会话里没有解锁任何工具,它改变的是模型组织回答的方式。
另外三份有值的tool-ids,含义是“加载这份手册之后才解锁的工具”:没加载的时候,模型的工具清单里看不见cancel_order。为什么要藏、怎么藏、什么时候收回,是后面讲加载技能前后工具清单怎么变的那篇的内容,本篇只定义到这里。
一份手册怎么写
1. 四段结构:什么时候用、办事步骤、怎么填、硬约束
把开头那份订单变更手册整段贴出来,库里现在是什么样就是什么样,822字符:
## 什么时候用
用户要取消订单或改收货信息时,按本手册办。
只问「多久不付款会自动取消」「发货后还能不能改地址」这类规则,走知识库,不用本手册。
## 办事步骤
1. 先用 query_order 确认是哪一单,拿到当前状态。用户只说「刚才那单」时,列最近几笔让他确认
2. 按状态分流,四个状态四条路:
- 待支付:能取消,也能改收货信息
- 已支付待发货:能取消(原路退款),也能改收货信息
- 已发货:两件都办不了。取消改成建议拒收,拒收后自动退款;改地址改成建议联系快递改派
- 已签收或已取消:两件都办不了。已签收要退就走售后申请,已取消不用再取消一次
3. 取消前先把这单会一起发生的事讲给用户:用掉的券退回、已支付的原路退款、库存回补。这段话写在发起 cancel_order 的那条回复正文里、放在调用之前,不能等提交结果出来再补;用户一上来就明确要取消,也照样先讲
4. 改地址前把要改的项和改成什么当场列出来,让用户确认无误再提交
5. 用 cancel_order 或 change_address 提交,把结果如实回给用户
## 这几项怎么填
- 订单号取自 query_order 的返回值,不要凭对话内容拼
- change_address 的收货人、手机号、地址三项,用户没说要改的就不要传:传了就会覆盖原值
- 订单查询返回的手机号是打码的,不要拿打码值当原值回填进去
- 地址要完整到门牌号,用户只说「换到公司」这类不完整信息时问清楚再提交
## 系统的硬约束
- 能不能取消、能不能改,由工具按库里的实时状态判定,不要拿几轮之前查到的状态下结论
- 只有待支付和已支付待发货能改收货信息,已发货之后改不动
- 手机号格式不合法、地址太短会被直接回绝,与其被打回不如先问清楚
- 提交时若返回「状态刚发生变化」,说明这单在两次操作之间被改了,重新查一次订单再决定,不要反复重试
五份手册都是这四段。每段给模型的东西不一样:
- 什么时候用:先说该用,再说不该用。“只问规则走知识库,不用本手册”这句是防模型“先看看”就加载,加载是有代价的,下文会算。
- 办事步骤:顺序和分支。第2步那四条路,就是序章说T10“分支最宽”那件事落成的文字。开头那次实跑里模型先查订单再取消,走的是第1步到第5步。
- 这几项怎么填:参数从哪来。工具schema只说
orderNo是字符串、receiverPhone是字符串,不说值该从哪取、哪些字段没说就别传。这一段补的是schema说不清的事。 - 系统的硬约束:哪些判断不归模型。能不能取消由工具判;返回“状态刚发生变化”要重查,不要重试。