ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从巨型Prompt到技能包:AI Agent开发中的Skills机制实践

从巨型Prompt到技能包:AI Agent开发中的Skills机制实践 刚接触 Agent 开发的时候我对skills这个词的理解还停留在把常用的提示词存成模板这个层面。真正让我改变想法的是一个被几千行 system prompt 逼疯的下午。指令越堆越长模型输出越来越飘改一句话要全量重发一遍上下文排查问题时根本分不清是哪条规则在起作用。后来我把整个项目从一个巨型 Prompt重构成了十几个按需加载的技能包效果立竿见影token 消耗降了一截输出稳定性也明显改善。这篇想认真聊聊我在这套机制上从拆解、编写、踩坑到编排的完整过程适合正在折腾 AI Agent、做 LLM 应用落地或者被超长 system prompt 折磨过的开发者参考。1. 为什么我把几千行 Prompt 拆成了 Skills1.1 上下文拼接的天花板传统做法是把所有能力一股脑写进 system prompt。比如我的早期版本就是一个巨大的系统指令先讲角色设定再讲回复风格然后是一大堆业务规则最后还附上各种输出格式要求。这个方式在小规模场景下能用但一旦业务复杂起来问题会接二连三地冒出来。首先是 token 成本。每轮对话都要带上全套指令用户问一句今天天气怎么样模型也要先读完两千行的规则才知道自己该干嘛。其次是注意力稀释指令条数越多模型对单条规则的遵循程度就越差。我实测过当 system prompt 超过 3000 字以后靠后的规则经常被忽略或者只在某些措辞下才触发非常不可控。最难受的是维护。新业务要加规则旧业务要调整逻辑你只能在几百行的文本里小心地插入修改一不小心就破坏了其他模块。这就像把几千条规章制度全贴在一个工位上员工上班时每条都扫一眼结果遇到具体场景时全靠临场发挥。1.2 从常驻内存到按需调取Skills 机制的核心思路完全不同它不再要求模型在每轮对话里记住所有规则而是把不同的能力拆成独立的技能包每个技能包有自己的名称、说明、指令文件和配套资源。模型在处理用户请求时先根据对话内容判断这次需要哪个技能再去读取对应的技能说明来执行。理解这个概念的最好方式是把模型比作一个有经验但需要查手册的员工。旧方案是让他把所有手册背在身上随时翻阅Skills 方案是告诉他工具间里有二十本手册每本封面都写了适用场景遇到对应任务再去拿对应那本。这样既不需要他把所有内容背下来又能保证他拿起手册时看到的是完整、聚焦的操作指南。这种机制对上下文窗口的利用效率提升非常明显。一个 500 字的技能说明只有在该技能被触发时才进入上下文其余时间完全不占空间。多个技能之间也不会互相干扰因为每个技能的规则都是独立封装的。1.3 和 Function Calling、MCP 的边界在哪里很多人会问Skills 和 Function Call函数调用、MCP模型上下文协议到底有什么区别我的理解是这三者解决的不是同一个层面的问题。Function Calling 解决的是模型如何决定调用外部函数的问题重点在结构化参数传递和结果返回MCP 解决的是模型如何标准化地访问外部工具和数据源的协议问题重点在打通工具生态而 Skills 解决的是模型如何获得完成一项任务所需的领域知识和操作规范的问题重点在知识的组织与加载。用一个生活化的比喻Function Calling 是电话拨号MCP 是通信协议标准Skills 是岗位操作手册。打生产系统里的 API 查询数据靠的是前两者知道遇到订单异常时应该按什么步骤排查、排查完按什么格式汇报靠的是 Skills。它们可以独立使用也可以组合Skill 里写出判断逻辑和操作流程流程中需要查库存时再调用 MCP 工具。我在实际项目里的分工是知识密集的部分放进 Skills操作密集的部分交给 Function Call / MCP。这样既能发挥各自优势又不会把技能包变成一个大杂烩。2. 一个技能包的解剖目录结构、元信息和加载规则2.1 标准目录布局与三种文件角色技能包说白了就是一个目录但目录里每个文件承担的角色完全不同。以我常用的布局为例skills/ └── log-anomaly-analysis/ ├── SKILL.md ├── resources/ │ ├── common_error_patterns.md │ └── example_logs.txt └── scripts/ └── parse_log.pySKILL.md是技能包的主文件里面写清楚这个技能的触发条件、执行步骤、输出格式和注意事项。resources/目录放辅助性的参考材料比如错误码对照表、规范文档、示例数据。scripts/目录放可执行的辅助脚本用来处理那些模型不适合直接完成的高精度计算或结构化解析任务。这三种文件的角色可以这么理解SKILL.md告诉模型你该怎么做resources告诉模型你有什么可参考的scripts告诉模型哪些事你不需要亲自做交给工具就行。三者互相配合才能形成完整的闭环。2.2 name 和 description 决定了这个包能不能被命中技能包的元信息通常写在SKILL.md开头的 Frontmatter 区域。我见过不少人忽视这段内容但实际上name和description这两个字段直接决定了模型在什么情况下会加载这个技能包。一个标准的 Frontmatter 长这样--- name: log-anomaly-analysis description: 分析系统日志中的错误与异常识别根因并给出处理建议。当用户提供日志片段、报错信息、或请求排查线上问题时使用。 ---name要简短、唯一、有辨识度。它相当于技能包的身份证在多技能协作时被互相引用。description则是触发器的核心依据它需要描述清楚三件事技能是用来做什么的、什么输入会触发它、执行后能产出什么。空泛的描述会拉低命中率比如处理日志这种写法和完全没写差别不大因为模型无法判断处理日志到底是否匹配当前场景。2.3 Resources 的引用姿势相对路径与按需读取资源文件不是越多越好关键是让模型知道什么时候应该去读、读哪部分。我在SKILL.md里通常会用明确的引用语句来指向资源文件比如当需要判断错误码含义时参考resources/common_error_patterns.md而不是错误规则请看 resources 目录。这里有个细节容易被忽略模型按需引用的粒度可以做到比文件更细。如果某个资源文件本身就很大建议在SKILL.md中用行号、章节号或关键词来引导比如参考resources/common_error_patterns.md中『数据库连接异常』一节这样模型就只读取相关片段避免把整套参考文档都塞进上下文。我早期犯过的一个错误是把所有参考材料打包成一个大文件结果模型每次执行技能时都把整个文件读一遍消耗了大量 token 还引入了无关信息的干扰。后来我把参考材料拆成按场景划分的小文件并在主文件中写清楚何时引用哪个文件问题立刻就解决了。3. 实操把日志异常归因写成 Skill3.1 需求拆解不是把旧 Prompt 换个壳很多人写 Skill 时会犯一个起步错误把原来的长篇 prompt 原封不动塞进SKILL.md然后改一下文件名就完事。这样做的效果非常差因为你只是把上下文里的凌乱规则变成了技能包里的凌乱规则核心问题没有解决。正确的做法是先做需求拆解。以日志异常归因为例我先把任务拆成四个部分触发条件、分析步骤、输出模板、参考规则。触发条件回答用户在什么场景下会用到这个能力分析步骤回答拿到日志后按什么顺序处理输出模板回答最终结论用什么格式呈现参考规则回答判断时需要依赖哪些领域知识。拆完之后你会发现原来的 prompt 里大量内容是角色设定和语气要求这些其实不该出现在技能里。技能包应该聚焦于任务本身角色和语气交给上层的主指令去控制否则技能包就失去了可复用性。3.2 SKILL.md 的编写与迭代拆解完成后我写出的SKILL.md大概长这样--- name: log-anomaly-analysis description: 分析系统日志中的错误与异常识别根因并给出处理建议。当用户提供日志片段、报错信息、或请求排查线上问题时使用。 ---下面接正文# 目标 根据用户提供的日志信息定位异常类型分析可能原因并给出可执行的处理建议。 # 分析步骤 1. 读取日志片段提取时间戳、日志级别、服务名、业务关键字。 2. 对照 resources/common_error_patterns.md 中的已知错误模式。 3. 若命中已知模式直接采用该模式的根因结论和处理建议。 4. 若未命中根据日志上下文推断最可能的异常原因并标注推断置信度。 # 输出格式 - 异常类型一句话概括 - 触发日志摘录关键日志行 - 可能原因分条列出 - 处理建议分条列出按优先级排序 - 置信度高 / 中 / 低 # 注意事项 - 不要猜测日志中没有依据的原因宁可不给结论也要标注证据不足。 - 当日志涉及敏感信息时只保留必要字段不输出完整堆栈。这份SKILL.md我只保留了三样东西任务目标、处理步骤、输出约束。它不包含你是一个专业的日志分析师这种角色设定也不包含大段的思维方式描述。模型的推理能力通过步骤引导来发挥而不是靠情绪感化。迭代过程中我发现最初版本的分析步骤太线性默认所有日志都会先查错误模式表。但实际使用中用户有时提供的是完整日志文件有时只是一行报错步骤一的提取时间戳并不总是适用。后来我把步骤改成了带分支的描述并强调根据日志类型灵活跳转命中率明显提升。3.3 配套资源与辅助脚本怎么配合资源文件的价值在于提供模型无法凭空生成的确定性知识。比如运维场景下的错误码含义、数据库常见异常的分类、不同中间件的超时机制这些内容靠模型训练数据里的印象来推断很容易出错放进resources/反而靠谱。以我的实战为例common_error_patterns.md里会记录类似这样的条目## 数据库连接池打满 - 关键日志Connection pool exhausted / waiting for connection timeout - 常见原因连接未释放、突发流量、连接池配置过小 - 处理建议先扩容连接池并重启服务再排查慢查询和连接泄漏而scripts/parse_log.py则负责做模型不擅长的精确解析。比如从大日志文件中批量提取时间分布、统计高频错误码、过滤噪声行。模型的强项是综合判断和文本生成弱项是精确的批量计算把这两类工作分开总体的执行效率和准确率都会更高。技能包开发到后期真正拉开差距的地方就在于你愿不愿意把那些模型可能知道但可能记错的知识沉淀成资源文件以及愿不愿意为高频场景写配套脚本。这一步做好了技能包才从提示词整理升级成了可复用资产。4. 加载背后的两段式逻辑与命中率优化4.1 先判断、后读取模型的两步动作理解技能包的加载机制最关键的是意识到模型执行的是先判断、后读取两步动作。第一步模型根据当前对话内容结合你注册的各个技能包的name和description判断这个任务是否需要某个技能第二步模型读取被命中的技能包主文件拿到指令和资源引用信息再按指令执行。这个机制意味着技能包只有被调用时它的内容才会进入上下文。所以描述写得是否清晰直接影响第一步的判断是否准确。如果description写得模糊模型可能该触发时不触发或者任何时候都凑合触发某个技能包反而造成副作用。4.2 Description 写法直接影响命中率我从大量实测中总结出来的经验是description里要放这个技能特有的动词和名词而不是通用词汇。比如分析这个词太泛任何任务都可以说自己在分析日志这个词也不够因为用户可能说的是帮我看看这段报错。更好的写法是把技能涉及的动作对象和典型场景都揉进去。对比一下写法效果处理日志相关请求太泛命中率低容易乱触发当用户提供日志片段、报错信息或请求排查线上问题时使用分析异常类型并给出根因结论与处理建议命中准确模型能清晰判断使用边界另外要注意description里可以加入否定提示比如不适用于性能优化类问题。这能帮助模型把边界划清楚避免两件相近的任务互相抢占。4.3 多个 Skill 冲突时的取舍标准技能包数量上去之后冲突是必然的。我遇到过两个技能包的description都覆盖了日志分析这个场景结果模型有时候走 A 流程有时候走 B 流程输出格式都不统一。后来我定了一个规则如果两个技能的目标产出有明确区别就保留单独的技能包但在描述里互相加否定提示比如 A 的描述末尾写如需要安全检查请使用 skill-b。如果两个技能的目标产出几乎一致只是细节不同就合并成一个技能包用内部步骤来区分分支场景而不是靠模型去猜用哪个。还有一个容易忽略的点技能包之间的命名空间冲突。如果 A 技能的资源文件和 B 技能的脚本重名模型在多技能协作时可能读错路径。我的习惯是资源文件名带上技能前缀比如log-anomaly-analysis-common-patterns.md虽然名字变长了但能肯定地避免跨技能包读错文件。5. 实测中踩过的三个坑和完整排查链路5.1 坑一上下文污染导致输出跑偏第一次上线技能包机制后我遇到一个很奇怪的现象用户让模型生成一封对外邮件模型居然在结尾加上了请不要在日志中输出敏感信息这样的提示。我一度以为是主指令的问题排查了很久才发现原因是邮件生成技能在读取配套资源时把合规检查技能的参考文档也一起加载进了上下文导致模型把不属于当前任务的规则也当成了约束。排查链路是这样的先确认主指令没有被修改再逐个检查技能包的SKILL.md引用指向最后才发现是资源目录里放了一个通用文档两个技能包都引用了它。问题是 A 技能引用它的本意只是取一小段信息但由于没有限定章节模型把整个文档读进去了。修复方法很直接把通用文档拆成按技能拆分的独立片段并且在SKILL.md里精确到章节引用。从此我给自己定了一条规则资源文件的引用永远要精确到章节或关键词范围不允许出现参考 resources 里相关文件这种模糊指引。5.2 坑二技能互相调用形成循环技能包之间是可以互相调用的这本来是个高级玩法但如果不加约束就会出问题。我踩过的一次循环是用户要求做周报A 技能负责整理数据B 技能负责生成报告结果 A 的说明里写了生成报告的步骤请参考 B 技能而 B 的说明里又写了数据整理步骤请参考 A 技能。模型在这个循环里来回跳跃上下文被反复插入最后输出的报告结构完全错乱。排查链路首先是看模型日志中的调用序列发现技能 A 和技能 B 在交替加载然后打开两个技能包的主文件比对引用关系确认是互相引用的死锁。修复方式是三层第一在SKILL.md中用明确的本技能不负责 XX来打断循环第二在调用关系上保持单向性即 A 可以引用 B但 B 不能反向引用 A第三在每个技能包主文件顶部加一行执行入口标记标明该技能包只能由顶层入口触发避免递归加载。经过这次教训我之后设计技能协作时都会先画一遍调用关系图确保是树状结构而不是环状结构。5.3 坑三相对路径和版本迭代的低级失误第三个坑更隐蔽。某个技能包更新后我把脚本文件从parse_log.py重命名成了log_parser.py但是SKILL.md里的引用没有同步更新。结果模型加载技能包后去读脚本读到的是一个不存在文件然后开始合理地推测脚本逻辑输出了一份看似正常但根本没有真正执行脚本的报告。这里的关键教训是模型和普通程序不一样普通程序遇到文件不存在会抛异常模型却会尝试用自圆其说的方式绕过错误。所以技能包里的任何引用都必须反复核验尤其是文件名、路径、版本号这些细节。给读者提供一个有效的排查方法每次修改技能包后都用一个固定测试用例跑一次完整流程检查最终输出是否真正触碰到了所有引用的资源文件和脚本。不要相信一次成功的输出要主动在测试用例中加入容易走到分支路径的输入看看那些引用是否依然健全。6. 把多个 Skill 编排成一条生产线6.1 多 Skill 协作的编排示例当技能包数量超过五个它们之间的关系就不只是简单的互不干扰了而是需要明确编排。拿我做的日报生成流程为例它拆成了三个技能>
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进