☰
告别巨型Prompt:Agent技能体系设计与实战
2026/10/13 8:31:48 网站建设 项目流程

第一次决定认真做 agent-skills 这套技能体系,是因为我已经受够了给 Agent 喂一条巨型 Prompt。当时我在维护一个文本处理类的 Agent 项目,单条指令超过六千字,用户随便问一句,模型经常把中后段的规则忘得干干净净。改 Prompt 的人来回拉扯了一个多月,成功率依然在六成上下晃。后来我试着把任务拆成几个独立的技能,让模型按需调用,结果一次小实验就把成功率拉到了八成以上。这个反差让我意识到,问题的关键不在于写更好的提示词,而在于把能力切成可复用、可测试的单元。这篇文章围绕 agent-skills 的实战经验,梳理我如何设计技能粒度、组织技能库、排查落地中的坑,以及怎么评估一套技能体系是否健康。适合正在做 Agent 应用的开发者,也适合被 Prompt 膨胀折磨了很长一段时间的同学,哪怕你没有完整做过技能框架,也可以从这里拿到一套可以直接照做的设计思路。

1. 为什么我决定把 Agent 改造成"技能驱动"而不是"提示词驱动"

先说说我原来是怎么干的。最早做 Agent 的时候,大家普遍的做法是把所有规则、约束、示例全部塞进系统提示词里,指望模型像读说明书一样把每一条都记清楚。早期任务简单,这一套还扛得住;任务一多,问题就全冒出来了。

1.1 单体提示词的四个隐性成本

第一个是注意力稀释。模型处理超长上下文时,对靠后的指令记忆明显变差。我在项目X里做过统计,交给模型的任务说明超过四千字之后,排在最前面的规则能被稳定遵守,越靠后的规则越容易在执行中丢失,用户高频问的几个问题恰好命中后半段规则,整体成功率自然上不去。

第二个是上下文预算被文字消耗。输入 Token 不是免费的,这些 Token 还要留给用户输入、工具返回结果、中间推理过程。提示词越长,留给真实任务的空间越小。我见过一个查询类 Agent,光是固定提示词就占了上下文窗口的三分之一,剩下的空间只够塞一小段用户请求,模型还没来得及思考就被截断了。

第三个是复用成本高。某个业务场景里调好的提示词,换一个业务场景基本就是重写。哪怕只是改一个字段名,也要在几千字的文本里小心翼翼地搜索替换,改完之后还得担心是否影响原先的某一条规则。一来二去,维护的人宁可复制一份出来单独改,也不碰原来的版本——代码库里的"提示词副本"就这样越来越多。

第四个最要命,是调试困难。提示词驱动的系统一旦出错,你根本说不清问题是"规则说得不够清楚""规则之间互相打架"还是"模型自己发挥偏了"。我在模拟项目X里排查过一次:某类结果总是格式不对,我在提示词里加了三遍格式化说明都没用,最后发现是模型把前面一条"输出尽量详细"的指令和后面的格式化要求冲突了。改那个提示词花了我三天时间,而这个时间本可以用来建设真正可复用的能力。

1.2 从单次对话到可复用的行为切片

转折发生在那次拆分实验。我在某文本处理系统上做了个小改动:把原来六千字的 Prompt 拆成五个独立技能,每个技能只负责一类子任务,模型根据用户需求自主选择调用。没有精调模型,没有引入复杂的编排框架,只是把能力说明从"统一的规则文本"变成了"分门别类的技能清单"。一周下来,整体成功率从 61% 提高到 82%,平均单次调用的输入 Token 减少了约 40%。

我把这个过程总结为"行为切片":一个技能不只是封装了一段函数或者一个 API,它同时封装了"这个能力在什么场景下使用"的语义。模型面对的不是一本几百页的 SOP,而是一张有分类、有说明、可检索的能力菜单。技能之间相互独立,可以单独测试、单独升级、单独回滚,也可以组合起来完成更复杂的任务。哪个技能出了问题,就修哪个技能,不会牵连整个系统。

这个经验直接催生了 agent-skills 的雏形。后面所有工作,都是围绕"技能"这个最小行为单元展开的:怎么定边界、怎么组织仓库、怎么评估质量。

2. "技能"的粒度:一个技能到底装多少事

设计技能库时最容易卡住的不是技术,是粒度。技能太粗,等于没拆,模型选不准;技能太细,模型选不过来,选择成本反而上升。我在 agent-skills 里至少见过三种失败的粒度划分。

2.1 三种常见的粒度错误

第一种是把流程当成技能。例如"处理用户输入"这种技能,听起来什么都能干,实际上等于把整条业务流程又塞回了一个黑盒里。模型选中它之后,内部还是一堆混乱的 if-else 和提示词拼接,完全没有达到拆解的目的。第二种是把操作当成技能。例如一个文档处理 Agent,把"打开文件""读取第一行""查找关键词"分别建技能,调用序列又长又脆,模型为了完成一个简单改写任务要连续调五六个技能,每多一次调用就多一次失败概率。第三种是技能描述缺乏辨识度。两个技能实际干的是不同的事,但因为描述里都提到了"文档处理",模型经常把名字相似、用途不同的技能混着用。

2.2 粒度判断的"三条测试"

后来我总结了一套简单的判定方法,给每个候选技能过三条测试:

  • 可命名性测试:能用一个短名字说清这个技能的行为意图吗?如果名字怎么起都含糊,说明它的边界没定好。
  • 可测试性测试:能用固定的输入输出样例覆盖它的主要路径吗?如果一个技能的内部分支多到没法列样例,说明它至少应该再拆一层。
  • 可组合性测试:它能和别的技能以自然顺序拼接成更大任务吗?比如"提取实体"后面可以接"生成摘要",但"格式化日期"后面就很难接一个更有意义的操作。

一个候选技能必须同时通过三条测试,我才会把它放进技能库。过不了测试的,要么合并进上级技能,要么拆成更小的行为单元。

2.3 三个例子:窄了、宽了、合适了

下面这张表格能直接看出粒度差异,场景是"文档处理 Agent"。

技能名判断原因
format_date过细功能太单一,无法通过组合性测试;模型要为每个小操作额外发起一次调用
process_document过粗命名模糊,几乎包含所有文档处理行为;可测试性差,内部路径多到无法覆盖
summarize_long_document合适名字清晰;只负责"长文档摘要"这一件事;可以在"提取关键实体"之后调用

这个例子说明,合适的粒度通常落在"一类用户可感知的任务"这个级别上,而不是"一个具体的函数操作"。用户不会说"帮我格式化日期",用户会说"帮我把这份报告的要点列出来"——后者才是技能应当对应的颗粒度。

2.4 技能描述文件是写给模型看的接口

粒度定了之后,真正决定模型会不会选对的,是技能描述文件的写作质量。我踩过的坑是:一开始把 description 写得像人用的产品文档,各种背景、边界、注意事项写了一大段。实测下来,模型选技能主要靠语义相似度,描述越长、噪音越多,选错概率越大。

现在我在 agent-skills 里的写法是:description 控制在三到五句话。第一句说明"什么情况下应该调用这个技能",第二句说明"这个技能不负责什么",后面简单交代输入和输出。边界条件尽量写进 input_schema 的枚举和校验规则里,不要塞进自然语言描述。让模型在选技能时看到的是一个简洁明确的货架标签,而不是一篇小作文。

3. 技能库的项目结构:从目录设计到加载器的完整实现

技能粒度定了之后,就要落到工程上。我建议把 agent-skills 建成一个独立仓库,而不是和主业务代码混在一起。独立仓库的好处是权限好控制、版本好管理、测试好隔离。仓库结构我实际跑下来比较顺的是这样:

agent-skills/ ├── registry.json ├── skills/ │ ├── extract_entities/ │ │ ├── skill.yaml │ │ └── impl.py │ ├── summarize_doc/ │ │ ├── skill.yaml │ │ └── impl.py │ └── translate_text/ │ ├── skill.yaml │ └── impl.py ├── loader.py └── executor.py

每个技能一个独立目录,目录名就是技能名,目录底下放一份 skill.yaml 定义文件和一份 impl.py 实现文件。这个结构让技能之间完全隔离,新增一个技能就是增加一个目录,不需要改任何现有代码。

3.1 技能定义文件:字段背后的设计逻辑

skill.yaml 是技能的核心元数据。我写过一个比较完整的例子:

name: extract_entities version: 1.2.0 description: 当需要从文本中提取人名、机构名、时间等结构化实体时使用;不适合用于关系抽取。 input_schema: text: type: string required: true entity_types: type: array required: false default: [people, organization, date] output_schema: entities: type: array items: object timeout: 10 tags: [nlp, extraction] owner: team-nlp

这些字段没有一个是多余的。name 是唯一标识,用于日志、监控和权限控制;version 是回滚和灰度发布的依据,我会在第五节专门说版本策略;description 是给模型看的接口说明,写得越明确,模型选得越准;input_schema 和 output_schema 用来做参数校验和结果解析,防止技能内部收到脏数据,也防止技能给模型返回格式错误的结果;timeout 防止某个技能卡死拖垮整个对话流程,我默认设 10 秒,重活单独调;tags 用于技能检索和分组,如果某个领域技能特别多,可以按 tag 做索引;owner 是团队协作时的责任人,技能出问题知道找谁。

3.2 加载器:动态发现比硬编码高明在哪

有了定义文件,还需要一个加载器把技能变成可调用的对象。核心逻辑并不复杂:

import json from pathlib import Path import yaml import importlib def load_skills(skills_root: str): skills = {} root = Path(skills_root) for skill_dir in root.iterdir(): if not skill_dir.is_dir(): continue yaml_file = skill_dir / "skill.yaml" impl_file = skill_dir / "impl.py" if not yaml_file.exists() or not impl_file.exists(): continue meta = yaml.safe_load(yaml_file.read_text(encoding="utf-8")) impl_module = importlib.import_module(f"{skill_dir.name}.impl") skills[meta["name"]] = { "meta": meta, "run": getattr(impl_module, "run"), } return skills def execute_skill(skill, params): # 参数校验、超时控制、日志记录都在这一层统一处理 ...

有人可能会问,为什么不直接在代码里写死一个 dispatch 函数,用 if-else 调技能?动态发现的好处有三点。第一,新增技能零成本——加目录就行,主流程不用改;第二,可以通过配置中心按需加载技能子集——灰度时只让技能 A 上线到 10% 的流量,不用改代码;第三,技能的调度权和执行权分离——调度由模型决策,执行由统一执行器负责,每个技能都被同样的超时、日志、异常处理逻辑包裹,不会出现某个技能忘了写 try-except 就把整个 Agent 搞挂的情况。

执行器是技能系统的安全网。我在 executor 里统一做了参数校验、超时控制、重试策略和结果格式化。技能作者只需要关心实现本身,不需要关心全局的稳定性问题。这样也方便后续加监控——执行器是所有技能调用的必经之路,在这里打点就能拿到全部调用数据。

4. 落地过程中我踩过的三个坑

工程结构搭好之后,测试用例也写了,我以为就要平稳运作了,结果连续踩了三个坑,每一个都值得单独拿出来说。

4.1 坑一:技能描述"太像人话"导致模型选错

第一个坑出现在某文本处理系统里。我建了一个"文本纠错"技能,写 description 时为了让模型充分理解,详细写了适用场景、适用文体、哪些情况不用管、哪些错误类型归它处理,足足写了两百多字。结果上线后,模型把"文本纠错"当成通用文本清理技能,遇到格式整理、关键词抽取一类的请求,也优先选它,导致用户要的是摘要,模型却返回一段"改正过"的原文字。

排查链路是这样的:先看调用日志,发现"文本纠错"的调用量异常高,而"摘要生成"技能几乎没被调用。然后把两个技能的 description 拿出来对比,发现"文本纠错"的描述里提到了"段落""句子""语法""措辞"这些词,和"摘要生成"想表达的内容有大量语义重叠。接着做了一次消融实验——把"文本纠错"的描述压缩成三句话,明确只在"文本存在明显错误且用户要求修改"时使用,其余情况不要调用。改动之后,两类技能的选择准确率明显回升。

这个坑的教训是:技能描述不是写给人看的文档,是写给模型看的接口说明。语义重叠的部分越少,模型越容易分清边界。

4.2 坑二:技能循环调用,日志里出现 A→B→A→B

第二个坑更隐蔽。某个 Agent 任务里,模型先调了技能 A,技能 A 的执行结果让模型觉得还需要技能 B,执行完 B 之后模型觉得还是回到 A 更合适,于是又调 A,又调 B……我在监控面板里看到一批请求的调用序列呈现 A→B→A→B 的无限循环,直到触发最大调用次数才被强制掐断。用户体感就是"越用越卡,同一个问题反复处理不出来"。

排查时先用链路追踪把循环调用序列打出来,发现 A 和 B 的处理逻辑确实存在互相补充的关系,模型无法判断"任务已经完成"。根因是技能 B 的输出格式里没有明确的状态标记,模型拿到结果后以为任务没完成,才继续尝试。解决办法有两层:第一层给执行器加了检测,单次任务内同一个技能最多被连续调用三次,超过就强制停止并让模型转向其他方案;第二层是规范技能输出,每个技能的 output_schema 里都要求包含一个 status 或 confidence 字段,让模型能判断这个步骤是否真的完成了。

我用这个例子想强调一点:技能循环和提示词里的逻辑矛盾一样,是编排层面的死锁问题。光靠模型自己很难 break 循环,必须在执行器这一层做硬性约束。

4.3 坑三:没有回归测试,一次"假升级"让线上服务失败了几十分钟

第三个坑是我自己的失误。当时我升级了技能 A 的实现,改了它内部的参数解析方式,顺带把输出格式里某个字段名从 a 改成了 b。我以为这个改动只影响技能 A,就没有跑其他技能的测试。结果技能 B 的内部逻辑依赖技能 A 返回的字段 a,技能 A 改了字段名之后,技能 B 拿不到数据,执行直接抛异常。线上监控告警响了,我跑去看日志才发现是字段名不兼容导致的。

后来我加了一道强制性流程:任何技能升级,必须跑一遍全量技能的集成回归测试,不能只测被改的那个技能。因为技能之间是有隐含数据依赖的,某个技能的输出很可能被另一个技能消费,表面上看它们彼此独立,实际数据流早就串起来了。

4.4 一张问题定位表

这三类问题不是个例。我把它们整理成一张定位表,方便你们遇到类似情况时快速对号入座:

现象可能原因排查入口
技能 A 调用量异常高、B 几乎无人用description 语义重叠或噪音过多对比各技能 description 的语义重叠度;做消融实验
单次任务出现 A→B→A→B 循环输出缺少完成状态;技能间互相补全链路追踪打印调用序列;检查 output_schema 状态字段
升级 A 后 B 大面积失败技能的字段依赖未被感知全量回归测试;检查数据流依赖关系

定位问题最重要的就是先看日志。技能系统的日志打点一定要做全,至少记录"哪个技能被调用、输入是什么、输出是什么、耗时多少、谁调用的"。没有这些打点,出现问题就只能靠猜,效率会低很多。

5. 技能质量怎么评估:从"能跑"到"好用"

技能能跑了,和技能真正好用,中间隔着一整套质量体系。我在 agent-skills 上跑了三个多月之后才补上这块,属于是"起了个大早、赶了个晚集"。现在我把质量评估拆成四步:指标、回归集、版本、淘汰。

5.1 四类核心指标

评估技能体系,单看"最终任务成功率"不够,还要分开看技能层面的表现。我在监控大屏上固定看四个指标:

指标说明改动依据
任务完成率用户请求最终是否有有效结果整体效果有没有变好
技能调用成功率单个技能执行时出错的比例单个技能要不要修
单任务 Token 开销完成一个任务平均消耗的 Token 数技能选择是否高效
技能切换次数完成一个任务平均调用技能的数量技能粒度是否合理

这四个指标是联动的。比如任务完成率下降,但技能调用成功率正常,问题多半出在模型选错技能或者技能组合顺序上;如果技能调用成功率本身低,那问题就出在技能实现上。如果单任务 Token 开销突然升高,要怀疑模型是不是把本该交给技能做的事,靠提示词硬推了。

5.2 回归测试集:用"期望技能序列"锁住行为

技能系统最大的风险是"悄悄变差"——模型从某一天开始不再调用某个技能,但没有任何报错。为了防这种情况,我建了一个模拟任务集,里面放一百个典型用户请求,每个请求都标注了期望技能序列和期望输出约束。每次技能库变更,跑一遍这个任务集,对比模型实际调用序列和标注序列是否一致。

这个回归集做起来很花时间,但绝对值得。它解决的是两个问题:技能改了没影响别家;模型的行为没有发生非预期漂移。后来我把回归集接入变更流水线,每次技能库上线前必须全绿才能放行。

5.3 版本管理与灰度发布

技能也要像服务一样做版本管理。我采用的版本规则是:主版本号改动说明技能行为发生了不兼容变更,比如流量团队分出了权限组,品牌团队分出了语调组,其他同事在自己的 local repo 里改动时不用担心影响别人。整体结构清晰、可控。

我还给每个团队配了一两份"共享技能文档",只有命名规范、描述模板和必须遵守的调用边界。某团队如果连续两周没有访问记录,我会主动去确认一下它是否还需要存在。我在实际中经常发现,有一些技能被其他团队调用得很少——不是因为不好用,而是他们根本不知道有这个技能。

6.2 技能贡献规范

技能库一旦放开给多人用,就得立规矩。我建议写一份"技能贡献准则",核心几条:技能必须通过学到的三条粒度测试和至少两个真实回放场景的验证;描述文件的 description 不得超过五句话;所有技能必须带回归样例,且样例必须来自真实日志,不能是手写的理想情况;上线前必须过全量回归集。

规矩听起来繁琐,但它能避免大量低质量技能挤占了模型的选择空间。我见过最坏的情况是技能库数量膨胀到几百个,模型选择准确率直线下降,最后不得不做一次大清理。与其事后清理,不如入口就卡严。

6.3 后续扩展方向

技能体系接下来能往哪走,我觉得有三个方向值得关注。第一是技能自动沉淀:把用户经常问但现有技能覆盖不到的任务,自动聚类成新技能的候选,减少人工梳理成本。第二是技能推荐:根据当前任务上下文,把最可能用到的两三个技能置顶,降低模型选择难度。第三是技能编排模板:把一些固定的技能组合序列沉淀为模板,比如"摘要生成"固定接"实体提取",这样模型不需要每次重新规划调用顺序。

这三个方向我都只是在模拟环境里跑过简单的实验,还没完全落地。不过我相信方向是对的——技能越细、越规范,模型做选择的复杂度越高,把选择工作前置或者自动化是必然趋势。

最后分享一个我坚持了很久的小习惯:每周花一个小时,翻一遍技能调用日志,看看哪些技能没人用、哪些技能被频繁调用、哪些技能描述导致的选错率最高。删掉没人用的,合并重复的,重新措辞选错率高的描述。这个简单的"大扫除"让我的技能库一直保持在一个健康的状态——数量不多,但每一个都能在正确的时机被模型选中。做技能体系最忌讳的是只增不减,定期清理才是让 agent-skills 长期保持稳定的真正秘诀。

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

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

立即咨询