AI代码生成不听话?用SKILL规范把提示词变成可验收的交接单
2026/9/7 10:01:07 网站建设 项目流程

最近我在做一个内部工具重构,顺手把“让AI生成代码”的流程重新捋了一遍。起因很简单:我有一套自己常用的方法,名字就叫SKILL,核心思想是把任何一次代码生成任务拆成“框架+细节”两个层次。结果真正按这套方法去让AI生成代码时,它反复不执行我的约束,答应得好好的,改出来的东西却不是我想要的。这篇文章就是这次实践的完整调试实录:SKILL规范怎么设计、AI的“不执行”有哪些典型表现、我是怎么一步步定位问题并验证出有效方案的。如果你也在用AI批量生成代码,或者想沉淀一套自己的提示词规范,这篇可以给你省不少事。

1. 这次尝试到底在解决什么问题

1.1 为什么是“框架+细节”

我最早用AI生成代码时,习惯把需求写成一段话:“帮我写个工具,扫一下目录里的文件,按类型归档,重名就处理一下。”这种写法对AI来说太自由。模型会基于训练数据里的“通用做法”补全代码,结果生成的程序能跑,但和我的预期经常差得远。后来我意识到,问题不是AI能力不够,而是我没给它一个确定性的坐标系统。

所谓“框架+细节”,就是把任务先拆成两层。框架层是程序的结构,包括入口、模块划分、数据流、调用关系;细节层是具体行为,包括边界条件、命名规则、输出格式、错误处理。框架负责“跑得起来”,细节负责“跑得符合预期”。这个拆分不是拍脑袋想出来的。我做过小范围的对比实验:同一任务,只给一句概述,AI生成的代码和最终可用代码的匹配率大概只有三四成;而给它一个清晰的目录结构和行为清单后,匹配率能到七八成。

核心原因是,模型在生成代码时有一个默认的“最可能路径”。如果不显式约束,它会沿着统计上最正常的路线走,而不会主动猜测你的特殊需求。举个生活中的类比:你让一个新手帮忙收拾书房,只说“整理一下”,他会按自己的习惯把书都摆进书架;但如果你说“先按开本大小分区域,再把常用书放在桌面右手边,其余装箱贴上标签”,结果就完全不一样。AI生成代码也是同一个道理,你不给结构,它就自己发明结构;你不给细节,它就默认走最常见的那条路。

1.2 SKILL不是提示词模板,是“交接单”

我给自己这套方法起名叫SKILL。它不是简单的一段提示词,而更像一份“任务交接单”:把目标、边界、模块、验收标准全部写清楚,再交给AI执行。这样做有几个好处。

第一,交接单可以复用。同类任务直接改改参数就能再用,不用每次重新想需求。第二,交接单能沉淀成技能文件,在支持自定义技能的平台上可以直接挂载,等于把自己的做事套路固定下来。第三,调试时可追溯。AI犯错了,我能看出是框架层写得模糊,还是细节层约束不够,不会一头雾水。

这个方法经过几次迭代后,变成了一套固定格式,包括五个区块:任务目标、框架约束、细节约束、禁止事项、验收标准。后来我甚至把验证过的SKILL保存成了可复用的技能文件,下次遇到同类任务直接加载,相当于给自己的调试经验做了一个“技能回放”。这种思路不仅适用于Python脚本,也适用于接口代码、数据处理任务,甚至一些配置类文件的生成。关键是让AI接收到的不再是模糊愿望,而是一个有边界、有检查点的执行单。

2. 首次实战:用SKILL生成一个文件整理工具

2.1 为什么选“文件归档”这个任务

为了验证SKILL,我挑了一个复杂度适中的任务:写一个Python命令行工具,把指定目录下的文件按扩展名归档到对应子目录,并生成CSV格式的归档日志。文件归档虽然逻辑简单,但麻雀虽小,五脏俱全。

它有命令行参数、配置文件、目录扫描、文件移动、日志输出、异常处理,足以测试“框架”是否管用;又有大量容易出错的细节,比如是否递归、同名文件怎么处理、日志写到哪,天然适合测试“细节”是否被执行。这个任务我原来也手写过,心里有标准答案,所以特别容易判断AI有没有“不执行”。相比之下,如果选一个我自己都拿不准技术方案的任务,就没办法区分问题是出在AI身上还是出在我身上。

我准备了一个测试目录,里面放了一些txt、jpg、pdf文件,还有一层子目录。测试目录结构是这样的:

test_data/ a.txt b.jpg doc.pdf sub_dir/ c.txt

我用这个极简结构来跑生成的代码,一眼就能看出AI有没有遵守“不递归”之类的约束。

2.2 我第一次写的SKILL规范初版

初版的SKILL长这样,我做了压缩展示:

# SKILL v0.1 文件归档工具 任务目标:将源目录下的文件按扩展名归档到目标目录。 框架约束: - 使用Python编写,入口为 main(argv) - 配置从 config.json 读取:source_dir, target_dir, recursive - 主流程:读取配置 -> 扫描文件 -> 归档 -> 输出日志 - 日志模块单独一个函数 write_log(entry) 细节约束: - 默认不递归子目录 - 两个同名文件归档时,后一个加时间戳后缀 - 日志为CSV,字段:time, old_path, new_path, size - 日志存放在 target_dir/archive_log.csv

当时我觉得已经写得足够清楚。任务目标有,框架有,细节也有。实际请AI生成后,它也确实给出了完整代码,能运行,但行为几乎每一处都不对:默认递归了子目录,同名文件直接覆盖,日志写到了当前文件夹而不是target_dir,还额外加了一个我没要求过的tqdm进度条。

我最初的反馈是:为什么给了这么细的约束还是不听?冷静下来之后,我开始逐条拆解,发现问题的根源并不在AI“笨”,而在约束表达方式有缺陷。

2.3 初版生成的“符合度”总结

我把初版结果做成了一个对照表,方便自己定位问题。这个表现在看很粗糙,但当时帮了大忙。

| 约束项 | 预期行为 | AI实际行为 | 是否符合 | | 递归扫描 | 不递归子目录 | 递归遍历所有子目录 | 否 | | 同名文件 | 加时间戳后缀 | 直接覆盖旧文件 | 否 | | 日志路径 | target_dir/archive_log.csv | 当前目录/archive_log.csv | 否 | | 额外功能 | 无 | 自行添加tqdm进度条 | 否 | | 程序框架 | main/config/scan/archive/log | 基本完整 | 是 |

从这个表可以看出,AI不是完全没按SKILL来,而是只在“框架层”基本听话,一到“细节层”就频繁越界。这也印证了我后来的判断:细节约束必须从“描述愿望”变成“可检查的断言”,否则AI会把它当成锦上添花的建议,而不是必须遵守的规定。

3. AI反复不执行的四种典型表现

3.1 表现一:把“要点”当成“建议”

这个现象在第一次实验中就出现了。我写“默认不递归子目录”,但模型生成的代码用了os.walk。理由是几乎所有文件索引任务都会遍历子目录,它凭统计偏好选了一条更常见的路径。这给我的启发很大:细节约束如果和模型训练数据中的“标准做法”冲突,模型大概率会选择标准做法,而把约束看作一个可以调整的要点。

我后来打破这个局面的办法,是把约束改成局部可见的。比如直接给出伪代码:“for entry in os.listdir(source_dir): if os.path.isfile(entry)”,然后明确禁止出现os.walk。让模型在局部看到具体的函数调用,比泛泛的“不要递归”可靠得多。模型对具体代码的模仿能力很强,但对抽象规则的遵循能力相对较弱,这就是为什么“给例子”比“给规则”更有效。

3.2 表现二:只交付片段,不交付整体

第二种不执行更隐蔽:AI生成的代码只有核心逻辑,没有入口函数,也没有完整模块。我把它拿起来准备运行时,才发现main函数没给,配置文件读取逻辑也被省略了。开始我以为是输出长度限制的问题,后来把生成参数里的最大token数调大,还是这样。

细问之下,AI的回答是“这部分比较基础,按你想要的结构补全即可”。它从“按需求生成完整代码”悄悄转成了“补全最有价值的部分”。这种表现说到底还是任务太大了,模型倾向于在达到一定长度后收尾,面对冗长需求时会主动砍掉“它认为不重要的部分”。解决办法是给SKILL加入硬性的交付物清单:必须包含哪些文件、必须包含哪些函数,少一个都不算完成。后来我在模板里加了一栏“交付物清单”,这个问题基本就不再出现了。

3.3 表现三:口头答应,实际不改

多轮对话里的“不执行”最让人崩溃。我让AI“把日志路径改成target_dir/archive_log.csv”,它回复“好的,已修改”,把完整代码复制出来,我diff一看,一行没变。连续三次都是这种状态。

后来我意识到,模型的“已修改”可能只是针对上文语义的顺承,它并没有真正把新要求融合到原有代码中。在长上下文的记忆里,旧版本代码占据了很强的位置,新指令的权重不够,模型从概率上更容易保持原状。这个阶段光靠“多说一遍”没用,必须给出明确的修改指令格式:先定位函数名,再说明要改什么,最后要求输出完整新函数,并配上测试用例验证。换成人话就是:不要和AI说“把那个路径改一下”,要说“请修改write_log函数中的csv_path变量,改为os.path.join(target_dir, 'archive_log.csv'),并输出整个函数”。

3.4 表现四:换一个会话就“失忆”

我还试过在另一个会话里加载同一份SKILL,结果生成结果和上一个会话差异很大。一个把日志字段命名为time,另一个命名为timestamp;一个用os.rename,一个用shutil.move。这说明在没有校验标准的情况下,同一条SKILL在不同会话中只是“指导思想”,不是“执行标准”。模型的采样随机性被放大了。

要解决它,就得靠验收标准把可变空间压住。后来我在SKILL里增加了“验收测试清单”,让AI先生成测试用例再写实现,不同会话即使实现略有差异,行为一致性也大幅提高。这个现象也让我明白了为什么很多团队在做AI编程时强调“测试先行”——测试不仅是质量保障,更是约束模型行为的锚点。

4. 调试全过程:从瞎猜到底层定位

4.1 先把“复现”做扎实

遇到AI反复不执行,我第一反应和调一个普通bug一样:先复现。我把任务固定下来,测试文件换成一个只有几个文件的目录,每次都用同样的SKILL文本生成,并把每次生成结果用git提交成不同分支。这一步很关键,因为没有固定基线时,你很难区分是SKILL的问题、生成参数的问题,还是模型随机性的问题。

我建立了一个问题日志,记录输入SKILL版本、生成工具、生成时间、现象描述、自己当时的猜测。三天下来,日志里积累了十几条记录,模式就开始浮现了。这个过程很像调试硬件问题时的“最小复现环境”:把变量控制到最少,才能看到哪个变量真正影响结果。温度参数、上下文长度、是否在对话中追加过其他要求,这些都会被我一并记下来。

4.2 把框架层和细节层分开测

下一步是做二分定位。我把SKILL拆成两份:一份只写框架约束,不写任何细节;另一份只写细节清单,让AI在已有骨架上补充。测完发现:框架层单独测时,AI的完成度很高,生成的程序结构基本正确;细节层单独测时,AI也能逐条执行。但只要两边合并成一份完整SKILL,细节执行就会崩。

这说明问题不在单个约束,而在于约束太多之后,模型的注意力分配发生了变化。它把更多资源放在理解结构和主流程上,细节约束被挤到上下文边缘,生成时被自然忽略。定位到这个原因后,方向就很明确:要给细节约束加更高的“权重”,用更短的语句、更靠前的位置、更直接的断言式表达。

4.3 用“验收测试”把细节变成硬指标

我想到一个办法:在SKILL里增加“必须先写测试,再写实现”的指令。具体是让AI先基于细节约束写一个测试脚本,把每个细节都转成断言,等测试写好了,再写实现代码。这个做法最有效的点在于:本来细节约束只是自然语言,模型看完了可以忽略;一旦变成测试断言,就变成了“功能规格”,模型在写实现时会下意识地朝着让测试通过的方向靠。

我实际跑下来,同样的细节约束,不写测试时AI的细节遵守率不到一半;写了测试后,细节遵守率达到八成以上。这是我这次调试中收益最大的一步。给AI加“先写测试”这个动作,本质上不是让它多干活,而是把抽象规则翻译成了它更擅长处理的“输入输出对”。测试就是一组明确的输入输出对,模型看到它之后,对代码行为的预测空间会急剧缩小。

4.4 自动化检查与人工diff互补

为了减少肉眼遗漏,我给每次生成物配了一个极简检查脚本:用grep检查是否出现禁止的API(比如os.walk),用pytest跑一遍验收测试,再人工看diff。人工diff主要看两类东西:函数签名是否符合预期,日志输出格式是否和断言一致。

这里有个经验:不要相信AI提供的“运行结果截图”,要自己跑;但也不要完全不信,而是把它的自述当作线索。比如它说“测试通过”,我再人工重现一遍,很快就能定位问题。自动化检查不可能覆盖所有问题,它的价值在于把低级的“不执行”快速滤掉,让精力集中在那些需要理解和判断的偏差上。整个过程和调试嵌入式程序很像:先用工具定位到可疑模块,再人工看代码逻辑,最后烧录验证。只是这里的“目标板”换成了AI生成的代码。

4.5 三条规律,对我后来的帮助很大

调试接近尾声时,我总结出三条规律。第一,AI对约束的理解和人类不太一样:人类会把“不递归”当成硬性规定,AI会把它当成一个普通特征,只有在它能够被验证时,它才拥有“硬”的属性。第二,约束越多,模型执行得越差,解决办法不是删减需求,而是把需求改成可执行检查项,让约束本身参与校验。第三,发现问题后不要停留在当前对话中反复口头纠正,最好回到SKILL源头去修订,然后重新生成,这样每次迭代都有版本记录,不会再出现“嘴上改好了、代码没变”的情况。

这三条规律看起来简单,但每一条都是从失败里磨出来的。尤其最后一条,几乎改变了我使用AI编程的习惯。以前我总在对话里和人掰扯,现在我会直接改SKILL文件、更新版本号、重新生成,效率反而高得多。

5. 最终验证有效的“SKILL v2”方案

5.1 SKILL v2规范模板

经历了几轮迭代,我最终把SKILL模板固定成下面这个结构。对比v1,最重要的变化有三点:细节约束全部改成“验收断言式”;增加了“禁止事项”;增加了“自我检查清单”和“交付物清单”。

# SKILL v2 文件归档工具 版本:2.0 适用任务:本地文件批量归档 ## 任务目标 把source_dir目录下的文件按扩展名移动到target_dir下的对应文件夹, 并生成归档日志。 ## 框架约束 - 入口:main(argv),支持 argv[1] 作为配置文件路径 - 模块:read_config, scan_files, archive_file, write_log - 配置字段:source_dir, target_dir, recursive - 日志模块统一使用 logging;业务日志写入CSV ## 细节约束(验收断言式) 1. scan_files 使用 os.listdir 和 os.path.isfile,禁止 os.walk。 2. 当 recursive=false 时,不进入任何子目录。 3. 同名文件:archive 前检查目标路径是否存在,存在则插入时间戳, 格式:name_YYYYMMDD_HHMMSS.ext 4. CSV 字段顺序:time, old_path, new_path, size 5. CSV 路径必须是 target_dir/archive_log.csv,不允许写到当前目录。 ## 禁止事项 - 不得引入 tqdm 等额外依赖。 - 不得修改源目录下的文件内容。 - 不得在未完成模块时提前return。 ## 交付物清单 - main.py(含全部四个模块) - test_archive.py(至少覆盖第1、2、3、5条约束) - README.md(一行命令运行方法) ## 验收标准 - python test_archive.py 全部通过。 - 对给定测试目录运行 main.py 后,target_dir 下存在 archive_log.csv。

这个模板看起来比v1长,但它把每一处容易“不执行”的地方都变成了可检查的断言。实际效果是,即使AI在不同会话里生成的实现方式不一样,输出行为也基本稳定。细节约束全部写成了“什么条件下、用什么函数、产生什么结果”的句式,模型能直接照着做。

5.2 在SKILL v2下的一次成功生成记录

我用v2重新生成,过程比之前顺利很多。AI先给出了test_archive.py,然后又给出了main.py。我第一次运行测试,发现有一个断言没过,原因是它把CSV路径写错成了source_dir/archive_log.csv。我这次没有在对话里说“请修改”就完事,而是回到SKILL v2里把第5条的表达再强化了一句:“CSV路径必须与target_dir拼接,使用 os.path.join(target_dir, 'archive_log.csv')。”重新生成一遍,测试直接全绿。

最终生成的main.py核心结构如下(节选):

import os import csv import sys from datetime import datetime def scan_files(source_dir: str, recursive: bool = False): if recursive: # 处理递归逻辑 pass return [entry for entry in os.listdir(source_dir) if os.path.isfile(os.path.join(source_dir, entry))] def archive_file(src_path: str, target_dir: str): ext = os.path.splitext(src_path)[1].lstrip('.') or 'others' dest_dir = os.path.join(target_dir, ext) os.makedirs(dest_dir, exist_ok=True) dest_path = os.path.join(dest_dir, os.path.basename(src_path)) if os.path.exists(dest_path): stem, suffix = os.path.splitext(os.path.basename(src_path)) stamp = datetime.now().strftime('%Y%m%d_%H%M%S') dest_path = os.path.join(dest_dir, f'{stem}_{stamp}{suffix}') os.rename(src_path, dest_path) return dest_path

这一段代码并不是多惊艳,但它每一行都踩在细节约束上:没有os.walk、同名文件有时间戳、路径都用join拼接。对我来说,AI“不执行”的问题第一次得到了系统性解决。后面的修改需求,我也都按“定位函数名+说明改动点+要求输出完整函数”的格式发过去,基本一轮就能改对。

5.3 v1和v2的效果对比

我做了几轮重复试验,取平均值,效果差异很明显。下面这个表是我个人测试环境下的记录:

| 指标 | SKILL v1 | SKILL v2 | | 细节约束完全符合率 | 约40% | 约85% | | 一次生成后测试通过率 | 0%(首次必改) | 60%左右 | | 平均修改轮数 | 4~6轮 | 1~2轮 | | 跨会话重启后行为一致性 | 低 | 高 |

这个结果并不代表所有AI任务都适用,但它至少说明:把约束转成可执行验收项,对抑制“不执行”非常有效,尤其适合本地脚本类、接口类、批量处理类任务。如果你遇到的任务类型完全不同,可以根据这个思路调整验收标准的写法,但底层逻辑是一样的:让规则可以被验证。

6. 常见问题速查与避坑技巧

6.1 常见问题速查表

我把这次调试中遇到的问题整理成一个速查表,后续再遇到类似状况可以直接对号入座:

| 现象 | 可能原因 | 处理建议 | | 约束被忽略 | 约束是自然语言描述,没有可验证性 | 改成断言或测试用例 | | 代码结构缺失 | 任务太长,模型主动截断 | 增加交付物清单,明确必须包含的函数 | | 多轮对话中“假修改” | 旧代码在上下文中权重太高 | 输出修改后的完整函数+测试,重新验证 | | 不同会话结果不一致 | 没有验收标准,随机性放大 | SKILL里加验收标准,让AI先写测试 | | 自行添加额外功能 | 只写了“要做什么”,没写“不做什么” | 增加禁止事项区块 | | 程序能跑但行为不符 | 细节层没有和高层框架绑定 | 把细节写成模块级注释或局部断言,而不是放在需求末尾 |

这个表里的每一行,都是我真实踩过的坑。最有意思的是“自行添加额外功能”那一行:tqdm进度条那次之后,我深刻意识到“不做什么”和“要做什么”同样重要。AI本质上是预测下一个最合理的字符,如果没有明确禁止,它就倾向于生成自己认知里“一个完整工具应该有的样子”,哪怕这个功能用户根本不需要。

6.2 几条花钱买不来的实操心得

第一,给AI写“禁止事项”比只写“应该事项”更有效。我最初只在SKILL里写“应该怎么做”,后来发现,单独列一栏“不要做什么”能显著减少自由发挥。比如明确写上“不得引入tqdm等额外依赖”,它就真的不会加。这可能是因为“禁止”在指令中的权重天然更高,也可能是因为它压缩了模型的搜索空间。

第二,一个SKILL只应对一类任务,不要试图覆盖“所有场景”。一旦模板变得通用,细节必然模糊,AI的执行力立刻下降。我一开始想把所有Python工具类任务都塞进同一个SKILL,结果发现约束之间互相冲突,比如一个任务需要递归,另一个不需要,模型分不清优先级。后来我改成按任务类型分别维护,效果立刻好起来。

第三,重要任务一定要让AI在交付前运行测试,并且用统一的格式汇报结果,比如“测试通过数量/失败数量/失败项”。这会逼它去检查自己的输出。如果它说测试通过,你还得自己再跑一遍,但至少它多了一道自检动作。

第四,如果平台支持自定义技能文件,可以把验证过的SKILL保存成可复用的技能文件,下次同类任务直接加载。这也是我从这次实践里尝到甜头最大的副产品。把自己的一套调试经验变成一个可加载的技能,相当于把隐性知识显性化了。

7. 我现在的使用习惯

现在我用AI写代码的习惯已经变了:写之前先花几分钟把SKILL交接单填好,生成之后先跑测试,测试没过就回SKILL改约束,而不是在对话里反复说“你改一下”。这种流程看起来比直接甩一句话给AI要麻烦,但省下来的返工时间远大于投入。经历过这次调试,我反而越来越欢迎AI“出错早、暴露快”的行为方式,它把问题在早期暴露出来,比最后交付一堆无法维护的代码强太多了。

如果你手头也有这种“AI反复不执行”的困扰,我建议你试试把需求拆成框架层和细节层,细节层全部改成可检查的断言,再加一列禁止事项。按这个思路走,多数情况下两三轮就能看到明显改善。最后再分享一个小技巧:每次调试完,把旧的SKILL版本保留下来,哪怕它不好用。复盘时看这些失败版本,往往比看成功版本更能让你理解模型的脾气。

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

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

立即咨询