[控场AI]
· 7 分钟阅读· 3,532 字

浏览器预过滤+服务端裁决:一小时拆解PR审查机器人

浏览器预过滤+服务端裁决:一小时拆解PR审查机器人

用一个小时结对,把PR审查机器人拆成客户端预过滤、服务端裁决、CI合并三层。

文章记录了一次为 PR 审查机器人重新划分架构的一小时结对过程。两位工程师先定下“什么数字让我们停下”这一预算问题,再依次否掉了三个方案:把机器人整体搬进浏览器(队列与规则文件出现双份真相源)、把原始 diff 直接发给共享端点(锁文件浪费上下文与费用)、只按 diff 哈希缓存裁决(模板或模型变更后静默复用陈旧摘要)。最终留下的是一条路由表:分类与修剪留在调用者机器,风险摘要放在共享服务器,裁决缓存以模型 id、模板哈希和 diff 哈希三者拼接为键,合并决定永远交给 CI 中的人。配套的预算脚本在启用 webhook 前探测冷/热延迟并推算 token,超预算即非零退出。作者还指出低量仓库、合规受限团队应跳过拆分,并强调先量出自己仓库的修剪节省比例,而不是引用博客数字。

一个自动审查机器人连续十一天给每个PR贴风险摘要,直到一位资深评审约了一小时的结对时间,要搞清楚一个边界问题:哪些工作该在浏览器里做,哪些必须交给服务器。这一小时里试了三条死路,最后只留下一个决定——一个脚本和一张路由表。

PR审查机器人架构拆分

白板上的三个问题

结对的前十分钟没有打开编辑器。白板上按顺序写了三件事:diff里哪些字节真的需要模型?模型跑在哪里——浏览器、共享服务器还是CI?什么数字能让这对搭档停止上线?

第三个问题是改变架构的那个,没有它,前两个答案都只是偏好。

死路一:把整个机器人搬进浏览器

最直观的方案是让浏览器本地推理,没有服务器、没有冷启动、没有出口流量,看起来像白捡的算力。

它败在状态而不是速度上。机器人需要知道哪些PR已经总结过,而一个浏览器标签页无法跨机器持有这个队列。把队列重新实现在IndexedDB里,等于在webhook日志旁边造了第二个真相源。另一个发现是本地文件分类器被复制进了浏览器包,同一份规则文件出现两份拷贝,随时可能漂移。

活下来的是:预过滤留在客户端,模型调用不行。“这是锁文件,跳过”这类规则廉价、确定、无需网络就能测试。

IndexedDB 是浏览器内置的客户端 NoSQL 数据库,支持事务和异步读写,一般用于缓存或离线存储。它能存在大量数据(通常按设备配额),但作用域仅在同一个浏览器、同一个源、同一台机器——换个标签页或设备就看不到。因此把机器人待总结的 PR 队列放在 IndexedDB 里,注册多台开发机时每台都会维护自己的副本,一旦有人更换浏览器环境,数据就丢失或分叉,等于在原有 webhook 日志旁另起一套“真相源”,需要额外同步逻辑才能对齐。

确定性纯函数指同一输入永远产生同一输出、且不依赖网络、时钟、随机数或外部状态。把锁文件识别、路径跳过这类规则写成纯函数,最大的好处是可以在本地单测里无需 mock 就断言结果,也意味着客户端跑出的过滤结果与服务端完全一致,不会因为环境差异漂移。

死路二:把所有diff发给共享端点

第二个尝试干脆删掉分类器,把原始diff直接发往托管端点。样本里第一个生成的锁文件就有900行,模型把上下文浪费在描述正则早就知道的依赖升级上。

修剪必须在请求之前发生,而不是在账单之后。作者给了一个确定性的trimDiff函数:按diff --git切块,用正则跳过package-lock.json、pnpm-lock.yaml、dist、vendor等路径,每个文件截断到120行。

关键在于不要相信博客里的节省比例,要在自己的仓库上量。作者给了一段shell,遍历最近30次合并,逐个计算保留字节占比。如果这个比例很高,说明预过滤没起作用,后面的拆分也帮不上忙。

diff 修剪之所以必须发生在请求前,是因为模型按 token 计费,而 token 与请求体大小近似线性。锁文件通常包含数千行自动生成内容,正则匹配文件名后整块跳过即可省下绝大部分字节;对保留文件截断到固定行数,则是用少量上下文损失换取预算可控。修剪函数的输入输出都是字符串,属于可单测的纯函数,因此可与分类步骤放在同一进程运行,无需额外网络往返。

字节占比实测的意义在于:不同仓库的锁文件、构建产物占比差异极大,博客里常说的“省 90%”未必成立。遍历最近 N 次合并的真实 diff,统计保留字节数除以原始字节数,得到的就是本仓库自己的模型输入比例。如果这个比例偏高,说明预过滤规则没有覆盖该仓库特有的生成文件路径,应先补正则而不是继续调参。

死路三:按提示词哈希缓存裁决

第三个想法最诱人也最危险:把裁决按提示词文本的哈希缓存,重复的diff就不用重新发送。两周后,一个已合并的PR带着旧模板生成的摘要上线了。

缓存键本身就是bug。一个裁决是diff、提示词模板和回答模型的函数,只哈希diff意味着上游改了标识符就会静默复用陈旧文本。最终通过评审的键包含三样东西:modelId、templateHash、diffHash,用\u0000拼接后做SHA-256。

结论很清晰:缓存预过滤决定是安全的,缓存裁决只有在三个输入齐全时才安全,而且模型标识符必须来自配置,不能有默认值。

缓存键设计是缓存正确性的第一道前提。一个裁决结果取决于三个独立变量:读到的 diff 内容、发给模型的提示词模板、以及执行推理的模型标识符。只哈希 diff,则模板升级或模型切换后,旧裁决仍会被静默命中,导致已修改内容带着旧模板的摘要上线。把三者用不可见分隔符(如 \u0000)拼接后做 SHA-256,可避免字段边界混淆(如拼接 ab+c 与 a+bc 得到相同字符串),同时缩短键长以适配缓存存储。

模型标识符必须来自配置、且无默认值,是因为各家托管端点会随版本更新悄悄改变行为甚至定价。若代码里写死默认值,上游切换模型时缓存键不会变,等于复用了完全不同模型的旧输出;而在配置缺失时显式失败,则把问题暴露在启动阶段,而不是生产流量里。

留下的决定:拆分加预算

一小时结束于一张路由表和一段拒绝在数字不达标时启用机器人的脚本。

工作运行位置原因失败场景
文件分类调用者机器零模型成本,纯函数规则文件与仓库结构漂移
Diff修剪与分类同进程确定性,可单测切片切断上下文
风险摘要共享服务器+托管模型需要队列和稳定webhook目标层级延迟或配额变化
裁决缓存服务器,三输入为键避免重复开销模型id未在配置中固定
合并决定CI,永不交给模型人保持问责永远不要委托这一步

配套的split-budget.mjs在第一次真实审查前运行:发两个小请求和一个代表性diff,记录冷启动与热延迟,按日审查量推算token,任何一个测出的数字突破budget.json声明的预算就非零退出。作者特别强调配置里不要硬编码配额,这样层级条款变化会表现为tokens_over_budget,而不是一张悄无声息的账单。

exit=0是启用webhook的许可,exit=1则点名原因并让机器人保持关闭。门禁小到资深评审能在最后一小时里读完每一行——这正是重点。

免费层级如何进入这次会话

(披露:本文作为MonkeyCode产品推广的一部分准备。)

这次结对需要两样笔记本给不了的东西:一个托管模型来回答warm-real探测,一台有公网地址的机器在测试期间接收webhook。运营方的免费层级同时提供了这两样——免费模型访问和免费服务器选项,且项目开源,路由代码可以在信任它处理diff之前先读一遍。运营方称该层级包含1000万token,这应当被视为一个带日期的声明而非常量。脚本因此从budget.json读取限制,从不硬编码配额。

局限与谁应该跳过

  • 低量仓库:每周大约20个PR以下,门禁和路由表花掉的注意力比省下的多。保留预过滤,放弃拆分。
  • 合规受限团队:把diff发给共享托管层级可能直接被禁止。修剪和分类阶段仍可单独工作。
  • 延迟未知:共享免费端点不适合作为合并路径依赖。如果冷启动行为无法测量,让机器人只做建议、永不阻塞。
  • token估算:基于字符的兜底在账本里标注为estimate是有原因的,在代码密集输入上会漂移,优先使用提供商报告的使用量。
  • 门禁只测量,不保证:通过意味着数字符合今天的配置,不代表下个季度仍然符合。

下一次结对小时的清单

  1. 数一数最近30次合并里有多少diff真的需要模型。
  2. 在配置中固定模型标识符,缺失时大声失败。
  3. 裁决缓存必须以模型id、模板哈希、diff哈希为键。
  4. 在webhook存在之前,先对真实端点跑预算脚本。
  5. 层级条款变化时重新检查账本。

这套拆分真正的价值不在代码本身,而在那个一小时的顺序:先问“什么数字让我们停下来”,再决定代码放哪里。大多数AI功能上线时的返工,恰恰是因为这个问题被留到了最后。

#PR审查机器人#浏览器预过滤#服务端推理#token预算#AI代码审查#diff修剪#裁决缓存
分享:

相关推荐