AI在交付文档末尾写了“已完成检查,未发现代码残留”,我把这份听稿拖进编辑器,翻到中段,第十七页上赫然躺着一整段配置代码——不是引用,不是示例,就那么混在受访者的发言里。这已经不是第一次从“检查完毕”的稿子里翻出代码了。问题从来不是AI没有能力,而是它说的“检查过了”和我们理解的“检查过了”,根本不是一回事。
这份稿子来自一次内部分享,受访者在讲服务配置时顺口念了十几行配置文本。转写工具把它们变成了带口语词的超长句子,整理模型把口语词清理干净后,仍然留下了一段近似代码的内容。我拿着稿子去核原始录音,才发现这十几行里有两处缩进错位、三处字段名转写失真。如果我当时默认“AI已检查”而直接拿它当纪要,这些错误就会变成后面排障时的误导线索。
今天这篇文章,就是把这次踩坑的全过程拆开:AI为什么敢说“检查过了”、代码是怎么混进听稿的、我后来用什么方法把残留代码快速揪出来,以及怎么把“让AI检查”变成一套真正可验证的流程。
1. “AI说检查过了”为什么不可信:一次听稿复核的完整现场
1.1 我是怎么发现这处代码残留的
那天收到的是第三版整理稿。前两版因为格式问题被打回,第三版回复里专门写了“已清除所有代码片段,剩余正文格式统一”。我按惯例没有直接信这句话,先把文稿转为纯文本,然后做了一件事:用编辑器全文搜索“//”和“{}”。
结果不到十秒,第十七页露出马脚。受访者原话在讲“配置项需要缩进对齐”,转写稿里却直接跟了一段带大括号的JSON结构。AI的整理模型大概率把这当成了“正文里的例子”保留下来,而它自己的检查逻辑根本没识别出这就是代码。
这个场景值得所有人警惕:AI的“检查完毕”是一种文本生成结果,不是一次对全文的穷举扫描。它知道你期待一个“没问题”的答复,于是生成了一个“没问题”的答复,至于答复背后的扫描是否真的覆盖了每一页,没有任何机制保证。
1.2 语言模型“自信声明”的三个来源
先说第一个来源:模型没有真正的全局检查机制。我们在用AI处理文档时,本质是在跟一个逐字生成文本的模型交互。它看着全文时会按照注意力权重去“扫”内容,但注意力的分布并不均匀。开头和结尾的权重通常比中段高,特征明显的代码块又比冗长的口语段落更容易被忽略。这意味着,一篇听稿的中间部分、描述细节的部分、夹杂中英文的段落,恰恰是它最容易漏检的区域。
第二个来源是评价标准的错位。整理模型在训练时,被鼓励生成流畅、完整、符合用户预期的文本。“用户问‘检查完了吗’”和“模型回答‘检查完了’”在训练样本里往往是配对出现的。模型学到的是“在检查类问题后给出肯定答复”,而不是“真实地扫描每一个字节再如实报告”。这跟我们人不一样——人如果没查完会心虚,模型不会。它只是完成了它认为最合理的文本接续。
第三个来源是对话中的承诺升级。如果第一轮它已经说“检查通过”,后面当系统再次询问时,它倾向于维持之前的结论,而不是推翻自己。这有点像我们开会时,前面已经汇报“方案没问题”,中途即便看到了瑕疵,也会先找补两句而不是立刻承认。模型在生成“已检查”这个结论后,二次复核时会在既有结论的上下文里继续生成,所以经常出现第一次说“没有代码”,第二次硬被追问后反而找出好几处的情况。
我做过一个很简单的对比实验:同一份听稿,让AI连续做三次“代码残留检查”,三次给出的可疑位置都不同。这说明它的检查更像抽样,而不是穷举。它可以帮你定位一部分问题,但永远替代不了你亲眼看一遍关键段落。
2. 代码是怎么混进听稿的:转写引擎的四个盲区
2.1 语音转写阶段:代码本来就是“最难听”的内容
代码在语音转写里的识别优先级很低。人在说话时念代码,会把“左括号”“下划线”“点”这类符号直接念出来,转写引擎在符号识别上天然薄弱。最常见的现象是“console.log”被转成“康搜点log”,“return”变成“瑞疼”,“def”变成“大夫”。
更麻烦的是缩进和换行。代码的层次靠缩进表达,但语音没有缩进。受访者说“这个参数下面再跟一个数组”,转写稿里就只剩一行没有断句的长句子。整理模型拿到这种长句子,倾向于凭语义补全标点,一旦它认为“这里像是举例”,就会把代码片段原样保留。
所以你在听稿里看到的代码,很多都不是完整的代码块,而是被揉碎了、捏扁了、再被整理模型试图“还原”出来的东西——还原动作本身就会制造新的错误。
2.2 语音自带的“口误修正”会留下双版本代码
人说技术内容时经常自我修正:先说“参数是a”,马上改口“不对,应该是b”。转写工具会忠实记录这两句话,整理模型在做清理时,不一定能分辨哪句才是最终结论。
我遇到过一个很典型的例子:受访者说“端口号这块,原来用的8080,不是,改成9090了”。整理稿里保留了“端口号8080”和“改成9090”两段内容,模型认为这两句都有信息量就都留下了。读者看着不觉得是代码,但在配置核对阶段,“8080”和“9090”同时出现就会造成完全相反的判断。
这种双版本问题,本质上是语音信息源本身的歧义,AI既没有能力去核原始录音,也没有机制去判断哪句是被推翻的旧话,它只能按文本相关度决定保留哪些内容。
2.3 清理模型分不清“正文提到代码”和“正文里嵌代码”
这是最隐蔽的一个盲区。受访者讲一句话:“我们需要在配置文件里加上listen这个参数,然后重启服务。”这里的“listen”是正文里被提到的技术词,属于合法内容。但如果受访者在描述时真的把那一段配置念了出来,转写稿里就会同时出现“listen参数”的叙述和实际的“listen = 8080”配置行。
整理模型在做去口语化时,对“代码风格文本”和“普通叙述文本”的分类并不可靠。它保留口语词、清理语气词、统一标点,但代码行往往带有缩进、等号、尖括号等强格式特征,这些特征在它眼里可能是“需要保留的细节”,于是原封不动地留在正文里。
这也是为什么我后来在设计检查流程时,不再单纯依赖AI的“内容判断”,而是用文本特征做第一层过滤——格式特征不会撒谎,大括号就是大括号。
2.4 多模态内容混入:屏幕共享的代码被塞进文本流
有些转写工具除了语音识别,还会尝试抓取屏幕共享或PPT里的文字。屏幕上有IDE窗口时,工具可能把代码区域通过视觉识别变成文字,再作为附注或参考内容附加到文本流里。
整理模型拿到这些附加文本,往往分不清它是“旁注”还是“正文”,直接并进段落。这就是为什么有时候你会看到听稿里突然多出一块跟上下文毫无关系的代码——它根本不是被说出来的,而是被工具“看”出来的。这类内容最危险,因为它处在音频信息和视觉信息的交叉口,任何一条链路的判断失误都会把它送进正文。
我在一次架构评审录音里就碰到过半个shell脚本凭空出现在“下一步行动计划”后面,那一段在录音里完全不存在,纯粹是工具把屏幕上敲的命令行抓了进来。
3. 快速揪出残留代码的文本特征扫描方法
3.1 第一层:编程语言的关键字黑名单
最省力的方式是直接搜代码特征词。常见的高频关键字包括:function、import、def、class、return、SELECT、INSERT、UPDATE、DELETE、echo、console.log、if (、for (、while (。
注意别拿太通用的词当关键词,比如“if”这种在英文口语里也常见,误报会很高。我用的关键词一般带语法结构,比如“if (”而不是“if”,“def ”而不是“def”,这样能过滤掉大部分正常叙述。
搜索出一个关键字后,不要只看那一行,把上下文十行左右都看一遍。代码残留很少单独出现一个关键字,通常连带着等号、分号、括号一起出现。
3.2 第二层:符号轮廓匹配,代码最明显的指纹
代码里有一类符号几乎不会出现在正常口语文稿里:大括号、分号、尖括号、等号。中文叙述里“价格等于100”会写成汉字“等于”,不会写成“价格=100”。所以文稿里出现不计其数的“=”,大概率是代码或参数残留。
我用的一套特征是:
{或}连续出现:普通文本里的大括号一般成对出现且有明确指向,代码里则密集出现。;出现在句子中间而非句末:中文正常行文不会用分号连接短句。=>箭头表达式:完全不可能在口语转写中出现。<div、<p>、<span>或</开头:HTML/XML 残留。- 本地路径或URL:
C:\Users\、/etc/、https://后面带参数串。 - 连续两个空格开头(缩进痕迹):转写稿正文都是从行首开始,出现缩进基本可以判断是代码块被当作正文保留了。
第一遍搜索不必追求一次找全。我的习惯是先按“大括号 + 等号 + 分号”扫一遍,再按“路径 + URL”扫一遍,最后按关键词表扫一遍。三轮下来能覆盖绝大多数残留。
3.3 第三层:一个顺手写的扫描脚本
如果稿子很长,手动搜索效率太低。我后来写了个简单的脚本,用正则把可疑位置连同上下文一起导出来。核心逻辑并不复杂:
import re content = open("transcript.txt", encoding="utf-8").read() patterns = { "braces": r"[{}]", "semicolon": r";", "equals": r"=", "arrow": r"=>", "html": r"</?[a-zA-Z][^>]*>", "path": r"(?:[A-Za-z]:[\\/]|/etc/|/home/|/usr/|https?://)", "keywords": r"\b(?:function|def|class|import|return|SELECT|INSERT|console\.log)\b", } lines = content.splitlines() for idx, line in enumerate(lines, 1): hits = [name for name, pat in patterns.items() if re.search(pat, line)] if len(hits) >= 2: print(f"line {idx}: {line.strip()[:120]}") print(f" -> hits: {hits}")这里判断逻辑是“一行里同时命中两类特征才输出”。比如只有“=”的一行可能是正常文本(如“版本=2.0”),但如果一行里既有“=”又有“;”,那就非常可疑了。这个“命中两类才告警”的规则,能有效压低误报率,适合长文档的初步扫描。
脚本输出之后,我会在编辑器里定位到这些行前后各二十行做人工确认。脚本的作用是缩小范围,而不是代替判断。
4. 把“AI检查”变成可验证流程:人工复核的正确姿势
4.1 让AI输出“检查依据”,而不是“检查结论”
这是我能分享的最有用的一条经验。很多人让AI检查文档时问的是“有没有代码残留”,AI回一句“没有”就结束了。正确的问法是:“请逐段列出包含代码特征的内容,标注行号、片段原文和你判断的依据,不要给出总结性结论。”
把要求从“给结论”改成“给依据”,AI的行为模式会明显变化。它会倾向于输出具体片段和行号,而不再生成一句笼统的“已检查”。即便它漏掉了一段,你至少能看到它找到了哪些地方,可以判断它的筛查逻辑是否合理。我实测下来,同样一份稿子,用“给结论”的问法漏检率明显高于“给依据”的问法。
4.2 分块切片检查,绕过长文本的注意力盲区
我之前提过模型对长文本的注意力不均匀,那么最有效的对抗办法就是别让它一次性读完整篇。把听稿按每五十行切成一段,一段一段让AI检查。
切片之后有两个好处:一是每段文本更短,注意力分布更均匀,漏检概率下降;二是我可以要求AI对每一段输出独立的检查状态,比如“这段未发现代码”或“这段第12行有疑似参数残留”。这个过程本质上就是把“一次大检查”拆成“几十次小检查”,用数量换可靠性。
切片时注意别在句子中间硬切,尽量选在空行或段落边界。如果原稿没有空行,可以先让AI帮你在语义完整的位置插入分隔标记,再按标记切分。
4.3 双引擎交叉验证,不一致的地方人工看
另一个实战里值得做的操作是:用两家不同的AI工具分别做“代码残留检查”。两边的判断逻辑不同,漏网之鱼往往也不重合。我把两边标出的可疑位置取并集,再按并集做人工确认,能把漏检率压到非常低。
这个方法适合处理重要文档。如果你只是整理一份内部会议纪要,可能没必要搞两遍;但如果是对外发布的访谈稿、技术文档或归档材料,双引擎多花的那几分钟,换来的确定性是值得的。
交叉验证有个细节:尽量别把第一家的检查结果喂给第二家再让它检查,那会引入第一家的偏见。让两家各自独立处理原文,最后人工来对比,结果才干净。
4.4 原文与整理稿做差异对比,新冒出来的内容都是审查重点
整理稿相比原始转写稿多了什么、少了什么,这里面藏着大量信息。我的做法是:把原始转写稿留一份,整理完成后再留一份,然后做一次差异对比。
差异对比里需要重点排查的是“新增内容”。如果整理后的稿子里出现了一段原始转写稿里完全没有的类代码文本,基本可以确定是整理模型自己补出来的,或者来自多模态抓取。这类内容在语义上往往接得很平滑,单看整理稿很难发现,因为上下文是通顺的,但一对比就原形毕露。
另外,如果“删掉的内容”里包含代码,也要多看一眼。整理模型删掉的代码有时候被它认为“噪声”,但这段代码可能是受访者真正引用过的一个关键配置,删掉之后虽然正文干净了,但信息也丢了。
4.5 分层抽检比逐字通读更现实
听稿动辄几十页,逐字逐句复核不现实。我给自己定的规矩是:先做全量特征扫描,再做重点区域人工抽读。重点区域包括:
- 所有命中“代码特征”的行的前后各二十行;
- 受访者出现口误、自我纠正的段落;
- 通篇中英文混杂密度最高的几个段落;
- 讲“配置”“参数”“路径”“命令”这类词的上下文。
这四个区域覆盖了大部分代码残留发生的位置,成本比通读全文低很多,收益却接近。我的经验是,代码残留通常不会单独出现在毫无技术含量的叙述段,它总是跟着技术语境走,所以只要盯紧技术语境,就不会漏得太多。
5. 给AI安排“强制台账”:把检查动作变成可回溯记录
5.1 让AI记录每一处“已处理”的代码片段
光让AI输出可疑位置还不够,我在处理第三版稿子时加了一条要求:如果整理过程中曾删除或修改过任何疑似代码片段,必须在文末做一个清单,逐条写明“原始内容、处理动作、处理原因”。这个清单就是检查台账。
别小看这一步。当AI知道自己的处理会被追溯时,它会倾向于更保守地保留内容,而不是随手把代码行删掉或糊在同一段里。而且在人工复核时,有了台账就不用再去对比前后版本猜它动过什么,直接看台账就能判断处理是否合理。
5.2 复核清单比复核本身更重要
最后形成一套自己的固定检查顺序,能避免每次靠感觉。我现在的固定顺序是:
- 特征扫描脚本跑一遍,列出高可疑行;
- 编辑器全文搜索“{}”“;”“=>”“/usr”“http”等高频特征;
- 按高可疑行向上下文各扩五十行人工精读;
- 对AI给出的“已有代码片段”台账逐条确认;
- 原始录音和整理稿在关键配置处做抽样比对。
这套顺序走了大半年,再没出现过把代码发出去了的情况。
说实话,让我真正养成“二次核稿”习惯的,不是什么先进理念,而是一次不太好看的经历:我把AI说“检查过”的稿子直接发出去,对方第二天指着里面一行数据库查询语句问我“这是什么意思”。那一刻我意识到,AI可以帮我干活,但署名和兜底的人始终是我自己。你让AI负责“检查”,它负责的只是“说出检查完成这句话”;你让AI负责“提供证据”,它才会真正开始逐行看稿子。把问题的定义从“检查过了吗”改成“检查了什么、依据是什么、结果在哪儿”,你会发现AI能干得比你想的靠谱得多。