← 全部极客知识库

工程化 / 大模型应用工程化

8. 调试与代码审查——让 AI 根据证据修复,而不是反复猜测

原文备份 · 第 10 / 21 篇 · 查看极客时间原知识库 ↗

本篇目录 · 9 节

来源:大模型应用工程化

搜索框里输入“RAG 流程”,网页却返回了问题“RAG流程是什么?”。两个字符串中间有没有空格,原本应该影响本项目的连续子串匹配。更奇怪的是,存储层检查全过了,只有网页检查报错。

现在让AI“优化搜索”,它可能换匹配算法、重写SQL,甚至把测试预期改成允许这个结果。改动越来越多,你却越来越难解释:到底哪一处引起了问题?

本篇只处理这个具体故障。我们先稳定复现,再比较相邻层的行为,找到输入在哪一步被改变,做一行修复,最后审查真实差异与验证证据。你会得到一份可以交给别人复核的故障记录和修复报告。

完整练习包 · 问题记录 · 审查报告 · 上一篇:测试与反馈

这是作者专门构造的教学回归,不是上一课真实存在的缺陷,也不是Kimi生成代码实录。核实日期2026年10月4日;作者已离线复现并在临时副本验证修复。没有真实Kimi或DeepSeek请求,没有Windows实机、浏览器视觉或性能压测。

一、这次的project确实带错,先认清起点

解压后进入外层lesson-08。本篇与前两课不同,包含两个应用目录:

lesson-08/
  project/                  故意带错,供你修复
  reference/                正确对照,只读保留
  verify_search.py          仍检查project
  verify_search_boundary.py 仍检查project
  verify_preview.py         仍检查project
  search-cases.json
  reference-fix.diff         作者最小修复对照,先不看
  docs/
    tasks/008-debug.md
    BUG_REPORT.md
    REVIEW_REPORT.md
    STATUS.md

reference的38个应用文件与第07篇正确版本完全一致。project只在一个文件中加入了一行错误处理,其他37个文件不变。交付包特意保留这个故障,让你能够从失败开始练习;“作者已验证修复”不表示你的练习起点已经修好。

三个验证器和固定案例沿用上一课,没有为了演练放宽预期。它们的目标仍是project;不要把参数改成reference,也不要打开第07篇的--counterexample。上一课的反例只在进程里生效,本篇错误实际存在于练习文件,重开终端不会自动消失。

先读README、AGENTS和任务008。应用仍使用DeepSeek,开发工具仍是Kimi。我们不需要个人数据库、模型Key或线上请求,也不准备修改模型配置、数据库结构和搜索规格。

如果你已经在自己的仓库继续开发,请另用这份独立练习包,不要把教学故障覆盖进正在使用的项目。

二、先保存基线,再取得一份稳定的失败

开始修改前,确认工作目录和已有变化。已有独立Git仓库的读者先运行git status --short,记下哪些是自己之前的修改,再按第03篇的方法保留检查点。本课不会替你提交或丢弃内容。

新解压包没有.git也可以练习:用编辑器把project/web_app.py的修改前内容复制保存为docs/debug-before-web_app.py.txt,原文件继续留在project。若同名备份已存在,换一个新名字,不覆盖过去的记录。备份只是一份审查基线,不参与程序执行。

Python环境按README准备:Python3.11或以上,虚拟环境安装project/requirements.txt。已有课程环境可复用。以下都从外层lesson-08运行。

macOS/Linux:

.venv/bin/python verify_search.py project --stage store
.venv/bin/python verify_search.py project --stage web

Windows PowerShell:

.\.venv\Scripts\python.exe verify_search.py project --stage store
.\.venv\Scripts\python.exe verify_search.py project --stage web

第一条预期2个方法通过。第二条会运行4个方法,并在固定案例S10出现一个断言失败。作者实际输出的关键部分如下,路径与运行时间已省略:

test_spec_cases_over_http ... (case='S10') ... FAIL
AssertionError: Lists differ: ['A01'] != []
Ran 4 tests
FAILED (failures=1)

这说明实际结果含A01,预期却为空;不是程序完全打不开,也不是全部搜索失效。S10的数据为:owner A,关键词RAG 流程,A01的问题是RAG流程是什么?。R3要求保留内部空格,R4要求连续子串匹配,因此预期不命中有独立依据。

先把命令、案例、预期和实际结果写进BUG_REPORT。如果你的结果是缺依赖、零测试或另一条断言失败,先处理环境或版本差异,不要直接套本篇结论。没有稳定复现时,“我猜也是这个原因”还不能成为修复依据。

三、失败位置给出线索,不直接宣判根因

存储检查通过、网页检查失败,说明两条执行路径的某个差异值得调查。它不自动证明数据库无错,也不自动证明模板有错。

先列出几个能被证据检验的假设:

假设 怎样核对 什么结果会削弱它
规格本来允许忽略内部空格 阅读当前R3/R4与S10 规格明确保留内部空格
存储层会把空格忽略掉 用同一输入直接检查search_history 直接调用返回空集合
网页入口改变了关键词 检查q读取后到存储调用前的处理 参数原样传入且其他条件一致
模板错误展示了不存在的记录 对比存储返回ID与页面ID 存储已经返回A01,模板只是显示它

不要同时沿四个方向改代码。先用最便宜、最直接的检查缩小范围:读现行规则,比较两个层次使用的输入,再看连接处。

本项目的路径很短:

浏览器关键词q
  → home读取request.args
  → 调用Store.search_history(owner, keyword)
  → 返回items与keyword
  → 模板显示结果和输入回显

读home时,注意传给存储的表达式,而不只是函数名。参数即使类型仍是字符串,内容也可能已经变了。读search_history时,核对它本来在哪里做两端空白处理;不要因为看到两个层次都可能处理输入,就再增加一处“保险清理”。

实际项目可以用断点、少量临时输出或测试替身观察参数。输出只记录定位所需的信息,避免把完整用户文本、凭据或数据库内容塞进日志;确认原因后移除临时调试输出。本课用公开构造词语,无需接触真实用户资料。

四、找到改变输入的那一步

现在打开project/web_app.py,在create_app内找到home。练习版中这行代码是:

result = store.search_history(session["owner"], keywords[0].replace(" ", "") if keywords else "")

replace(" ", "")会删除字符串中的普通空格,包括中间那个。于是HTTP请求中的RAG 流程在进入存储前,已经变成RAG流程。存储层按照收到的词匹配到A01,模板又按返回内容显示出来。

作者用临时案例数据库跟踪这条路径,观察到:

观察点 实际值
直接传给存储的关键词 RAG 流程
直接存储返回ID 空列表
网页路径实际传给存储的关键词 RAG流程
网页返回ID A01
网页关键词回显 RAG流程

这份对照比“网页层可能有问题”更具体:同一套构造数据下,入口改变了关键词,后续行为与改变后的输入相符。它还解释了为什么仅运行存储测试会漏掉这个回归。

不需要为了这次调查启动真实服务器。原有检查使用Flask测试客户端,可以在进程内经过请求处理与模板,适合观察这个边界;它仍然不是浏览器视觉测试。Flask测试客户端

根因现在可以写成一句因果完整的话:网页入口删除了关键词内部空格,使按原规格不该匹配的输入变成能够匹配的输入。不要只写“搜索bug”或“字符串问题”,因为那样无法说明修复应该落在哪一层。

五、只恢复原来的输入传递

将上述调用改回:

result = store.search_history(session["owner"], keywords[0] if keywords else "")

存储方法已经按契约使用strip()去除两端空白,并保留内部空格。恢复原样传递后,入口不再额外改变语义;长度检查、大小写处理、owner隔离和结果排序仍由既有路径负责。

这次最小修复是一行,但“最小”不是机械要求任何bug都只能改一行,而是每一处修改都有证据和任务上的理由。若问题涉及多个接口,可能确实需要多处协调;本例没有证据要求重写数据库或模板。

有两个看起来省事的方向不能接受。第一,把S10预期改成A01,只是让测试迎合错误,改变了已批准行为。第二,在模板里隐藏包含空格的关键词结果,只是遮住症状,还可能错误隐藏本来合法匹配的问题。

搜索无结果也不必转交给模型“智能理解一下”。那会把连续子串搜索变成另一个产品功能,带来新的语义和费用。需求如果要变,应另行明确,而不是混进一次回归修复。

没把握时可以查看reference-fix.diff和reference文件,但不要只复制整个正确目录后宣布“定位完成”。练习的价值在于解释错误与修复之间的因果关系。

六、先确认失败消失,再检查相邻行为

修复后,先重跑刚才失败的HTTP检查。macOS/Linux:

.venv/bin/python verify_search.py project --stage web

Windows PowerShell:

.\.venv\Scripts\python.exe verify_search.py project --stage web

预期4个方法通过,S10不再失败。该组还核对输入回显、重复q、错误状态、转义、owner与原链接,因此比只手动看到一次空列表更有说服力。

然后运行相邻检查。macOS/Linux:

.venv/bin/python verify_search.py project --stage store
.venv/bin/python verify_search_boundary.py
.venv/bin/python verify_preview.py project
.venv/bin/python project/acceptance.py

Windows PowerShell:

.\.venv\Scripts\python.exe verify_search.py project --stage store
.\.venv\Scripts\python.exe verify_search_boundary.py
.\.venv\Scripts\python.exe verify_preview.py project
.\.venv\Scripts\python.exe project/acceptance.py

预期依次为2、4、5、10个方法通过。作者在临时修复副本实际执行了这些检查,未改变测试或案例。交付包的project仍保留起点故障,方便你自己重复整个过程。

本例已有S10能稳定阻止同类回归,不必为了这次一行修复再复制一个完全相同的测试。如果实际故障没有被现有案例覆盖,则应先保留一个能复现它的检查,再确认修复能让它通过。

若修复后又出现新的失败,先看它是否与这次变化相关、是否原本就存在,以及测试环境是否发生变化。不要为了追求全绿连续改动多个模块;每一轮只推进有证据支持的假设,保留失败信息和真实差异。

七、代码审查看实际差异,也看上下文

测试通过后,仍要审查。测试只覆盖已写出的情形,而一次修改可能夹带无关配置、放宽权限或删除其他分支。反过来,短小的diff也不自动意味着正确。

若使用了前面保存的单文件备份,可运行以下命令,两平台相同,需要Git:

git --no-pager diff --no-index -- docs/debug-before-web_app.py.txt project/web_app.py

--no-index比较两个磁盘路径,不要求它们已在Git仓库里。输出中的减号表示旧内容,加号表示新内容;有差异时通常退出1,不应误认成验证脚本失败。Git官方文档说明该模式隐含按差异返回退出状态。git diff文档

本例应只看到搜索调用去掉了.replace(" ", "")。还要读这行周围的getlist、重复q检查、异常处理与session取owner逻辑,确认它们仍完整存在。只看孤立的一行,可能看不出上下文被谁改过。

已有Git仓库则从这些命令检查,注意它们看的范围不同:

git status --short
git diff --stat
git diff -- project/web_app.py
git diff --cached -- project/web_app.py

普通diff看未暂存改动,cached看已暂存改动;新建未跟踪文件还要依据status打开检查。不要只看到一份空diff,就推断没有修改。其他人的既有改动要单独识别,不能为让本次差异好看而删除。

完成后,可再与作者正确文件比较:

git --no-pager diff --no-index -- reference/web_app.py project/web_app.py

完全按参考修复时无输出、退出0。与参考相同是一项对照证据,并不代替规格和测试;别的实现也可能正确,但任何额外变化都需要说明理由。

审查报告里要回答:触发条件是什么,为什么这一行能解决问题,有没有改测试预期,有没有绕过owner或确认,有没有残留调试输出,还有哪些未验证。不要用“AI说已修复”替代这些判断。

八、让Kimi提出可验证的假设,再授权小范围修改

已自行安装并登录的读者,可以在外层lesson-08启动kimi --plan。本机核对版本为2.0.2,当前官方文档说明Plan模式支持先提出计划供审阅;这有助于先看调查方向,不能代替代码权限和结果验证。Kimi交互说明

第一次请求可只做诊断:

请读取AGENTS、任务008、BUG_REPORT、搜索契约与相关源码。
project是故意带错的练习起点,reference和reference-fix.diff先不要看。
已知store阶段通过,web阶段S10实际['A01']、预期[]。
请提出最多三个可检验假设,沿q到search_history的路径核对。
先给证据、根因与最小修复计划,不编辑文件,不读取个人data或凭据。
不要改测试预期,不要将本次修复扩大为语义搜索、数据库重构或模型迁移。

核对它是否引用了真实路径、解释了两层结果的差别,再明确要求仅修改project/web_app.py的相关输入传递,并按本篇命令验证。一次诊断、一次修改通常足以完成这个练习;若持续往返仍无法解释原因,先停下检查证据,不要让它无限尝试。真实开发对话可能使用Kimi额度,作者没有替你执行。

修复后让它审查自己的diff可以帮助发现遗漏,但这仍是同一系统对自己工作的解释。你应独立查看差异和测试输出,必要时让另一位读者或另一个会话根据规格审阅。是否需要第二位审阅者取决于风险,不是所有小修复都必须增加一个Agent。

九、把修复交付成别人能复核的结论

完成BUG_REPORT与REVIEW_REPORT后,试着写一段交付说明:当关键词包含内部空格时,网页入口曾删除空格并改变匹配结果;现已恢复原词传入存储,保留既有两端空白处理;只修改web_app.py一行,HTTP及相邻离线检查通过,未验证真实模型、视觉和性能。

这段话同时说明了触发条件、前后行为、改动范围和证据,比“修了一个搜索bug”更能帮助后续维护。你没有执行的检查应从自己的交付结论中删去或标成未运行,不能直接复制作者记录。

如果复现不出本篇故障,先确认自己是否误用了reference或上一课的正确代码;如果HTTP仍然红,确认修改保存到了project,并核对S10的实际差异;如果只剩依赖错误,回到环境检查,不继续改业务。把失败分清楚,调试才不会变成随机改动。

本篇结束后,请从修复后的project或正确reference继续,不把故意带错的起点带入后续正常项目。下一篇进入AI应用评测:代码行为正确之后,怎样进一步检查检索、引用与回答语义,尤其是资料不足时是否能可靠地说明不知道。