Skip to content

LLM 如何评审 Merge Request:机制、提示词与一个演示案例

开发者在 GitLab 上点下“创建 MR”,几秒后 MR 页面出现“🔴 代码审查中“,再过一两分钟变成“🟢 已通过,自动批准“或”🔴 存在高风险问题“。背后是一个 LLM 代码审查平台在完整跑一遍“先阻断 → 逐文件审查 → 结构化输出 → 门禁决策 → 回写结果”的闭环。本文用一条演示 MR 把整个过程拆开看。

文中所有主机地址、端口、真实部署路径、模型名称均以占位符/通用表述表示,不含内部部署信息。


一、整体流程:从 MR 事件到审查结论

Section titled “一、整体流程:从 MR 事件到审查结论”
GitLab MR 事件 → 审查服务 /webhook(token 验证 + 事件过滤)
↓
立即阻断合并(取消批准 + blocking discussion + 标签 + 描述标记"审查中")
↓
拉取 MR 变更文件(过滤二进制/第三方依赖)
↓
逐文件并行调用 LLM(默认并发 3)──────────┐
↓ │ 每个文件:
结构化 JSON 输出(summary + issues[]) │ 系统提示词 + few-shot 示例
↓ │ + 文件路径/新增行号/文件内容/diff
行号硬过滤(只保留"新增代码行"内的问题) ┘
↓
门禁决策:存在 ≥高风险 或 有文件审查失败 → blocked
否则 → approved(解阻 + 自动批准)
↓
回写 MR 描述 / 发邮件 / 问题入库

六个关键动作:触发 → 阻断 → 拉取 → 审查 → 过滤 → 决策。


二、LLM 怎么评:提示词的三段式设计

Section titled “二、LLM 怎么评:提示词的三段式设计”

每个文件的审查请求由三段拼接而成,通过标准输入传给模型,并要求只输出 JSON(用 JSON Schema 强校验,格式不符算失败并重试):

1. 系统提示词:定义角色、流程与铁律

Section titled “1. 系统提示词:定义角色、流程与铁律”
你是一个严格的代码审查专家,执行结构化审查。
**审查流程(必须逐项执行):**
1. 遍历每个变更(以 + 开头的行是新增代码)
2. 对每个变更,按照以下风险类型检查:
- 高风险:SQL注入、XSS、命令注入、密码硬编码、空指针、内存泄漏、严重逻辑错误、性能问题
- 中风险:代码规范、可读性问题、潜在bug
- 低风险:代码风格、注释建议
**输出要求(严格遵守):**
- 只输出 JSON,格式见下方 schema
- line字段必须是新增代码的实际行号
- 只报告确定的问题,不确定时跳过
- 同一个问题不要重复报告
- **重要:只报告由新增代码引起的问题,不报告与新增代码无关的问题**
**判断标准(严格遵循):**
- 密码/密钥/Token出现 → 高风险
- 用户输入拼接到SQL/命令/HTML → 高风险
- 可导致程序崩溃的空指针 → 高风险
- 代码逻辑明显错误 → 高风险
- 变量命名不规范 → 低风险
- 缺少注释 → 低风险

要点是分级(高/中/低)和边界(只审新增代码、只报确定问题)——审查范围收窄到 diff,避免模型对存量代码“指手画脚”。

2. Few-shot 示例:给模型一个标准答案样板

Section titled “2. Few-shot 示例:给模型一个标准答案样板”
示例diff:
+ password = "placeholder-password"
+ query = f"SELECT * FROM users WHERE name='{user}'"
正确输出:
{
"summary": "发现2个问题",
"issues": [
{"severity": "高", "file": "config.py", "line": 10, "description": "发现密码硬编码", "suggestion": "使用环境变量"},
{"severity": "高", "file": "user.py", "line": 25, "description": "SQL注入漏洞", "suggestion": "使用参数化查询"}
]
}

3. 场景输入:把“审什么”讲清楚

Section titled “3. 场景输入:把“审什么”讲清楚”
请结合以下文件的内容审查代码变更:
**文件路径:** src/auth/login.py
**本次变更新增代码行号(必须只报告这些行的问题):** 14, 17, 18
**文件新内容:**
```...完整文件内容,作上下文...```
**代码变更内容(diff):**
```...该文件的 diff...```

注意“新增代码行号“这一行——它既在提示词里约束模型,代码里还会做一次硬过滤(见 §四),双保险确保不审存量代码。

模型必须返回 {"summary": "...", "issues": [...]},每个 issue 含:

{ "severity": "高|中|低", "category": "风险类别", "file": "路径",
"line": 行号, "description": "描述", "suggestion": "修复建议",
"code_snippet": "代码片段", "language": "语言" }

三、演示案例:一个登录接口的 MR

Section titled “三、演示案例:一个登录接口的 MR”
  • MR:feature/refactor-login → main,改动 1 个文件 src/auth/login.py
  • 开发者新增了 3 行代码:管理员口令判断、拼接查询、日志打印
def handle_login(username, password):
user = find_user(username)
if user:
return issue_token(user)
+ if password == "placeholder-password": # 第 14 行(新增,示例值)
+ return placeholder_demo_token # 第 15 行(新增,示例值)
+ query = f"SELECT id FROM users WHERE name='{username}'" # 第 17 行(新增)
+ print("login: " + username) # 第 18 行(新增)

审查服务把上面的提示词组合好后发给模型,得到:

{
"summary": "发现 3 个问题(高:2 中:1 低:0)",
"issues": [
{
"severity": "高", "category": "密码硬编码",
"file": "src/auth/login.py", "line": 14,
"description": "硬编码管理员密码,任何拿到代码的人都可以绕过认证",
"suggestion": "密码应存环境变量或密钥管理服务,禁止明文写代码"
},
{
"severity": "高", "category": "SQL注入",
"file": "src/auth/login.py", "line": 17,
"description": "用户输入直接拼接进 SQL,可被注入改写查询逻辑",
"suggestion": "改用参数化查询或 ORM"
},
{
"severity": "中", "category": "日志泄露",
"file": "src/auth/login.py", "line": 18,
"description": "将用户输入打印到日志,可能泄露敏感信息",
"suggestion": "日志脱敏或移除该行"
}
]
}
  1. 行号过滤:三个问题的行号(14/17/18)都在新增行集合内 → 全部保留(若模型报了第 5 行旧代码的问题,会被过滤掉)
  2. 门禁判断:存在 severity=高 的问题 → has_blocking = true
  3. 结论 blocked:保持阻断(MR 页面显示 🔴 高风险),更新 MR 描述列出 3 个问题,作者收到高风险邮件,合并被阻止
  4. 开发者修复(用环境变量取密码、参数化查询、删日志行)→ 再次 push 触发新一轮审查 → 通过 → 自动批准,MR 变绿可合并

条件 结果
存在 ≥高风险 问题 blocked:保持阻断 + 邮件 + MR 描述回写
只有中/低风险 approved(或 warning)
有任何文件审查失败 视为 blocked(保守策略:没审到就当有问题)
  1. 只审新增代码(双重约束):提示词声明“只报告由新增代码引起的问题” + 代码按 diff 解析出的新增行号做硬过滤。原因:存量问题应该走全量扫描/单独工单,MR 审查的价值是“这一版引入了什么新风险”,而不是把历史债务堆在开发者脸上
  2. 失败即阻塞:某文件审查失败(超时/额度/解析失败)时,MR 不会“没审到就放行”,而是保守阻断并提示人工查看——避免静默漏检
  3. 行号真实性:模型必须给真实新增行号(schema 约束 + 过滤校验),保证问题能精准定位到代码行
  4. 模型容错:额度不足/限流时自动切换备用模型或等待重试,审查任务不因瞬时故障中断
  • 不做存量代码全量体检(那是全量扫描的职责)
  • 不替代人工评审(LLM 是“先审一遍 + 卡高风险”,人仍然要 review 设计/架构)
  • 不阻断无新增代码的变更(纯删除/纯移动不触发审查重点)

LLM 评审 MR 的本质,是把“代码审查”翻译成一个高度约束的生成任务:

  • 范围约束:只审 diff 新增行 → 提示词声明 + 行号硬过滤
  • 形式约束:只输出合法 JSON → Schema 强校验
  • 质量约束:只报确定问题、风险分级 → 系统提示词 + few-shot
  • 决策约束:高风险或失败即阻断 → 保守门禁

演示里那条“硬编码密码 + SQL 注入”的 MR,从触发到“🔴 blocked“全自动完成,作者无需任何人工介入就拿到带行号的修复建议——这就是这套机制最实用的价值。