Uber 的工程师团队最近聊到的这个工程实践非常值得拆开看:内部已经有 70% 的代码 PR 由 AI Agent 接管处理,同时 AI 相关账单没有出现明显增长。这两个数据放在一起,比单纯说一句“我们团队用 AI 写代码”有意义得多。70% 说明自动化已经跑通了真实生产流程,不只是做个 Demo;账单零增长说明这套系统不是靠硬堆预算烧出来的,背后一定有明确的规则过滤、模型路由和成本降级策略。
这篇文章不打算停留在新闻解读层面,而是直接拆解一个问题:如果我们也想在自己的代码仓库里搭一个类似的 PR 审查 Agent,应该怎么做?内容会覆盖这个案例的关键特征、最小可运行的系统设计、GitHub/GitLab 的 PR 数据获取、LLM 调用的 agent 循环、成本控制手段,以及上线前必须注意的代码合规边界。读完你至少能搭出一个可以跑在本地方仓库上的审查机器人,并知道怎么观察它的 token 消耗和审查质量。
1. 核心能力速览
先说清楚:这不是一个在 GitHub 上搜到就能一键启动的开源项目,而是一类工程实践。它的核心组件包括:代码仓库 Webhook、PR Diff 获取、规则引擎、LLM Agent 调用、结果回写和成本控制。下面这张表总结了这类系统的关键能力节点:
| 能力项 | 说明 |
|---|---|
| 核心功能 | 自动获取 PR 变更内容,生成 review 建议并回写到 PR 评论 |
| 主要卖点 | 替代重复性人工 review,覆盖 70% 左右的常规 PR,成本可控 |
| 实现方式 | Agent 连接代码仓库 API,按规则过滤后调用 LLM,再自动发布审查结论 |
| 硬件门槛 | 不需要 GPU,常规 CPU 服务器即可运行,核心瓶颈是 LLM API 的 token 和延迟 |
| 典型依赖 | Python 3.10+、Git、GitHub/GitLab Token、LLM API Key |
| 接口能力 | 依赖仓库平台 Webhook 和 REST API,也可以封装成 HTTP 服务 |
| 批量任务 | 支持按 PR 队列异步处理,适合低峰期批量 review |
| 适合场景 | 规范明确的成熟代码库、团队 review 人力紧张、需要统一代码风格约束 |
| 不适合场景 | 业务语义极重的遗留代码重构、需要多人线下讨论的架构级 PR |
从这套能力来看,它的价值不是“帮你把代码写完”,而是“在代码进主干之前,先过一道自动化检查”。常规的 lint、格式、单测覆盖问题交给 Agent 处理,人的 review 时间留给真正的架构和业务问题。
2. 这个方案到底做了什么:70% PR 被 Agent 接管的含义
70% 这个数字并不意味着 70% 的合并决定由 AI 来做,更合理的理解是:70% 的 PR 在打开之后,可以由 Agent 完成第一轮或者多轮自动化审查,并且审查结果可以直接在 PR 页面上展示给开发者和维护者。换句话说,机器先看,人再看;机器看过的部分,人只需要确认有没有误报。
这类系统在生产中一般按这个逻辑运行:
- 开发者提交 PR,仓库 Webhook 把事件推给 Agent 服务。
- Agent 先拉取 PR 的 diff,而不是整个代码库。
- 规则引擎先跑一轮:是否改动超过阈值、是否涉及安全敏感文件、是否有明显格式问题。
- 规则处理不了的部分,交给 LLM 做语义理解,比如“这个函数是否可能空指针”“这里的事务范围对不对”“有没有遗漏异常处理”。
- Agent 把结论作为 review 评论提交到 PR,带上置信度标签。
- 维护者根据 Agent 的建议决定是否合并或者要求修改。
“AI 账单零增长”这几个字,拆开看主要有三种可能,也是这类系统最常见的成本控制路径:
- 按需路由:只有规则无法覆盖的 PR 才调用大模型,简单 PR 用轻量模型或者直接跳过。
- 上下文裁剪:不把整个仓库塞给模型,只传 diff 以及必要的上下文行,token 消耗会小很多。
- 预算熔断与降级:当一段时间的 token 消耗超过阈值,系统自动降低模型规格或者改为人工处理队列。
对普通团队来说,这个案例最重要的启发是:AI 代码审查能不能落地,关键不是模型多聪明,而是你的规则、流程、成本预算能不能一起配套。模型只是其中一个环节。
3. 适用场景与使用边界
搭 PR 审查 Agent 之前,先判断自己的团队到底适不适合。
3.1 适合的场景
- 规范化程度高的仓库:已经有 commit 规范、目录规范、错误处理规范,AI 可以按规则去检查一致性。
- PR 数量多、重复检查多:很多 PR 的问题是类似的,比如缺少输入校验、日志不完整、边界条件没考虑。Agent 处理这类问题效率很高。
- review 人力紧张:核心维护者时间有限,可以靠 Agent 完成第一轮粗筛,把明显问题挡回去。
- 团队的代码会经过统一 CI 流程:说明你已经有比较完善的流水线,Agent 只需要作为其中一环接入。
3.2 不适合的场景
- 早期快速原型:代码变动大、结构不稳定,agent 的误报率会很高,容易让团队感到烦躁。
- 架构级和业务决策型 PR:涉及重大模块拆分的 PR,需要人基于上下文讨论,Agent 不能替代。
- 强合规项目:银行、医疗、涉密系统的代码如果对数据出域有严格限制,外部 LLM API 可能根本不能使用,需要先做数据安全评估。
3.3 必须注意的边界
- AI 只做建议,不做最终合并决策。风险等级高的时候,系统应该主动要求人工复核。
- 代码是公司资产。在把 diff 发送给任何 LLM API 之前,确认你是否有权限做这个操作;如果没有私有化部署条件,优先选择数据协议允许的 API 服务,或者做源码脱敏后再发送。
- 不要在未经授权的情况下上传私有仓库代码。很多第三方 API 有数据留存条款,团队合规同学必须先确认。
- 审查结果不是绝对正确。LLM 会有幻觉,也会有漏检,必须有人的兜底环节。
4. Agent 拦截 PR 的典型工作流
在实现层面,PR 审查 Agent 并不是一个“你上传 diff 它返回评论”的静态脚本,而是一个可以连接多个工具的 Agent 循环。常见工作流可以拆成下面几段:
| 步骤 | 系统行为 | 对应能力 |
|---|---|---|
| 触发 | 仓库 Webhook 收到 pull_request 事件 | GitHub/GitLab Webhook 或轮询 |
| 预处理 | 拉取 PR 元数据、diff、changed files 列表 | 仓库 REST API |
| 规则过滤 | 检查改动文件数量、文件类型、是否触碰关键目录 | 规则引擎 |
| 上下文构建 | 把 diff 和相邻上下文整理成适合模型输入的格式 | prompt 模板 |
| Agent 推理 | 调用 LLM 生成问题列表和修改建议 | LLM API |
| 结果整理 | 将模型输出解析为结构化 review 评论 | 输出解析器 |
| 回写 | 在 PR 上创建 review,附上严重等级和建议位置 | 仓库 Review API |
| 兜底 | 高风险内容转人工,普通问题直接展示 | 人工队列 |
这里面最关键的设计点有两个:规则过滤决定成本,Agent 推理决定质量。
如果所有 PR 都进入大模型,token 开销会很快变得不可控;如果规则过强,很多真实问题又会被漏掉。好的做法是让规则引擎负责“低成本拦截”,Agent 负责“语义理解和判断”。
5. 本地搭建最小可运行的 PR 审查 Agent
下面我以一个最小实现为例子,演示怎么在自己仓库里跑通“获取 diff → 调用 LLM → 写回 review”的完整闭环。这里以 GitHub 为例,GitLab 的接口路径和请求头需要按平台调整。
5.1 环境准备
建议先准备一台 Linux 或 macOS 服务器,或者直接用你本地的开发机也可以。需要满足这些条件:
- Python 3.10 及以上
- git 命令可用
- 一个测试用 GitHub 仓库,最好自己创建,避免在正式仓库里测试
- 一个 GitHub Personal Access Token,需要
repo权限 - 一个 LLM API Key,OpenAI、Anthropic、Azure OpenAI、本地模型服务都可以
不需要 GPU,不需要 CUDA,这类任务的主要消耗是 API 调用时长和 token 数量。
mkdir pr-agent-demo cd pr-agent-demo python3 -m venv .venv source .venv/bin/activate pip install requests openai5.2 项目目录结构
pr-agent-demo/ ├── agent.py # Agent 主循环 ├── config.py # 配置读取 ├── requirements.txt ├── rules.py # 规则过滤 └── outputs/ # 审查结果日志5.3 获取 PR 的 diff
GitHub 的 REST API 支持直接返回一个 PR 的 diff 文本。只要在请求头里指定 Accept 为 diff 格式即可。
import os import requests GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "") REPO = "your-org/your-repo" PR_NUMBER = 1 headers = { "Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3.diff" } url = f"https://api.github.com/repos/{REPO}/pulls/{PR_NUMBER}" resp = requests.get(url, headers=headers, timeout=30) if resp.status_code == 200: diff_text = resp.text print(f"拿到 diff,长度: {len(diff_text)} 字符") else: print(f"获取失败: {resp.status_code}") print(resp.text)如果仓库是私有的,要确保 token 有对应权限;如果是企业自建 GitHub,地址要替换成内网域名。
5.4 规则过滤:低成本拦截
再写一个规则模块,先过滤掉明显不需要大模型处理的 PR。
MAX_FILES = 20 # 改动文件数超过阈值转人工 MAX_CHANGES = 2000 # 改动行数过大,提示人工介入 SENSITIVE_PATHS = ["src/auth", "src/payments", "deploy/"] def need_llm_review(changed_files, additions): if len(changed_files) > MAX_FILES: return False if additions > MAX_CHANGES: return False for path in changed_files: for sensitive in SENSITIVE_PATHS: if path.startswith(sensitive): return True return True规则引擎的实际价值是:把不需要模型参与的 PR 挡在 LLM 调用之前。比如只改了 README 的 PR,完全不需要花 token 去 review。
5.5 调用 LLM 生成 review 建议
这里用一个简化版本的 Agent:给模型 diff 和固定审查要求,让它返回结构化问题列表。注意 prompt 要强调“不能乱猜问题”。
from openai import OpenAI client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1") ) PROMPT_TEMPLATE = """ 你是一个严格的代码审查工程师。请分析下面的 PR diff,只输出你确认存在的问题。 格式要求:每条问题一行,格式为 严重级别|问题描述|建议修改。 严重级别只允许 P0、P1、P2。 如果没有任何问题,输出:NO_ISSUE diff 内容: {} 请开始输出: """ def generate_review(diff_text: str) -> str: prompt = PROMPT_TEMPLATE.format(diff_text[:12000]) # 裁剪长度 resp = client.chat.completions.create( model=os.getenv("LLM_MODEL", "gpt-4o-mini"), messages=[{"role": "user", "content": prompt}], temperature=0.2, max_tokens=3000, ) return resp.choices[0].message.content注意,这里对 diff 做 12000 字符的截断只是示例。实际生产场景中应该基于 token 数量动态截断,避免超长输入导致成本和延迟飙升。
5.6 把 review 结果写回 PR
拿到 LLM 输出后,把结果作为 review 提交到 PR。
def post_review(commit_id: str, review_body: str): url = f"https://api.github.com/repos/{REPO}/pulls/{PR_NUMBER}/reviews" headers = { "Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json" } payload = { "commit_id": commit_id, "body": "AI Agent 自动审查结果\n\n" + review_body, "event": "COMMENT" } resp = requests.post(url, headers=headers, json=payload, timeout=30) print(resp.status_code, resp.json() if resp.status_code < 400 else resp.text)这里的commit_id需要从 PR 详情接口返回的head.sha字段获取。只提交评论,不自动 approve 或 request changes,保持人工决策权。
5.7 主循环组装
用一个简单的agent.py把所有步骤串起来:
import requests def review_pull_request(repo: str, pr_number: int): diff_text, commit_id, changed_files, additions = fetch_pr_info(repo, pr_number) if not diff_text: print("无需审查") return if not need_llm_review(changed_files, additions): print("触发规则过滤,转人工") return review = generate_review(diff_text) if review.strip() == "NO_ISSUE": print("Agent 未发现明确问题") return post_review(commit_id, review) log_to_outputs(pr_number, review) if __name__ == "__main__": review_pull_request("your-org/your-repo", int(os.getenv("PR_NUMBER", "1")))这样一个最小可运行的 PR 审查 Agent 就成型了。首次验证时,不需要马上接入 Webhook,手动设置PR_NUMBER跑一次更稳妥。
6. 功能测试与效果验证
在正式接入仓库之前,先用一个测试 PR 验证系统是否可靠。建议准备一个包含以下问题的测试 PR:
- 缺少
None判空的函数 - 异常处理空白
- 明显可以合并的重复代码
- 日志缺失的函数入口
6.1 测试维度
| 测试项 | 操作 | 预期结果 |
|---|---|---|
| 获取 diff | 运行获取 diff 脚本 | 能拿到完整变更内容 |
| 规则过滤 | 只改 README 的 PR | 日志提示“无需审查” |
| 规则过滤 | 改动敏感目录文件的 PR | 走 LLM 审查流程 |
| LLM 输出 | 空 diff 或短 diff | 不调用模型 |
| LLM 输出 | 问题明显的 diff | 输出包含 P0/P1/P2 分级 |
| 写回 PR | 审查结束后查看 PR 页 | 出现 AI review 评论 |
| 稳定性 | 连续跑 20 次 | 不超时、不报错、不重复评论 |
6.2 判断成功标准
- 能稳定拿到 PR 的 diff,并且能定位到 commit_id。
- LLM 返回的问题和真实问题有较高重合度,而不是无脑输出一堆“建议”。
- 评论准时出现在 PR 页面,且不会重复提交。
- token 消耗可以在日志中统计出来,而不是黑盒。
6.3 失败时排查什么
- 拿不到 diff:检查 token 权限、仓库可见性、网络策略。
- LLM 输出内容不稳定:调整 prompt,要求模型只输出明确问题,并给出格式约束。
- 评论没有出现:先看调用返回值是不是 4xx/5xx,再检查 commit_id 是否正确。
- 重复评论:需要在业务层做幂等处理,同一 PR 的同一 commit 只允许提交一次。
7. 成本控制:AI 账单零增长怎么做到
“70% PR 被 Agent 接管”听起来很重,但现实是,如果 100% 的 PR 都调用同一档大模型,账单一定会很快涨上去。要做到成本可控,核心思路是不要让所有请求走同一条昂贵的链路。
7.1 成本估算公式
先建立一个基础公式,方便自己估算:
单次审查成本 = 输入 token 数 × 输入单价 + 输出 token 数 × 输出单价在实际日志里可以这样统计:
# 伪代码,用于统计 token usage = resp.usage print("prompt_tokens:", usage.prompt_tokens) print("completion_tokens:", usage.completion_tokens)7.2 降低 token 消耗的手段
- 规则优先:能用脚本检查的先检查,比如 eslint、ruff、pre-commit。只有脚本覆盖不了的问题才交给模型。
- 模型路由:简单 PR 用小模型,复杂 PR 或者安全相关改动才用大模型。路由条件可以按改动文件数、文件目录、diff 长度、是否包含敏感路径判断。
- diff 上下文裁剪:不把整个文件发给模型,只发送变更行之前的若干行上下文。
- 缓存公共代码块:同一个工具函数在不同 PR 里被重复改动,可以把审查结果缓存下来,避免重复调用。
- 预算熔断:设定一个每日/每周 token 预算,超过就用降级模型,或者暂时转人工队列。
- 异步批量:把非紧急 PR 放到夜间批量处理,既能控制 API 并发,也能避开高峰期限额。
这种设计思路对应到那个案例里,“账单单零增长”并不是因为 AI 用得少,而是因为大量 PR 根本不会进入大模型队列,进入队列的又会根据复杂度匹配不同档位模型,成本被整体摊薄了。
8. 接口 API 与批量任务设计
把 PR 审查 Agent 封装成服务之后,你可以用 Webhook 接收仓库事件,也可以用定时任务扫描待处理 PR。
8.1 封装成 HTTP 服务
一个简单的 Flask 服务可以接收 GitHub Webhook:
from flask import Flask, request, jsonify import os app = Flask(__name__) @app.route("/webhook/pr", methods=["POST"]) def pr_webhook(): payload = request.json action = payload.get("action", "") if action not in ["opened", "synchronize"]: return jsonify({"status": "ignore"}) repo = payload["repository"]["full_name"] pr_number = payload["pull_request"]["number"] # 异步执行审查,避免 Webhook 超时 submit_review_task(repo, pr_number) return jsonify({"status": "accepted"}) if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)8.2 批量任务队列
如果 PR 数量比较多,建议不要同步调用 LLM,而是把任务写入队列,由 worker 逐个消费。简单的做法是把待处理 PR 写入 Redis 队列或者数据库表,再启动一个 worker 循环处理。
# worker 示例 import time import redis r = redis.Redis(host="127.0.0.1", port=6379, db=0) def start_worker(): while True: task = r.lpop("pr_review_queue") if task: repo, pr_number = task.decode().split(":") review_pull_request(repo, int(pr_number)) else: time.sleep(2) if __name__ == "__main__": start_worker()8.3 失败重试
LLM API 不稳定时,常见失败是超时和限流。对这类任务,建议设计重试策略:
- 超时重试:每次间隔递增,最多重试 3 次。
- 限流重试:等待
Retry-After指定的秒数。 - 对重复评论做幂等:记录
PR编号 + head commit sha,防止重复提交 review。 - 审查失败时把任务标记为
failed,转人工处理,不要静默丢弃。
9. 资源占用与性能观察
PR 审查 Agent 不是 GPU 密集型任务,它的瓶颈集中在LLM API 延迟、Webhook 响应速度、队列消费速度三个地方。
9.1 关键观察指标
| 指标 | 观察方式 | 正常表现 |
|---|---|---|
| LLM 调用延迟 | API 返回耗时 | 大部分在几秒到几十秒,超长 diff 可能更久 |
| token 消耗 | usage 字段累计 | 每 PR 的 token 消耗应保持相对稳定 |
| Webhook 响应时间 | 网关日志 | 必须低于平台超时限制,否则用异步任务 |
| 队列积压 | Redis queue 长度 | 高峰后能逐步消费完,不是无限增长 |
| 错误率 | API 错误日志 | 低于 1%,主要是限流和超时 |
9.2 如何降低资源占用
- 不要同步调用 LLM,webhook 只负责入队。
- 给 LLM 客户端设置超时和最大重试次数。
- 控制并发数,避免同时开几十个请求把 API 限额打满。
- 本地日志做轮转,避免审查日志占满磁盘。
- 如果部署在服务器上,单机 2 核 4G 内存足够跑框架和 Worker;真正消耗在外部 API 上。
9.3 端口与进程问题
如果服务启动后访问不到,先确认进程是否在跑,再检查端口是否被占用:
# 检查端口 lsof -i :8000 # 换端口启动 python agent_service.py --port 8001Webhook 回调地址也需要保证仓库平台能访问到。如果仓库在内网,Webhook 地址必须是内网可达 URL;如果跑在公网,务必加签名校验。
10. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 拿不到 PR diff | token 权限不足或仓库不存在 | 查看 API 返回状态码和错误信息 | 确认 token scope、仓库名是否为 owner/repo 格式 |
| 调用 LLM 报 401 | API Key 配置错误或环境变量未加载 | 打印配置值,确认 base_url 是否正确 | 修正 Key 或 base_url |
| 评论没有出现在 PR 上 | commit_id 不正确或 review 接口失败 | 打印响应体,看错误信息 | 改用head.sha字段 |
| 同一 PR 被重复审查 | 幂等逻辑缺失 | 查看日志中 PR 处理次数 | 以 PR 编号加 commit sha 做唯一索引 |
| 误报太多 | prompt 约束不够 | 抽样对比人工 review 结果 | 强化 prompt,要求只输出确定问题 |
| 成本上升过快 | 规则过滤没生效 | 检查多少请求进入了 LLM | 增加规则拦截,启用模型路由 |
| Webhook 收不到事件 | 地址配置错误或平台无法访问 | 查看平台投递日志 | 使用内网/公网可达地址,添加签名校验 |
| Agent 长时间卡住 | LLM 调用无超时或重试机制 | 打印调用耗时和异常 | 加超时、重试和队列超时拒绝 |
11. 最佳实践与使用建议
11.1 小流量灰度
第一周不要对所有 PR 开启 Agent,先选定一个目录,或者只处理opened事件,等模型输出稳定后再扩大到synchronize事件和其他目录。灰度期间重点观察误报率和工程师反馈。
11.2 规则与模型结合
能通过脚本解决的问题,不要用模型去“判断”。对代码格式、未使用变量、明显越权路径等使用静态检查工具;模型只负责语义层面的问题。这样成本低,稳定性高。
11.3 人工兜底与审计
Agent 的 review 结果必须有人的确认环节,尤其对P0级别问题要严格控制权限。建议给每条评论加上“AI Agent 自动生成”的标记和对应的日志链接。后期如果出了事故,能够定位是哪一次审查、哪一个模型、哪一个 prompt。
11.4 隐私与数据合规
代码的审查数据往往比代码本身更敏感,因为 diff 里会暴露内部架构和业务逻辑。在上生产之前:
- 确认该仓库的代码是否可以发送给外部模型 API;
- 如果不能外发,就部署私有化模型服务,或者对变量名、注释做脱敏处理;
- 对 AI 审查日志设置访问权限,不要默认公开;
- 定期清理日志中的敏感内容。
11.5 输出格式统一
让 LLM 输出统一格式,建议使用 JSON 而不是自由文本:
{ "issues": [ { "level": "P1", "file": "src/user_service.py", "line": 42, "message": "user_id 为空时会触发空指针异常", "suggestion": "增加空值判断" } ] }结构化输出可以直接对接飞书、钉钉、Slack 通知,也方便之后做统计和误报分析。
11.6 效果评估
每个迭代周期统计以下数据:
- Agent 审查问题里被人工确认为有效问题的比例;
- 被 Agent 拒绝后又修改再提交的 PR 比例;
- 平均每个 PR 的审查时长;
- 单 PR token 成本。
这些指标比单纯看“覆盖了多少 PR”更能说明问题。
12. 总结与下一步
这个案例最值得试的点不是“复刻 Uber 的 70% 数字”,而是把规则过滤、模型路由、Agent 审查、成本熔断、人工兜底这套链路跑通。普通团队完全可以从一个小的自动化起步:先写一个脚本拉取 diff,再接入统一审查 prompt,最后再上 Webhook 和批量队列。最先应该验证的是“这个模型的误报率你能不能接受”,因为它直接决定工程师愿不愿意用。
最容易踩的坑是两类:一是所有 PR 都扔给大模型,成本失控;二是没有人工确认机制,模型幻觉导致错误评论直接误导开发者。这两点都要在系统设计阶段就考虑进去。
后续可以扩展的方向包括:把 Agent 从“审查代码”扩展到“生成修复建议并创建变更分支”;配合 CI 在阻塞合并前自动执行低风险修复;再进一步采集人工 review 记录,用真实反馈微调 prompt 或者本地模型。等这一套稳定了,就把它接入团队自己的文档系统和变更管理流程,同样可以复用这套“规则优先、模型兜底、成本可控”的架构思路。如果你正在搭自己的 AI 代码审查工具,建议把这份流程收藏备用,从最小的 PR 数据闭环开始验证。