我最近在调试一条 AI Agent 流水线时注意到一个很有意思的现象:两种架构,跑同一个任务。一种每次开启会话都会重新推理,连参数格式都要现查工具文档,碰上同样的异常就在同一个地方再栽一次。另一种在跑第三次之后,几乎不再重复踩坑,出错次数明显下降,输出格式也一次比一次稳。后者的关键差异,就是它多了一个“经验沉淀层”:任务跑完先复盘,把可复用的操作抽出来,存进一个索引库,下次遇到同类任务,先查库再动手。这套机制,就是今天要聊的 SkillX,也是我一直想好好展开的一次实践总结。
它的定位其实很朴素:让智能体不再从零开始。如果你正在做 Agent 工程化,或者你发现自己搭的 Agent 总在同一个坑里反复摔跤,这篇内容你大概率用得上。下面我会从设计动机说起,拆一遍完整实现链路,再聊聊那些只有实际跑过才会意识到的坑。整个过程中我不会只讲概念,凡是能落地的步骤、模板和数据,我都会尽量放出来。
1. 两个 Agent 跑同一条流水线,为什么一个越跑越顺,一个原地打转
1.1 大模型天生“不长记性”,每次对话都是一次重推理
先想一个很基础的问题:同样是让 Agent 调一个内部接口,第一次它把日期参数写成了“2025.04.01”被接口拒了,第二次它可能又换一种错法,写成“2025/04/01”。这些东西在人眼里属于“同一个教训”,但在 Agent 眼里,每次都是全新的任务,每次都要重新猜。
原因是底层的模型本身是无状态的。你给它一个 Prompt,它基于上下文做一次前向推理,输出一段 token,服务端不会长久保存什么“上次失败学到的东西”。所谓多轮对话,也只是把历史消息继续丢进上下文窗口里而已。更麻烦的是,工具调用链的每一步都存在随机性:模型可能这次选 A 工具、下次选 B 工具,参数顺序不同、错误处理不同,最终结果自然也不稳定。
这就是“从零开始”最直接的含义:不仅慢,而且每一次“重新开始”都不是简单地重复上一遍,而是重新探索一遍。探索是有代价的,代价就藏在 token 消耗和失败重试里。
1.2 算一笔账:重复推理的成本比想象中高
我拿一个很典型的任务举例:让 Agent 拉取近 7 天销售数据,生成一份中文摘要。这个过程大致可以拆成 10 步:生成 SQL、校验字段、执行查询、解析结果、判断空值、生成摘要、补充计算、格式化输出、自检、返回。
每一步模型的调用平均消耗 800 token 左右,一条顺利的链路就是 8000 token。但如果第一步有个小错误,比如表名写错,Agent 自己发现后需要重新生成,整套链路直接翻倍,变成 20 步,16000 token。如果错误发生在第 7 步,重试代价会更夸张。按这个量级,一天跑 100 次同类任务,每多一次无谓重试,浪费的就是几十万 token。这不是一个可以忽略的数字。
token 成本还只是显性支出。更隐蔽的是时间成本:用户等 Agent 结果,每多一轮工具调用,就要多等几秒钟。在交互式场景里,多等十秒,体验完全不同。一个没有经验沉淀的 Agent,本质上是在用用户的等待时间为自己的“记忆缺失”买单。
1.3 结果漂移的问题,比成本更致命
如果说成本是慢刀子割肉,那结果不稳定就是直接把桌子掀了。
同一份销售数据,让 Agent 跑三次,三次输出可能都不一样。第一次给的是表格,第二次变成了一段散文式的描述,第三次又加了一行“数据仅供参考”。如果你后面接的是一个自动解析脚本,这种格式漂移足以让下游任务直接挂掉。更让人头疼的是,Agent 自己并不觉得哪里不对,因为它没有“上次我输出的长什么样”的基准。
结果漂移的本质,是缺少一份稳定的“操作记忆”。人在熟悉的工作里为什么稳定?因为大脑里存了上次成功时的输出样式、步骤顺序、容易踩的坑。Agent 没有这种记忆体,就只能靠运气。这也是我在项目里最早就确定要解决的问题:既然模型本身不提供长期记忆,那就从外部给它补一个。
1.4 为什么微调不是现阶段的最优解
有人可能会问:既然想让 Agent 稳定,为什么不直接微调模型?这是很多人第一时间想到的方案,我也解释一下为什么我在做 SkillX 时绕开了它。
微调的本质,是把经验写进模型的权重里。听起来很诱人,但现实门槛非常高。首先,你需要一批高质量的训练数据,每一条都要经过严格标注;其次,训练周期以天甚至周计算,等模型训练完,业务逻辑可能已经变了;最后,也是最关键的,微调后的行为几乎是不可解释的——你知道它“好像变好了”,但说不清楚它到底继承了哪一段经验,也没法单独撤回某一条坏经验。
相比之下,把经验外置成“技能库”就灵活得多。一条经验可以单独增删,可以追溯来源,可以被审计。它的性质更像工作手册,而不是改造大脑。在工程落地场景里,工作手册比大脑改造便宜得多,也靠谱得多。SkillX 走的就是这条路:用机制帮助 Agent 积累经验,而不是重训模型。
2. SkillX 的经验沉淀链路:复盘、压缩、入库、复用
2.1 复盘:先把一次执行变成结构化轨迹
SkillX 最先要解决的一个问题是:经验从哪里来?答案是从已完成的执行轨迹里来。所谓“轨迹”,不是模型输出的对话文本,而是一系列结构化的事件记录。每调用一次工具,就产生一条事件,包含工具名、入参、出参摘要、错误标记、重试次数这些字段。
我给你们看一段简化后的轨迹示例,理解起来会更直观:
{ "events": [ { "event": "tool_call", "tool": "get_sales_summary", "input": {"date_from": "2025-04-01", "date_to": "2025-04-07"}, "output_summary": "ok, rows=42", "error": null, "retry_count": 0 }, { "event": "tool_call", "tool": "format_output", "input": {"style": "markdown_table"}, "output_summary": "formatted_markdown", "error": "missing_field: total_amount", "retry_count": 1 } ], "final_status": "success_after_retry", "total_cost_tokens": 8123 }有了这种结构化的轨迹,我们才能客观地判断“哪一步是对的、哪一步是错的、哪一步是费了很大劲才兜回来的”。如果只记录一句“任务成功”,那经验就无从谈起。所以复盘这一步的核心,是把执行的每一个细节都暴露出来,供后续压缩成技能时取用。
2.2 压缩:从轨迹里提炼出“技能卡”
轨迹是原始素材,不能直接拿来复用。就好比你看了一次别人的完整操作录像,但你不可能把录像整段塞进下一次任务里,你需要提炼出“关键步骤”和“注意点”。SkillX 的压缩环节,就是把轨迹转换成一个结构化的技能卡。
下面是一张经过压缩后的技能卡示例:
{ "skill_id": "SK-2025-0411-01", "name": "调用内部销售汇总接口并生成周报", "trigger": "任务包含'销售周报'、'近7天销售额'关键词,且目标库为report", "steps": [ "1. 先获取access_token,再调用业务接口", "2. 日期参数统一使用YYYY-MM-DD格式,窗口为end-6d到end", "3. 调用/summary接口,page_size固定取100", "4. 返回字段为空时,只重试一次,间隔5秒" ], "constraints": [ "不适用于生产账号,生产环境需切换认证方式", "若接口返回401,不要反复重试,先检查token是否过期" ], "confidence": 0.87, "version": 3 }这里有几个设计细节非常重要。第一,“去具体化”:所有具体数值、具体 ID 都要替换成变量,否则技能就只能用在一个孤例上。第二,把失败节点写进“constraints”:轨迹里那些走弯路的地方,恰恰是下一条经验的核心价值。第三,操作步骤用命令式短句,不要写长段落,因为 Agent 在参考这些步骤时,每一句话都可能被直接当成执行指令。
2.3 入库索引:新任务怎么找到老经验
技能卡生成之后,不能扔在文件里就算完事,它必须能被新任务检索到。SkillX 在这里用的是双通道检索:语义向量通道加标签通道。
语义向量负责“相似性检索”,比如新任务说“帮我拉一下这周销售数据”,即使没有出现“周报”这个词,向量检索也能匹配到销售周报相关的技能卡。标签通道负责“场景锁定”,每张技能卡入库时都会手动或自动打上一些场景标签,比如“销售、周报、内部库”。检索时先按标签过滤掉明确不相关的类目,再在剩下的小范围里做语义排序。
这一步看似简单,但它是整套机制里最容易出问题的地方。检索太宽,会把不相干的技能带进来;检索太窄,又会让该命中的技能落空。我的建议是:技能卡里的“trigger”和“constraints”两个字段,必须在入库时单独解析成索引条件。trigger 是正面的匹配条件,constraints 是负面的排除条件。检索时两者同时生效,能在很大程度上避免“长得像但不适用”的误命中。
2.4 复用注入:让 Agent 抄作业,但保留它自己的判断
技能库建好了,缓存有了,最后一步是如何在任务运行时把技能用起来。这一步最忌讳的做法,就是把整张技能卡原封不动地塞进模型上下文。那样做有两个问题:一是上下文污染,技能卡里的冗余信息会干扰模型对当前任务的理解;二是 Agent 会过度服从,哪怕当前任务的输入和技能卡描述不一致,它也可能照抄不误。
SkillX 的做法是把技能卡拆成两段注入。操作步骤作为“参考路径”给出,约束条件作为“必须遵守”的规则给出。同时在系统提示词里明确写上一句:如果当前任务的输入和技能卡描述不一致,立刻停止参考,重新规划。
举个例子,技能卡说“日期参数统一使用 YYYY-MM-DD”,如果新任务明确给的是时间戳格式输入,Agent 需要先判断这是不是同一场景。如果发现不匹配,它有权放弃这条技能,回到自由推理路径。保留这个判断权很重要,否则技能库会从“助手”变成“枷锁”,这个我在后面翻车场景里还会细说。
3. 什么经验值得沉淀,什么经验应该直接扔掉
3.1 可迁移的操作范式才值得入库
技能库不是保险箱,什么都往里塞反而会成为垃圾场。我在实践中总结出的第一条筛选标准,就是看经验是否“可迁移”。
什么叫可迁移?举两个例子。一条技能如果写的是“调用外部接口前先获取 access_token,并检查过期时间”,这套经验可以套用到几十个不同接口上,那它就值得入库。另一条如果写的是“本次查询返回了 880 条记录”,这就是纯粹的数据,换一个查询场景就完全失效,不值得存。
判断标准很简单:换一组输入值,这套操作是否还成立。如果成立,说明它沉淀的是操作范式;如果不成立,说明它只是一次性事件。在压缩环节,我会专门要求提炼者把“具体值”挑出去,只保留“怎么一步步做”的部分。坚持这个原则,技能库才不会被大量一次性碎片淹没。
3.2 环境耦合的经验必须写清边界
还有一种经验,看起来可迁移,实际上被环境绑死了。我踩过一个很典型的坑:某次在测试环境调接口,Agent 为了准备数据先调了一个 mock 接口,预置数据成功后任务顺利通过。于是它把“先调 mock 接口预置数据”写成了经验。后来同一个技能在另一个任务里被检索命中,结果在正式环境里也去调 mock 接口,直接把一套真实数据流程搅乱了。
这类问题的根源,是技能卡缺少“适用范围”的显式声明。现在我给自己定了一条铁律:任何技能卡如果只有“怎么做”,没有“在哪儿做/在什么条件下做”,那宁可不入库。上一条例子里,如果卡里写清楚“此步骤仅适用于测试环境,生产环境跳过”,后续的误用就不会发生。
环境边界写清楚之后,检索时还能多一层天然过滤。一个只适用于测试环境的技能,在标签匹配阶段就会被生产环境的任务排除掉,而不是等到执行时才出错。这比事后补救成本低得多。
3.3 最容易混进技能库的“伪经验”
第三个要小心的,是那种“一次性成功被误当成规律”的伪经验。我见过最生动的例子:Agent 在一次任务中先清了缓存,然后任务通过了,它就把“任务开始前先清理缓存”写成了技能。但实际上那次任务能过,只是因为上游服务延迟恢复了,清缓存和成功之间没有因果关系。
这种伪经验混进库里的危害,在于它会污染后续所有任务。每次执行都多一步无意义的清缓存操作,而且万一清缓存本身影响了别的状态,反而制造出新问题。我在团队里定的规则很简单:同一技能至少要有两次独立成功才允许自动入库,低于这个门槛的只能进入“候选区”,等人来复核。这条规则可能会拖慢技能库早期的增长速度,但长期看,它保住了整个库的品质。
4. 同一任务跑五轮,有没有 SkillX 的差距有多大
4.1 实验设计:拿一个真实的内部报表场景说话
光讲机制太抽象,我放一组我们内部跑过的对照实验数据。场景很简单:从 report 库拉取近 7 天销售数据,生成中文摘要,遇到接口异常自动重试。两组 Agent 使用同一个底层模型,同样的任务描述。区别只在于,一组启用了 SkillX 的技能沉淀链路,另一组没有。
评估指标有四个:任务成功率、每次任务的平均工具调用次数、平均耗时、结果格式漂移次数。格式漂移指的是连跑多次过程中输出结构发生变化,比如这次是表格下次是段落,这种变化会直接导致下游解析失败。
4.2 五轮对比数据
下面这组数据来自我们 5 轮重复执行的实测结果:
| 轮次 | 无 SkillX 成功率 | 无 SkillX 平均调用次数 | 无 SkillX 平均耗时 | 有 SkillX 成功率 | 有 SkillX 平均调用次数 | 有 SkillX 平均耗时 |
|---|---|---|---|---|---|---|
| 第 1 轮 | 50% | 8.6 次 | 25s | 50% | 8.5 次 | 24s |
| 第 2 轮 | 55% | 8.1 次 | 23s | 55% | 6.8 次 | 18s |
| 第 3 轮 | 50% | 8.9 次 | 26s | 80% | 5.2 次 | 14s |
| 第 4 轮 | 45% | 9.3 次 | 28s | 90% | 4.5 次 | 12s |
| 第 5 轮 | 60% | 8.0 次 | 22s | 90% | 4.5 次 | 11s |
完整的五轮里,无 SkillX 组的格式漂移出现了 4 次,有 SkillX 组只有 1 次,而且那 1 次出现在第一轮技能库还没有积累的时候。这组数据样本量不算大,不能说明所有场景,但趋势是清楚的:技能库一旦命中,成功率、调用次数、耗时、稳定性全面改善。
4.3 第一次为什么没快起来,第三次才明显改善
仔细看上面表格,会注意到第一轮两组几乎没有差别。这很正常,第一轮技能库是空的,Agent 和其他组一样,都在从零探索。第二次虽然入库了一条技能,但成功率也只提升到 55%,并没有立刻“起飞”。
原因是第二条技能卡在第二轮被误用过一次。技能卡生成的步骤没有写清楚日期边界,Agent 错误地套到了一个日期格式不同的接口上,导致任务失败。我们当时做了一次人工修正,把约束字段补全,后续轮次才稳定在 90% 左右。
这个曲线非常真实:技能库不是装上就立竿见影,它需要一个“先试错,再修正,后稳定”的过程。第一版技能卡大概率是粗糙的,真正让它变好的是后续的持续校对。别指望一条经验刚沉淀下来就完美,它更像是一个需要迭代的产品。
4.4 除了指标,技能库带来的隐形收益
上面这组数据只能说明性能和稳定性层面的提升。实际用下来,还有几个很难量化但非常重要的收益点。比如,新同学接手这套 Agent 时,不用再通过翻阅原始日志去猜测整个流程的设计思路,直接读技能卡就能理解大半。技能卡是用人话写的,可读性比日志高得多。
再比如,当任务出错时,我们可以在日志里直接看到命中了哪一条技能卡、在哪一步偏离了技能卡。这种可追溯性,让 Agent 系统的调试从“玄学”变成了“工程”。以前出了问题,我们只能猜是模型幻觉还是提示词写得不好;现在能直接定位到具体是哪条经验出了问题,改掉之后立刻能看到效果。
5. 经验库翻车的三个隐蔽场景
5.1 检索误命中:技能长得像,场景差得远
经验库最容易翻的第一个车,就是检索阶段命中了“看起来相似但完全不适用”的技能。我见过一个非常典型的线上事故:两个报表接口,一个的日期字段接受 “YYYY-MM-DD” 字符串,另一个只接受 Unix 时间戳。技能库里有一条技能,写的步骤是“把日期转成字符串再传参”,它在第一个接口上反复验证有效,于是被加上了很高的置信度。
结果一个属于第二个接口的任务进来,语义检索匹配到了这条技能,Agent 照着把日期转成字符串,第二个接口直接报错。问题出在技能卡里没有写“此技能仅适用于接口 A”的边界条件,于是检索系统把那条技能也当成了候选。这个事故之后,我把“不适用场景”字段设为技能卡的必填项,任何一张没有写清边界的卡片不进正式库。这是整个机制里我最看重的一条防线。
5.2 经验僵化:越用越熟,也越用越旧
第二个隐蔽问题,是技能库会让 Agent 的探索意愿下降,最终导致整条流水线“僵化”。当某条技能卡长期保持高命中率和高成功率时,检索系统会越来越倾向于把它排在最前面,Agent 也会越来越依赖它,不再尝试可能有更优解的新路径。
这种“越走越窄”的模式一旦形成,外部接口哪怕发生了小改动,整个链路也可能继续按旧方式跑,直到某个时刻突然大面积失败。解决这个问题,我给技能加了两个机制:一个是置信度衰减,一条技能如果长时间没有被验证成功,它的排名权重会逐日下降;另一个是执行失败后的“回退机制”,一旦 Agent 按照技能卡执行时发现某一步失败,就立刻停止参考技能,切换回自由推理模式。双保险可以在很大程度上避免经验库变成一条道走到黑。
5.3 自证偏见:把运气当成本事
第三个坑比较隐蔽,和 Agent 自己给结果打标这件事有关。很多 Agent 在任务结束时会生成一个“成功”的自评标记。但这个标记很不可靠——有时候它是在重试了三次、烧了很多 token 之后才成功的,却被记录成了一条“顺利的路径”;有时候它是靠环境波动侥幸通过的,却被当成了可复现经验。
我把这个现象叫做“自证偏见”。破解它,要靠外部结果而不是自评来判定成败。比如下游数据是否被正确消费、接口是否真实返回了期望数据,这些由系统硬校验;技能卡的成功率也要由此统计。另外,每张技能卡要求至少写一条“失败案例”作为对照,有了失败样本,成功样本才不会显得那么理所当然。
6. 想把这套机制搬进自己的项目,先从这几个习惯开始
6.1 选一个高频且评价标准清晰的场景先试水
如果你想在自己项目里引入一套类似的技能沉淀机制,我最大的建议是:不要一开始就铺到全场景。先挑一个重复性最高的业务动作,比如“调用外部 API 并汇总结果”,或者“生成固定格式的周报”。这个场景最好具备两个特征:频率高,且成功与否有客观标准。
频率高意味着经验能被快速验证,一天跑几十次,几天就能积累出足够样本。成功标准客观意味着技能卡的质量可以被数据衡量。如果成功标准是主观的,比如“生成一段软文”,那你怎么判断这条技能改对了没有?你会发现维护成本立刻失控。评价标准越机械,技能库越好维护。
6.2 技能卡模板要克制,字段宁少勿多
技能卡模板一定要克制。我见过有人设计出十几二十个字段的模板,看起来面面俱到,实际维护起来非常痛苦。每张卡都要填一大堆字段,入库的人会敷衍,调用的模型也会被冗余信息干扰。
我推荐的字段就七个:skill_id、name、trigger、steps、constraints、confidence、version。流水线相关的场景可以再加一个 failure_case 字段,用来保存最典型的一次失败样本。字段越少,填写负担越轻,入库质量反而越高。记住,技能卡是给 Agent 和人一起看的,它必须简洁得像一张索引卡,而不是一篇说明书。
6.3 更新技能保留版本,替换前先观察
技能卡一旦入库,并不代表永久有效。接口变了、任务定义变了,旧经验会逐渐失效。所以在更新流程上,我强烈建议保留旧版本。当一条新经验要替换旧经验时,不要立刻删除旧卡,而是先把新卡标记为“候选”,在之后的一段观察期内和旧卡并行,比较两者的成功率。
这个做法类似代码发布里的灰度替换。等确认新经验稳定优于旧经验之后,再归档旧卡。我自己见过太多次“新卡看起来很美,实际跑起来还不如旧卡”的情况,如果没有版本保留机制,这种回退会非常痛苦。多留一版坏数据不丢人,直接删掉能救命的数据才丢人。
6.4 每周做一次技能库体检
技能库是需要“保洁”的。我每周会看几个关键维度的统计报表:每条技能的命中次数、任务成功率、长期零命中的技能列表、执行即失败的技能列表。然后按三个动作处理:归档那些一个月都没被命中的卡,拆解那些命中率极低但任务又离不开的卡,回滚那些曾经正常但最近开始频繁失败的卡。
这个过程我把它叫“技能库体检”,和写代码要定期清理技术债是一个道理。如果你长期不做,技能库就会像堆积的文档一样,看起来内容很丰富,实际上已经长满了灰尘。我统计过一个现象:超过 30 天零命中的技能卡,在它再次被选中时,成功率平均比新任务还要低。理由不复杂——它太久没有被验证,很可能对应的业务背景已经变了。
6.5 让失败样本和成功样本住在一起
最后一条建议,也是我从运行这个机制里得到的最有价值的经验:每张技能卡旁边,一定要记录一条失败样本。我记得有一次排查一个持续低成功率的技能,原本打算直接删掉,后来翻到它的 failure_case 字段,发现它记录的全部问题都指向同一个根因——上游接口 401 返回时旧 token 未清理。
顺着这条线索,我们修复了 Agent 的 token 处理逻辑,而不是简单删除技能。如果当初没有把失败样本和技能放一起,这类问诊根本无从下手。成功经验告诉你“这样做能成”,失败样本告诉你“上次是那样挂掉的”。真正让技能库有价值的,从来不是前者,而是两者的组合。
我在实际把 SkillX 这套思路跑起来之后,最大的体会是:它最先改变的并不是 Agent 的性能指标,而是我对 Agent 的调试方式。以前 Agent 出错了,我要翻日志、猜状态、试各种 Prompt;现在先看它命中了哪条技能、用没用对、在哪一步偏离了技能卡。可定位性上来了,很多问题就不再是玄学。最后再分享一个小建议:如果你只打算做一件事,就把每张技能卡的“不适用场景”和“失败案例”两个字段填上。这比多沉淀十个成功经验都有用。