AI+CICD: 使用本地大模型进行代码审核

需求来源

疫情后对产品研发工作的最大影响,是随着经济下行,公司在不停地裁员,导致研发团队人力资源严重不足。 事情多得做不完,过去还在定期组织的质量控制手段–code review,现在也忙得没有时间组织了。

早就听说*-coder系列开源大模型在编码方便挺强悍,于是决定尝试使用本地大模型加入到CICD流程中,在团队提交代码的时候,由AI进行code review工作。

计划

我们的源代码管理环境使用gitea,只要配置workflow工作流就可以了。在工作流中把diff信息交给本地编程大模型进行评估,并把结果输出,根据结果决定后续行为。

所以,我们需要:

  1. 一个workflow配置文件
  2. 一个程序负责提取diff并调用到模型并反馈结果
  3. 一个本地大模型服务

我们倒序进行实现。

本地编程大模型服务

参考《LLM:使用Ollama访问本地大模型》,我们使用Ollama搭建本地编程大模型服务。

curl -fsSL https://ollama.com/install.sh | sh
ollama serve

编程大模型这里选择口碑极好的qwen3-coder:30b

ollama pull qwen3-coder:30b

代码检测程序

使用Python调用ollama非常容易,但是考虑到gitea的runner是一个docker环境,在同一的镜像中,运行Python和相关的库需要从网络拉取依赖包会导致一些麻烦。所以这里使用go静态编译,直接生成一个bin文件放在本地文件服务器上,runner直接拉取一个bin就可以工作,不需要任何依赖环境,流程更简洁速度也更快。

定义本地ollama地址和模型

var (
	ollamaURL = getEnv("OLLAMA_ENDPOINT", "http://ai.internal:11434")
	model     = "qwen3-coder:30b"
)

获取diff信息:

func getGitDiff() (string, error) {
	cmd := exec.Command("git", "diff", "HEAD^", "HEAD")
	output, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("获取 git diff 失败: %w", err)
	}
	return string(output), nil
}

最后传递这样一个提示词

    你是一个代码质量审查专家。请分析以下 Git Diff 代码变更,并严格按 JSON 格式返回结果。
    
    JSON 格式要求:
    {{
        "score": (0-100的整数,代表代码质量和安全性),
        "summary": "简短的总结",
        "risks": ["风险点1", "风险点2"],
        "suggestions": ["改进建议1"]
    }}

    代码变更内容:
    {diff_text}

静态编译后,放置在本地文件服务器上

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ai-codereview ai-codereview.go
rsync -vhP ai-codereview user@fileserver:/gitea/action/

在工作流中加入AI代码审查

现在万事俱备,只欠东风。先找一个不那么重要的项目进行实验,比如testcase项目,

首先在gitea的设置的action中,添加变量 OLLAMA_ENDPOINT=http://ai.internal:11434

然后创建工作流

# 创建工作流
mkdir -p .gitea/workflows
touch ai-codereview.yaml

在jobs中添加流程:

    - name: AI Code Review
        env:
            OLLAMA_ENDPOINT: ${{ secrets.OLLAMA_ENDPOINT }}
        run: |
            curl -sSL http://10.0.1.1/gitea/action/ai-codereview -o /tmp/ai-codereview
            chmod +x /tmp/ai-codereview
            /tmp/ai-codereview 2>&1 | tee ai_report.log

提交代码,这个项目就已经具有AI智能代码审核功能了。

本次提交的AI评审报告:

---AI评审报告---
得分:75
总结:代码变更引入了新的AI审核机制,通过调用ollama模型进行代码质里分析。整体结构清晰,但存在安全和可维护性风险。
风险:使用硬编码的默认 ollama 地址可能导致配置错误或安全漏洞,从外部 URL 下载可执行文件(/tmp/ai-codereview)存在执行未验证代码的风险,未对AI返回结果做充分校验,可能影响审核逻辑的准确性
[结果] ✅ 代码质量合格。

验证

创建TUI的helloword代码,提交

--- AI 评审报告 ---
得分: 65
总结: 代码功能变更但存在安全隐患和可读性问题
风险: 使用了非ASCII字符和特殊符号,可能导致终端显示异常或编码问题, 代码可读性差,难以维护和理解原始功能意图
[结果] ✅ 代码质量合格。

给项目增加个README

--- AI 评审报告 ---
得分: 100
总结: 本次变更主要涉及工作流名称的修改和README文件的更新,均为非核心代码变更,无安全风险。
风险: 
[结果] ✅ 代码质量合格。

下面编写一个存在CVE漏洞的代码并提交,得到:

--- AI 评审报告 ---
得分: 35
总结: 代码存在严重安全漏洞,包括路径遍历攻击和不安全的文件操作
风险: 路径遍历攻击漏洞:直接使用用户输入的文件名拼接路径,可访问任意文件, 使用已废弃的ioutil.ReadFile函数:应使用os.ReadFile替代, 缺少输入验证和过滤:未对用户输入进行任何安全检查
[结果] ❌ 代码质量未达标,拒绝合并!

实际效果达到预期

高危代码阻断工作流

上述实验中使用的方法是通过Push 触发的 Action,代码是进入仓库后才执行的审核,无法实现高危代码拦截。 可以通过gitea的Pull Request工作流(简称PR)来实现合并代码前拦截,思路如下。

  1. 禁止直接 push 到 main 分支
  2. 强制通过 PR 合并代码
  3. PR 触发 AI 审核,审核通过才合并到main

在gitea的设置/分支中,做如下设置:

  • main分支禁止直接 push

开发流程稍作调整,本地在分支上开发,提交后创建PR,并由AI进行审核,审核通过后才可以合并到main

改进后的流程如下:

开发者 → feature分支 → 创建PR到main → 触发AI审核 → 通过才能合并
                                         ↓
                                    失败则阻止合并

这样就可以防止高危代码进入main分支了。

结论

一些toB的AI商业产品也在做类似的事情,其实自己做一个实现也并不复杂。

实验证明,本地离线大模型确实可以识别风险代码,并对代码问题提出有价值的改进建议,使用4090显卡的话,30b大模型可以秒级完成代码审核(测试代码改动幅度小于一屏的条件下),完全值得你把它放到CICD流程中来。