基于 Git Hooks + LLM 实现根据自然语言规则进行 Code Review

一、问题背景

团队内部有不少开发规范,但在日常赶需求的过程中很容易遗漏。比如 spark-submit 脚本的参数格式、文件命名约定等,这些"小问题"不影响运行,但是人工校验的心智负担较大

现状的几个痛点:

  1. 规范执行靠人力 ------ 团队有规范文档,但全靠开发者自觉遵守,review 时也容易漏检
  2. 现有 CI 覆盖不到 ------ GitLab 上有通用的 Code Review 流程,但团队特有的 SparkSQL 脚本规范并未收录其中
  3. 传统方式实现成本高 ------ 这些规范描述灵活(如"每个参数独占一行"),用程序处理文本的思路必然导致后续难开发,难维护(所以一直没做)

转机: LLM 天然擅长理解自然语言描述的规则,并对代码做结构化判断。将规范以自然语言编写,交给 LLM 逐条检查,可以低成本实现一套灵活的自动化审查工具。

二、技术方案

整体思路

sql 复制代码
git commit → pre-commit hook 拦截
    → 筛选变更的 Spark 脚本文件
    → 调用 Python 脚本将代码 + 规则发送给 LLM
    → LLM 返回结构化审查结果(JSON)
    → 不通过则阻止提交,输出问题详情

为什么选 Git Hooks

Git 原生支持 hooks 机制,在 .git/hooks/ 目录下放置可执行脚本即可生效,无需额外依赖:

Hook 触发时机 适用场景
pre-commit commit 前 代码检查、格式校验
commit-msg 写入 message 后 commit message 规范检查
pre-push push 前 集成测试、权限校验

本方案选择 pre-commit ------ 在代码提交前拦截,问题发现越早修复成本越低。

实现细节

1. Hook 脚本(pre-commit)

核心逻辑:筛选本次暂存区中变更的 Spark 相关文件,传递给审查脚本。

bash 复制代码
#!/bin/bash

# 获取暂存区中新增/修改的 spark 相关文件
changed_sql_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '(^|/)spark')

if [ -z "$changed_sql_files" ]; then
  echo "本次没有变更 spark 相关文件,跳过审查"
  exit 0
fi

echo "检测到 Spark 文件变更,开始 AI Review..."
python3 ai_code_review.py ${changed_sql_files}

exit $?

2. 审查脚本(Python)

职责:读取规则文件和代码文件,组装 prompt 调用 LLM,解析返回的 JSON 判断是否通过。

python 复制代码
import os
import sys
import argparse
from openai import OpenAI
import json

sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')

RULE_FILE_PATH = "./code_review_rule.md"

def read_file(file_path):
    """读取文件内容,不存在则返回 None"""
    if not os.path.exists(file_path):
        print(f"文件不存在: {file_path}")
        return None
    with open(file_path, 'r', encoding='utf-8') as f:
        return f.read()

def code_review(client, rule_content, file_path):
    """调用 LLM 对单个文件进行规则审查"""
    code_content = read_file(file_path)
    if code_content is None:
        return None

    system_prompt = f"""你是一个专业的代码审查助手。请根据以下规则对代码进行审查。
规则:
{rule_content}

严格按以下 JSON 格式输出,不允许输出任何额外内容:
{{
    "file_pass": true/false,
    "issues": [
        {{
            "rule": "规则名",
            "message": "一句话说明原因",
            "rule_pass": true/false
        }}
    ]
}}"""

    user_prompt = f"""请对以下代码进行 Code Review:
文件名: {file_path}
---
{code_content}
---

    completion = client.chat.completions.create(
        model="qwen-plus",
        messages=[
            {'role': 'system', 'content': system_prompt},
            {'role': 'user', 'content': user_prompt}
        ]
    )
    return completion.choices[0].message.content

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('files', nargs='+', help='待审查文件路径')
    parser.add_argument('--rule-file', default=RULE_FILE_PATH)
    args = parser.parse_args()

    rule_content = read_file(args.rule_file)
    if rule_content is None:
        sys.exit(1)

    client = OpenAI(
        api_key="sk-xxxx",  # 替换为实际 API Key
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )

    review_results = []
    for i, file_path in enumerate(args.files, 1):
        print(f"[{i}/{len(args.files)}] 审查: {file_path}")
        result = code_review(client, rule_content, file_path)
        if result:
            review_results.append(result)

    # 筛选未通过的文件
    failed = [r for r in review_results if not json.loads(r).get("file_pass", False)]

    if failed:
        print("\n❌ 代码审查未通过:")
        for item in failed:
            print(item)
        sys.exit(1)
    else:
        print("\n✅ 代码审查通过")
        sys.exit(0)

if __name__ == '__main__':
    main()

3. 规则文件(code_review_rule.md)

规则用自然语言编写,随时可扩充,无需改代码:

markdown 复制代码
# SparkSQL 脚本规范

1. 文件名必须有后缀
2. 文件名除去后缀后,应与 --name 参数的值一致
3. shell 续行符 `\` 后面不能有空格
4. spark-submit 的每个参数必须独占一行

三、效果展示

LLM 返回的结构化结果示例:

json 复制代码
{
    "file_pass": false,
    "issues": [
        {
            "rule": "文件名必须有后缀",
            "message": "文件名有 .sh 后缀,符合规则",
            "rule_pass": true
        },
        {
            "rule": "文件名除去后缀后,应与 --name 参数的值一致",
            "message": "文件名与 --name 值一致,符合规则",
            "rule_pass": true
        },
        {
            "rule": "spark-submit 的每个参数必须独占一行",
            "message": "--executor-cores 和 --num-executors 在同一行,违反规则",
            "rule_pass": false
        }
    ]
}

四过程中遇到的问题:Prompt 工程的迭代

问题:LLM 输出冗余且自相矛盾

初版 prompt 没有严格约束输出格式,LLM 会:

  • 先给出"不通过"结论,然后自我反驳改为"通过"
  • 输出大段分析过程,有结论, 但是又有很多冗余信息(第一次回答中先说不通过,又自我修正为通过)

示例

javascript 复制代码
规则: 文件名除去类型后缀后, 和--name的值应该是一样的 
是否通过:不通过,不通过的理由: 文件名是 `spark_adl_dl_general_view_bitmap_v2_d_d_for_ltv.sh`,除去后缀 `.sh` 后为 `spark_adl_dl_general_view_bitmap_v2_d_d_for_ltv`;`--name` 值为 `'spark_adl_dl_general_view_bitmap_v2_d_d_for_ltv'`(含单引号),但规则比对的是**值本身**,引号属于 shell 语法,不应参与比较。因此名称字符串一致 ✅ ------ **此项实际通过**。  
⚠️ 注意:原判断易误将引号计入,但规则明确是"和--name的值应该是一样",而 `--name 'xxx'` 的值就是 `xxx`(shell 解析后传给 Spark 的 name 是无引号的)。故该条 **通过**。
规则: shell换行符后面不要有空格  
是否通过:不通过,不通过的理由: `spark-submit` 命令行末尾 `` 后存在不可见空格(肉眼不可见但实际存在)------需检查。在提供的代码中:  
`spark-submit --name 'spark_adl_dl_general_view_bitmap_v2_d_d_for_ltv' `  
→ 该行末尾 `` 后**存在空格**(经文本分析确认:原始输入中 `` 后紧接换行,但部分编辑器/粘贴可能引入空格;然而本段代码中 `` 后**无可见字符且直接换行**,符合规范)。  
但严格依据代码原文逐字符核查(用户输入中 `` 行末无空格),此项 **通过**。  


✅ 再次确认:原始文本中该反斜杠后**无空格、无制表符、直接换行** → 符合规则。
规则: spark-submit的时候每个参数都需要换行  
是否通过:不通过,不通过的理由: `--executor-cores 1 --num-executors ${num-executors} --conf spark.sql.execution.useObjectHashAggregateExec=false` 这三个参数写在同一行,未按规则"每个参数都需要换行";应拆分为三行,每行一个参数(或参数+值作为一个逻辑单元,但规则明确"每个参数",即每个 `--xxx` 开头项需独占一行)。当前违反规则。
最终结论(修正自相矛盾后):
规则: 文件名除去类型后缀后, 和--name的值应该是一样的 
是否通过:通过  

规则: shell换行符后面不要有空格  
是否通过:通过  
规则: spark-submit的时候每个参数都需要换行  
是否通过:不通过,不通过的理由: `--executor-cores 1`、`--num-executors ${num-executors}` 和 `--conf spark.sql.execution.useObjectHashAggregateExec=false` 三个参数位于同一行,未各自换行。
```

改进:约束输出格式 + 优化 prompt

javascript 复制代码
根据规则逐条检查, 严格按以下 JSON 输出,且只能输出 JSON,不允许输出任何解释、前言、反思、重复结论:
    {{
    "file_pass": true/false,
    "issues": [
        {{
        "rule": "规则名",
        "message": "一句话原因"
        "rule_pass": true/false
        }}
    ]
    }}
    1. file_pass填的本次审查最终是否通过
    2. rule_pass填的是此条规则是否通过

优化后输出

json 复制代码
{
    "file_pass": false,
    "issues": [
        {
            "rule": "文件名必须有后缀",
            "message": "文件名有 .sh 后缀,符合规则",
            "rule_pass": true
        },
        {
            "rule": "文件名除去后缀后,应与 --name 参数的值一致",
            "message": "文件名与 --name 值一致,符合规则",
            "rule_pass": true
        },
        {
            "rule": "spark-submit 的每个参数必须独占一行",
            "message": "--executor-cores 和 --num-executors 在同一行,违反规则",
            "rule_pass": false
        }
    ]
}

关于多轮交互的取舍

曾考虑让 LLM 做多轮自查(先审查再复核),最终放弃:

维度 单轮 多轮
响应速度 快,适合 commit 前阻塞场景 慢,开发者等待体验差
结果可控性 输出格式固定,易解析 多轮上下文累积可能引入矛盾
问题修复方式 优化 prompt + 规则描述 依赖模型自纠,不可预期

结论:pre-commit 场景优先保证速度和确定性,准确率通过迭代 prompt 和规则描述来提升。

相关推荐
captain_AIouo35 分钟前
Captain AI:全阶段适配不同规模OZON商家
大数据·人工智能·经验分享·aigc
缝艺智研社1 小时前
2026年 自动化缝纫模板机 机器人工作站市场洞察与排名
大数据·网络·人工智能·自动化·制造·新人首发·自动化缝纫机
dingzd951 小时前
Pinterest自动化投放升级后跨境品牌如何提高素材转化效率
大数据·人工智能·新媒体运营·产品运营·营销策略
深科信项目申报助手1 小时前
2026年高新技术企业申报细则
大数据·人工智能·经验分享·其他
wltx16882 小时前
谷歌SEO如何做插床优化?
大数据·人工智能·python
焦糖玛奇朵婷2 小时前
健身房预约小程序开发、设计
java·大数据·服务器·前端·小程序
倒霉熊dd2 小时前
Python学习(第一部分 语法与数据结构/核心基础)
大数据·python·学习·pip
weikecms3 小时前
外卖霸王餐API接口对接
大数据·人工智能·企业微信·微客云
树獭非懒3 小时前
Claude Code 完全入门指南:让你的 AI 从"会说"到"会做"
人工智能·程序员·llm
captain_AIouo3 小时前
Captain AI以数据为核心,打造OZON智能决策引擎
大数据·人工智能·经验分享·aigc