skip to content
tlj 的工程笔记
← 项目

AI 代码审查工具

比赛

拉一个真实 PR 自动审出问题。核心不是能报问题,而是不乱报——两段式模型路由控误报,对一个正确的已合并 PR 实测 0 误报,对故意塞 bug 的 PR 6 个候选丢 2 留 4。

时间
2026-06
角色
独立开发(审查引擎 / 前端 / 部署)
成果
deepseek-chat 扫候选 + deepseek-reasoner 逐条确认 + Azure 高危交叉验证;正确 PR 0 误报

七牛云实训营的题目三。给一个 GitHub PR,自动审查代码、列出问题。这类工具最容易翻车的不是漏报,而是乱报——对一段没问题的代码硬挑一堆假问题,开发者看两次就不信了。所以我把整个设计的重心放在误报控制上,并且用真实 PR 把它测出来。

两段式模型路由:扫和判用不同的模型

审查分两遍,各司其职:

逐条单独调用是刻意的,不是图省事。 reasoner 的思维链是按整次调用返回的,如果一次性批量判所有候选,多条问题会共享一段思维链,前端就没法逐条展开”它为什么觉得这是个问题”。逐条调用 = 每条 finding 一段独立思维链。代价是 N 个候选 N 次 reasoner 调用,所以用 DEFAULT_MAX_DEEP_READ=12 封顶防爆,超限的保留扫描结论但标记未深读。

误报控制的主力就一句话:verdict==false_positive 的候选直接跳过,不进最终结果。

用真实 PR 把误报控制测出来

对一个正确的 PR——0 误报。 拿 psf/requests 一个已合并的正确修复(no_proxy 在 302 重定向被忽略,改了 7 行)去跑:第一遍 chat 扫出 1 个候选,第二遍 reasoner 深读后判定它是误报并丢弃(理由:“这里的早期返回是正确处理,不是跳过环境代理”),最终输出 0 条。用时 23.8 秒,21776 tokens。对真实正确代码不硬报,这就是误报控制在起作用。

对故意塞 bug 的 PR——精准命中。 我自建了一个塞了 SQL 注入、空指针、float 存金额且无事务三个高危 bug 的 demo PR:第一遍 chat 扫出 6 个候选,第二遍 reasoner 丢掉 2 个误报(都是对诱饵代码的过度报警)、保留 4 个真问题,每个高危项置信度 1.0,理由具体到”用户不存在时返回 None,未判空直接访问会 TypeError""float 有精度误差不适合金融,两个 UPDATE 未显式事务、部分失败会导致资金状态不一致”。

高危问题再加一道交叉验证

对 reasoner 确认的高危问题,再用 Azure 上的 GPT-4.1-mini 独立判一次(只验高危确认项,省 Azure 配额)。两个不同厂商的模型——DeepSeek 系 + OpenAI 系——结论一致就给置信度加分(+0.15),不一致就降级成中危并标”模型有分歧”(-0.2)。异构是关键:同一个模型投两次只是采样噪声,跨厂商的两个独立判断一致,才真的说明结果稳。上面那个 demo PR 的 3 个高危项,Azure 全部 agree。

一个测试本身救了我

写到加交叉验证那版,全套单测从 1 秒内暴涨到 143.85 秒(但还是全过)。我没忽略它——“143 秒,之前不到 1 秒,肯定有什么在发真实网络调用还带重试退避”,用 pytest --durations=8 一眼定位到:缓存测试构造的 ReviewService 默认建了一个真的 CrossValidator(enabled=True),而测试桩返回了一个高危 finding,于是每跑这条测试就发真实 Azure 调用加退避重试。修法是让缓存测试显式 enable_cross_validate=False(它们只测缓存不该碰网络),改完回落 0.9 秒。

教训三条:单测变慢是信号不是噪音,要查;引入一个”默认启用外部调用”的组件后,所有间接构造它的测试都会被悄悄拖下水;--durations 是定位这类问题的利器。

分层上下文:宁可诚实说没看全

按 PR 改动行数选上下文层级(token 预算 24000):小 PR 给整文件,中等给”import + hunk±40 行 + 同文件函数签名”的抽取式,超大 PR(>3000 行)只给 patch 并主动声明”跨文件引用未分析,本段为局部 review”。测过一个新增约 4400 行单文件的 PR,它正确判为最高层级、输出 0 条、summary 老实写”无法对代码逻辑有效分析,建议人工复核或拆分 PR”。比起假装看全了乱判,诚实说没看全更有用。

拿自己审自己

最容易藏 bug 的是我自己写的那个 SSE + asyncio + 多线程的 PR。我用本工具审它,它真报出一个高危并发 bug:“多个 SSE 连接共享同一个 asyncio.Queue 会导致事件流错乱”(Azure 也 agree)。我没盲修,逐条人工分流——有的记为已知限制,有的发现是更早的 PR 已经修过(模型审的是旧快照看不到,恰好印证”增量 review 看的是 PR 当时的状态”),真正该修的修掉。

前端:刻意不做成 AI demo 的样子

初版用了紫色渐变、半透明叠色、伪 emoji,一眼”AI 模板味”,当场推翻重做。最终定调:单主色琥珀/赭(像编辑器警告色和 CI 工具),严重度只用左边框语义色,findings 和日志用等宽 JetBrains Mono、UI 用 IBM Plex Sans(等宽配无衬线本身就是 IDE 气质),进度区做成真终端面板(终端提示符、闪烁块、极淡扫描线)。目标是让它看起来像一个给开发者用的工具,不是一个营销页。

诚实说没做的

误报控制讲了一路,但我没有做出一个带标注的离线误报率评测集——所以”0 误报/4 真阳”是几个真实 demo 上的结果,不是一个量化的统计指标。这条我明确列进了未完成项,没有拿 demo 结果冒充评测数字。

小结

一个代码审查工具的可信度,恰恰来自它”敢对干净代码说没问题”。这个项目我最满意的是它的克制:两段式控误报、跨厂商验高危、超大 PR 老实认怂、UI 不堆花哨,以及没有用 demo 结果假装成评测指标。