
大概是从去年下半年开始我陆续帮几个团队搭建Agent应用发现大家最容易卡住的地方不是模型选型也不是Prompt怎么写而是“技能”这件事。明明大模型能力已经很能打了工具也都接上了可Agent一旦放到真实场景里要么选错工具要么执行路径飘忽不定要么同样的任务换个说法就崩。后来我逐渐意识到问题出在把“给模型接个工具”和“给Agent配一套技能”混为一谈了。今天这篇就围绕Agent技能的拆分、注册、路由、评估这条主线把我从项目里摸出来的方法和踩过的坑一次性说清楚给正准备做Agent工程化、或者已经在做但效果不稳的朋友做个参考。1. 先理清一件事Agent技能不等于“给模型接个工具”很多教程里Agent开发好像很简单注册几个工具函数写清楚函数名和参数大模型自然就会调用。我在早期也这么干过结果被现实教育得不轻。模型确实会调用但它会把该调用的任务塞给最像的那个工具哪怕那个工具跟当前意图八竿子打不着。原因很简单工具调用本质上是一层“接口暴露”它告诉模型“你能用这些”但没告诉模型“什么时候该用、用了之后要满足什么目标”。技能这个概念恰恰是用来补齐这一层的。1.1 工具和技能的本质区别先看一张我常用的对比维度工具Tool技能Skill关注点单个函数能不能被调用能不能稳定达成一类任务内容构成函数、入参、出参目标、触发条件、步骤、工具集、约束稳定性来源接口清晰场景边界明确 成功判定明确失败表现工具报错任务中断技能层补兜底策略任务可回退复用方式函数级复用任务级复用上下文消耗每次调用都得重新解释固定描述 可压缩执行记录工具是技能的零部件技能是把零部件拧成一套动作序列的“操作手册”。你用同样的工具但技能设计不同Agent的表现可以天差地别。我见过一个很典型的客服项目团队给Agent接了一个“订单查询”接口想着用户问订单状态时直接查。结果用户说“我东西怎么还没到”时模型认为这是物流问题去调了物流查询但订单信息又没传过去导致一连串失败。后来把“订单查询”升级成“订单全链路进度查询技能”技能内部绑定订单号解析、多源数据拉取、状态归一化三个流程才真正把这类问题兜住。1.2 为什么技能是给Agent装“肌肉记忆”对普通软件来说逻辑是代码写死的输入输出是确定的。但Agent面对的是自然语言同一个意图可以有几十种表达方式。如果你把每种表达都交给模型临场判断它的行为会漂移今天能对明天换一个对话语境可能又不对了。技能的核心价值是让Agent在常见任务上形成类似“肌肉记忆”的执行路径。技能一旦被选中大模型不需要从零开始规划每一步而是按照技能内部定义好的步骤走模型要做的只是根据当前输入把参数填上在执行卡住时轻微调整策略。这样整个系统的可预期性大幅提高——这在真实业务里就是能不能用的差别。我把这种设计思路叫“宏观放权、微观收紧”任务选择和大方向判断交给模型具体执行路径和约束藏在技能里尽量不让模型临场发挥。2. 拆技能的三把尺子原子性、可组合、可评估技能拆成多大没有一个绝对正确的答案。同一个领域拆得太粗技能内部会变成一个“小系统”逻辑复杂模型不好理解也不好调试拆得太细光路由选择就让模型头晕还容易选错。我自己的实践经验是用三把尺子来卡。2.1 原子性每个技能解决一个完整意图一个技能对应的应该是一个“用户能够明确表达出来的目标”而不是一个底层操作。比如“查询天气”可以是一个技能“查询天气并把结果剪切成一段适合播报的短句”这听起来很像一个技能但它包含了生成逻辑最好拆成“查询天气”和“天气播报文案生成”两个技能再组合使用。但原子性不等于越细越好。如果技能是“把摄氏温度转华氏温度”这种单一数学操作它更适合做成工具函数而不是技能。一个技能至少要包含一段“任务上下文”即模型需要知道何时用、怎么用、注意什么。技能太细这些说明没法放放哪都别扭。我在团队里经常说一句当你看一个技能的描述需要超过三行才能解释清边界时继续拆。2.2 可组合技能之间能拼成流程Agent往往不是只完成一步操作而是多步。比如“处理客户退货”这个流程至少涉及“校验订单状态”“生成退货单”“计算退款金额”“回写库存”。这四个步骤每个都可以是独立技能也可以塞进一个大技能里。我的建议是跨系统、跨数据源、跨权限的操作一定要拆开。因为真实场景下每一步都可能因为外部接口变化而失败。拆开后每步技能都可以单独重试、单独降级而且路由层可以灵活编排顺序比如某些订单不支持退货时直接跳过后两步。技能的可组合性还体现在命名上。我曾经给技能起过类似“processRefundOrder”这种名字表面没问题但模型在选择时很难从名字里判断它跟“returnOrder”的区别。后来我改成“generateRefundForReturnedOrder”把“场景动作对象”都放进去选择准确率直接涨了一截。2.3 可评估技能必须带明确的成功标准这一点容易被忽略但我认为是区分业余和专业的核心。设计技能时就应当顺手定义“什么算成功”。是返回了一个结构化结果还是调用了某接口还是用户确认“解决了”举个例子一个“酒店预订”技能成功标准不是“调用了下单接口返回200”而是“拿到了用户的入住离店日期、房型偏好、联系方式并且在确认页让用户点了确认”。如果技能内部没有这个判定标准Agent很可能在信息不齐的情况下提前“完成”任务用户还得回头补一堆材料。我建议每个技能在定义文件里带一个success_criteria字段用自然语言写清楚。这个字段有三个作用一是给模型一个明确的收敛目标二是给执行链路日志埋点三是给离线评估提供测试基准。下面是我常用的技能定义模板示意{ name: checkOrderProgress, description: 查询订单全链路进度包括支付、出库、物流签收状态适用于用户发起物流催促或状态查询的场景。, when_to_use: 用户询问订单何时送达、卡在哪里、为什么没收到货时使用。, when_not_to_use: 用户仅咨询退换货政策、修改地址、申请售后时不使用。, steps: [ 从用户表述中抽取订单号若缺失先追问, 调用订单服务拉取订单状态, 调用物流服务拉取轨迹, 把两路数据合并为进度简报 ], success_criteria: 拿到了订单号和不可缺失的状态字段并向用户输出了至少包含当前环节与预计送达时间的结果。, input_schema: { order_id: string, 12位数字或字母, force_refresh: boolean, 是否强制刷新缓存 } }这种定义文件写完后面所有的工程动作——路由、记忆、评估——都围绕它展开。3. 从注册到路由技能被正确选中的关键工程细节技能设计好了工程上还得让它“转得起来”。很多项目死在注册方式太随意、路由策略太粗糙导致前面辛辛苦苦写的技能定义根本没被模型有效利用。3.1 注册到模型时别把所有描述一股脑塞进去早期做Function Calling时大家习惯把全部工具的描述直接拼进请求里模型要在一堆长文本里找合适的效率低且容易误选。技能体系的优势在于它天然可以在模型面前只暴露“选单”而不是“全文”。我的做法是把技能分成两层第一层是路由描述每个技能只给一句话说明 触发关键词第二层是完整技能定义只有在路由选中后才注入上下文。这样大模型每次决策时看到的候选信息足够小选择准确率会高很多。路由描述需要注意人称和场景比如- checkOrderProgress: 用户查订单、催物流、问送达时间时使用。 - createReturnRequest: 用户要退货、申请退款、因为商品问题发起售后时使用。 - adjustShippingAddress: 用户要改收货地址、换收货人时使用。这里的技巧是不要写成API文档式的“该工具用于……”而要写成“用户说……时使用”。模型对齐的是用户意图不是接口语义。3.2 技能内部要不要再做子路由有些人会把一个技能做成“小Agent”里面再套一层LLM调用。我试过效果很难控。除非你有充分理由否则技能内部步骤应该尽量确定性执行能走规则的走规则能用代码判断的用代码判断只有真正需要生成自然语言或理解非结构化输入的地方才调大模型。把技能看成“业务代码 模型调用点”的混合体比把技能看成一个“模型子代理”稳定得多。模型调用点在技能里越少技能行为越可预测日志也越好查。下面是我在一个项目中真实用过的技能执行骨架简化版class Skill: def __init__(self, skill_id, definition): self.id skill_id self.definition definition self.llm get_llm_backend() def execute(self, user_input, context, history): # 1. 抽取参数尽量用规则/正则先抽 params self.extract_params(user_input, context) missing self.required_missing(params) if missing: return {status: need_more_info, missing: missing} # 2. 按步骤执行 intermediate {} for step in self.definition[steps]: result self.run_step(step, params, intermediate) intermediate[step[name]] result if result.get(stop): break # 3. 判定成功标准 if self.meets_success_criteria(intermediate): return {status: success, result: intermediate} else: return {status: incomplete, result: intermediate}这个骨架很朴素但胜在稳定。遇到参数不全就先追问绝不让Agent蒙着头往下猜每一步的结果都进中间缓存后续步骤可以做引用最后还有一道成功判定不合格就返回incomplete让上层决定是重试还是转人工。3.3 路由弹性单一技能 vs 技能组合的判定大部分场景一个轮次只需要命中一个技能。但真实对话里常有复合意图。比如用户说“我这个订单不要了顺便帮我把地址里的电话也改掉”——这是退货改信息的复合请求。我的经验是第一阶段先做单技能路由保证单意图请求的高准确率模型或规则判断出有多个意图时再进入多技能编排模式按顺序执行并汇总结果。不要一上来就支持多技能并行那样调试成本翻倍。路由评分也很关键。我见过有人直接把所有技能描述丢给模型让它选结果模型经常选一个“看起来差不多”的。后来我加了一层轻量规则预筛先拿正则和关键词把候选集从30个压到3-5个再让模型从压缩后的集合里选。这个改动让路由准确率从78%提到了91%而且因为候选集小了模型响应也快了。3.4 上下文和记忆技能执行完别把垃圾留在对话里技能调用过程中会产生大量中间数据接口返回的原始JSON、日志、临时计算结果。这些数据如果全被塞回对话历史上下文很快爆掉后续轮次模型会被无关信息干扰。我处理的方式是技能结束后只回填“面向用户的总结”和“对后续有用的结构化关键字段”到记忆里。比如订酒店技能执行完回填的是{hotel: XX, check_in: 2025-03-01, check_out: 2025-03-05}而不是整段携程返回报文。这样既保留连续对话所需的状态又不给上下文增加噪声。如果某个技能的原始返回很长比如列表型查询结果我还会做一层摘要再决定是否回填。实测下来全局上下文体积能下降一半以上模型在长对话中的指令遵循能力也稳定不少。4. 用评估和数据把技能养起来技能不是写完上线就结束了。真实业务里用户话术千奇百怪技能边界会被不断突破。没有一套评估机制你根本不知道技能是变好了还是变坏了只能靠“感觉”。4.1 离线评估给技能建测试集我给每个技能都配套一个测试集至少包含几十条典型的用户表述覆盖正常请求、边界请求、缺参数请求和语义相近的干扰请求四种情况。评估时批量跑统计四个指标指标说明我的目标值命中率正确的技能被选中≥ 95%参数提取准确率关键参数抽取无误≥ 90%成功率技能正常执行并达到成功标准≥ 85%误召率不该触发时触发了≤ 3%有一次我在测试集里发现一个技能连续三天命中率偏低查日志才发现问题出在路由描述里用了太多内部术语真实用户根本不会那么说。我照着用户的真实说法改掉描述后命中率一下就上去了。这些反馈只有靠测试集才能稳定发现。4.2 在线监控把“失败样本”捞出来离线测试集覆盖不了所有线上问题所以在线日志里一定要做两件事记录当前轮命中了哪个技能、执行结果状态是什么记录用户是否对技能输出表达了负面反馈比如追问“不对吧”“不是我说的意思”。失败样本捞出来后每周过一次把高频失败的场景整理成新话术追加到测试集里。这个闭环坚持一个月后技能成功率通常会有肉眼可见的进步。4.3 版本管理技能配置也要走发布流程技能定义本质上是配置数据它的改动会影响线上所有Agent行为。我见过某团队直接在生产环境改技能描述结果一个晚上用户对话全乱套。所以我现在要求技能定义必须走版本管理每次改动要有变更记录改完先在小流量上观测再逐步放量。版本放量时还留了回滚开关一旦某技能新版本导致成功率下降超过阈值自动切回旧版本。这套机制看似笨重但能极大降低试错成本。5. 我踩过的一些坑和对应的补救办法这部分是我最想分享的。技能设计看似有方法论真落地时坑相当多。我挑几个有代表性的按“现象-原因-解决”的顺序讲。5.1 技能描述写得像论文模型反而选不准早期我为了让模型更准确理解技能把描述写成了一段非常严谨的文档包含了各种边界条件、例外情况甚至还有引用。结果模型在选择时反而犹豫不决频繁选错。后来我才明白大模型对描述的理解方式跟人不一样它更依赖短句和关键词的强关联。太长的描述会把关键信号稀释掉。解决方法是把描述拆成“一句话路由描述”和“完整执行定义”两部分路由时只看短描述完整定义只在技能选中后才展示给模型。这个改动很有效。5.2 技能内部步骤全是LLM调用排查问题像大海捞针某个项目为了让技能更“灵活”每个步骤都用LLM做意图判断、字段抽取、结果改写结果出问题时根本分不清是哪一步抽风了。补救办法就是前面说的把技能内部步骤变成“代码优先模型兜底”。凡是可以用规则判断的地方比如订单号格式验证、字段缺失判断、翻页逻辑全部用代码写死LLM只负责识别非结构化输入和生成自然语言输出。这样一来技能的可测试性回来了错误定位也从小时级缩短到分钟级。5.3 技能返回格式不统一上层解析直接崩溃一个技能返回的是Markdown文本另一个返回的是JSON字符串还有一个直接返回了Python字典的字符串表示。上层Agent在编排多个技能结果时解析逻辑写了一堆fucking兼容还是偶尔崩。后来我统一了技能返回的数据结构每个技能返回一个SkillResult里面含status、data、message三个字段data永远是一个字典业务数据放里面展示文案放message里。所有技能遵守同一规范后编排层的代码变得非常干净。5.4 技能互相叠加上下文悄悄爆炸有多技能组合执行时如果把每个技能的原始输出都留在上下文里几轮下来模型就“失忆”了。这个坑最隐蔽因为不是立刻出问题而是对话轮数增加后质量断崖式下跌。解决方式是给每个技能的执行结果设置“可压缩”属性默认只保留精炼后的关键字段完整原始数据放到独立存储中等到真正需要时再按引用去取。这个优化对长对话场景简直立竿见影。5.5 为了追求最新模型忽视了技能本身的适配有一阵子我频繁升级底层模型结果发现某些技能成功率波动大。排查后才发现新模型对JSON Schema的处理风格跟旧模型不一样有些字段名被它自己改名了。所以我现在升级模型的流程是先在离线测试集上把所有技能全量跑一遍对比升级前后的命中率、成功率和误召率全部达标才放线上。技能体系的稳定性不能建立在“模型不作妖”的假设上。6. 沉淀一套自己的技能资产库最后聊点长远的。我越来越觉得Agent应用做得久最终沉淀下来的核心资产不是某段Prompt也不是模型本身而是一套经过实战打磨的技能库。技能的拆法、描述方式、边界设定、踩坑记录都是可以跨项目复用的。我现在会在每个项目结束后把项目的技能定义文件、测试集、失败样本分析统一归档。新项目起来时先从旧库里捞一批通用技能比如客服场景的订单查询、物流跟踪、售后处理再针对新业务补充几个垂直技能。这样新项目的第一版Agent其基础成功率就能比完全从零开写高出一大截。需要注意的是技能资产库里的描述语言最好保持统一风格。我曾经从两个老项目里各抽了几个技能拼在新项目里结果一个描述走口语风一个走书面风路由层被搅得晕头转向。统一描述风格这件事看似是洁癖实际是让路由稳定的隐性前提。如果你正准备给Agent做技能体系我的建议是不要一上来就想做大而全的通用框架挑选一个场景闭环最频繁的方向先拆出5-8个技能配好路由、测试集、日志跑通后自然就知道下一步该怎么调整了。技能体系是越用越有感觉的东西动手比什么都重要。