详细教程演示用Python脚本+GitHub Actions为LLM应用搭建自动code review流程,含完整代码和系统提示词设计。
最近我上线了一个自动化代码审查 Agent,运行在 CI 流水线内部。它读取 Git diff,在人类审阅者打开 Pull Request 之前标记潜在的 Bug、缺失的测试和代码风格问题。在本教程中,我将分享我使用的完整 Python 脚本和 GitHub Actions 工作流,基于 Oxlo.ai 推理驱动。
前提条件:
pip install openai我们从一个简单的 Python 文件开始,加载 Oxlo.ai 客户端并通过 stdin 接收 diff。这样做可以让 Agent 无状态,便于从任意 CI runner 调用。
import os
import sys
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ["OXLO_API_KEY"]
)
def get_diff():
return sys.stdin.read()
if __name__ == "__main__":
diff = get_diff()
if not diff.strip():
print("No diff provided.")
sys.exit(0)
System Prompt 是 Agent 唯一需要的配置。我将它放在一个独立变量中,这样可以在不触碰逻辑的情况下调整它。
SYSTEM_PROMPT = """You are a senior staff engineer performing code review.
Review the provided git diff and output a JSON object with exactly two keys:
- "issues": a list of objects, each with "severity" (critical, warning, or note), "file", "line", and "message".
- "summary": a one-sentence overview of the change.
Be concise. Only flag real problems: logic errors, missing error handling, security risks, or unclear naming. Do not comment on formatting unless it hurts readability."""
我使用 Llama 3.3 70B,因为它能可靠地遵循结构化指令,且在 Oxlo.ai 上运行无冷启动问题。我们启用 JSON 模式并解析响应,以便 CI runner 能够对其进行处理。
import json
def review_diff(diff_text: str):
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Review this diff:\n\n{diff_text}"},
],
response_format={"type": "json_object"},
temperature=0.2,
)
raw = response.choices[0].message.content
return json.loads(raw)
if __name__ == "__main__":
diff = get_diff()
result = review_diff(diff)
print(json.dumps(result, indent=2))
流水线步骤需要通过或失败。我统计 critical 级别的问题数量,当发现任何 critical 问题时返回非零退出码,这将阻止合并,直到人类手动覆盖。
def report_and_exit(result: dict):
issues = result.get("issues", [])
critical_count = sum(1 for i in issues if i.get("severity") == "critical")
for issue in issues:
icon = {"critical": "❌", "warning": "⚠️", "note": "ℹ️"}.get(issue["severity"], "•")
print(f"{icon} [{issue['severity'].upper()}] {issue['file']}:{issue.get('line', '?')} - {issue['message']}")
print(f"\nSummary: {result.get('summary', 'No summary provided.')}")
print(f"Found {critical_count} critical issue(s).")
if critical_count > 0:
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
diff = get_diff()
result = review_diff(diff)
report_and_exit(result)
CI runner 不应依赖宿主机的 Python 环境。一个极简的 Dockerfile 允许我们固定 OpenAI SDK 版本,并在本地和云端运行相同的镜像。
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY review.py .
ENTRYPOINT ["python", "review.py"]
在同目录下保存以下 requirements 文件。
openai>=1.0
在推送之前先在本地构建和测试。
docker build -t llm-review-agent .
git diff HEAD~1 | docker run --rm -e OXLO_API_KEY=$OXLO_API_KEY -i llm-review-agent
最后一步是一个工作流,它在 Pull Request 触发时运行,将 diff 发送给 Oxlo.ai 驱动的 Agent,并将结果以内联方式发布。由于 Oxlo.ai 采用按请求计费的扁平定价策略,审查大尺寸 diff 的成本是可预测的,这在每次 push 都会触发流水线的场景下尤为重要。
name: LLM Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build review agent
run: docker build -t llm-review-agent .
- name: Run Oxlo.ai review on PR diff
env:
OXLO_API_KEY: ${{ secrets.OXLO_API_KEY }}
run: |
git diff origin/${{ github.base_ref }}...HEAD | \
docker run --rm -e OXLO_API_KEY -i llm-review-agent
以下是整合了上述所有步骤的完整 review.py。导出你的 Oxlo.ai Key,然后将任意 git diff 管道给它。
import os
import sys
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ["OXLO_API_KEY"]
)
SYSTEM_PROMPT = """You are a senior staff engineer performing code review.
Review the provided git diff and output a JSON object with exactly two keys:
- "issues": a list of objects, each with "severity" (critical, warning, or note), "file", "line", and "message".
- "summary": a one-sentence overview of the change.
Be concise. Only flag real problems: logic errors, missing error handling, security risks, or unclear naming. Do not comment on formatting unless it hurts readability."""
def get_diff():
return sys.stdin.read()
def review_diff(diff_text: str):
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Review this diff:\n\n{diff_text}"},
],
response_format={"type": "json_object"},
temperature=0.2,
)
raw = response.choices[0].message.content
return json.loads(raw)
def report_and_exit(result: dict):
issues = result.get("issues", [])
critical_count = sum(1 for i in issues if i.get("severity") == "critical")
for issue in issues:
icon = {"critical": "❌", "warning": "⚠️", "note": "ℹ️"}.get(issue["severity"], "•")
print(f"{icon} [{issue['severity'].upper()}] {issue['file']}:{issue.get('line', '?')} - {issue['message']}")
print(f"\nSummary: {result.get('summary', 'No summary provided.')}")
print(f"Found {critical_count} critical issue(s).")
if critical_count > 0:
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
diff = get_diff()
if not diff.strip():
print("No diff provided.")
sys.exit(0)
result = review_diff(diff)
report_and_exit(result)
针对最近一次提交进行测试。
export OXLO_API_KEY="sk-oxlo.ai-..."
git diff HEAD~1 | python review.py
一次真实审查的示例输出。
⚠️ [WARNING] auth.py:42 - Hardcoded timeout may cause flaky tests under high load.
ℹ️ [NOTE] auth.py:55 - Consider renaming `do_thing` to `validate_token`.
Summary: Adds bearer token validation to the auth middleware but introduces a hardcoded timeout.
Found 0 critical issue(s).
你可以通过将大尺寸 diff 拆分成文件块并并行调用 Oxlo.ai 来扩展这个 Agent。扁平化的按请求计费意味着同时向十个文件发起的请求成本与向一个文件发起的请求相同,这保证了流水线的经济性。另一个不错的后续步骤是将结果以 commit SHA 为 key 缓存到 Redis 中,这样相同 diff 的重复运行就不会消耗请求额度。