☰
agent-skills:用技能文件体系终结 AI Agent 提示词失控
2026/10/28 21:39:56 网站建设 项目流程

最近半年我一直在一个怪圈里打转:提示词越写越长,Agent 的行为却越来越飘。直到把自己项目里的提示词彻底拆掉,换成了一套叫agent-skills的技能文件体系,情况才真正稳下来。这套体系的核心并不复杂——把 Agent 的每一项能力拆成独立、可复用的技能文件,按需加载。它解决的正是 AI Agent 开发里最常遇到也最头疼的问题:提示词模块化、能力可维护、行为可测试。如果你也被"一个系统提示词管所有任务"折磨过,或者想让一套能力库在多个业务线之间复用,这篇文章应该对你有用。我会从设计思路讲到文件结构,再记录几个实际踩坑的过程,最后聊聊技能库的工程化维护。

1. 一切要从提示词失控说起:agent-skills 想解决的问题

1.1 单体提示词的三宗罪

先说为什么我会主动去折腾一套新的技能体系。最早做 Agent 的时候,我跟大多数人一样,习惯把所有东西塞进一个 system prompt:角色设定、对话风格、任务规则、行业知识、输出格式、边界条件……全写在一起。一开始只有几十行,效果确实惊艳,但随着业务变复杂,这个文件越来越长。

我的客服 Agent 就是个典型例子。最开始只有"礼貌回复用户,不要编造信息"这种简单要求,后来陆续加入订单查询、售后流程、退款规则、库存判断、物流解释,甚至还要处理发票和投诉升级。等这个 system prompt 涨到六七百行、将近六千字的时候,问题集中爆发了。模型开始遗忘前面的规则,说话变得前后矛盾,昨天还好的功能,今天换个问法就失忆。这就是单体提示词的三宗罪:

  • 上下文被无关信息占满。不管当前任务是什么,模型都要处理整份规则。长上下文里,注意力被稀释,真正对当前任务有用的规则反而容易被忽略。
  • 耦合太重,改动成本高。任何一个小需求的调整,都要去修改全局提示词,稍不留神就影响其他地方。改完还要人工回归测试所有场景,非常痛苦。
  • 行为无法标准化。没有明确的模块边界,模型没有"步骤化执行"的意识,全靠自由发挥。同一个任务跑十次,输出风格和完成路径可以差出十万八千里。

如果你也在做 Agent,可能对这三点深有体会。尤其是第二点,当团队里多个人同时维护同一个 system prompt 时,那种"谁改坏了谁的逻辑"的拉扯感是真实存在的。

1.2 技能(Skill)与提示词、工具的边界

agent-skills 的思路,是彻底改变"一个提示词管所有事"的组织方式。它把能力拆成一个个技能文件,每个文件描述一个可复用的子任务。Agent 收到任务后,先从技能库里检索出匹配的技能,再把这个技能的说明注入上下文,作为本次执行的指导。

很多人会问:这和工具调用(Function Calling)有什么区别?我一开始也混淆过。实际用下来,边界其实很清楚:

维度系统提示词工具调用技能文件
职责描述 Agent 的身份和全局规则让模型调用外部函数指导模型如何完成一个复杂过程
加载时机所有对话常驻按需触发函数按任务描述动态检索注入
粒度整份全局规则单一原子操作多步骤的操作流程
核心价值定义角色连接外部系统约束方法论

工具是"手脚",负责执行具体的动作,比如查天气、发邮件、读写数据库。技能是"操作规程",负责告诉模型遇到什么情况应该走什么流程、先做什么后做什么、每步有什么标准。一个好的技能里,可以引用多个工具,也可以不引用任何工具,纯粹约束模型的推理步骤。

我当时理解到这一层之后,立刻做了一个决定:把所有任务型规则从 system prompt 里搬出来,转成技能文件。只保留最核心的角色设定和价值观在全局提示词里。这一步操作之后,Agent 行为的稳定性提升得立竿见影,也让我确信这条路值得深入。

2. 技能文件的骨架:一个能稳定运行的 skill 长什么样

2.1 前置元信息:告诉模型"什么时候该用我"

一个技能文件的开头通常是一段 YAML frontmatter,相当于给技能的"身份证"。这段元信息决定了 Agent 在什么情况下会检索到这个技能,是整个技能体系里最关键的部分。

我建议至少包含这五个字段:

--- name: code-review-guide description: Use when the agent needs to review code changes for logic issues, security risks, and code style problems. when_to_use: Triggered when the user asks to review a pull request, inspect a diff, or evaluate code quality. input: git diff text or a file path output: structured review comments grouped by severity version: 1.2.0 ---

name用动词短语加短横线命名,方便检索和引用。description和when_to_use是检索的核心,必须写清楚技能适用的场景,还要写清楚不适用什么场景。这一点我一开始做得不好,后来付出了不少代价,后面会单独讲。input和output用来约束技能的输入输出形态,防止模型在使用时想当然。version字段单独提一下,技能文件一旦多起来,没有版本管理基本等于失控,这个字段后面维护时会很有用。

2.2 指令正文与示例:少而准的写作原则

元信息下面就是正文。正文是真正会被注入上下文的指令内容,写作原则和网上的提示词教程完全不同。这里不需要文采,只需要清晰的操作路径。

我的正文结构固定为三段:执行步骤、每步验收标准、反例约束。

## Steps 1. Identify the changed files and their purpose. 2. Check each changed line for logic errors, potential null pointers, and security issues. 3. Review naming, formatting, and function length. 4. Summarize issues by severity: Critical, Warning, Suggestion. ## Acceptance Criteria - Every issue must reference the actual line number. - Do not rewrite the code; only report problems. - If no issues are found, explicitly say "No obvious issues". ## Don't - Don't comment on unchanged files. - Don't suggest large refactoring unless directly related.

为什么强调"少而准"?因为技能正文一旦写成几百行的大部头,模型在短上下文里很难坚持执行,反而会挑关键片段敷衍。一个技能的正文我基本控制在 200 到 400 词以内。需要补充的背景知识,放在单独的参考文件里,不在主正文中堆砌。这样执行质量最高,上下文开销也可控。

2.3 技能目录的组织与管理约定

技能文件多了之后,目录结构必须统一。我的经验是:一个技能一个目录,主文件固定叫SKILL.md,配套资源放在子目录里。

skills/ review-web-code/ SKILL.md reference/ security-checklist.md format-changelog/ SKILL.md examples/ good.md bad.md generate-test-plan/ SKILL.md

这样的组织方式有几个好处。第一,每个技能的边界天然清晰,不会出现"两个技能混在一个文件里"的尴尬。第二,配套资源按需加载,技能正文里只需写"参考 security-checklist.md"一句话,模型用到时才读取,平时不占上下文。第三,后续做自动化测试、版本对比、权限管理都有天然的目录边界。命名上保持小写短横线命名(kebab-case),不搞花活,否则检索和脚本处理时很容易出问题。

3. 手把手创建一个可用技能:从需求到文件落地

3.1 选择合适场景:什么任务值得做成技能

很多人第一次接触 agent-skills 时,容易走向两个极端:要么什么任务都做成技能,要么觉得只有复杂任务才配做技能。这两种都不对。

我最开始也踩过"什么都做"的坑,给 Agent 写了二十多个技能,大部分用都没用过。后来总结出判断标准,至少要满足三条,才值得做成技能:

  • 任务会频繁出现。比如每周都要做的代码评审、日志摘要、发版说明生成,这类任务频率高、动作固定,做成技能收益明显。反过来,一次性调研任务就不太值得。
  • 有明确但非原子的执行流程。如果只是调用一个 API 就能完成的事,用工具调用就够了,不需要技能。技能应该对应"多步骤、有判断分支"的过程。
  • 模型当前经常做错或有歧义。比如生成规范的 commit message、按公司格式写周报,这种任务自由发挥空间大,不约束就乱。技能能把"怎么做"讲死。

我实际做的第一个技能是summarize-nginx-errorlog。当时环境里每天有大量服务日志需要排查,完全靠人工一段段看效率太低,让 Agent 自由总结又经常抓不住重点。我按上述三条标准一对照,完全符合,于是决定从它开始练手。

3.2 写出优质技能描述与步骤的实操指南

创建技能不是坐在编辑器前凭空想,而是先手动跑一遍任务,再把自己的操作过程拆成步骤。我是这样做的:

第一步,手动处理两三份真实任务,一边做一边把思考过程记录下来。比如处理 nginx 日志时,我先看状态码分布,再找 5xx 集中出现的接口,然后查对应时间段的错误信息,最后按影响面排序输出。这个过程记录下来的自然语言,就是技能正文的种子。

第二步,把流程改写成结构化步骤,每个步骤都是一个动词开头的动作,比如"identify"、"filter"、"group"、"rank"。同时给每步配验收标准,让模型知道做到什么程度才算完成。

第三步,写描述和触发条件。这里我有一个很实用的技巧:描述里不只写"什么时候用",还要写"什么时候不用"。因为检索阶段模型理解语义,如果只写一堆适用场景,它会倾向于过度召回。

给一个我当时写的简化版:

--- name: summarize-nginx-errorlog description: Use to summarize nginx error logs and identify the most impactful failures. Do not use for access log analysis or performance metrics. when_to_use: Triggered when the user provides error.log content and asks to find high-impact errors or summarize root causes. input: raw nginx error log text output: a ranked list of failure types, occurrence counts, and suggested next steps ---

第四步,补一个简短示例,让模型对输出格式有具体的参照。示例不需要多,一正一反就够了。正例展示标准输出,反例展示"不要这样做"的输出。反例的效果非常强,后面我还会专门说。

3.3 用本地运行验证技能的"命中率"与"执行质量"

技能文件写完后不能直接上线,必须先做验证。我每次都会跑两轮测试:召回测试和执行质量测试。

召回测试很简单。我准备一个包含 10 到 20 条历史任务描述的测试集,每条任务都标注了期望命中的技能名。然后模拟 Agent 的检索过程,看看技能是否被正确召回、有没有多余技能被误召回。这个环节用一段 Python 伪代码来跑:

tasks = [ ("请总结一下这份 nginx 错误日志", "summarize-nginx-errorlog"), ("帮我看看 access.log 的访问趋势", None), # 期望不命中 ] for task_text, expected_skill in tasks: matched = retrieve_skill(task_text) if matched == expected_skill: print(f"PASS: {task_text}") else: print(f"FAIL: {task_text} -> {matched}")

召回测试通过后,再跑执行质量测试。我准备几份真实的 nginx 日志输入,让 Agent 使用技能生成输出,然后人工检查三个维度:是否严格按技能步骤执行、输出是否可操作、有没有偏离技能边界。刚开始几次测试,命中率可能很高但执行质量一般,这时候不要急着调正文,先检查是不是描述与实际输入不匹配,再检查步骤是否太抽象。

等到召回和执行都稳定了,这个技能才算真正可用。我一共迭代了三个版本才把summarize-nginx-errorlog调稳,但也正是这个过程,让我彻底理解了技能文件与普通提示词的本质差异。

4. 技能加载与调用的运行时机制:为什么它又省 token 又稳定

4.1 检索与匹配:根据任务描述挑选技能

技能文件写好后,接下来是整个体系最核心的运行时机制。Agent 每收到一个任务,并不会把整个技能库都塞进上下文,而是先做一次检索匹配,只把相关技能加载进来。

大致流程是这样:用户消息进来后,系统先解析当前任务的意图与关键词,然后与技能库里的描述做匹配。匹配方式可以有两种。早期技能库小,我用的是关键词加规则,简单直接,但漏召回和误召回都偏高。技能库超过二十个之后,我换成了语义检索方案,把技能的description和when_to_use字段做向量化,每次任务来了用同样的模型向量化查询文本,取 Top-3 相似技能,再在上面跑一层规则过滤。

这一层过滤很重要。比如用户说"帮我看看这个接口为什么 502",向量检索很可能同时命中summarize-nginx-errorlog和analyze-api-latency,这时候我会在规则层里判断:只要消息里有"error"或"错误日志"字样,就优先保留日志类技能。这样下来误召回的次数大幅减少。

4.2 按需注入:上下文里只放最相关的内容

为什么这套机制省 token?我算过一笔很直观的账。

假设技能库里已经积累了 30 个技能,每个技能正文平均 300 词,再加元信息和示例,约 500 token。如果走传统老路,把这 30 个技能全部注入上下文,就是 15000 token 的固定开销。而按需注入的话,每次任务只加载 1 到 2 个匹配技能,平均成本只有 700 到 1000 token,并且只在执行相关步骤时存在。

token 成本之外,更重要的是稳定性的提升。模型在长上下文里很容易丢失注意力,特别是中间部分的指令,常常"说了当没听见"。技能按需注入后,当前任务相关的指导出现在上下文靠后的位置,距离生成位置较近,模型遵循度明显更好。这也是很多 Agent 框架最终走向"检索式指令"的根本原因,不是模型不支持长上下文,而是长上下文本身会稀释指令的强度。

我实践下来的一个直观感受是:改用 agent-skills 之后,同样一个任务,用户不再需要反复补充"你要按我说的步骤来"这类话,因为技能正文里的步骤本身就在最近的上下文中,模型天然会顺着它走。

4.3 技能之间的依赖与调用方式

技能越做越多以后,你迟早会遇到一个问题:两个技能之间存在依赖关系。比如summarize-nginx-errorlog产出的排查建议,可能正好需要format-incident-report这个技能来输出成规范报告;format-incident-report又可能涉及check-severity-level这个更小的判断技能。

我的建议是:正文里显式写依赖,而不是让模型自行串联,更不要让技能递归引用。递归一旦发生,轻则上下文失控,重则运行时报错,调试难度极大。显式写法很简单,在步骤里直接写清楚:

## Steps 4. After identifying the top 3 failures, call the skill format-incident-report and fill in its template.

这样执行时,模型会在当前技能的基础上加载被引用的技能,形成短期上下文,处理完就退出。我在实践中常用这种"技能作为能力节点"的思维,而不是去搞复杂的技能流引擎。保持技能的扁平化设计,每个技能尽量独立完成一个目标任务,依赖问题自然就少了。

5. 踩坑实录:我在实践 agent-skills 时遇到的五个问题

5.1 技能描述写得"太贪心",导致误召回

第一个坑也是最大的坑,是技能描述写得太贪心。我起初为了让技能显得通用,把描述写得范围很大,比如把日志分析技能写成"Use for analyzing any kind of text logs, reports, or data files"。结果就是不管用户要求总结客服聊天记录,还是分析 PDF 报告,这个技能都被触发。

排查链路是这样的:先看 Agent 的运行日志,发现明明没到日志场景,summarize-nginx-errorlog却总被加载进上下文。再回头读描述文件,发现问题就出在 "any kind of text" 这几个词上。向量检索只看语义相似度,你给了它这么大的范围,它就敢接所有相关任务。修复方案是把描述收缩到具体场景,同时加上排除条件:

description: Use to summarize nginx error.log and identify high-impact failures. Do not use for access.log, app-level logs, or text file summarization.

改完后误召回立刻降下来。这个坑让我彻底明白:技能的命中率不是在代码里调出来的,而是在描述文件里约束出来的。

5.2 文件格式不统一引发的解析错误

技能数量上去之后,团队里开始有人贡献技能文件,这时候新的问题出现了:frontmatter 格式不统一。有人用"name"加引号,有人不加;有人写when_to_use,有人写triggers;有人正文里用了三个反引号嵌代码,结果把 YAML 解析器搞崩了。

这类问题表现得很隐蔽,往往不是直接报错,而是技能被静默跳过。某个技能明明存在且很合适,Agent 就是不加载它,因为解析失败被系统忽略。排查了很久才发现是元信息字段缺失导致 filter 阶段被丢弃。

解决方式有两个。一是立一个技能模板,谁写技能都从模板复制;二是在 CI 里加一个静态检查脚本,对 SKILL.md 做 YAML 解析、字段校验、格式校验。我后来写了不到一百行 Python,就把技能库从"手工管理"推进到了"半自动检查"。

5.3 技能正文过长,反而冲淡了主指令

第三个坑最反直觉。我一度觉得技能正文写得越详细,执行就越精准。于是把一个代码评审的技能写到了三千字,包含公司所有团队的不同规范、十几种代码模式的检查清单、大量示例。结果实测效果非常差,Agent 经常只做前几步就开始总结,后面的规范根本没看。

原因不难理解:技能正文也是上下文的一部分,超过一定长度后,模型对中间和靠后的指令注意力急剧下降。我做了个对比实验,把同样的技能正文从三千词压缩到三百词,只保留核心步骤、验收标准和一条关键反例,执行质量反而提升。这也验证了"少而准"的原则。长内容不是不能放,而是不能全放在主正文里。我把细节整理成独立参考文档,让技能正文按需引用,这个问题才彻底解决。

5.4 技能漂移:更新了文件但测试一直过

技能文件不是写完就不动了,业务变化后必然要改。但改完之后,我遇到过一种很诡异的情况:技能文件里的版本已经升到 1.3,测试报告仍然显示旧逻辑通过,Agent 也还在用旧行为。查到最后发现,是测试脚本里写死了旧技能的路径和输出样例,即使 SKILL.md 更新了,断言用的还是旧格式。

这个坑的根因是"技能与测试不同步"。后来我要求每次修改技能必须同步改三样东西:SKILL.md 的版本号、对应测试用例的期望输出、CHANGELOG 里的一条记录。没有这三样,改动不允许合并。这个流程看起来繁琐,但让技能库在长时间迭代中保持可信,绝对值得。

5.5 跨模型效果差异:同一个技能换模型就翻车

还有一个不太容易发现的问题:同一个技能文件,在不同模型上的执行效果差异巨大。有些模型对长指令遵循度好,能完整走完步骤;有些模型则倾向于压缩步骤,跳步执行。特别是技能正文里有较多的否定句式时,一些模型理解成"不要评论未修改的文件"会很好,另一些模型则会被"don't"这个高频词干扰,直接跳过整个步骤。

我现在的做法是双轨测试:技能在主力模型上验证通过后,还会在备用模型上跑一遍相同的测试集,观察差异点。如果差异过大,就调整正文措辞,把"不要做什么"改成"只做什么",通常能显著提升跨模型的一致性。这一步对于要部署到不同客户环境的 Agent 特别重要。

6. 技能库的工程化维护:命名规范、测试与多智能体共享

6.1 统一命名与评分体系

当技能库超过二十个之后,光靠记忆已经管理不过来了,必须有工程化的维护手段。第一件事是统一命名规范。我建议所有技能名都用 kebab-case 的动词短语,比如review-web-code、format-release-notes,避免出现中英文混排、空格和大写字母。

与此同时,我建了一张技能维护表,精确到每个技能的触发场景、更新时间和成功率:

技能名主要触发场景版本最近测试日期执行成功率
summarize-nginx-errorlog错误日志分析与高影响故障定位1.3.02025-06-1194%
format-incident-report事故报告生成1.0.22025-06-0889%
generate-warning-message生成用户可见的缓存预警文案0.9.1已有回归测试待稳定

这张表我每周更新一次。成功率低于 80% 的技能会列进待整改清单,重新检查是描述已经漂移了,还是业务场景变了。这个机制让技能库处于持续可见的可信状态,而不是"写了就不管"。

6.2 技能测试集:与其赌运气,不如跑一遍

技能库的回归测试非常关键。我建立了一套轻量测试集,每个技能配 3 到 5 条测试用例,统一跑自动化脚本。测试集本身不用复杂框架,一个 Python 脚本加一个期望输出清单就够了:

python run_skill_tests.py --skills-dir ./skills --tests ./tests

脚本做的事情很简单:读取每个技能的 frontmatter 和正文,对测试集中的任务做检索验证,再对样例输入跑一次生成评估,最后输出每个技能的通过率。跑完的结果直接对接上面的维护表。

测试集本身也有边界。它只能验证已知场景,覆盖不到真实对话里的意外输入。所以我每隔两周会把真实用户会话里成功和失败的案例抽一批出来,添加到测试集里。测试集越来越大,技能库的质量兜底也就越来越稳。

6.3 多智能体共享技能库的分层策略

最后聊聊多智能体场景。当 Agent 不止一个,而是拆成了客服、运维、数据分析等多个角色时,技能库很容易互相污染。客服 Agent 的沟通技巧技能,跑到运维 Agent 里就没意义,反而干扰检索。

我的解法是把技能库分成三层。第一层是base/,放所有 Agent 共用的通用技能,比如format-changelog、write-concise-summary。第二层是team/,按团队放业务相关技能,比如运维团队放summarize-nginx-errorlog,客服团队放handle-refund-request。第三层是personal/,存放个人偏好类技能,比如某个用户独有的输出风格要求。

加载顺序是base优先,其次team根据当前 Agent 身份过滤,personal只加载用户相关部分。这套分层机制看起来简单,却是我用下来解决多智能体冲突最有效的方案。它让技能库从"一个人的私有文件夹"变成了"一个组织可信任的公共能力平台"。

我在实际使用中发现,agent-skills 最大的价值不在于省了多少 token,而在于它改变了管理 Agent 的方式:技能文件可以被创建、测试、评分、淘汰,就像代码库一样进入工程化轨道。最后分享一个小技巧:如果你只改进一件事,那就去给每个技能的描述里加上反例约束,写清"这个技能不处理什么"。这一条的效果,比任何复杂提示词技巧都来得直接。先把一个技能打磨到稳定,再把它复制到更多场景,整套体系的收益才真正开始显现。

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

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

立即咨询