大模型Agent从Demo走向生产,最尴尬的阶段不是模型不够聪明,而是它"什么都会一点点,但什么都干不利索"。我做过不少Agent项目,早期版本都有一个通病:把工具调用逻辑全部堆在系统提示词里,结果模型经常自己发明函数签名,出错了也不知道怎么恢复。后来我意识到,问题的核心不在于模型本身,而在于我没有给Agent建立一套可复用、可测试、可演进的"技能系统"——也就是agent-skills的思路:把Agent需要的能力拆成独立技能模块,每个技能有清晰的描述、参数协议、执行逻辑和错误处理,再由Agent动态编排调用。
这篇文章就是围绕这套思路展开的。如果你正在做AI Agent相关开发,或者准备给现有业务接入Agent但不知道怎么组织工具,这篇文章应该对你有用。我会把技能库的设计思路、具体模块实现、质量保障和排障经验都拆开聊,尽量给出能直接参考的做法。
1. 技能库:Agent从"能做"到"做得好"的分水岭
1.1 为什么提示词堆不出生产力
很多团队的第一版Agent是在系统提示词里写"你可以调用以下工具:get_order、get_user_info、send_sms,参数分别是……"。这种方式在小范围验证时能跑通,但一旦工具数量超过十几个,问题会集中爆发。模型会开始混淆相似功能的工具,比如把查询订单状态的参数填成查询用户信息的参数;会在工具返回空结果时反复重试同一调用;还会在同一个对话里把上下文日志越滚越长,最后把最重要的指令挤出注意力窗口。
我见过最夸张的一次,是有个Agent在处理退款时,连续调了七次查询接口,每次都带着几乎一模一样的参数,最后用户等了四十秒才收到一个"可能查不到"的提示。这种体验显然不能上线。问题的表面原因是提示词组织不够好,本质原因是缺乏结构化的技能管理——工具没有边界,参数没有约束,失败没有预案。
1.2 技能(Skill)的准确定义
在agent-skills的框架里,一个"技能"不再是一行函数描述,而是一个包含完整生命周期的模块。它至少需要包含以下几个部分:
- 技能元信息:技能名称、用途描述、适用场景、不适用场景。这些信息直接影响模型的调用决策,写不清楚Agent就会乱选。
- 输入协议:参数名、类型、必填性、取值范围、默认值。这是最容易被忽略但最容易引入线上问题的部分。
- 执行逻辑:实际干活的代码,包括API调用、数据解析、内部编排。
- 输出协议:成功时的返回结构、失败时的错误码与错误描述。
- 护栏(Guardrails):前置校验、后置校验、权限检查、敏感信息脱敏。
- 失败策略:什么时候重试、什么时候降级、什么时候直接向用户承认做不了。
把技能想成"一个人类员工接到的岗位说明书 + 工作手册 + 应急预案",就不难理解为什么要单独设计了。Agent本身是多面手,但具体每件事怎么做、做到什么标准、做砸了怎么补救,必须由技能模块来约束。
1.3 技能库与普通工具列表的差别
普通工具列表是一张扁平清单,Agent自己摸索着用。技能库则多了一层治理能力:技能之间可以组合,可以设置依赖,可以做版本升级,可以单独测试,可以配置灰度。相当于从"给员工一堆零件"升级成"给员工一套工具箱和操作规范"。
另一个关键差别在于召回方式。工具列表模式下,模型每轮都要从头"看"所有工具。技能库模式则可以在每轮任务开始时,先根据用户意图做一层技能预筛选,只把可能相关的技能描述注入上下文,其他技能留待需要时再加载。这一步对降低Token消耗、减少决策干扰非常有帮助。我把这两种方式的差别拉了一个简单对照:
| 维度 | 普通工具列表 | 技能库 |
|---|---|---|
| 组织方式 | 扁平清单 | 分层 + 依赖 + 组合 |
| 调用决策 | 全靠模型每轮判断 | 预筛选 + 动态加载 |
| 失败处理 | 模型自由发挥 | 技能内建重试/降级策略 |
| 可测试性 | 弱,联调困难 | 强,可单测可回归 |
| 迭代成本 | 改提示词,容易连锁污染 | 改单个技能,独立发布 |
所以我在后续项目中基本放弃了纯提示词工具表方案,转为技能库模式。这篇文章后面提到的"技能",都指这种结构化模块。
2. 我从零搭建技能体系时的分层思路
2.1 原子技能、组合技能与应用层技能
很多人一开始想把技能设计得大而全,一个技能解决一类完整问题。我试过,结果就是技能之间大量重复,改动一处要连带改好几个模块。后来的经验是必须先分层:原子技能、组合技能、应用层技能。
原子技能是最小颗粒度的能力,不能再拆分。比如"查询订单状态""计算用户年龄""发送短信验证码",每个原子技能只做一件事,参数简单、逻辑直接。组合技能则编排多个原子技能或嵌套其他组合技能,完成一个相对完整的业务子任务,比如"处理退款申请"可能包含"校验订单归属""检查退款状态""发起退款""通知用户"四个步骤。
应用层技能通常对应一个用户可见的完整需求,比如"售后服务"技能。应用层技能内部不直接写业务逻辑,而是维护一张路由表,把不同意图分发到对应的组合技能上。这样分层带来的直接好处是:底层原子技能稳定不变,组合技能可以灵活调整流程,应用层技能只需要关心意图路由。
2.2 技能之间如何做依赖管理
组合技能一定会调用多个底层技能,这就产生依赖关系。我的做法是为每个技能声明一个显式的依赖清单,在技能注册时由框架做拓扑校验,确保没有循环依赖。比如"处理退款"依赖"校验订单归属"和"发起退款",而"发起退款"不能再反过来依赖"处理退款"。
依赖声明还有一个额外的作用:框架可以在Agent决定调用某个组合技能之前,先把全部依赖技能的描述和必要状态预加载出来,避免Agent中途发现缺少参数再临时搜索。这样虽然简单,但真的能明显降低多轮调用的失败率。
2.3 注册中心与技能描述的可检索性设计
技能库需要一个注册中心,本质上就是一个带索引的技能目录。每个技能在注册时除了代码实现,还要写入结构化的描述,包括:技能ID、名称、意图关键词、输入输出Schema、依赖列表、版本号、负责人、变更日志。
描述文本的质量需要单独打磨,因为模型的选择大概率取决于描述是否清晰、是否与其他技能有区分度。我总结了一个判断标准:把同类的几个技能描述放在一起,让一个不了解业务的人(或模型)能在三秒内选出正确的一个。比如"查询订单状态"和"查询订单物流"很容易被写混,前者侧重订单生命周期状态(待支付、已支付、已取消),后者侧重物流轨迹(发货地、当前位置、签收状态)。描述里必须把这些细微差别写透,否则再聪明的模型也会选错。
3. 落地最频繁的五个技能模块及实现细节
3.1 外部API调用技能:参数校验是第一道防线
任何Agent只要对接了外部系统,就一定会用到API调用技能。这个技能看似简单,最容易翻车的地方是参数。用户说"帮我查下昨天那个订单",模型可能把"昨天"解析成日期字符串,也可能解析成时间戳,还可能直接不传。所以我在API调用技能里加入了前置校验环节,在校验不通过时输出一个结构化错误,而不是直接把缺参请求发给外部系统。
下面是一个简化版的参数定义示例:
{ "skill_id": "query_order", "description": "按订单号查询订单状态,适用于用户询问订单进展的场景", "parameters": { "order_no": { "type": "string", "required": true, "pattern": "^[A-Z0-9]{8,24}$", "description": "业务订单号,仅允许字母和数字" } }, "prechecks": [ "order_no 存在", "order_no 格式正确", "当前账号有该订单的访问权限" ] }前置校验不应该是生硬地拒绝,而是给模型一次修正机会。比如检查发现order_no格式不对时,返回的错误信息要带上具体期望格式,模型拿到就能马上改。如果直接说"参数错误",模型大概率会懵掉,然后开始瞎猜。
3.2 记忆管理技能:别让上下文变成垃圾桶
很多Agent项目的上下文管理是放任式的,每次对话把全部历史记录都塞给模型。随着对话变长,Token成本猛涨,而且旧信息会稀释模型对新指令的注意力。我的方案是把记忆拆成三层:工作记忆、会话记忆、长期记忆。
工作记忆只放当前任务的关键变量,比如订单号、用户ID、当前操作步骤,每轮结束自动刷新。会话记忆保留用户本轮对话的完整消息,但在超过一定条数后做摘要压缩。长期记忆存储跨会话有用的信息,比如用户偏好、历史投诉记录,这部分内容不是每轮都加载,而是按需检索。
这个技能对业务效果的影响非常大。我之前遇到过一个真实案例:用户在第一轮说过自己"在深圳",第五轮问"那我现在该怎么办"时,Agent因为没有长期记忆,回答成了面向一般用户的通用建议。加入长期记忆检索后,同样的场景下Agent会主动关联地域信息,回答质量提升明显。
3.3 规划拆解技能:把大目标切成可执行步骤
当用户提出一个复合需求,比如"帮我对比三款手机,然后推荐一款,再帮我下单",Agent需要先做任务规划。规划技能的核心输出不是一段自然语言,而是一个结构化的步骤列表:
[ {"step_id": 1, "skill": "search_products", "params": {"keywords": "手机"}}, {"step_id": 2, "skill": "compare_specs", "params": {"models": ["A", "B", "C"]}}, {"step_id": 3, "skill": "recommend_product", "params": {"strategy": "性价比优先"}}, {"step_id": 4, "skill": "create_order", "params": {"product_id": "..."}} ]规划结果不一定要立刻执行,它有两个作用:一是让模型自己推演一遍流程,提前发现缺参数或逻辑冲突;二是向用户展示"我打算这样做",用户可以中途纠正。我认为这第二点在C端场景尤其重要,因为直接闷头执行容易出信任问题。
3.4 自省校验技能:给结果加一道质检
Agent的输出不只是一段文本,很多时候还包括结构化动作或数据结论。自省校验技能负责在最终输出前做一遍质检:检查提取出的实体是否有来源依据、生成的SQL是否符合预期、给用户的金额计算是否准确。本质上就是让模型"再想想"。
我会在自省环节用一次额外的模型推理,但对于高精度要求的场景(比如财务计算),更可靠的做法是把关键结论抽出来,用确定性代码重新算一遍。举个例子,Agent告诉用户"退款金额是108元,其中包含运费8元",自省代码会重新计算100 + 8是否等于108,并校验金额是否为正数。模型可能有幻觉,但数学代码不会。
3.5 人工介入技能:承认做不了也是一种能力
有些任务确实超出了Agent的能力边界,比如需要调一个没有对接的系统、需要线下的身份验证,或者用户表达出强烈的情绪需要安抚。硬撑反而会把事情搞砸。我专门做了一个"移交人工"技能,触发条件包括:模型连续两次无法完成任务、检测到高危操作、用户主动要求转人工。
这个技能的关键在于移交时要带走足够多的上下文。不是简单说一句"我帮你转人工",而是把用户的需求、Agent已执行的步骤、当前卡点、需要人工关注的风险全部打包成一张交接单。这个设计对运营同事非常友好,他们不用追问用户一遍"您刚才遇到什么问题了",体验差距非常明显。
4. 给技能库配一套质量保障流程
4.1 单技能测试集与回归策略
技能是可以单独部署的模块,那就应该单独测。我维护了一套分技能测试集,每个技能下至少有几十条覆盖正常路径、边界路径、异常路径的用例。测试不只在发布前跑,更重要的是每次修改后都跑,防止改一个逻辑把另一条依赖链路弄坏。
对于涉及模型决策的技能,测试断言不能只看最终结果,还要看中间调用序列。比如"处理退款"技能,可能要断言:必须先校验权限、再发起退款、最后发通知,这三步顺序不能乱。这类断言能有效防止模型在某个版本突然开始跳步骤。
4.2 线上效果评估:不能只看"成功率"
技能上线后要持续观察几个核心指标:任务完成率、技能调用准确率、平均调用轮次、单任务Token消耗、平均延迟。我尤其关注两个容易被忽视的指标:技能误调率(调用了一个不该调的技能)和修复率(第一次调用失败后,Agent通过修正参数在本次任务内成功)。
误调率高的技能,通常描述有问题,需要重写描述或增强区分度。修复率低则说明技能的失败策略设计不合理,模型不知道怎么从错误中恢复。这些指标不能等用户投诉才看,我建议放到每日监控面板上,变化超过阈值就告警。
4.3 技能的版本管理与兼容性
技能迭代会改变输入输出协议,如果没有版本管理,上游组合技能可能还在按旧协议传参,结果全军覆没。我的做法是采用语义化版本号:主版本号在协议不兼容变更时递增,次版本号在功能增强时递增,补丁号在缺陷修复时递增。
组合技能在声明依赖时,要显式写明可接受的版本范围。比如依赖query_order >= 2.0.0 且 < 3.0.0。升级到不兼容的主版本时,上游依赖方必须主动适配,不能混着用。这个机制保证了技能库能持续演进,又不会让线上环境突然崩掉。
5. 四类高频事故的真实排查记录
5.1 事故一:Agent反复调用同一个技能导致超时
现象:一个用户查询场景中,Agent连续调用了5次查询接口,参数几乎一样,每次返回结果都相同,最终超时。
排查过程:我先看了调用日志,确认模型不是收到错误后重试,而是在正常返回结果后依然重复调用。进一步检查提示词和技能描述,发现技能描述里写了一句"如果需要获取更多信息,可以多次查询",这句模糊的话被模型理解成了"可以无限重复查询"。同时,组合技能的终止条件没有设置上限,模型不知道任务在何时算完成。
修复手段:一是删除技能描述中的模糊表述,明确"查询成功后无需重复查询";二是在规划技能里加入终止条件校验,同一个技能在同一个任务子步骤中最多调用两次,超出后强制进入汇总阶段。修复后误调现象消失,任务轮次从平均7轮降到了4轮。
5.2 事故二:技能描述区分度不足导致张冠李戴
现象:用户询问"我到哪儿了",Agent调用了"查询订单状态"技能,返回"订单已签收",而用户实际想查的是物流轨迹。
排查过程:我把两个技能的描述打印出来对比,发现"查询订单状态"和"查询物流轨迹"都写了"查询订单进展"字样,语义高度重叠。模型只能靠运气去选。进一步翻历史日志,发现这个问题已经存在很久,只是用户反馈不强烈没有爆发。
修复手段:重写两个技能的描述,将"查询订单状态"限定为订单生命周期节点(待支付、已发货、已签收等),将"查询物流轨迹"限定为物流流转明细(转运点、派送员、当前位置)。同时在意图预筛阶段增加一轮关键词路由,当出现"到哪儿""派送""快递"等词时直接命中物流技能。修复后两个技能的选型准确率从72%提升至96%。
5.3 事故三:上下文过大导致模型"忘记了"最早的用户诉求
现象:一个长对话中,用户先说了要对账,然后聊了5分钟其他话题,再回来问"那笔钱到底差在哪",Agent给出的回答与对账任务毫无关联。
排查过程:我查看模型调用时的上下文,发现工作记忆区已经被大量闲聊内容挤占,最关键的对账任务参数被挤出了有效窗口。问题不在模型能力,而在于我没有及时清理记忆。
修复手段:改用分层记忆策略,把任务关键变量放在工作记忆区并设置独立优先级,只要任务未完成就不被清理。同时会话记忆超过10轮自动摘要压缩,把历史细节转化为更精简的概述。修复后同类场景下的任务保持率明显提升,效果很好。
5.4 事故四:失败策略设计不当导致死循环
现象:外部接口偶发性超时,Agent收到异常后不断重试,每次重试叠加延迟,最终单次任务总计耗时接近两分钟,用户直接流失。
排查过程:查看错误处理日志,发现失败重试的退避策略是固定3秒,且没有最大重试次数限制。更隐蔽的问题是,重试时参数没有变化,意味着即使接口恢复也需要重试同样次数。
修复手段:重写技能失败策略,改为指数退避(1秒、2秒、4秒),同时设置最大重试3次,超过后走降级路径——返回部分结果并告知用户"当前服务繁忙,请稍后再试"。另外加入重试前置条件判断:当前一次错误是超时而非业务拒绝时才允许重试。修复后该技能的超时类故障处理时间从120秒降到10秒以内。
6. 框架选型和自研衡量:我的取舍标准
6.1 现有Agent技能框架的共通优势与不足
现在市面上谈到Agent技能,通常会落到LangChain的工具机制、OpenAI的Function Calling/Assistants、以及各类开源Agent框架上。这些框架确实解决了一部分问题:函数调用协议标准化、自动生成调用参数、简化了从模型到代码的对接。有团队直接基于这些框架快速搭起Agent服务,初期效果明显。
但等技能数量上来后,框架自带能力的边界也就显现了。多数框架解决的问题是"模型调用工具",但"技能治理"这块——依赖管理、版本控制、回归测试、灰度发布、线上指标监控——很多框架没有现成的成套方案。遇到这些需求时,团队还是得自己搭一层管理平台。框架能省掉最开始的造轮子成本,但业务复杂到一定程度后,治理层的设计终究绕不开。
6.2 我的分层建议:不排斥框架,但治理层自主设计
我的经验是,调度层可以放心用成熟框架,让框架负责协议解析、模型交互、工具路由这些通用能力。但技能注册、依赖校验、版本管理、质量评估这四块治理能力,建议团队自己掌控,因为它们是业务特有的,不同行业不同场景的治理规则差异巨大。
比如金融场景对审计日志和权限校验要求极高,只能自研;内容社区场景对UGC内容审核技能的依赖也很独特,通用框架里没有。把这些治理逻辑放在框架之外,既不容易被框架升级绑架,也方便与公司内部已有的权限、监控体系打通。
6.3 自研技能库的最小可行设计
如果你的团队决定自研技能库,我建议从最小闭环开始,不需要一开始做得很重。先具备几个核心能力:技能注册接口、技能描述存储、依赖拓扑校验、运行时调用拦截器、基础日志埋点。第一版甚至可以不用图形界面,用配置文件和命令行工具就能维护。
我见过不少团队在自研时犯一个错误:技能库还没建起来,先花大量精力开发管理后台。其实前期完全不必。先把规则用版本化配置管理起来,跑通流程之后再逐步做可视化。技能治理核心是规则,不是界面。
7. 技能演进路径上的最后几点体会
技能库不是一次性建完就结束的静态资产,它需要跟着业务一起迭代。我在实际运营中感受最深的一点是:技能库里真正长期活跃的技能其实占比不高,不少技能在上线后始终无人调用。与其追求技能数量,不如定期做一次"技能下线"评审,把那些描述模糊、无调用量的技能清理掉,避免它们干扰Agent的决策。
新技能的加入也应该走一个轻量评审流程:至少要明确该技能与现有技能的边界、说明用户会在什么场景触发它、以及列出三条供测试验证的场景。这个流程不需要很重,但能挡住很多"感觉有用"实则冗余的技能。
另外,技能描述不是写一次就完事的文案。模型版本一升级,模型对描述的理解能力也会变化。我建议每次更换底层大模型版本后,重新跑一遍技能选型测试集,对比新旧模型下的选型准确率。之前遇到过换了新模型后,"查询订单状态"和"查询物流轨迹"的误选率从4%涨到15%,查了好久才发现是模型版本导致的语义理解偏移。类似情况用回归测试来找根因,往往比人肉看日志快得多。
回到最初的问题——Agent为什么总是"什么都会但干不利索"?很大程度是因为把技能藏在了提示词里,而不是做成体系的工程模块。把每一次工具调用、每一步规划、每一段记忆当成一个可测试、可版本化、可治理的技能,Agent才能真正稳定起来。这也是我在项目里反复验证之后才彻底相信的一点。