
1. 小模型做智能体为什么“技能选择”才是真正的胜负手过去一年我一直在折腾智能体Agent的落地踩过最大的一个坑就是盲目迷信大参数模型。一开始我们团队用 70B 级别的模型做工具调用和技能路由效果确实稳但推理成本高得离谱一个稍微复杂点的多轮任务光模型调用费用就够买好几杯咖啡。后来我们尝试把模型换成 12B 这个量级第一版直接翻车——技能选择准确率从 92% 掉到 61%模型经常在“该查天气”的时候去调“发邮件”或者在“该总结文档”的时候去调“搜索网页”。问题出在哪不是小模型“笨”而是我们把技能选择这件事完全甩给了模型自己去悟。大模型参数量足够大能在隐层里隐式地记住“什么场景该用什么工具”这种映射关系但 12B 级别的模型它的容量有限你让它同时记住语言理解、推理规划、工具参数生成还要它自己判断该调哪个技能这就好比让一个刚毕业的实习生同时干产品、开发、测试三份活不出错才怪。Agent Skill 框架的核心思路就是把“技能选择”这件事从模型的黑盒推理里拆出来变成一个显式的、可编排的、可校验的决策层。模型不再需要自己“悟”出该用哪个工具而是框架先根据当前对话状态、历史上下文、任务类型把候选技能缩小到一个很小的集合再让模型在这个小集合里做选择。这样一来12B 模型只需要做它擅长的事——在有限选项里做语义匹配和参数填充准确率自然就上去了。我实测下来这套思路跑通之后12B 模型的技能选择准确率能稳定在 88% 到 91% 之间和 70B 模型的差距缩小到 3 个百分点以内但算力成本直接砍半。这个账很好算12B 模型的推理显存占用大约是 70B 的六分之一单次推理延迟降低 40% 以上在同等并发下GPU 数量可以减少一半。对于中小团队来说这意味着原本只能跑一个 70B 实例的预算现在可以跑两个 12B 实例做负载均衡吞吐量翻倍。这篇文章我会把 Agent Skill 框架的完整设计思路、技能路由的算法细节、12B 模型上的调优经验、以及我们踩过的坑全部拆开讲清楚。不管你是刚接触智能体开发的新手还是已经在做多技能编排的老手都能从里面找到可以直接抄作业的东西。2. Agent Skill 框架的整体架构把“选技能”从模型里拿出来2.1 为什么不能让模型直接输出技能名很多人做智能体的时候习惯让模型直接输出一个 JSON里面包含skill_name和parameters。这种做法在大模型上勉强能用但在 12B 模型上问题很大。原因有三个第一技能名称的语义空间和自然语言空间不一致。模型在预训练阶段见过的是自然语言不是query_weather、send_email这种下划线命名的函数名。你让它直接生成这些 token它其实是在做“翻译”而不是“理解”。12B 模型的翻译能力不够强就容易把query_weather和query_stock搞混。第二技能数量一多输出空间就爆炸。假设你有 30 个技能模型要在 30 个候选里选一个还要保证格式正确这对小模型来说负担很重。实测中我们发现当技能数量超过 15 个时12B 模型的直接输出准确率会断崖式下跌。第三没有校验机制。模型输出query_weather但当前场景其实应该调query_forecast框架如果不做二次校验错误就直接传到下游了。Agent Skill 框架的做法是模型不直接输出技能名而是输出一个“意图向量”或“场景描述”由框架的技能路由器来做最终决策。模型只负责它最擅长的语义理解部分技能匹配交给专门的检索和排序模块。2.2 三层架构意图解析层、技能检索层、参数填充层整个框架我把它拆成三层每层的职责非常清晰意图解析层输入是用户的原始 query 和对话历史输出是一个结构化的“意图表示”。这个意图表示不是技能名而是一组语义标签比如{domain: weather, action: query, entity: city, time: tomorrow}。12B 模型在这一层的表现很好因为它的任务本质是信息抽取和分类而不是生成。技能检索层拿到意图表示后框架会在技能库做检索。每个技能在注册时都会附带一组“触发条件”比如query_weather的触发条件是domainweather AND actionquery。检索层用倒排索引或向量相似度把候选技能从 30 个缩小到 3 到 5 个。这一步完全不依赖模型纯工程手段速度快且稳定。参数填充层候选技能确定后模型只需要从原始 query 里抽取参数值填充到技能模板里。比如query_weather需要city和date两个参数模型就从“明天北京天气怎么样”里抽取出city北京、date明天。这个任务的难度远低于“选技能填参数”一起做。三层拆开之后12B 模型在每一层的准确率都能做到 90% 以上乘起来整体准确率也能维持在 85% 到 90% 之间。而如果让模型一步到位准确率只有 60% 出头。2.3 技能注册表的设计让每个技能“自描述”技能注册表是整个框架的基础。每个技能在注册时必须提供以下字段字段名类型说明示例skill_idstring技能唯一标识query_weatherdomainstring所属领域weatheractionstring动作类型querydescriptionstring自然语言描述查询指定城市指定日期的天气trigger_conditionslist触发条件列表[domainweather, actionquery]parameterslist参数定义[{name: city, type: string, required: true}]exampleslist示例 query[明天北京天气, 上海后天会下雨吗]这个注册表的设计有个关键点trigger_conditions必须是结构化的不能是自然语言。我见过一些团队把触发条件写成一段描述然后让模型去匹配这就又回到“让模型悟”的老路了。结构化条件可以用代码直接做布尔运算速度快、可解释、可调试。另外examples字段非常重要。在技能检索层我们会把每个技能的 examples 做向量化存到向量数据库里。当用户 query 进来时先做一次向量召回把语义最接近的 5 个技能捞出来再用 trigger_conditions 做精确过滤。这样即使意图解析层出了小偏差向量召回也能兜底。3. 技能路由的核心算法从 30 个候选到 3 个精选3.1 倒排索引 向量召回的双路检索技能路由的第一步是“粗筛”。我们的技能库通常有 20 到 50 个技能直接让模型看 50 个技能描述它会晕。粗筛的目标是把候选降到 5 个以内。我们用了两路并行的检索策略倒排索引路基于domain和action字段建倒排表。比如用户 query 被解析出domainweather那倒排表直接返回所有domainweather的技能。这一步是 O(1) 复杂度几乎不耗时。向量召回路把用户 query 和所有技能的 examples 做 embedding计算余弦相似度取 Top 10。这一步稍微慢一点但在 12B 模型上embedding 可以用一个小的双塔模型来做延迟控制在 20ms 以内。两路结果做并集通常能得到 5 到 8 个候选技能。然后我们再用一个轻量级的交叉编码器cross-encoder做精排把候选压到 3 个。这个交叉编码器可以是一个 1B 级别的小模型专门在技能选择数据上微调过推理成本极低。3.2 触发条件的布尔表达式设计触发条件是技能路由的“硬规则”。我们设计了一套简单的布尔表达式语法支持AND、OR、NOT和括号。比如# query_weather 的触发条件 trigger (domain weather) AND (action query OR action forecast) # send_email 的触发条件 trigger (domain communication) AND (action send) AND (NOT intent draft)意图解析层输出的结构化标签会直接代入这些表达式做求值。求值为 True 的技能进入候选集False 的直接淘汰。这套机制的好处是可解释性极强。当路由出错时我们可以直接看是哪个条件判断错了是意图解析把action标错了还是触发条件写得太宽或太窄。在大模型的黑盒里你根本不知道它为什么选了 A 没选 B但在布尔表达式里每一行都能追溯。3.3 精排模型的训练数据构造精排模型是技能路由的最后一道关。它的输入是用户 query 和候选技能描述输出是一个相关性分数。我们用一个 1B 级别的模型做微调训练数据构造方式如下正样本从真实对话日志里找出“用户 query 实际调用的技能”配对。比如用户说“明天北京天气”实际调了query_weather这就是一个正样本。负样本从候选集里随机选一个非实际调用的技能和 query 配对。但随机选效果不好因为有些技能明显不相关模型学不到东西。我们用了“难负样本挖掘”先用基础模型跑一遍把那些模型给了高分但实际没被调用的技能挑出来作为难负样本。这样训练出来的精排模型区分能力更强。训练数据量不需要很大我们实测 5000 到 8000 条高质量样本就能让精排模型的 Top-1 准确率达到 93% 以上。关键是样本质量不是数量。3.4 路由失败时的降级策略再好的路由也有失败的时候。我们设计了三级降级策略第一级如果精排模型给出的最高分低于阈值比如 0.6说明模型也不确定该选哪个。这时候框架会触发“澄清询问”让智能体反问用户“您是想查天气还是想查股票”把选择权交还给用户。第二级如果用户不回应澄清或者场景不允许反问框架会走“默认技能”兜底。每个 domain 下都有一个默认技能比如 weather domain 的默认技能是query_weathercommunication domain 的默认技能是send_message。第三级如果连 domain 都没解析出来框架会调用一个通用的fallback_skill通常是一个基于规则的关键词匹配器把 query 里的关键词和技能描述做字符串匹配选一个最接近的。这三层降级下来整体路由失败率能控制在 2% 以内。而且每一次降级都会打日志方便后续做 bad case 分析。4. 12B 模型上的调优实战从 61% 到 90% 的完整路径4.1 第一版为什么只有 61%意图解析层的标签体系太粗我们第一版跑 12B 模型的时候意图解析层只输出了domain和action两个字段而且action的取值只有query、create、update、delete四种。结果就是大量技能无法区分。比如query_weather和query_stock的 domain 不同还能分开但query_weather和query_forecast的 domain 和 action 完全一样模型就懵了。后来我们把标签体系细化到三层domain、action、sub_action。sub_action用来区分同一 domain 下的细粒度操作比如 weather domain 下有current查当前天气、forecast查预报、history查历史天气。这样query_weather对应sub_actioncurrentquery_forecast对应sub_actionforecast路由就能分开了。标签体系细化之后意图解析层的准确率从 72% 提升到 89%。这个提升直接传导到最终路由准确率整体从 61% 涨到 78%。4.2 意图解析的 Prompt 设计少样本 结构化输出约束12B 模型做意图解析Prompt 设计非常关键。我们试过三种方案方案一零样本直接问。Prompt 是“请分析以下 query 的意图输出 domain 和 action”。结果模型经常输出自然语言解释格式不稳定解析成功率只有 65%。方案二少样本 JSON Schema 约束。在 Prompt 里给 3 到 5 个示例每个示例都是query - {domain: ..., action: ..., sub_action: ...}的格式。同时在推理时用 JSON Schema 做输出约束强制模型只能输出合法的 JSON。这个方案把格式解析成功率拉到 98% 以上。方案三少样本 思维链 JSON 约束。在方案二基础上让模型先输出一段简短的推理过程再输出 JSON。实测发现对于复杂 query比如包含多个意图的句子思维链能提升 5 到 8 个百分点的准确率。但对于简单 query思维链反而增加了延迟收益不大。我们最终采用的是动态方案先用一个轻量级分类器判断 query 的复杂度简单 query 走方案二复杂 query 走方案三。这样在准确率和延迟之间取得了平衡。4.3 技能描述的向量化用双塔模型而不是大模型技能检索层的向量召回我们一开始用 12B 模型做 embedding效果确实好但延迟太高单次 embedding 要 80ms。后来换成一个 6 层的小型双塔模型专门在技能描述和 query 的配对上做了对比学习微调embedding 延迟降到 8ms召回率只掉了 2 个百分点。这个双塔模型的训练数据就是从真实日志里挖出来的“query-技能”配对。我们用了 in-batch negative 采样batch size 设到 256训练 3 个 epoch 就收敛了。模型文件只有 40MB可以轻松部署到 CPU 上完全不占 GPU 资源。4.4 参数填充的槽位对齐技巧参数填充是最后一公里。12B 模型从 query 里抽参数常见错误是“抽多了”或“抽少了”。比如“明天北京天气”应该抽city北京、date明天但模型可能把“天气”也当成一个参数值。我们的解法是槽位对齐训练在微调数据里每个参数都标注了它在 query 中的 span起始和结束位置。模型不仅学习输出参数值还学习输出参数值在原文中的位置。推理时框架会根据 span 从原文里截取参数值而不是直接用模型生成的文本。这样能避免模型“改写”参数值保证参数和原文严格一致。这个技巧让参数填充的准确率从 82% 提升到 94%。而且因为模型只需要输出 span 的起止 token 索引输出长度大大缩短推理速度也快了 15%。5. 算力成本减半的账12B 和 70B 的实测对比5.1 显存占用与并发能力的硬数据我们在同一台 8 卡 A100 服务器上分别部署了 12B 和 70B 模型用相同的技能路由框架跑压力测试。结果如下指标12B 模型70B 模型差异单实例显存占用24GB140GB减少 83%单卡可部署实例数3 个0.5 个提升 6 倍单次推理延迟P50180ms420ms降低 57%单次推理延迟P99350ms890ms降低 61%最大并发 QPS4512提升 3.75 倍技能选择准确率89.2%92.1%差距 2.9%这张表里最关键的是“单卡可部署实例数”。70B 模型在 A100 80GB 上只能勉强跑一个实例而且还要用 INT8 量化。12B 模型用 FP16 就能跑 3 个实例如果用量化还能更多。这意味着同样的硬件12B 方案的吞吐量是 70B 的 3 倍以上。5.2 成本折算每万次调用的实际花费按某云厂商的 A100 按需实例价格折算每万次技能路由调用的成本对比如下70B 方案需要 1 台 8 卡 A100 服务器每小时成本约 120 元每万次调用耗时约 14 分钟成本约 28 元。12B 方案需要 1 台 4 卡 A100 服务器或 8 卡跑双倍并发每小时成本约 60 元每万次调用耗时约 6 分钟成本约 6 元。每万次调用成本从 28 元降到 6 元降幅 78%。即使算上技能路由框架本身的 CPU 开销倒排索引、向量检索、精排模型总成本也能控制在 10 元以内相比 70B 方案降低 60% 以上。标题里说的“算力成本降低 50%”其实是保守估计实际降幅更大。5.3 什么场景下 12B 不够用需要回退到 70B12B 模型不是万能的。我们实测发现以下三种场景下12B 的技能选择准确率会明显下降需要回退到 70B场景一技能数量超过 80 个。当技能库非常庞大时即使经过粗筛候选集里仍然有 10 个以上语义相近的技能12B 模型的区分能力就不够了。这时候要么用 70B要么把技能库做分层先选大类再选小类。场景二多意图混合 query。比如“帮我查一下明天北京天气然后给张三发个邮件提醒他带伞”这一句话里有两个意图。12B 模型在意图拆分上容易出错70B 的准确率明显更高。场景三需要复杂推理的技能。比如“根据我上个月的消费记录推荐一个适合我的理财产品”这种需要多步推理才能确定技能的场景12B 模型的表现不如 70B。我们的策略是动态切换框架先判断 query 的复杂度简单 query 走 12B复杂 query 走 70B。实测下来只有 15% 的 query 需要走 70B85% 的 query 用 12B 就能搞定。这样整体成本仍然比全量 70B 低 50% 以上。6. 踩坑实录技能路由中那些文档不会告诉你的细节6.1 技能命名冲突query_weather和get_weather的坑我们早期技能库里有query_weather和get_weather两个技能功能几乎一样只是不同团队注册的。结果模型在路由时经常在这两个之间随机选导致下游系统收到重复请求。教训技能注册必须做命名规范审查。我们后来加了一个注册前的检查流程用编辑距离和向量相似度检测新技能和已有技能的重叠度超过阈值就拒绝注册要求合并或重命名。6.2 触发条件写得太宽domainweather导致误召回有个技能的触发条件写的是domain weather没有限制 action。结果用户说“把天气信息发给张三”意图解析出domainweather, actionsend这个技能也被召回了但它其实是个查询技能不能发消息。教训触发条件要尽量精确宁可少召回不可多召回。少召回可以用向量召回兜底多召回会引入噪声让精排模型更难做。我们后来的规范是每个技能的触发条件必须至少包含 domain 和 action 两个维度的约束。6.3 意图解析的标签体系膨胀从 5 个标签到 50 个标签的失控一开始我们只有 5 个 domain 标签后来业务扩展domain 增加到 20 个action 增加到 15 个sub_action 增加到 30 个。标签体系膨胀之后12B 模型在意图解析层的准确率从 89% 掉到 76%。教训标签体系要分层管理。我们后来把标签分成三级一级 domain 只有 8 个二级 action 有 12 个三级 sub_action 按 domain 分别定义每个 domain 下不超过 5 个。这样模型只需要在每一级做小范围分类准确率就回来了。6.4 精排模型的过拟合训练集 95%测试集 72%我们第一版精排模型在训练集上准确率 95%上线后测试集只有 72%。排查发现是训练数据里同一个技能的样本太多模型记住了技能名而不是语义。解法训练数据要按技能分层采样每个技能的样本数不超过总样本的 5%。同时加入 10% 的“跨领域负样本”比如把 weather 的 query 和 stock 的技能配对让模型学会区分不同领域。6.5 降级策略的日志缺失出了问题不知道哪一层挂了早期我们没在降级策略里打详细日志结果线上路由失败时只能看到“最终选了 fallback_skill”不知道是意图解析错了还是检索错了还是精排错了。解法在每一层都打结构化日志记录输入、输出、耗时、置信度。我们后来用了一个简单的 trace_id 串联三层任何一个失败 case 都能在 5 分钟内定位到具体哪一层出了问题。7. 把这套框架迁移到你自己的场景几个关键决策点7.1 技能数量少于 10 个不需要上框架如果你只有 5 到 8 个技能而且语义差异很大比如查天气、发邮件、订机票那直接让 12B 模型输出技能名也能做到 85% 以上的准确率。上框架反而增加了工程复杂度。判断标准当你发现模型经常在 2 到 3 个语义相近的技能之间选错时就是上框架的时机。7.2 技能数量 10 到 30 个框架收益最大这个区间是 Agent Skill 框架的“甜蜜点”。粗筛能把候选降到 5 个以内精排模型轻松做到 90% 以上。而且工程复杂度可控一个人一周就能搭起来。7.3 技能数量超过 50 个需要分层路由技能太多时单层路由不够用。我们的做法是两级路由第一级先选 domain比如 weather、communication、finance第二级在 domain 内选具体技能。每一级的候选集都很小12B 模型完全够用。7.4 没有标注数据怎么冷启动冷启动阶段没有真实日志可以用合成数据让 70B 模型生成一批“query-技能”配对人工审核后作为训练数据。我们实测 2000 条合成数据 500 条人工标注就能让精排模型达到 85% 的准确率足够上线跑第一版。上线后把真实日志里的 bad case 挖出来持续迭代准确率会逐步爬升。7.5 延迟敏感场景的优化把精排模型放到 CPU 上如果你的场景对延迟极其敏感比如要求 P99 低于 200ms可以把精排模型从 GPU 移到 CPU。我们用一个 1B 的蒸馏模型在 CPU 上单次推理只要 15ms虽然准确率掉了 1.5 个百分点但整体延迟降低了 30ms。另外倒排索引和向量召回都可以用内存数据库比如 Redis FAISS来做把检索延迟压到 5ms 以内。8. 我在这套框架上的一些个人体会这套 Agent Skill 框架我们跑了大概半年从最初的 61% 准确率一路调到 89%中间踩的坑比预想的多得多。最大的体会是小模型不是不能用而是要用对地方。12B 模型在语义理解、信息抽取、短文本分类这些任务上和 70B 的差距其实很小差距大的是复杂推理和长尾知识。把技能选择拆成“意图解析 检索 精排 参数填充”四步之后每一步都是 12B 擅长的任务整体效果自然就上来了。另一个体会是工程手段往往比模型手段更有效。我们花了很多时间调 Prompt、调温度参数收益都不如把触发条件写精确一点、把标签体系设计合理一点。模型是概率的但工程是确定的。能用确定逻辑解决的问题尽量不要交给模型去猜。最后分享一个小技巧每次路由失败都存下来每周做一次 bad case review。我们坚持了三个月积累了 400 多条失败样本按失败原因分类之后发现80% 的错误集中在 5 个技能上。把这 5 个技能的触发条件和示例数据优化一遍整体准确率直接涨了 6 个百分点。这个投入产出比比调模型参数高多了。