☰
Agent Skills与上下文工程:按需加载优化AI代理的token消耗与行为稳定性
2026/10/21 7:46:00 网站建设 项目流程

如果你手里有一个正在跑业务的AI代理系统,上下文窗口就是它的“工作台”,也是它最容易翻车的地方。给它塞任务描述、历史对话、工具返回、角色设定,很快它就会把最早的关键指令忘光,表现得像个刚入职三天的新人。我这两年在维护一套客服问答代理时,反复被这类问题折磨,最后把大量精力放在一件事上:把那些高频、稳定、可复用的处理逻辑封装成 Agent Skills,用它来做上下文工程(Context Engineering)的抓手。这也就是这个项目标题想表达的核心意思。

Agent Skills 不是什么神秘算法,它的本质是:把一类任务需要的提示词片段、参数校验、执行逻辑、后处理规则打包成一个独立技能,代理只在需要时把它加载进上下文,用完就释放,平时它不占内存。它解决的是上下文拥挤、信息互相干扰、token 浪费、行为漂移这类老难题。适合正在搭建或维护AI代理系统的开发者、算法工程师,也适合想优化大模型应用成本的架构师。这篇文章,我会把自己踩过的坑和验证过的做法完整拆出来。

1. 整体设计拆解:为什么通用Prompt让位于“技能化上下文”

1.1 Context Engineering到底在解决什么问题

上下文工程这个词,说白了就是对模型可见token空间的管理。模型每次推理,能看到的只有你给它的那一小段文本,它没有真正的长期记忆,也不存在“读完整个数据库还能记住”这回事。超出窗口的部分,要么被截断,要么被压缩成摘要,很多细节就丢了。

我观察到的现象是:大多数代理系统出问题,不是模型能力不够,而是上下文组织得太糟糕。把几十页的规章制度全文塞进系统提示词,再把最近20轮对话拿出来,还没开始干活,窗口就快满了。更麻烦的是,不同来源的信息会相互干扰。角色设定要求“先安抚情绪”,最近的用户消息又在说“我要投诉”,模型很可能被最近的信息牵引,忘了角色设定。这就是信息冲突带来的行为漂移。

所以 Context Engineering 做的不是“把话写好”,而是“把信息摆在正确的位置,并让不重要的信息及时退场”。你需要对上下文做四件事:预分配、优先级排序、动态注入、及时回收。这四件事,靠手写 Prompt 是维持不住的,需要一套机制。Agent Skills 就是在这套机制里承担“任务级上下文单元”的角色。很多人以为只要加大上下文窗口就能解决问题,但窗口再大也是有限资源,而且窗口越大,模型对中间信息的注意力越涣散,成本也越高。上下文工程真正的价值,是在资源不变的前提下,通过设计提升信息的信噪比。

1.2 Agent Skills 的核心价值:按需加载的上下文单元

Agent Skills 这个词,我把它拆成两个层面理解。第一层是“技能”,即一个代理能调用的原子能力;第二层是“上下文工程单元”,也就是它如何在有限窗口里被组织、加载和卸载。

一个技能应该具备三个特性。原子性:一个技能只处理一类明确定义的任务,比如“提取订单号并查询状态”,而不是“处理所有用户问题”。自描述性:技能的描述要足够精确,让代理的意图识别模块能一眼判断当前场景该不该触发它。低侵占性:在空闲状态下,技能不进入上下文;只有触发后才注入。这个设计很像电脑里的“移动硬盘”,平时放在架子上,需要时插上,用完拔掉,不占机器内置存储。

相比把所有规则全局常驻,按需加载有非常直接的优势:token 消耗明显下降,指令间的互斥矛盾少了,行为可预期性提高了。项目后期我在客服代理上实测,把二十多个技能全部做成按需加载后,平均单次任务消耗的 token 比原来滚动拼接方式下降了约四成,而这些技能覆盖了九成以上的高频工单场景。这个收益不是靠压缩文本得到的,而是靠“不加载无关内容”得到的。上下文里少一堆当前任务用不到的规则,模型做决策时的选择空间小了,错误自然就少了。

1.3 与常见方案的选型对比

我把大家常用的几种方案放在一张表里感受一下各自的定位:

方案上下文占用维护成本行为稳定性适用场景
全量规则写进系统提示词高,随规则数量线性增长低差,规则多了容易互相打架规则极少、演示级系统
Few-shot 缓存中,只在需要时插入示例中较好,但示例和真实任务容易混淆格式要求稳定的抽取任务
RAG 检索中,检索结果按需注入中高中,依赖检索质量知识问答、文档处理
Agent Skills低,调用时注入、用完回收初始较高,后期稳定高,职责边界清晰高频、结构化、可流程化的代理任务

选型上我的观点是:不要试图用 Agent Skills 替代 RAG。知识广度的刷新靠 RAG,行为稳定性和流程可控靠技能。它们之间是配合关系,不是替代关系。如果一个任务高度依赖长尾知识,技能里只需要放“如何检索并使用知识”的流程,而不是知识本身。一个典型的误区是,有人把文档内容直接塞进技能模板,技能变成了一堆死知识。一旦知识更新,技能就要改版,成本很高。正确做法是技能模板里写“调用检索工具,输入以下关键词,获取前5条结果摘要,再基于摘要作答”,把知识内容放到外部知识库,由RAG负责保鲜。

2. 核心细节解析与实操要点

2.1 一个合格的 Agent Skill 应该包含哪些部分

我习惯把一个技能定义成六块:标识信息、触发条件、上下文模板、参数 Schema、执行逻辑、退出清理。

标识信息包括技能ID、名称、版本、简短描述。这里描述不是给人看的,是给代理的意图匹配器看的,所以要写成“当用户表达需要查询物流状态且包含订单号时,使用此技能”,不能只写“物流查询”。因为意图匹配器吃的是文本语义,描述越具体,检索命中越准。

触发条件写清楚技能在什么场景生效,什么场景必须被抑制。这个字段很多人忽略,导致两个技能互相抢活。比如“查询天气”和“规划出行”都涉及地理位置,如果不加抑制条件,代理可能给“今天能出门吗”匹配到天气技能而不是出行建议技能。我一般会在触发条件里写否定句式:“当用户询问户外活动安排时,不要触发天气查询,优先触发出行规划。”这样能明显减少误用。

上下文模板是技能被加载时注入的那段文字,包含角色说明、任务步骤、约束条件、输出格式。模板要尽量短,我一般控制在300到500 token以内。很多人觉得模板长一点信息更完整,但实际上,长模板会把模型对关键指令的注意力摊薄,而且占用工作内存,导致其他信息无处安放。参数 Schema 定义这个技能需要从对话里提取哪些字段,比如订单号、时间范围。执行逻辑则是代码层面的处理函数,它可能调用外部API,也可能只是对模型输出做后处理。退出清理负责在技能执行结束后,把临时注入的内容从上下文里回收,或者替换成一行执行摘要。

六块内容对应到代码里,通常就是一个类的属性或一个 JSON 包的字段。我建议技能定义和业务逻辑分离,用一套统一的注册协议,这样后续才能做热更新,不需要每次加技能都发版。实践上,我把技能定义放在一个目录下,命名规则是“技能ID_版本号.json”,业务逻辑放在另一个目录下,用接口绑定。这样运营同学可以直接调整触发描述,无需改代码,开发同学也方便做单元测试。

2.2 上下文窗口预算与回收机制

上下文预算这件事,我见过最质朴也最容易翻车的做法:全部拼在一起,谁失控就去调 prompt。更可靠的思路是给上下文分段设预算。我把系统提示词、对话历史、工作内存、工具结果各设一条预算线,技能调用时再从“工作内存”里切一块临时额度。

一个常见比例是这样:系统提示词占20%,对话历史占30%,技能模板加工具结果占30%,工作内存占20%。这个比例具体根据场景调整,但关键是“工作内存”必须预留,否则技能加载时没有空间,只能挤压对话历史,而历史挤压过头,代理就忘了用户前面说过什么。我吃过这个亏:有一版系统把工作内存设置为零,技能调用时强制淘汰对话历史,结果用户连续追问三个问题后,代理完全不记得第一个问题,对话连贯性彻底崩了。后来我把工作内存设成固定的500 token,宁可在长会话里做历史摘要,也不让技能无家可归。

回收机制是最容易被低估的部分。技能执行完,如果把它注入的几百 token 原样留在上下文里,一轮对话还好,十轮之后就会积累成灾难。我的做法是:执行结束后,将完整技能上下文替换成一条固定格式的结果摘要,比如“已调用技能[查询订单状态],结果:运输中,预计明日到达”。摘要通常只需要几十个 token。这样既保留了结果信息,又不让过程文本持续占用。回收要放在统一框架层去做,不能指望技能作者自己记得清理。我见过不少团队把清理代码写在技能内部,结果几个人写的技能有的没清理,有的格式不一致,最后框架层统计上下文时完全乱掉。

2.3 技能注册表与冲突仲裁

当技能数量超过十五个,光靠描述匹配已经不够,需要引入注册表。注册表负责三件事:技能清单登记、能力索引、冲突仲裁。

技能清单登记就是记录所有可用技能的ID、版本、状态,状态包括启用、暂停、下线。能力索引是把技能描述做一次向量化,方便用语义匹配替代字符串匹配。这里有个细节:向量化是把描述字段转成 embedding,所以描述写得是否具体,直接影响检索质量。冲突仲裁则是当多个技能同时匹配时,决定谁先生效。仲裁规则可以是显式的优先级数字,也可以基于工作流状态。

实操中我建议不要把仲裁逻辑写得过于复杂。先用一个简单的优先级字段解决80%问题,只在少数场景用状态机判断。我在一个文档处理代理里就把优先级字段定为1到99,默认50,高优先级技能用于处理合规约束,低优先级用于格式化输出。这样设计简单,效果好,也方便后续排查。如果你一上来就上复杂规则引擎,排查成本会成倍上升。仲裁还有一个容易被忽略的点:同一个技能被连续触发两次时,是否允许重复执行。我通常给技能加一个“幂等性”标记,比如查询类技能幂等,可以重复执行;下单类技能非幂等,同一对话上下文内禁止重复触发。这个标记放在注册表里,由框架层强制检查,能挡掉不少事故。

3. 实操过程与核心环节实现

3.1 从0定义一个技能:代码示例

我用一个“客服工单状态查询”技能来说明最简实现。定义部分是一段 JSON:

{ "skill_id": "order_status_query", "name": "订单状态查询", "version": "1.2.0", "description": "当用户询问订单物流或配送进度,且对话中包含订单号时使用此技能。如果用户只想申请退款,不要触发本技能。", "triggers": { "include": ["订单", "物流", "配送", "到哪了"], "exclude": ["退款", "退货"] }, "priority": 55, "idempotent": true, "template": { "system": "你是订单查询助手。请从用户消息中提取订单号,调用查询接口,用简洁的中文回复物流状态。不要编造物流信息。", "steps": [ "提取订单号并校验格式", "调用订单查询接口", "将接口结果转成一句通俗表达" ], "output_format": "订单号开头的简短状态说明" }, "parameters": { "order_id": { "type": "string", "required": true, "pattern": "^[A-Za-z0-9]{10}$" } }, "cleanup": "replace_with_result_summary" }

然后注册器逻辑:

from skill_registry import SkillRegistry registry = SkillRegistry() registry.load_dir("./skills") def on_user_message(message: str): candidates = registry.match(message) skill = registry.arbitrate(candidates) params = registry.extract(skill.parameters, message) skill_result = registry.execute(skill, params) registry.release(skill, skill_result) return skill_result

这段代码里最需要注意的是 template 内不能出现对具体参数值的依赖,所有可变内容都要通过参数插值。比如不能说“订单号是123456”,而应该是“订单号为 {order_id}”。让模板保持“可复用”的状态,是上下文工程的关键。我在实际审查代码时发现,有同事图省事,在模板里直接写死了一批测试订单号,结果模型在线上真的以为这些订单存在,闹出不少乌龙。

3.2 代理调用的完整流程:从意图到回收

一次调用在代码层面是这样走完的:

  1. 每轮请求进来,框架先把当前用户消息交给意图识别模块。
  2. 意图识别模块在注册表里做语义检索,得到候选技能列表。
  3. 仲裁模块根据优先级和当前状态选出最终技能。
  4. 框架从用户消息中抽取参数,填充到技能模板里。
  5. 将填充后的模板注入上下文,让模型生成执行结果。
  6. 执行结果做规则校验,比如订单号格式、时间戳格式;不合格就重试一次。
  7. 调用结束后,将技能模板整体替换为摘要,释放上下文空间。

这里第6步很多人会跳过,但我建议至少保留一条不可绕过校验。我在订单查询场景里要求返回结果必须包含一个合法的日期,否则就让模型重新生成;如果重试两次仍失败,就走人工兜底。这条规则把模型“胡编”造成的客服投诉率降到了很低的水平。

举一个具体例子。用户问:“我的手机订单走到哪了,昨天买的。”系统抽取订单号,注入模板,调用“订单状态查询”技能,得到“已发货,预计后天到达”。随后,这段模板被替换成“技能[订单状态查询]结果:已发货,预计后天到达”的摘要。下一步对话里,即使用户说“那退款怎么走”,系统依然知道此前的订单状态,但不会残留技能模板的干扰。这就实现了“记得结果,忘记过程”的理想状态。

3.3 实战案例:用技能压缩代码解释器的上下文

另一个我反复验证的场景是代码解释器。让大模型解释代码文件时,如果直接把整个文件塞进去,大文件会立刻撑爆窗口。我的做法是把“看代码”这件事拆成三个技能:目录浏览、函数签名提取、具体函数阅读。

目录浏览技能只读取文件列表,注入几十行文本;函数签名提取技能扫描关键函数,生成签名和注释摘要;只有当需要理解某一函数实现时,才把该函数完整代码注入。这套流程让一个几万行的代码库的上下文占用被控制在几千 token 以内,并且准确率没有明显下降。这个案例说明:技能化的本质不是压缩,而是对“信息粒度”的按需调度。就像你需要了解一栋楼的布局时,先看楼栋平面图,再进入某个房间细看,而不是把整栋楼的施工图纸全部摊开。

在代码解释器里,我还给“具体函数阅读”这个技能加了一个参数:函数名。模型必须先通过签名提取技能找到函数名,再带着函数名调用阅读技能。这个设计让上下文只加载一个函数,不加载整个文件。实测一个 2 万行的 Python 工程,原来直接读文件需要 3 万 token,拆成技能链路之后,平均只花 4000 token,响应时间也从 20 秒降到 5 秒内。对团队里负责代码审查的同事来说,这个改进直接让效率翻倍。

4. 常见问题与排查技巧实录

4.1 高频坑 Top5

我把实际运行中遇到的典型问题整理成一个列表,希望帮你避开。

  • 技能误触发:两个技能描述相似,仲裁模块选错。排查时发现是描述里都出现了“订单”一词。解法:描述中加入否定条件和适用场景。
  • 上下文回收不及时:技能执行完没有替换为摘要,多个技能轮番调用后上下文爆满,代理开始胡言乱语。解法:在统一框架层做回收,不要在技能内部自己处理。
  • 参数抽取错乱:抽取器把上一轮的值当成这一轮的参数。比如用户先问“订单号12345”,下一轮问“那物流呢”,结果模型取到上一轮的订单号,看起来没问题,但如果用户说“另一单呢”,系统就会取错。解法:给参数 Schema 加来源标记和新鲜度校验,明确参数必须来自当前消息。
  • 模板注入频繁失败:技能模板过长,超过工作内存预算,直接被截断。解法:压缩模板措辞,把详细步骤移到执行函数的注释中,而不是模型可见的上下文中。
  • 技能版本失控:技能更新后,旧版本仍被低优先级调用路径使用,行为不一致。解法:注册表增加版本约束,每次调用写日志记录技能版本。

4.2 排查思路:回放和 Token 消费分析

当代理表现不稳定,我的第一动作不是调 prompt,而是开回放。把所有请求和对应的技能调用日志拉出来,看每个请求命中了哪个技能,参数抽取是否合理,上下文在每一步的 token 占用曲线。

回放时重点找三个特征:有没有技能在连续请求中高频触发却输出相似结果?这往往说明缓存没做,重复计算浪费 token;有没有技能被注入后未执行逻辑就退出?这往往是触发条件写得过宽,误匹配进入后又不满足内部校验;有没有技能在同一轮里被重复加载?这往往是幂等标记缺失,导致查询接口被调了两次。这三个特征分别对应缓存缺失、逻辑短路、加载重复。

另外建议统计每个技能的命中率、平均延迟、平均 token 消耗。命中率高而消耗高的技能,值得做模板压缩;命中率低的技能,考虑是描述不够可鉴别还是真的没人用。如果没人用,直接下线,避免干扰注册表检索。我见过一个代理里躺着几十个低频技能,匹配时总被检索到,虽然最终仲裁不会选,但每一个候选都会增加一轮过滤计算,拖慢整体响应。

4.3 快速排查速查表

现象可能原因排查动作解决建议
两个技能总选错描述语义不够区分对比候选技能描述文本增加否定条件与场景关键词
上下文占用持续升高技能结果未替换为摘要查看上下文消费曲线框架层统一清理回收
参数取到旧值抽取参数来源未限定当前消息打印参数抽取日志增加新鲜度校验
技能被截断无法工作模板超预算检查工作内存剩余量压缩模板或扩大预算
同一技能重复执行缺失幂等标记查看调用日志中技能出现次数注册表加 idempotent 约束

5. 从“技能”到“上下文工程体系”的进阶思路

5.1 上下文生命周期管理

单一技能简单,但多个技能组合成工作流后,需要引入上下文生命周期管理:技能 A 的输出成为技能 B 的输入,中间哪些该保留,哪些该消除,必须显式声明。我自己的经验是引入“上下文阶段”概念,每个技能声明它读入哪些信息、产出哪些信息,框架统一做数据传递和清理,避免技能之间的隐式依赖。

举一个实际场景:用户申请退款,系统先调用“订单状态查询”技能确认订单可退,再把订单信息传给“退款申请”技能,最后调用“退款进度查询”技能。三个技能共享订单号,但第一个技能的完整输出不应进入第三个技能,否则上下文里会出现很多中间判断。通过生命周期管理,框架只把“订单号+可退标记+退款单号”传给下一环,其他内容全部回收。这个设计让多技能工作流像工厂流水线一样清晰,每个环节只看到自己需要的零件。

5.2 量化上下文效率的指标

如果你不知道怎么评估上下文工程做得好不好,就看三个指标:每有效任务 token 消耗、技能命中准确率、上下文重试率。每有效任务 token 消耗越低说明越高效;技能命中准确率反映意图匹配质量;上下文重试率反映窗口溢出和冲突频率。这三个指标可以在日志中连续统计,作为后续优化的依据。

我在体系上线后定期拉这三个指标,发现某版本因为新技能上线,命中准确率从 92% 掉到 85%,顺着日志定位到新旧技能描述冲突,快速回滚后恢复正常。数据不撒谎,建立指标后,优化才有了明确靶子。每有效任务 token 消耗可以和业务收益挂钩,比如客服场景里,能不能用一半 token 完成同一张工单处理。如果技能模块做得好,这个指标的改善是肉眼可见的。把这三个指标放进看板,定期回顾,你就能持续发现“哪些技能该拆、哪些该合、哪些该删”。

5.3 经验复盘:克制比堆集更重要

最后我的体感,Agent Skills 做一段时间之后,风险不是没有技能可用,而是技能泛滥成灾。每次需求来了就加一个技能,技能数量从二十膨胀到八十,注册表检索效率下降,仲裁冲突增多。这时候最该做的不是继续加技能,而是合并和下线。我已经不只一次把几个细碎技能合并成一个大技能,反而效果更好,因为描述更聚焦,参数更统一,上下文中占用更少。

我给自己定了一条原则:同一个层级上的技能数量尽量不超过三十个。超过三十,先审视是不是有技能做得太碎或太宽。太碎是指一件事拆成多个步骤却各自独立,太宽是指一个技能覆盖了太多不相关的子任务。这个数字不绝对,但它能帮你守住设计的底线。技能化不是一个“越多越好”的游戏,而是一个“边界清晰、按需加载、用完即走”的纪律。

以我维护这套系统的经验来看,Agent Skills 真正难的不是实现,而是克制和纪律。有些规则你很想全局常驻,觉得这样放心,但只要全局常驻,它就一定会和别的规则抢上下文。我现在的习惯是:先砍一半全局指令,把它们按场景拆成技能,跑两个星期看指标。这比任何智能优化都来得立竿见影。最后再分享一个小技巧:技能描述里的动词越具体越好,比如把“处理”换成“校验并转换”,命中率会明显提高。

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

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

立即咨询