利用开源MonkeyCode项目的免费额度,通过GitHub Actions对PR进行自动化AI审查,提供完整工作流代码和实施步骤,首个PR即可回收成本。
Automated code review 是少数在第一个 Pull Request 就能收回成本的 AI 工作流之一,如果你围绕模型的限制来设计流水线,而不是对抗这些限制,完全可以在免费额度内运行整个流程。我构建了一个 GitHub Actions 工作流,它调用开源 MonkeyCode 项目的免费模型来对 Pull Request 进行评论,结果是一个能发现真实问题而不会刷屏的评审工具。披露:本文是 MonkeyCode 产品推广的一部分。
开源 MonkeyCode 项目目前提供一千万 token 的免费模型额度和一个免费服务器选项,虽然本流水线不需要服务器,但如果你想把同样的逻辑迁移到托管的 webhook,它就会派上用场。设置故意做得很简单,因为价值在于工作流本身,而不是模型。工作流在 pull_request 事件时触发,检出完整历史的仓库,然后将统一的 diff 传递给一个 Python 脚本,由模型生成结构化的反馈。
第一步是一个工作流文件,用于捕获 base 分支和 head 分支之间的 diff。使用 fetch-depth: 0 确保 git 可以计算精确的变更,diff 被保存到一个临时文件中,供 review 脚本读取。环境变量携带 API key 和 GitHub token,因此不会有密钥出现在仓库中。
name: AI PR Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get diff
run: git diff origin/${{ github.event.pull_request.base.ref }}...HEAD > /tmp/diff.txt
- name: Run AI review
env:
MONKEYCODE_API_KEY: ${{ secrets.MONKEYCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python review.py /tmp/diff.txt
pull_request 事件在 opened 和 synchronize 时都会触发,这意味着每次向分支新推送代码都会触发一次新的评审。这通常是期望的行为,但同时也带来了重复评论的问题,脚本必须处理。git diff 命令比较合并基准和 head,因此只包含实际属于该 Pull Request 的变更。
Python 脚本是设计决策的关键所在。diff 在发送给模型之前会被截断到合理的大小,因为一个 Pull Request 很容易超过免费层端点的输入限制,而 prompt 则要求模型返回一个包含 path、line 和 body 的 JSON 数组。模型被指示在没有问题时返回一个空数组,这使得解析逻辑非常简单。
import json, os, sys, urllib.request
def review_diff(diff_path):
diff = open(diff_path).read()[:12000]
prompt = (
"You are a senior code reviewer. "
"Review this diff and return a JSON array of comments. "
"Each comment must have 'path', 'line', and 'body'. "
"Only comment on real issues. If no issues, return [].\n\n"
f"Diff:\n{diff}"
)
payload = json.dumps({
"model": "free-model",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.2
}).encode()
req = urllib.request.Request(
"https://api.monkeycode.example/v1/chat/completions",
data=payload,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['MONKEYCODE_API_KEY']}"
}
)
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
content = data["choices"][0]["message"]["content"]
try:
return json.loads(content)
except json.JSONDecodeError:
return []
脚本随后通过 GitHub API 发布每条评论,但前提是该评论尚不存在。这个去重步骤至关重要,因为一个在 opened 和 synchronize 事件上都会触发的工作流,否则会在每次推送时重复同样的反馈。脚本还将评论总数限制在五条,这可以保持低噪音,并强制模型优先处理最重要的发现。
def post_comments(comments):
existing = fetch_existing_comments()
posted = 0
for c in comments:
if posted >= 5:
break
key = (c["path"], c["line"], c["body"])
if key in existing:
continue
create_comment(c["path"], c["line"], c["body"])
posted += 1
这个工作流有真实的局限性,你在复制之前应该了解。免费模型偶尔会产生误报,所以这些评论是建议而非阻止项;JSON 输出可能格式不正确,此时脚本会直接跳过本次评审。diff 截断意味着非常大的 Pull Request 只会被部分评审,而且模型除了 diff 本身之外对周围代码库没有感知,所以它无法发现跨文件的问题。这不是人类评审的替代品;它是一个分类层,在人类查看代码之前先暴露显而易见的问题。
最让我惊讶的是,要让这东西真正有用,需要的代码量竟然如此之少。整个工作流不到一百行,最难的部分不是 API 调用,而是去重和输出解析——这些问题你在任何外部评审工具中都会遇到。MonkeyCode 的免费模型额度对于小型团队的 Pull Request 量来说足够充裕,而免费服务器选项在你希望从 GitHub Actions 迁移到可以处理其他 AI 任务的自托管 webhook 时就变得有意义了。
如果你想尝试这种模式,请查看 MonkeyCode 仓库以获取当前的免费模型访问方式和免费服务器详情,因为配额和端点会随时间变化。工作流本身才是值得保留的部分,它可以迁移到你以后可能使用的任何模型提供商。免费层的 AI 评审工具不是噱头;这是一种切实可行的方式,把少量额度花在每天能节省真实注意力的事物上。