
1. 为什么我最终把项目拆成了“一仓库的技能”1.1 连续翻车三次之后我意识到问题不在模型这个项目最初的起点非常朴素我想做一个能自动处理“客服工单知识库问答数据报表查询”的对话式助手。一开始我采用的是最传统也最省事的做法——把所有需求、规则、提示词全部写进一个超大号的系统提示里一个模型走天下。结果就是看似什么都做了实际什么都做不好。客服工单分错了类数据查询偶尔算错口径知识库回答倒是流畅但遇到稍微偏门的问题就开始一本正经地胡说八道。更麻烦的是每次想优化其中某一个能力改动一处提示词就可能干扰到其他功能整个系统像一锅越搅越稠的粥。后来我把输出做成了“多个独立功能模块由主模型统一调度”的架构但新的问题又来了每当要新增一种能力我得改主逻辑、加判断分支、更新测试用例改动成本高得离谱。第三轮重构的时候我偶然看到某个开源项目里把功能模块按“Skills”的形式组织每个skill文件夹里放着单独的指令文档和参数定义主程序只需要读目录、注册、运行。这个思路当时没引起我足够重视直到某天我把二十多个功能全部搬进这个框架之后才发现这才是正确答案。1.2 “Skills”到底是什么简单来说Skills就是一种把“模型会做的事”封装成“可以被动态加载、独立复用、单独调优”的最小功能单元。它不是一个库也不是一个插件系统它更像是一份“技能说明书”你告诉模型什么情况下该用这个技能、输入参数应该长什么样、内部处理步骤是什么以及最后怎么输出结果。这种思路最早脱胎于工具调用和函数调用但比它们更进了一步。函数调用强调的是“把请求转成结构化参数去调外部API”Skills强调的则是“把一段模型本身就能完成的推理任务变成可管理、可组合、可替换的工作流单元”。换句话说前者是让模型学会按按钮后者是让模型拥有可随时取用的“肌肉记忆”。这个区分非常关键。在我这个项目的最终形态里Skills的粒度既覆盖“调用外部天气接口”这种工具型能力也覆盖“判断用户情绪并选择回复语气”这种纯推理型能力。前者背后是代码后者背后是提示词但它们都被包装成了同一种格式因此能被主模型统一调度。这也就是标题“skills”最核心的价值让功能的组织方式变得极简把复杂度收拢到每个技能内部。2. Skill的标准结构我总结出来就四件事2.1 技能声明模型靠什么决定“什么时候用你”任何Skill都必须回答一个问题模型凭什么知道这个技能适合当前任务这靠的是“技能声明”中的名称和描述。名称要短但要唯一描述要详细但核心是“说明这个技能解决什么类型的任务最好附带典型场景示例”。我踩过一个很典型的坑早期某个技能描述写得特别泛结果用户在问“今天周几”的时候模型居然把它路由到了“日期计算”技能而不是“日历查询”技能最后给出的答案格式奇怪不说还没附带上上下文。后来我调整描述加入了“你正在写一个类似‘某年某月某日’的日期解析任务用户可能不带年份只带月份”这种条件化表述路由准确率一下子就上来了。我的理解是描述本质上是在教模型做分类好的描述应该像产品说明书里“适用场景”那一栏而不是广告语。技能声明里还有一项很容易被忽略技能ID。我强烈建议从一开始就采用稳定的命名规范用“动词对象”的结构比如order_query、ticket_classify、log_analyze这样无论是日志追踪还是权限控制都会轻松很多。等技能数量超过三四十个的时候你会庆幸当初没有随意起名。2.2 参数设计结构化输入决定了技能的上限参数是Skills体系里最容易被轻视、但影响最深远的部分。很多人在初期图省事把参数定义成自由文本——比如“用户输入原文”然后指望模型自己从原文里抽取所需信息。这种方法在小规模demo阶段跑得通一旦进入复杂场景问题就会集中爆发。我的建议是凡是能被结构化的字段都应该在参数定义里明确给出。以“工单分类”技能为例它的参数不能只有ticket_text还应该有user_name、order_type、priority_hint、source_channel等。这样做的价值有两层第一层模型在抽取参数时会有意识地关注这些字段实际上相当于做了一次初步的NLP信息抽取第二层后续技能内部逻辑可以直接按字段处理不需要再做整段文本解析鲁棒性高得多。参数定义还要明确类型、是否必填和枚举范围。我见过很多失败的案例都是因为参数枚举值写得太随意导致模型输出了一种“看起来像枚举、实际不是枚举”的内容最终在类型校验环节崩掉。如果你采用的是支持JSON Schema的工具那一定要把enum、pattern这些约束用满宁可校验严格一点也不要给模型自由发挥的空间。2.3 指令内容把“怎么做”写成模型能复盘的步骤指令是Skill的核心执行逻辑通常是一段精心撰写的提示词。但这里有个和普通提示词工程不太一样的地方普通提示词面向的是“让你做一件事”Skill指令面向的是“让你在这个技能内部完成特定的处理流程”。我在写指令时最核心的方法就一句话把任务拆成模型可self-check的步骤链。比如一个“客户情绪识别”技能指令会明确写先判断用户文本中是否包含明显情绪词汇再根据上下文推断情绪烈度最后输出positive/neutral/negative三分类并附上置信度。这种写法看起来很简单但它给模型提供了一条可遵循的思考路径也给我后续的排查提供了依据。哪个环节出错了直接看输出对应步骤的结果就行不再需要把整段回复当黑盒去猜。另一个经验是指令里要明确“什么不该做”。模型有一个天然倾向就是当输入信息不足时自行脑补。所以我在很多Skill指令里会强制加一句如果参数缺失或信息不足请在结果里标记insufficient并说明缺失项严禁猜测。这等于给系统装上了一个保险丝避免错误数据一路传导到下游。2.4 一个完整的Skill示例从零到可用光是讲抽象结构不够放一个我实际项目里精简改造过的案例。这是一个“根据用户问题检索知识库并生成引用答案”的技能我称它为kb_query_answer。skill_name: kb_query_answer description: 当用户询问产品功能、使用流程、报错含义等知识库相关问题时使用。 典型场景用户提到“如何……”“是什么”“怎么办”且问题与当前产品有关。 不要用于数学计算、代码编写、闲聊。若用户问题明显与产品无关请调用其他技能。 parameters: question: type: string required: true description: 用户原始问题保持原文不要改写。 user_context: type: object required: false description: 用户会话上下文中的关键字段如当前版本号、用户类型等。 instructions: | 1. 基于question提取检索关键词移除停用词和无意义修饰语。 2. 在知识库中检索最相关的3到5条文档片段。 3. 若检索结果之间冲突优先采用版本号最新且发布时间最近的文档。 4. 基于检索片段组织回答回答必须附带引用索引形如[1][2]。 5. 如果检索结果不足以回答问题在答案中明确写“暂未找到相关信息”并建议用户联系人工客服严禁编造。 6. 输出格式{answer: ..., citations: [...]}这个定义看起来并不复杂但它在实际运行中帮我扛住了绝大部分知识类问题。如果你准备搭自己的技能体系我建议也从这样一个“单输入、单输出”的小技能开始练手跑通之后再逐步增加复杂度。先让一个技能稳定比一口气堆十个不稳定技能有用得多。3. 技能编排从单技能到多技能协同的进阶路线3.1 一个模型全权路由 vs 显式路由配置当技能数量超过一定阈值之后最核心的问题就从“怎么写技能”变成了“怎么让模型选对技能”。我试过两种路线。一种是把所有技能声明全部塞给模型让它在推理时自行选择。这种方法的优点是零配置、上手快缺点也很明显技能数量太多时模型会面临“选择困难”尤其是两个技能描述比较相似的时候误路由概率明显上升。另一种是引入单独的“路由层”。在技术实现上路由层可以是一个更小的分类模型也可以是一段专门的提示词甚至可以直接用规则脚本去命中关键词。我的建议是如果技能数量在十个以下直接让大模型路由即可一旦超过十五个就必须引入某种形式的显式路由。我自己采用的是“规则模型”两层结构。第一层根据预设关键词和正则做快速过滤把候选技能缩小到三到五个第二层把这几份技能声明交给模型精排。相比原来让模型从二十多个技能里盲选这个方案的准确率提升非常明显而且技术成本很低。规则的维护也不像想象中那么复杂大概每新增一个技能增加个几行关键词匹配就行。3.2 多技能协作的三种模式单一技能搞定任务是理想情况现实中更多的是多个技能串联协作。我总结出三种最常见的协作模式串联模式技能A的输出作为技能B的输入。比如“日志分析”技能先产出异常摘要再把摘要交给“工单生成”技能生成用户可见的说明。这种模式下每个技能应当保持边界清晰只做一件事否则耦合度一高就会变成灾难。并行模式多个技能互不依赖分别处理同一个输入最后汇总结果。比如“客户反馈分析”时情绪识别技能、主题分类技能、是否包含辱骂内容检测技能可以并行运行各自产出子结果最后合并。这种模式能显著缩短延迟但要注意对输出做归一化否则合并阶段会变成新的瓶颈。校验模式一个技能执行另一个技能专门校验。我实际用下来比较有效的组合是“SQL生成技能”配“SQL审查技能”后者负责检查前者生成的语句有没有越权、有没有未加LIMIT之类的低级错误。这种“执行审查”的思路听起来多耗了一轮调用但换来的是线上事故的大幅减少非常划算。3.3 配置化管理技能不是写死的是动态发现的Skills体系走到一定阶段之后一定会面临“如何管理越来越多技能”的问题。我的方案是所有技能定义全部写成独立的Markdown或YAML文件放在固定目录里程序启动时统一扫描注册运行时支持热加载。这样每新增一个技能只需要添加一个文件不需要动主程序代码。为了便于检索目录结构我是按“领域/技能”分层的比如sales/order_query.md、support/ticket_classify.md。同时每个技能文件头部都会维护元信息包括版本号、维护者、依赖的模型能力标签和变更记录。这种做法的另一个好处是不同模型的兼容性变得容易处理。有的模型擅长工具调用有的模型不擅长只需要在技能注册表里加一个“兼容模型列表”字段路由层看到当前模型不在列表内时就自动改用纯提示词方式注入指令。这样一来技能的鲁棒性不再依赖某个特定模型的特性切换模型成本下降不少。4. 技能的评估与调优没有度量就没有优化4.1 我关注的三个指标技能写得好不好不能靠感觉。我实际使用中评估Skill体系主要看三个指标第一是路由准确率。它的定义是给定N个测试样本其中系统主动选中正确技能的比例。这个指标衡量的是“模型能不能找到对的技能”是所有后续效果的前提。路由错了技能内部写得再好也白搭。第二是技能执行成功率。定义是技能被选中后输出结果能否通过结构化校验比如参数完整、格式正确、没有insufficient标记。这个指标反映的是技能指令本身的质量是提示词工程的直接产物。第三是业务结果达成率。这个指标最主观也最关键。比如“工单分类”技能它不仅要输出正确类别还要让下游系统真正按这个类别处理。这一步评价的不是文字对不对而是行为对不对。我建议每个技能都提前写好五到十个典型评测样本每次改完指令就跑一遍回归不然很容易出现“改好一个样本、弄坏三个样本”的情况。4.2 如何定位“是路由的错还是技能内部的问题”排查问题时第一步永远是定位层级。我自己的排查流程一般是这样先看日志里最终选中的技能ID和置信度如果选错了那就是路由层的问题去调技能描述或路由规则如果选对了但输出结果不对再去分析技能内部的指令链看是哪一步逻辑断了。为了支撑这个流程我在设计之初就给每个技能加了“过程输出”字段也就是在执行完中间步骤时把结果透出到日志里。比如“SQL审查”技能会把“审查规则命中列表”写进调试信息这样一旦线上出现问题打开日志就能看到它到底因为哪条规则拒绝了整条SQL。在这个机制落地之前每次线上出问题我都要靠猜现在基本能在一分钟内锁定大方向。实际中还有一个高频场景路由选对了但技能描述里没有覆盖某种特殊情况导致模型在技能内部乱来。这种情况我一般会在指令里增加一条“边界条件”说明并且把特殊情况补充到技能描述里。记住描述不只是给路由层看的它也在给技能内部的推理提供背景信息。4.3 调优顺序先定边界再调措辞最后动结构我在调优过程中踩过不少弯路最大的教训是不要一上来就改指令措辞。正确的顺序应该这样来第一步先看边界也就是这个技能的定义是否清晰描述里能不能区分“该用它”和“不该用它”。如果边界模糊调整内部措辞只是在错误的地基上盖楼。第二步才去调指令内部的措辞比如换一种更明确的步骤表达或者加入反面示例。第三步如果前两步都做了效果还不够才需要考虑改技能结构本身——比如把一个技能拆成两个更细粒度的技能。这里举个我踩过坑的真实例子我早期有一个“意图判断”技能想把“查询订单”“修改地址”“取消订单”全塞进一个技能里结果模型经常把“修改地址”识别成“查询订单”因为两者的触发语义太接近。后来我把这三个意图拆成了三个独立技能并给每个技能各自补充了明确的“不要处理XX情况”说明整体准确率立刻有了明显提升。这让我意识到技能本身就是一种人为划定的边界边界划得越贴合任务的天然分类模型就越容易做对。5. 常见问题快查表与工具选型心得5.1 高频问题与解决路径我整理了这段时间实际遇到最多的六个问题并以快查表的形式分享出来问题现象大概率原因推荐排查方式模型总是用错技能技能描述边界模糊或相互重叠先清理名称和描述加入“不要用于……”句式参数输出频繁缺字段参数定义太宽松未设必需项将关键字段设为required并启用JSON Schema校验同一个技能时好时坏指令步骤不够明确模型自行发挥把执行步骤拆成自检链每一步都要可验证新增技能后旧技能被“抢活”新技能描述覆盖了旧技能场景在描述中补充更细的适用条件并将两者做对比测试技能输出格式不稳定未提供输出示例或结构定义在指令中增加输入输出示例建议给两到三个系统整体变慢路由层把问题传给太多技能引入关键词预过滤缩减候选集规模这些问题的共性根源其实都很朴素大部分不是因为模型能力不够而是因为技能定义的规范化程度不足。规范化的投入越早后期省下的时间就越多。5.2 工具链选择轻量起步、逐步升级很多人在技能系统的技术栈选择上会很纠结担心没有重型框架就做不起来。以我的实践来看初期根本不需要复杂框架。我最早期的版本只有两个Python文件一个负责扫描目录读取技能定义一个负责把技能注入到模型调用里。核心逻辑加起来不到两百行已经能支撑十个技能的正常运行。到了需要处理权限、限流、技能版本管理、批量测试的阶段再考虑引入更完整的框架或自研平台也不迟。工具选择标准我觉得就三条第一技能定义是否易于外部编辑最好就是一个纯文本文件第二是否支持运行时热加载第三是否方便接入不同模型供应商。满足这三点的工具或自研方案基本不会走偏。另外一个具体建议是所有技能定义文件都纳入版本管理。我因为早期偷懒没做这件事结果有一次调整了某个技能描述导致线上路由大面积混乱想回滚却找不到上一个版本的技能文件。从那以后技能文件的每一次变更都走commit和改代码一样严谨。6. 一些真正让我少走弯路的体会最后说几点不那么技术、但对做好整个系统非常有帮助的体会。第一件事技能粒度宁小勿大。我见过很多项目把技能定义为“全能工具人”让它既做分析又做汇总还要负责输出美化。结果模型一旦遇到需要兼顾多重任务的情况输出质量就会明显下降。反过来把一个大型任务拆成三四个小技能串联每个技能只做一件事整体稳定性和可维护性都会好很多。第二件事技能描述是不是写给模型“看”的也是写给你未来的同事“看”的。一份描述含糊不清的技能三个月之后你自己回来看也未必能快速理解它的意图。我在写描述时都有一个习惯想象自己是一个完全不了解项目的新人读这段描述能不能搞清楚它解决什么、不解决什么。如果不能那就说明还要继续打磨。第三件事评测集比技能本身更值得投资。我每个技能都会维护一个评测样本文件每次改动技能定义之后就跑一遍。这套评测机制看起来增加了工作量实际上是在保护你的迭代速度。没有评测集的技能系统就像没有仪表盘的飞机飞得越快越容易偏航。我自己目前还在做的事情是把技能和具体业务指标打通比如工单分类技能的分类结果直接和“用户后续是否重复咨询”做关联分析。这样就能从业务结果层面反推技能好坏而不再依赖人工抽检。这条路还在不断打磨但至少已经验证了可行。