Agent技能工程化:拆分、注册、路由与评估实战指南
2026/9/16 8:52:09 网站建设 项目流程

大概是从去年下半年开始,我陆续帮几个团队搭建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,里面含statusdatamessage三个字段,data永远是一个字典,业务数据放里面,展示文案放message里。所有技能遵守同一规范后,编排层的代码变得非常干净。

5.4 技能互相叠加,上下文悄悄爆炸

有多技能组合执行时,如果把每个技能的原始输出都留在上下文里,几轮下来模型就“失忆”了。这个坑最隐蔽,因为不是立刻出问题,而是对话轮数增加后质量断崖式下跌。

解决方式是给每个技能的执行结果设置“可压缩”属性:默认只保留精炼后的关键字段,完整原始数据放到独立存储中,等到真正需要时再按引用去取。这个优化对长对话场景简直立竿见影。

5.5 为了追求最新模型,忽视了技能本身的适配

有一阵子我频繁升级底层模型,结果发现某些技能成功率波动大。排查后才发现,新模型对JSON Schema的处理风格跟旧模型不一样,有些字段名被它自己改名了。

所以我现在升级模型的流程是:先在离线测试集上把所有技能全量跑一遍,对比升级前后的命中率、成功率和误召率,全部达标才放线上。技能体系的稳定性,不能建立在“模型不作妖”的假设上。

6. 沉淀一套自己的技能资产库

最后聊点长远的。我越来越觉得,Agent应用做得久,最终沉淀下来的核心资产不是某段Prompt,也不是模型本身,而是一套经过实战打磨的技能库。技能的拆法、描述方式、边界设定、踩坑记录,都是可以跨项目复用的。

我现在会在每个项目结束后,把项目的技能定义文件、测试集、失败样本分析统一归档。新项目起来时,先从旧库里捞一批通用技能(比如客服场景的订单查询、物流跟踪、售后处理),再针对新业务补充几个垂直技能。这样新项目的第一版Agent,其基础成功率就能比完全从零开写高出一大截。

需要注意的是,技能资产库里的描述语言最好保持统一风格。我曾经从两个老项目里各抽了几个技能拼在新项目里,结果一个描述走口语风,一个走书面风,路由层被搅得晕头转向。统一描述风格这件事,看似是洁癖,实际是让路由稳定的隐性前提。

如果你正准备给Agent做技能体系,我的建议是不要一上来就想做大而全的通用框架,挑选一个场景闭环最频繁的方向,先拆出5-8个技能,配好路由、测试集、日志,跑通后自然就知道下一步该怎么调整了。技能体系是越用越有感觉的东西,动手比什么都重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询