七牛云实训营的题目三。给一个 GitHub PR,自动审查代码、列出问题。这类工具最容易翻车的不是漏报,而是乱报——对一段没问题的代码硬挑一堆假问题,开发者看两次就不信了。所以我把整个设计的重心放在误报控制上,并且用真实 PR 把它测出来。
两段式模型路由:扫和判用不同的模型
审查分两遍,各司其职:
- 第一遍
deepseek-chat(便宜,temperature=0):快扫整个 diff,产出 summary + 候选清单。追求召回,宁可多捞。 - 第二遍
deepseek-reasoner:对每一条候选单独调一次,裁决 confirmed / false_positive / uncertain,并保留每条的reasoning_content(思维链)。判为误报的直接丢掉。
逐条单独调用是刻意的,不是图省事。 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 结果假装成评测指标。