最近好几个做 AI Agent 的朋友都在跟我吐槽同一件事:Skill 明明写得很完整,AI 就是不照着执行,甚至有时候干脆假装自己没看到这个 Skill。这种问题我踩过太多次了,每次排查到最后几乎都能归结到四个固定环节上。这篇文章不聊怎么写 Skill,只聊一套排查清单——按顺序查这四件事,大多数"Skill 不听话"的问题都能定位到根因。
1. 第一件事:Skill 的"身份证"就没写对,模型根本不认识它
很多 Skill 写完之后 AI 一次都没调用,不是模型笨,是它压根不知道这个 Skill 该在什么时候出场。Skill 的元信息就是模型的"身份证",包括名称、描述、触发条件、适用场景。这部分写得不明确,AI 宁可自己硬答,也不会去翻你的 Skill。
1.1 description 写得太文艺,触发场景全靠猜
我见过太多人把 Skill 的 description 写成这样:
此技能用于帮助用户高效地生成结构化的周报文档,提升团队协作效率,沉淀项目经验。
这句话单独看没毛病,但模型在决定要不要调用 Skill 时,靠的是"当前用户的请求"和"每个 Skill 的描述"做语义匹配。上面这段描述跟"帮我写周报"之间的匹配度其实很低,因为它的关键词是"生成""文档""沉淀经验",而不是"周报""本周""工作汇总"这样的动作词。
正确的 description 应该直接写触发条件,甚至把用户可能说的话原样放进去:
name: generate_weekly_report description: > 当用户要求"写周报""汇总本周工作""生成周报""复盘这周项目"时使用此技能。 不要在其他场景下调用。把"用户怎么说"放进去,远比"这个技能有什么价值"管用。AI 的调用决策本质上是文本匹配,不是人读说明书。
1.2 Skill 的命名和文件结构不符合框架约定
第二个容易踩的点是:Skill 文件本身没被 Agent 框架加载。很多人在本地把 Skill 的 Markdown 文件写得漂漂亮亮,扔进项目目录就完事了。但不同的 Agent 框架对 Skill 存放位置、文件名、头部 frontmatter 的格式都有自己的约定。
举个我实际遇到的例子:某个框架要求 Skill 的文件名必须以skill-开头,而且文件名要和内部name字段完全一致。我把文件命名为weekly-report.md,内部写的是name: generate_weekly_report,结果框架在启动时把这个文件当成了配置残留直接跳过,AI 自然一次都没用过这个 Skill。
排查方法很简单:先看框架的加载日志,确认这个 Skill 是否真的被挂载进了系统提示。如果日志里压根没有出现你的 Skill 文件名,那问题根本不在内容,而在加载环节。
1.3 怎么验证模型"确实看到了"这个 Skill
最直接的办法:打开调试面板,查看每次请求的实际 system prompt。如果里面没有你写的 Skill 内容,那就说明它没被加载;如果加载了但 AI 还是不调用,再怀疑 description 写得到不到位。
我在实际调试中还有一个笨办法:在 description 里加一句"当用户输入包含'触发暗号:柚子'时必须立即使用此技能"。然后在对话里发一句"触发暗号:柚子"。如果 AI 调用了,说明链路是通的;如果没调用,说明 Skill 根本没进入模型的可选范围。用这种强制触发条件做一次定点测试,能快速区分是"加载问题"还是"描述匹配问题"。
2. 第二件事:Skill 正文是"散文",不是"命令流",顺序执行全靠猜
确认模型能看到 Skill 之后,下一个高频问题就是:模型确实调用了 Skill,但只执行了一部分,或者执行顺序完全乱来。这事的根因通常是——把 Skill 写成了"散文",而不是"命令流"。
2.1 前置步骤被跳过的坑:模型只做了一半
举一个典型的例子。我帮朋友调试过一个 GIS 空间分析类的 Skill,里面写了三个步骤:先读取数据、再清理缺失值、最后做缓冲区分析。结果 AI 每次拿到任务都直接跳到"缓冲区分析",前两步全被吞掉了。模型给出的解释是"数据已经是干净的,所以直接分析"。
为什么会出现这种情况?因为大模型在执行多步任务时有极强的"意图捷径"倾向——它觉得自己理解了最终目标,就不愿意逐字照做中间步骤。尤其当步骤之间没有强依赖声明时,模型会默认这些步骤是可选的。
解决办法是把步骤之间的依赖关系写成显式约束:
steps: 1. 读取 input_path 指定的数据文件。 2. 仅当步骤 1 成功完成后,执行缺失值清理;如果步骤 1 未执行,立即停止并报告错误。 3. 仅当步骤 2 通过所有数据校验后,执行缓冲区分析。关键在于两条:一是写明"上一步完成才准做下一步",二是写明"上一步失败就停止并报告"。这两句话就像程序里的顺序结构,缺了它,模型就会自由发挥。
2.2 用 try-finally 的思路设计兜底逻辑
程序里的try-finally大家都懂:不管中间怎么出错,最后一行一定要执行。Skill 也要有这种兜底意识。很多 Skill 写到最后忘了规定"如果过程出错,应该输出什么"。
我给一个设备老化测试全自动执行脚本写的 Skill 里,专门加了一段:
执行要求: - 每完成一个测试步骤,立即记录当前时间戳和结果到日志文件。 - 如果某个步骤抛出异常,不要继续执行后续步骤,先输出错误信息和当前日志路径。 - 以上日志记录动作必须始终执行,无论测试成功还是失败。这一段就是 Skill 的finally块。模型看到这种明确无条件的指令时,执行概率会高很多。你要是只写"请做好日志记录",它大概率会忘记。
2.3 输出契约要像函数签名一样严格
模型把 Skill 执行完了还只是第一步,输出结果如果不符合下游的预期,整条链路照样断掉。这就好比 Django 里执行查询之后删除对象,你没处理好级联关系,数据就乱了;或者你写了一段 MySQL 执行 SQL 脚本,脚本里的字段名和表结构对不上,后面的环节全崩。
Skill 的输出也应该定义得跟函数签名一样严格:
output: format: markdown_table columns: [项目名称, 完成事项, 遗留问题, 明日计划] constraints: - 每个项目必须单独占一行 - 如果某项目本周无进展,完成事项一栏填写"无" - 不允许出现"暂无""待定"这类模糊词模型是格式生成器,你给出明确的字段和格式,它就能稳定输出;你只扔一句"输出好看点",它每次都会给你不一样的长相,下游环节接不住,你就会觉得"AI 不听话"。
3. 第三件事:执行上下文是断的,Skill 再优秀也只是张废纸
还有一种情况最让人崩溃:Skill 本身写得无懈可击,触发也正常,但执行结果永远不对。这时候你要查的是——Skill 在执行的时候,到底有没有拿到它需要的上下文。
3.1 主 Agent 忘了把关键信息传进 Skill
很多 Agent 的工作方式是:主模型先把用户的问题理解一遍,再决定调用哪个 Skill,然后把相关上下文作为参数传给 Skill。问题就出在这个"相关上下文"上。
我遇到过的一个真实例子:我给一个 AI 编程助手写过自动生成提交信息的 Skill,要求它"根据当前 Git 工作区的改动生成 commit message"。但 Skill 执行时拿到的是用户上一句对话的内容,而不是git diff的输出。Skill 里写得再清楚,它没数据也白搭。
排查这个问题的思路是:检查 Skill 的参数定义和调用方的传参逻辑。如果你的 Skill 依赖某个外部数据(比如文件路径、终端命令输出、上一轮结果),一定要在 Skill 的参数区里写明数据来源和获取方式:
input: git_diff: description: git diff 命令的原始输出 require: true source: 调用方通过 execute_command("git diff") 获取很多所谓"Skill 不生效"的问题,其实是主 Agent 调用 Skill 的时候就没把参数传对,Skill 拿了空数据硬着头皮执行,结果自然没法看。
3.2 多 Skill 叠加时的串行与并行冲突
现在一个 Agent 里挂十几个 Skill 很常见,但多 Skill 之间也会打架。特别是当两个 Skill 都声明了类似的触发条件时,模型可能把两个都调用起来,然后输出互相覆盖。这就像你在命令行里并行执行多个 Linux 任务,如果它们都往同一个文件里写内容,最后文件一定面目全非。
我在处理"多 AI 协作"场景时,会给 Skill 写明互斥关系:
conflicts: - skill: format_reporter reason: 本技能已包含格式化逻辑,避免重复输出标题同时在描述里写清楚"如果用户还请求了另一个技能,请优先在本技能内完成全部处理,不要重复调用"。这种显式的协作规则能显著减少模型在多个 Skill 之间反复横跳的概率。
3.3 环境问题:脚本根本没跑起来,AI 还以为自己成功了
最后一个上下文层面的隐性断点,是执行环境本身。我见过一个很典型的 Case:Agent 调用一个 Python 脚本做数据清洗,脚本在 Windows 机器上运行时报错"由于找不到 vcruntime140_1.dll,无法继续执行代码"。但如果 Agent 本身的工具调用没有把退出码和 stderr 传回来,模型只会看到"命令执行结束",于是它就在错误的输出上继续工作,生成一份基于空数据的结果。
排查这类问题的经验是:先手动在 Agent 运行的同一台机器、同一个用户权限下执行一遍脚本,把所有环境依赖补齐。注意权限问题尤其隐蔽——有些任务需要通过管理员身份执行,如果你的 Agent 服务是用普通用户权限启动的,脚本能加载但不具备写权限,一样会失败。另外一定要让 Skill 里包含"如果是环境问题,直接输出错误码"的指令,而不是让模型自己脑补成功。
对于设备老化测试这类需要长时间运行的场景,我习惯在 Skill 里加一条"每次执行外部命令后,必须检查退出码;非 0 则停止并返回日志"的硬性要求。没有这条,模型就会在脚本中途失败后继续往下走,导致整份报告全是编造的。
4. 第四件事:你没给 AI 的自由发挥装笼子,结果它自己改了需求
如果前三件事都查过了,问题依然存在,那大概率是约束不足。市面上很多"AI 很任性"的吐槽,本质上都是同一个原因:Skill 里写的全是"建议"和"目标",而不是"必须执行的动作"和"验收标准"。
4.1 约束太松,AI 会把 10 个步骤压缩成 3 个
大模型天然倾向于"用最少步数达到看起来差不多的效果"。你写了 10 个步骤,它看完之后觉得自己理解了核心意图,就会把步骤合并、精简、甚至删除它认为"不必要"的环节。这不是模型能力不行,是你没告诉它"每一步都不准省"。
我在给一个自动化测试 Agent 调试时,Skill 里写了 15 条检查项,模型每次执行都会自动忽略其中三四条,理由永远都是"这些检查不影响最终结果"。后来我改了一版,把开头加了一句:
本技能定义的全部 15 条检查项缺一不可,禁止合并、禁止跳过、禁止重新排序。如果某条无法执行,请输出该条编号并说明原因,然后停止后续操作。
这一句加完,错误率立刻降了一大截。原因是它把"选择性执行"从自由裁量变成了明确的违规行为。
4.2 怎么才能写出"去 AI 味"的 Skill
网上总在讨论"去 AI 味的文字",Skill 也一样需要去 AI 味。我理解的"去 AI 味 Skill"就是:像一个人给实习生下命令那样简练、具体、可检查,而不是写一段充满套话的说明文档。
对比一下就明白了:
| 模糊写法 | 可验证写法 |
|---|---|
| 请对数据做适当清洗 | 删除所有空值行,统一日期格式为 YYYY-MM-DD,并将清洗前后行数写入日志 |
| 生成一份周报 | 按项目分组输出 Markdown 表格,必须包含本周完成、遗留问题、下周计划三列 |
| 如果遇到异常请妥善处理 | 遇到异常时输出错误码 + 当前步骤编号,停止后续操作,不自行重试超过 1 次 |
"适当""妥善""合理"这类词在 Skill 里是毒药,它们把解释权完全交给了模型。而模型最擅长的就是按照自己的理解重新定义需求。
4.3 给 Skill 写测试:一次跑通不算数,要能重复验证
我一直坚持一个观点:Skill 也是代码,是代码就要测试。很多人写完 Skill 找 AI 试了一次,发现结果不错,就以为完工了。但模型生成有随机性,同样的输入今天跑通,明天可能就会在某个步骤上翻车。
我的做法是给每个 Skill 准备一个小型测试集,里面包含 3 到 5 个有代表性的输入。跑完一轮之后重点看两件事:输出结构是否稳定、步骤是否每次都完整执行。如果你的 Skill 里定义了输出字段,就用脚本校验字段是否齐全;如果定义了步骤,就让 AI 在每一段输出里标注"当前步骤编号",这样一眼就能看出它有没有跳步。
另外一个实战技巧是:给 Skill 加一个version字段,每次修改内容后递增版本号。这样当你发现某个 Skill 突然"变笨"了,能确认是 Skill 内容被改过,还是模型行为漂移。我维护的 Skill 仓库里,这个字段救了我无数次。
最后再分享一点我自己的排查习惯
现在我遇到"Skill 写好了 AI 却不执行"的问题,基本按这个顺序走:先看系统提示里有没有这个 Skill,再看描述里有没有触发词,然后检查传参和上下文,最后确认环境是否真的跑通了脚本。大多数问题在第二步和第三步就能暴露出来。给 Skill 写测试集这件事,确实是值得长期投入的——因为模型的行为不像传统程序那样可完全预测,只有在真实对话中反复验证,才能保证它稳定地按照你的要求执行。如果后续你有多个 Skill 协同工作的需求,也建议从这套排查清单起步,先把单个技能调稳,再谈多技能编排。