AI Agent这两年从“能聊几句”进化到“真能干活”,瓶颈早就不是模型本身的推理能力,而是它手头有没有顺手的家伙事儿。我今年把好几个内部工具链统一重构了一遍,核心就干了一件事:把散落在各个Prompt里的临时功能,收敛成一套可注册、可复用、可评估的“技能系统”。这套东西跑起来之后,同样的任务,Agent的完成率从七成左右提到了九成以上,返工率降了一大截。今天把agent-skills这套设计和踩坑记录整理出来,给正在折腾Agent落地的朋友做个参考。
这个内容适合已经在用LangChain、OpenAI Function Calling或者其他Agent框架,但感觉“模型老是听不懂人话、工具老是调不对”的开发者。也适合准备从零搭一个内部Agent平台、想一开始就把技能层设计清楚的架构师。如果你还停留在“写个Prompt试试看”的阶段,这篇文章能帮你少走三个月的弯路。
1. agent-skills到底在解决什么问题
1.1 从Prompt到技能,是一次组织方式的转变
早期做Agent,大家习惯把功能直接写进系统Prompt,比如“当你需要查天气时,调用weather_api”。这种做法的最大问题是你没法管理。Prompt越来越长,模型越来越迷糊,一会儿调这个工具一会儿调那个工具,参数填错你都不知道是什么时候引入的。
我把这层东西拆出来,抽象成一个独立的技能层(Skill Layer),核心思路是:不要教模型“如何调用工具”,让模型只负责“判断该用哪个技能”,调用的细节全部由技能定义来保证。类似工作里工具函数和模型之间多了个稳定的中间层,模型只需要知道“有什么可用、什么情况下用”,不用关心函数签名怎么传、异常怎么处理。
实际改造完,系统的可维护性直接提升了一个量级。新增一个能力不再需要改Prompt,只需要注册一份新的技能定义,几行JSON就能上线一个新功能。这和以前“改Prompt就像踩地雷”的体验完全不一样。
1.2 做技能化之前,先想清楚三件事
动手改造之前,我建议你先回答三个问题,答不上来就先别动工。
- 你的场景里有哪些高频、重复、确定性高的操作?这些才值得做成技能。一次性、探索性的任务不应该被固化,做进去反而是负担。
- 你用什么指标判断一个技能“好用”?推荐直接看任务完成率、平均执行轮数、工具调用成功率、用户返工率这几项,后面详细讲怎么设计评估。
- 你的工具调用链路里,有哪些环节依赖模型“临场发挥”?比如参数抽取、格式判断、超时处理。这些恰恰是技能层要消灭的不确定性。
这三个问题想清楚了,再决定技能怎么切分、怎么设计接口,后面才不容易推倒重来。
2. 核心技能的统一抽象设计
2.1 技能定义的关键元素与规范
一个技能至少要包含name、description、params、returns、version五个核心字段。description尤其重要,它是模型决定“该不该用这个技能”的唯一依据,一定要写清楚“这个技能能做什么、什么时候适合用、什么时候不要用”。我见过太多人把description写成一堆营销文案,“本技能用于高效地完成”这种废话,模型根本抓不住重点。
提供一份经过实战验证的字段示例,可直接参考:
{ "name": "fetch_webpage_text", "description": "抓取指定URL的正文文本内容,去掉导航、页脚、广告等噪声。适用于需要分析网页正文的场景;不适用于需要登录、动态渲染严重的页面。", "params": { "url": {"type": "string", "description": "目标网页的完整URL", "required": true} }, "returns": { "type": "object", "fields": { "title": {"type": "string"}, "content": {"type": "string", "description": "清洗后的正文Markdown文本"}, "fetch_time_ms": {"type": "integer"} } }, "version": "1.2.0", "tags": ["web", "extract"] }注意看description的写法,后半句明确写了“不适用于需要登录、动态渲染严重的页面”,这就是在主动告诉模型“别瞎用”,能大幅降低乱调用导致的任务失败率。这种负向约束比堆砌功能描述管用得多。
2.2 技能注册表与依赖机制
技能多了之后,管理就成问题。我将所有技能定义集中放在一个技能注册表里,支持按标签检索和版本管理。Agent每次启动时,根据任务描述做一次技能检索,只把最相关的几份定义注入上下文,而不是把所有技能一股脑塞给模型。这一步对控制Token开销和减少模型混淆都很有帮助。
再一个容易忽略的设计是技能依赖。我实际遇到过这种情况:抓取网页的技能依赖“URL规范化”组件,翻译技能依赖“语言检测”组件。组件不拆,技能之间就互相耦合,没法单独演进。所以我在技能定义里增加了depends_on字段,注册的时候自动检测依赖项是否可用,缺失就直接标记为不可用,而不是让模型调用后才发现报错。这种前置校验特别重要,等于把错误扼杀在入口处。
2.3 技能数量与粒度怎么取舍
技能切得太粗,一个技能干十件事,description怎么写都写不清,模型选择困难。技能切得太细,光检索就浪费时间,每次光匹配就快赶上实际执行了。我实践下来的建议是:
- 每个技能只解决一个原子任务,比如“抓网页”是技能,“抓网页并总结要点”就是两个技能加一段编排。
- 一个Agent上线的首批技能控制在8~12个,跑通后再慢慢扩充。技能池超过20个,必须上检索筛选,否则模型的选择准确率会明显下滑。
- 相近技能做合并,比如“查A系统库存”和“查B系统库存”,抽象成“查库存(指定系统)”一个技能,通过参数区分。合并之后,同样规模的场景,技能数量能减少四成。
3. 规划、工具调用与记忆的协同实战
3.1 任务规划怎么与技能配合
技能层建立之后,Agent的推理流程我一般拆成三步,和LangChain这类框架结合得很顺。第一步是任务理解,模型把用户的一句话目标翻译成结构化的任务清单;第二步是技能匹配,针对任务清单里的每个子任务,从技能注册表里检索最匹配的技能;第三步是执行编排,按依赖顺序执行,每个技能的输出可能成为下一个技能的输入。
一个完整的电商售后场景举个例子:用户说“我上周买的手机壳还没到,帮我查物流,如果明天不到就申请退款”。技能库里就有查订单、查物流、提交退款、发通知四个技能。模型会在第一步把用户这句话拆成两个子任务,分别匹配到查物流和提交退款。这看似简单,但如果没有技能层,模型往往会凭借印象乱调工具,尤其是查订单和查物流的顺序经常会搞反。
我建议在规划阶段就把“最大执行轮数”设好,比如每轮最多允许3个技能串联。超过就中止并让用户确认,避免模型自嗨型地连续调用好几个技能,最后把一个简单问题绕成一场事故。
3.2 工具选择的判定逻辑与参数注入
工具选择的核心,是让模型在“什么时候用什么技能”上做判断,而不是在“这个工具内部怎么做”上做判断。所以我在技能定义里增加了一个usage_hints字段,说明典型的触发场景和反例。这个字段不需要很复杂,但一定要具体。
参数注入方面有一个醒脑的教训:别太信任模型自己生成的参数。我的做法是,技能内部对参数做严格校验,URL必须符合http/https格式,日期必须是ISO格式,ID必须存在。校验不过就返回结构化错误码,而不是让模型重试。这看着增加了定义的成本,实际省掉了大量无效重试和Token浪费。
举一个真实踩坑:最初让模型自己填日期范围,它经常填错,导致查询结果为空。后来改成技能内强制默认“近30天”,除非显式传入起止日期,成功率立刻上去了。所谓“把智能留给判断,把笨活留给代码”,就是这个意思。
3.3 记忆与上下文窗口的管理技巧
技能执行过程中的中间结果,如果全部塞回对话上下文,窗口很快就被塞满。我走的是双轨记忆方案。短期记忆用来存当前任务的执行轨迹和中间结果,任务结束就清理;长期记忆用来存技能执行的关键结果摘要和用户的偏好,存进向量库,后续任务先做相似度召回。
比如用户经常要求“汇总结果要按价格排序”,这个偏好会被记入长期记忆,后续凡是触发汇总类技能,就会自动加上排序偏好。这个功能单独看不大,但累积起来对体验的提升非常明显,因为用户减少了重复描述需求的次数。要注意给长期记忆设定TTL,时间太长的旧偏好反而会干扰新任务,我有一次就因为半年前的偏好没清理,导致新的查询逻辑一直被带偏。
3.4 技能测试与效果评估怎么做
我为每个技能配置了一套独立测试集,包含正常输入、边界输入、异常输入三类用例。正常输入验证主流程,边界输入验证空字符串、超长文本、非法格式,异常输入验证应该返回错误码而不是报崩溃。技能代码有改动时,直接跑测试集,只要通过率低于95%就算回归失败。
系统层面关注四个指标:
- 任务完成率:用户发起的任务中,Agent自主完成的比例。
- 平均执行轮数:完成一个任务平均需要调用多少个技能。越低说明规划越准、技能越匹配。
- 工具调用成功率:技能执行成功次数除以总调用次数。低于85%就要警惕技能定义或内部逻辑出了问题。
- 用户返工率:用户发出纠正指令的比例。返工率高,大概率是description写得不清楚,或者技能粒度过粗。
我把这些指标做成看板之后,每次改动技能定义都能看到数值变化,很快就能定位是哪个环节退化。没做评估之前,全靠用户抱怨才知道出问题,效率差太多了。
4. 常见问题与实战排查实录
4.1 模型就是不选正确的技能怎么办
排查顺序我建议这样走:先看技能的description是不是太泛;再看技能的name是不是有歧义;最后看技能是否真的被注册同步了,别改了半天根本没生效。
Description太泛是最常见的原因。想一下你现在这个技能,如果让一个不熟悉你代码的人读description,他能准确判断什么时候用吗?如果不能,就重写。我做过一次描述重构,把“处理文档”改成“提取PDF中的表格内容,返回二维数组,适用于扫描版或电子版PDF”,匹配准确率提高了将近两成。挑技能就是挑工具,“名字清楚、用法清楚、边界清楚”三条缺一不可。
4.2 工具调用返回结果不稳定
这种问题早期的原因基本都在技能内部逻辑,不在模型。我当时排查过一个“网页抓取偶尔超时又偶尔正常”的问题,一开始怀疑是模型没传对URL,后来发现是技能内部对慢响应站点没有超时控制。在技能层加上超时参数,并规定超时后返回明确错误信息,问题就解决了。
不要看到一个失败就急着让模型重试。合理的做法是:技能内部先做一层重试和降级,比如抓取Detail页失败时自动抓取摘要页;真的失败了就返回清楚的错误码,模型基于错误码做下一步决策。这套机制上线后,整体重试次数减少了四成。
4.3 评估指标上去了但用户体验变差
这种情况一般是指标设计有偏差。我遇过一次,任务完成率明明在涨,用户却反馈“你们这个Agent越来越自作主张”。后来查下来,完成率上涨是因为模型倾向于“用一个错误但差不多完成的任务替代用户真实需求”,于是任务被标记为完成,实际上并没有满足用户。
针对这个问题,我在评估体系里增加了用户反馈验证环节,即任务完成后主动询问“这个结果符合你的预期吗”,把负向反馈率纳入核心指标。从那以后,研发方向不再盲目追完成率,而是真正去看用户满意度。不管你指标怎么做,一定要把“伪完成”的漏洞堵上,不然数据好看,实际问题一点没解决。
4.4 技能设计好后如何持续迭代
技能不是一次写完就完了,它是跟着业务一起演进的生命体。我建议每两周做一次技能利用率分析,把调用频次最低、成功率最低、返工率最高的几个技能拉出来,逐个判断该改进还是该下线。频次低但成功率高的技能,要注意是不是使用门槛太高,模型不会主动用;频次高但成功率低的技能,说明内部逻辑或描述有问题,优先级最高。
版本管理上,技能定义一样要走Git流程,每次改动必须留changelog。我见过有同事直接改了线上技能没记录改动,一周后整个Agent行为变化了,排查三天才发现是技能逻辑改了。版本记录这种东西,平时觉得麻烦,出问题的时候就是救命稻草。
5. 团队协作与规范沉淀
5.1 技能的评审机制
技能数量多了以后,质量问题就不只是代码问题,更是协作问题。我在团队里定了一套技能评审清单,提交新技能时逐项检查:description是否有负向约束、参数是否做校验、是否有测试集、是否写明错误码含义、是否记录版本变更。这套清单看起来琐碎,实际执行下来能拦住大量低质量技能流入线上。
另一个很容易被忽略的问题是技能命名规范。我根据实际教训规定:技能名必须体现行为动词加对象,禁止使用相对词和模糊词,比如process_data、handle_thing这类,谁提交谁重写。命名混乱的直接后果是模型和人都不知道该选哪个技能,一地鸡毛。宁可多花十分钟取名,也不要上线后天天为混淆买单。
5.2 从单Agent到多Agent的技能复用
做完单Agent的技能化之后,自然就会遇到多Agent协作的需求。我的经验是,技能层和Agent层分开管理,技能注册表是全局的,Agent根据自身职责声明自己“启用”哪些技能。这样同一个技能,比如查库存,可以被销售Agent用,也可以被客服Agent用,逻辑只有一份,不会出现两边各写一套、还经常不一致的窘境。
多Agent场景下还要注意技能互斥问题,比如两个Agent同时调用写权限技能,容易产生数据冲突。我加了一层简单的技能级别锁,写操作技能同时只能被一个Agent实例调用,读操作技能不加锁。这个机制加上去之后,数据不一致的问题被大幅消除。
我自己在实际推进这套体系过程中有很深的体会:技术的核心难点不是模型,而是怎么设计一套边界清楚、可维护、能复用的技能系统。就像给一个能干但粗心的助手制定工作手册,手册写明白了,他才能发挥真实力。agent-skills这套方法我已跑了大半年,从最初乱成一团的工具调用,到现在技能注册、评估、迭代都有章可循,省下的维护精力非常可观。
如果你也在做Agent,建议先挑自己场景里最痛的那两个功能,按上面这套规范改成技能,用两周时间测一测,看看指标变化。就我个人经验来说,这个投入的回报比几乎是我今年做的所有优化里最高的。