☰
告别提示词膨胀:为Agent构建可插拔技能库的实战方法论
2026/10/8 4:56:36 网站建设 项目流程

做 Agent 项目做久了,你多半会撞上同一堵墙:模型越强,你越想把所有本事都塞给它,结果提示词越写越长,长到模型开始选择性失忆,长到你都不敢随便改一个标点。我自己是在第三次把二十多条技能说明硬塞进 system prompt 之后彻底放弃这个做法的,转而搭了一套独立的 agent-skills 技能库——把能力拆成一格一格可插拔的技能单元,让模型按需调用、按需挂载。这篇文章就把我踩过的坑、最后的落地结构和完整方法论一次讲清楚,适合正在做智能体应用、被工具管理和提示词膨胀搞到头大的开发者和产品经理。

1. 为什么 Agent 需要一套独立技能库:从提示词膨胀说起

1.1 我最初的做法:把技能写成"行为准则"塞进提示词

做第一个 Agent 的时候,我并没有"技能库"这个概念。当时的需求很朴素:让模型根据关键词调用一个内部检索接口。于是我在 system prompt 里写了一段说明,大意是"当用户提到某某关键词时,调用某某接口,然后把返回结果整理成列表"。第一个版本挺好用,第二个、第三个功能也照这个思路加进去。

三个月后,提示词已经攒到四千多字。模型开始出现两个很典型的毛病:一是把较早写进去的技能规则忘掉,用户问得直白它都反应不过来;二是遇到两个语义相近的技能说明时,开始随机抽风,今天调用 A,明天调用 B,完全不可控。更糟糕的是,每轮对话都要把所有技能说明完整发一遍,单轮 token 成本肉眼可见地上涨。

后来我意识到,这本质上不是一个"提示词怎么写"的问题,而是架构问题。大模型每一轮对话都像是一个全新的人接手工作,他唯一的任务依据就是当前上下文。如果我让他同时记住几十条行为准则,他一定会按照自己的偏好挑选一部分、忽略一部分。而技能库要做的,就是把这部分记忆从提示词里搬出来,让模型只在他真正需要某项能力的时候,才看到对应的完整说明。

1.2 技能、工具、知识与提示词的边界到底怎么切

在动手搭技能库之前,我先把概念理了一遍。因为如果连什么是技能都说不清,后面写出来的规范一定很混乱。我的划分逻辑是这样的:

  • 提示词是常驻记忆,负责设定角色的基本立场、输出风格、默认行为边界;
  • 知识是被动记忆,比如文档、FAQ、数据,模型在需要时查询,没有查询动作就不会主动使用;
  • 工具是外部动作接口,比如发请求、执行代码、写文件,模型通过调用工具改变外部世界;
  • 技能是可复用的操作程序,它是一段结构化的指令,告诉模型在特定场景下按什么步骤执行任务,并且可以组合工具完成目标。

举个例子:让模型调用一个发邮件的接口,这是工具;让模型知道"当用户要求批量发送周报邮件时,先读取收件人列表,再逐封生成正文、调用发信接口、最后汇总失败项",这是技能。技能的特别之处在于,它包含决策逻辑,但又是独立、可命名、可版本化的单元。

| 维度 | 提示词 | 知识库 | 工具 | 技能 | | 作用 | 设定行为立场 | 提供事实 | 执行外部动作 | 编排操作流程 | | 是否常驻上下文 | 是 | 按需检索 | 按需调用 | 按需加载 | | 能否单独评测 | 难 | 可 | 可 | 可 | | 典型故障 | 上下文膨胀 | 检索不准 | 调用失败 | 命中不准 |

1.3 技能库真正要解决的三件事:复用、评测、降级

想明白边界之后,我给自己定了三个目标,后来发现这三个目标其实就是技能库存在的全部理由。

第一是复用。同一套"生成结构化会议纪要"的技能,既可以在客服 Agent 里用,也可以在内部分析 Agent 里用,不需要复制粘贴几十遍 prompt。只要技能文件是可挂载的,任何 Agent 都能在需要时加载它。我在实际项目里建立了两个技能库:一个是通用库,放每个人都可能用到的技能;另一个是领域库,按业务线拆分,各团队自己维护。

第二是可评测。提示词没法单独跑一遍测试,但技能可以。我给每个技能都配了独立的任务样本,分布在触发正确性、步骤完整性、输出格式三个维度上。每当有人修改技能,我就跑一遍回归,看有没有把别的场景带崩。这个做法听起来简单,但确实帮我拦住了绝大多数悄悄劣化。

第三是可降级。真实环境里没有哪个能力是永远可靠的。技能库里必须有一条明确的降级路径:A 技能失败时,是尝试 B 技能,还是退回主 Agent 的基础回复。我在技能字段里专门配置了 fallback,目的就是让失败的时候系统有一个体面的出口,而不是让模型硬着头皮胡编。

2. 技能单元的接口设计:把"能力"变成可被调用的规格

2.1 一份技能清单应该包含哪些字段

确定要搭技能库之后,我参考了不少开源 Agent 项目和内部工具的经验,最终把技能单元收敛成下面这套字段。核心思路是:让技能既能让模型读懂,也能让人维护。

name: weekly_report version: 1.3.0 description: 根据群聊/邮件/任务系统记录生成周报。适用于用户要求"总结本周工作""生成周报""周报帮我写一下"等场景。 trigger: - user_says: ["周报", "weekly report", "本周工作"] - explicit_mention: true instruction: | 1. 先调用 message_finder 拉取最近 7 天与当前用户相关的记录; 2. 将记录按【项目】【已完成】【进行中】【阻塞】四类分组; 3. 每组输出不超过三条,每条保留关键结果数字; 4. 使用 markdown 表格呈现最终周报。 parameters: since: type: string default: "7d" description: 统计起始时间偏移。 audience: type: enum values: [leader, team, self] default: leader output: format: markdown_table required_fields: [summary, progress, blockers] fallback: basic_agent_reply

这套字段里,name 和 description 是模型能否正确认识这个技能的关键,instruction 是执行层面,trigger 和 fallback 是路由和容错。我在实际运行中还加过不少字段,比如 tags、author、last_validated,但最终归档时保留的就是这份精简集合,因为字段越多越容易在维护时互相打架。

2.2 描述文本的写法:先让它能被"检索"到,再谈执行

我踩过最直接的坑就是 description 的写作方式。一开始我习惯把技能完整步骤都写进 description,结果发现模型反而抓不住重点。因为技能加载的机制是:先由路由层或模型根据用户输入检索技能,检索本质上是语义匹配,一段几百字的 description 会让相似度计算变得非常模糊。

后来我把 description 压缩成两句话:第一句说明技能做什么,第二句说明在什么场景下激活,最多再补一个"不要使用"的场景。例如上面这个例子,description 就明确写了"适用于用户要求总结本周工作、生成周报"。那些完整的操作步骤全部放进 instruction 字段,只在技能被命中之后才被加载进上下文。改完这个之后,我的技能命中率明显上升,上下文开销也小了很多。

2.3 目录结构、版本号与挂载规则:维护者友好也很重要

技能库不是一堆文件的随便堆叠,目录规范决定了一个团队能否长期协作。我的习惯是在仓库里用这样一个结构:

skills/ common/ meeting_minutes/ SKILL.md assets/ tests/ weekly_report/ SKILL.md tests/ internal_tools/ ...

每个技能一个目录,目录名是全小写、下划线分割;SKILL.md 是唯一的主文件;tests 目录放评测用例;assets 放技能执行时可能需要的模板文件。版本号我放在 SKILL.md 头部,不单独建文件,因为改技能逻辑时几乎总是要同步改文档,放在一起才能避免文档和代码分家。

挂载规则上,我倾向于给每个 Agent 配一个 manifest 文件,显式声明它需要加载哪些技能,而不是让系统自动扫描全部技能。自动扫描的优点是省事,坏处是模型可选的技能太多,路由噪音变大。经过实测,单 Agent 挂载 15 到 25 个技能是比较舒服的范围,再往上,命中准确率会出现可察觉的下降。

3. 技能编排的实战拆解:单技能命中与多技能协作

3.1 触发策略:显式调用、隐式路由还是混合

技能库搭好后,另一个问题摆在面前:技能什么时候被触发。我先后试过三种策略,最终选择了混合方案。

显式调用是指用户在对话里直接说出技能名,比如"调用 weekly_report 技能"。这种方式最可靠,但把负担推给了用户,普通用户几乎不会这么说话,只能作为调试手段。

隐式路由是指模型根据用户输入自行判断,从技能库里挑一个合适的技能来执行。这是最自然的体验,但也是误命中风险最高的路径。我见过最典型的翻车是:用户问"这个月的数据怎么下载",系统匹配到了"数据导出"技能,结果把一份汇总报表发出去,而不是原始文件。问题出在 description 没有写清楚技能的边界。

混合策略是我最后用的方案:系统先做一轮轻量规则匹配,如果在用户输入里检测到明确的技能关键词,就直接触发对应技能;如果没有检测到关键词,再由模型从技能列表中选择。这样既保留了自然对话的体验,又给了系统一个确定的兜底。规则匹配层不需要很复杂,一个关键词表加少量正则就行。

3.2 一个多技能协作的例子:自动周报管线

技能真正发挥威力,是在多技能协作的场景里。我拿周报来说:单独一个 weekly_report 技能做不了什么,因为它的输入来自好几个数据源。我把整个流程拆成三个技能,每个技能负责一个阶段。

  1. message_finder:拉取用户最近七天的群聊、邮件和任务系统记录,输出结构化 JSON,字段包括来源、时间、内容摘要;
  2. session_digest:把上一步的 JSON 记录按主题聚类,生成摘要和关键结果,输出分组数据;
  3. weekly_report:接收 digest 的输出,结合用户希望的周报受众,渲染成最终 Markdown 周报。

这样做的好处是每一步都可以单独调试。比如 message_finder 返回的数据格式变了,只需要改动第一个技能,后面两个完全不用碰。我在真实项目中经常这样拆:每个技能最好只做一件事,超过三件事就直接考虑拆分。

3.3 状态与上下文的传递:技能之间不要互相"扒上下文"

多技能协作最容易忽略的是数据传递。很多人误以为技能 A 跑完之后,技能 B 自然能看到 A 的所有过程,这在纯对话模型里可能是因为同一份上下文延续了,但一旦技能执行涉及外部工具调用、或者上下文被裁剪,它就不成立了。

我的做法是:每个技能输出一个明确的结构化结果,最常见的就是 JSON。下一个技能只依赖这个结果,不依赖上一技能的内部日志。如果一个中间结果太大,比如拉回来的记录有几十条,我会让技能先压缩成摘要再往下传,把上下文占用控制住。这有点像是工厂流水线:每个工位只接收上一工位交验的半成品,不直接翻上一工位的垃圾箱。

提示:所有技能之间的数据契约最好集中定义在一个 schema 文件里,改任何一个技能的输入输出格式时,先改契约再改实现,避免"A 改了输出 B 悄悄坏了"这类回归问题。

4. 落地踩坑记录:命中率下降、资源膨胀与降级策略

4.1 坑:description 越来越长,命中率反而下降

这个坑我在前文提过,但值得单独复盘一遍完整链路。当时有个"数据导出"技能,description 写得很详细,包含了导出格式、导入系统、权限说明、失败重试步骤,一共四百多字。上线第一周效果不错,第二周开始用户反馈"明明在说导出,Agent 却去执行了别的技能"。

我的排查过程是这样的:先看路由层日志,发现模型确实看到过这个技能,但它被排在候选列表中间偏后的位置;再看模型候选排序,发现"数据导出"与"报表生成"的语义非常接近;最后定位到根因——过长的 description 让排序器难以抓住它最核心的辨识特征。

修复方案是重写 description,只保留"做什么"和"何时触发",把细节全部移入 instruction,并在末尾补了一句"如果用户只是要查看数据而不是生成文件,请不要使用本技能"。

4.2 坑:技能互相干扰——语义拥挤与命名冲突

多个技能放在一起,模型不一定能分清它们的边界。我遇到过一次很典型的冲突:一个技能叫 data_query,负责查询业务数据库;另一个叫 data_export,负责把查询结果导出成文件。表面看边界很清楚,但在实际对话中,用户说"帮我把这个数据拉出来",模型在两秒钟内犹豫不决,最后随机选中一个。

排查方法也很直接:把两个技能的 description 并排放在一起读,看它们是否共享了太多关键词。果然,两个 description 都写了"数据""查询""结果",区分度严重不足。我最后不只是在文字上做了区分,还调整了名称语义:data_query 改成 query_raw_records,data_export 改成 export_records_to_file,让模型看到名字就能猜到边界。

这个改动看起来不起眼,但效果非常明显。我后来总结出一条经验:技能的命名和 description 一样,都是给模型做的导航标识,千万不要只图人类读起来顺口。一个内部叫了十年的历史名词,在模型眼里可能和另一个技能完全撞车。

4.3 坑:技能失败后模型开始胡编或死循环

技能执行不是百分之百成功的,尤其涉及外部接口时。最让我头疼的是模型在技能失败后的行为:有些模型会在报错之后假装成功,有些会陷入同一技能的反复调用。前者产生虚假结果,后者烧掉大量 token。

我的修复是在技能调用层加了一个统一的失败协议:技能返回结构化错误码,同时给出下一步建议;如果错误码被判定为可重试,最多重试两次;如果仍然失败,就直接走 fallback,不再让模型继续折腾。这里给所有做同类系统的同行一个建议:不要在技能执行成功路径上写太多逻辑,把失败处理写清楚,比把成功写漂亮更重要。

4.4 我踩完坑之后固定的资源管理清单

复盘完所有坑之后,我把技能库的资源管理收敛成下面这张清单,每次新项目接入时照着检查一遍。

  • 单个 Agent 挂载技能数控制在 15 到 25 个,超出就拆分 Agent 或用子 Agent;
  • 每个技能 description 不超过 120 字,原则是"两句话,一个负向场景";
  • 每个技能必须有独立 tests 目录,改动后必须跑回归;
  • 所有技能间数据传递使用统一 schema,禁止直接引用对方内部状态;
  • 失败处理必须有 fallback 字段,不允许无兜底挂载。

5. 给技能库做体检:评测集建设与迭代节奏

5.1 技能评测的四个维度:触发、可用、副作用、耗时

技能和算法一样,必须能量化验证。我在实践中把评测收缩为四个维度,覆盖了技能从被选中到执行结束的全过程。

  • 触发准确率:在正确场景下,技能被正确命中的比例;
  • 结果可用率:技能执行完,输出能被下一步消费的比例;
  • 副作用率:技能在执行过程中产生不该有的副作用的比例,比如误调了权限较大的接口;
  • 耗时与 token 开销:一次完整技能调用的大致成本,用于决定是否需要拆分或精简。

这四个维度不是平级的,触发和可用是基础的生死线,副作用是风险线,耗时是成本线。我一般先保证前两项稳定,再谈优化后两项。项目早期如果直接去抠 token 成本,往往会捡了芝麻丢西瓜,因为技能能不能被正确选中,才是所有收益的前提。

5.2 最小评测集怎么建:从真实日志里抽,而不是自己编

很多人在这一步习惯自己写测试用例,我强烈建议不要。自己写的问题往往是"想法中的用户",而不是"真实的用户"。我建评测集是从线上日志里抽真实对话样本,按类型去重后最少留 30 到 50 条,每个技能至少覆盖五种变体。

| 用例类型 | 数量 | 判断标准 | | 直接触发 | 10 | 应当命中目标技能 | | 语义等价 | 10 | 换种说法仍应该命中 | | 歧义场景 | 5 | 应当命中更合适的技能 | | 负向场景 | 5 | 不应当命中任何技能 | | 乱序混合 | 5 | 多技能场景下调用顺序正确 |

这个表格是我每次给新技能建评测集时的模板。负向场景尤其容易被忽略,但它其实是防止误伤最重要的部分。没有负向样本,你会发现自己改完 description 之后,命中率数字反而变好看了,因为模型开始什么都往这个技能上靠。

5.3 迭代节奏:一次只改一个变量,避免批量翻车

技能库最大的维护风险是并行变更。团队里两个人同时改两个技能,如果评测集不够强,根本不知道谁的责任。我的流程是:一次只改一个技能;改动前先跑一遍旧评测集确认基线;改完再跑一遍,对比两个版本的触发准确率。任何指标下降超过两个百分点,直接回滚。

就这样循环几轮之后,我的技能库才变得真正敢改。后来我又在 CI 里接了一个自动跑评测的脚本,每次 push 技能目录的改动,就会自动出这份对比报告。回归成本从原来的手动一两个小时,降到了几分钟,项目的可维护性彻底上了一个台阶。

写到最后想分享一个体会:技能库的建设没有终点,每次模型版本升级、每个新业务接入,都会暴露新的边界问题。别试图一次设计成完美答案,先让 10 个技能跑稳,再逐步扩张,比一开始就铺几百个技能文件扎实得多。如果你正在被 Agent 项目的提示词膨胀和工具调用混乱折磨,从一套 10 个左右的技能库开始,三个月后你会回来感谢当时的决定。

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

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

立即咨询