目录
一、背景描述
- 一句话介绍就是:基于开源大模型 + 知识库的 Code Review 实践,类似一个代码评审助手(CR Copilot)。
- 信息安全合规问题:公司内代码直接调 ChatGPT / Claude 会有安全/合规问题,为了使用 ChatGPT / Claude 需要对代码脱敏,只提供抽象逻辑,这往往更花时间。
- 低质量代码耗费时间:业务每天至少 10~20 个 MR 需要 CR,虽然提交时 MR 经过 单测 + Lint 过滤了一些低级错误,但还有些问题(代码合理性、经验、MR 相关业务逻辑等)需要花费大量时间,最后可以先经过自动化 CR,再进行人工 CR,可大大提升 CR 效率!
- 符合公司安全规范,所有代码数据不出内网,所有推理过程均在内网完成
- 团队 Code Review 规范缺少执行:大部分团队的 Code Review 停留在文档纸面上,成员之间口口相传,并没有一个工具根据规范来严格执行。
- 🌈 开箱即用:基于 Gitlab CI,仅 10 几行配置完成接入,即可对 MR 进行 CR。
- 🔒 数据安全:基于开源大模型做私有化部署,隔离外网访问,确保代码 CR 过程仅在内网环境下完成。
- ♾ 无调用次数限制:部署在内部平台,只有 GPU 租用成本。
- 📚 自定义知识库:CR 助手基于提供的飞书文档进行学习,将匹配部分作为上下文,结合代码变更进行 CR,这将大大提升 CR 的准确度,也更符合团队自身的 CR 规范。
- 🎯 评论到变更行:CR 助手将结果评论到变更代码行上,通过 Gitlab CI 通知,更及时获取 CR 助手给出的评论。
二、技术原理
三、基于大模型与知识库的Code Review实践
基于大模型与知识库的Code Review实践,具体实施细节可以归纳如下:
1、选择合适的大模型与知识库
- 大模型选择:
- 优先选择具有广泛训练数据和强大语言理解能力的模型,如GPT系列(如GPT-3.5或更高版本)。
- 考虑模型的领域适应性,选择对编程语言和代码结构有深入理解的模型。
- 评估模型的性能,包括处理速度、准确性和可扩展性。
- 知识库构建:
- 收集行业标准和最佳实践,确保知识库内容的权威性和准确性。
- 整合常见的代码问题和解决方案,构建易于查询和检索的知识库。
- 不断更新和维护知识库,确保其内容的时效性和完整性。
2、集成大模型与知识库到Code Review流程
- 自动化工具开发:
- 利用大模型开发自动化代码分析工具,如静态代码分析器、代码风格检查器等。
- 这些工具可以自动检测代码中的潜在问题,如语法错误、逻辑错误、性能问题等。
- 工具的输出结果可以作为Code Review的参考依据。
- 代码注释和文档生成:
- 利用大模型的文本生成能力,为代码片段自动生成注释和文档。
- 这可以提高代码的可读性和可维护性,便于其他团队成员理解和使用代码。
- 集成知识库:
- 在Code Review过程中,集成知识库作为参考资源。
- 审查者可以通过查询知识库来快速获取相关的行业标准和最佳实践等信息。
- 审查者可以利用这些信息来更准确地评估代码的质量和可靠性。
3、结合人工审查
- 审查者角色:
- 审查者需要具备一定的编程能力、自然语言处理能力和领域知识。
- 审查者需要关注模型的输出结果,判断其是否准确可靠,并根据实际情况做出调整。
- 审查者还需要关注代码的逻辑、业务逻辑等方面的问题,确保代码的正确性和可靠性。
- 审查流程:
- 审查者首先使用自动化工具对代码进行初步分析。
- 然后,审查者可以结合知识库中的信息对代码进行深入审查。
- 审查者需要记录审查过程中的问题和建议,并与开发人员进行沟通和讨论。
4、持续优化与改进
- 数据收集与反馈:
- 收集Code Review过程中的数据和反馈,包括审查结果、审查者建议等。
- 分析这些数据,了解Code Review的效果和存在的问题。
- 模型优化:
- 根据收集的数据和反馈,对大模型进行优化和改进。
- 例如,可以更新模型的训练数据、调整模型的参数设置等,以提高模型的性能和准确性。
- 知识库更新:
- 定期更新和维护知识库,确保其内容的时效性和完整性。
- 整合新的行业标准和最佳实践到知识库中,为审查者提供更全面的参考资源。
通过以上具体实施细节,可以确保基于大模型与知识库的Code Review实践能够高效、准确地评估代码的质量和可靠性,提高开发效率。
四、技术细节
1、gitlab配置mr的webhook
在 GitLab 中,当你想要配置 Merge Request (MR) 的 webhook(也称为“Web 钩子”或“HTTP 钩子”)时,你实际上是在设置当某个事件(如新的 MR 被创建、MR 被更新、MR 被合并等)发生时,GitLab 会向指定的 URL 发送 HTTP 请求。
以下是如何在 GitLab 中配置 Merge Request 相关的 webhook 的步骤:
- 登录 GitLab 并导航到你想要配置 webhook 的项目。
- 在项目的侧边栏中,选择 Settings(设置)。
- 在左侧菜单中,选择 Integrations(集成)或 Webhooks(具体取决于 GitLab 的版本和配置)。
- 在 Webhooks 页面上,你会看到一个用于添加新 webhook 的表单。
- URL:输入你想要接收 webhook 请求的 URL。这通常是一个 API 端点或 webhook 处理器的地址。
- Secret Token(可选):这是一个可选的令牌,用于在发送 webhook 请求时验证请求的源。如果你在此处设置了令牌,则你的 webhook 处理器也必须包含该令牌以验证请求。
- Trigger(触发器):在下拉列表中选择你想要触发 webhook 的事件。对于 Merge Request,你可能会选择 Merge Request events 或其他相关选项。确保选中了与 Merge Request 相关的事件,如
Opened
(打开)、Updated
(更新)、Merged
(合并)等。 - Add Webhook(添加 webhook):完成上述设置后,点击此按钮以保存 webhook 配置。
默认本地访问会报错,需要按照如下方式开启本地访问权限
现在,当指定的 Merge Request 事件发生时,GitLab 会向你的 webhook URL 发送一个 HTTP 请求。这个请求将包含与事件相关的数据,你可以在你的 webhook 处理器中解析这些数据并执行相应的操作。
gitlab的webhook原理
项目代码变动往gitlab上推送相应的事件,例如代码push,新建tag,创建merge request等等;
gitlab收到相应事件,触发对应的webhook,设置HTTP请求的header以及request body,然后发送HTTP请求到配置的webhook的URL;
HTTP请求到达对应的处理服务器以后,对request body和header进行解析,包装通知内容;
注意:确保你的 webhook 处理器能够处理来自 GitLab 的请求,并且你的 GitLab 实例和 webhook 处理器之间的网络连接是安全的。如果你在生产环境中使用 webhook,请考虑使用 HTTPS 和身份验证来确保请求的安全性。
创建MR请求
获得回调结果
{
"object_kind": "merge_request",
"event_type": "merge_request",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"email": "[REDACTED]"
},
"project": {
"id": 2,
"name": "ruoyi",
"description": "ruoyi",
"web_url": "http://localhost:8200/root/ruoyi",
"avatar_url": null,
"git_ssh_url": "ssh://git@localhost:8200/root/ruoyi.git",
"git_http_url": "http://localhost:8200/root/ruoyi.git",
"namespace": "Administrator",
"visibility_level": 20,
"path_with_namespace": "root/ruoyi",
"default_branch": "main",
"ci_config_path": null,
"homepage": "http://localhost:8200/root/ruoyi",
"url": "ssh://git@localhost:8200/root/ruoyi.git",
"ssh_url": "ssh://git@localhost:8200/root/ruoyi.git",
"http_url": "http://localhost:8200/root/ruoyi.git"
},
"object_attributes": {
"assignee_id": 1,
"author_id": 1,
"created_at": "2024-06-10 01:26:41 UTC",
"description": "Uat",
"head_pipeline_id": null,
"id": 2,
"iid": 2,
"last_edited_at": null,
"last_edited_by_id": null,
"merge_commit_sha": null,
"merge_error": null,
"merge_params": {
"force_remove_source_branch": "1"
},
"merge_status": "preparing",
"merge_user_id": null,
"merge_when_pipeline_succeeds": false,
"milestone_id": null,
"source_branch": "uat",
"source_project_id": 2,
"state_id": 1,
"target_branch": "main",
"target_project_id": 2,
"time_estimate": 0,
"title": "Uat",
"updated_at": "2024-06-10 01:26:41 UTC",
"updated_by_id": null,
"url": "http://localhost:8200/root/ruoyi/-/merge_requests/2",
"source": {
"id": 2,
"name": "ruoyi",
"description": "ruoyi",
"web_url": "http://localhost:8200/root/ruoyi",
"avatar_url": null,
"git_ssh_url": "ssh://git@localhost:8200/root/ruoyi.git",
"git_http_url": "http://localhost:8200/root/ruoyi.git",
"namespace": "Administrator",
"visibility_level": 20,
"path_with_namespace": "root/ruoyi",
"default_branch": "main",
"ci_config_path": null,
"homepage": "http://localhost:8200/root/ruoyi",
"url": "ssh://git@localhost:8200/root/ruoyi.git",
"ssh_url": "ssh://git@localhost:8200/root/ruoyi.git",
"http_url": "http://localhost:8200/root/ruoyi.git"
},
"target": {
"id": 2,
"name": "ruoyi",
"description": "ruoyi",
"web_url": "http://localhost:8200/root/ruoyi",
"avatar_url": null,
"git_ssh_url": "ssh://git@localhost:8200/root/ruoyi.git",
"git_http_url": "http://localhost:8200/root/ruoyi.git",
"namespace": "Administrator",
"visibility_level": 20,
"path_with_namespace": "root/ruoyi",
"default_branch": "main",
"ci_config_path": null,
"homepage": "http://localhost:8200/root/ruoyi",
"url": "ssh://git@localhost:8200/root/ruoyi.git",
"ssh_url": "ssh://git@localhost:8200/root/ruoyi.git",
"http_url": "http://localhost:8200/root/ruoyi.git"
},
"last_commit": {
"id": "a39deeac9763e89f81993bf85cc5f13efb9c3bad",
"message": "error\n",
"title": "error",
"timestamp": "2024-06-10T08:16:19+08:00",
"url": "http://localhost:8200/root/ruoyi/-/commit/a39deeac9763e89f81993bf85cc5f13efb9c3bad",
"author": {
"name": "luomao1",
"email": "luomao1@lenovo.com"
}
},
"work_in_progress": false,
"total_time_spent": 0,
"time_change": 0,
"human_total_time_spent": null,
"human_time_change": null,
"human_time_estimate": null,
"assignee_ids": [1],
"state": "opened",
"blocking_discussions_resolved": true,
"action": "open"
},
"labels": [],
"changes": {
"merge_status": {
"previous": "unchecked",
"current": "preparing"
}
},
"repository": {
"name": "ruoyi",
"url": "ssh://git@localhost:8200/root/ruoyi.git",
"description": "ruoyi",
"homepage": "http://localhost:8200/root/ruoyi"
},
"assignees": [{
"id": 1,
"name": "Administrator",
"username": "root",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"email": "[REDACTED]"
}]
}
获得差异代码
{
"assignee": {
"avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"external": false,
"id": 1,
"name": "Administrator",
"state": "active",
"username": "root"
},
"author": {
"avatarUrl": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"external": false,
"id": 1,
"name": "Administrator",
"state": "active",
"username": "root"
},
"baseSha": "cafe2e5ea1d0892b8e770d84862686fb5dd9ad11",
"changes": [{
"aMode": "100644",
"bMode": "100644",
"deletedFile": false,
"diff": "@@ -33,6 +33,8 @@ public class UserController {\n @GetMapping(\"userinfo\")\n public ModelAndView userinfo() {\n SysUserVo sysUserVo = sysUserService.findByLoginName(SecurityUtil.getLoginUser().getUsername()).getData();\n+ // 密码设置错误\n+ String password = sysUserVo.getPassword();\n sysUserVo.setPassword(null);\n return new ModelAndView(\"user/userinfo\", \"user\", sysUserVo);\n }\n",
"newFile": false,
"newPath": "src/main/java/cn/huanzi/qch/baseadmin/user/controller/UserController.java",
"oldPath": "src/main/java/cn/huanzi/qch/baseadmin/user/controller/UserController.java",
"renamedFile": false
}],
"changesCount": "1",
"closed": false,
"createdAt": 1717982801579,
"description": "Uat",
"downvotes": 0,
"forceRemoveSourceBranch": true,
"headSha": "a39deeac9763e89f81993bf85cc5f13efb9c3bad",
"id": 2,
"iid": 2,
"labels": [],
"latestBuildStartedAt": 1718002898301,
"mergeStatus": "can_be_merged",
"mergeWhenPipelineSucceeds": false,
"merged": false,
"projectId": 2,
"sha": "a39deeac9763e89f81993bf85cc5f13efb9c3bad",
"sourceBranch": "uat",
"sourceProjectId": 2,
"squash": true,
"startSha": "cafe2e5ea1d0892b8e770d84862686fb5dd9ad11",
"state": "opened",
"targetBranch": "main",
"targetProjectId": 2,
"title": "Uat",
"updatedAt": 1718006208667,
"upvotes": 0,
"userNotesCount": 3,
"webUrl": "http://localhost:8200/root/ruoyi/-/merge_requests/2",
"workInProgress": false
}
2、请求通义千问获得review结果
如何快速入门通义千问API_模型服务灵积(DashScope)-阿里云帮助中心
java调用通义千问API的详细完整步骤_java_脚本之家
前提条件
-
DashScope SDK提供了Python和Java两个版本,请确保您已安装最新版SDK:安装DashScope SDK。您也可以通过HTTP接口调用,详情请见:HTTP调用接口。
-
已开通服务并获得API-KEY:开通DashScope并创建API-KEY。
-
-
我们推荐您将API-KEY配置到环境变量中以降低API-KEY的泄漏风险,详情可参考通过环境变量配置API-KEY。您也可以在代码中配置API-KEY,但是泄漏风险会提高。
-
# 用您的 DashScope API-KEY 代替 YOUR_DASHSCOPE_API_KEY ## 如果您的Shell类型是Zsh,运行以下命令 echo "export DASHSCOPE_API_KEY='YOUR_DASHSCOPE_API_KEY'" >> ~/.zshrc
-
-
同时DashScope提供了与OpenAI兼容的接口访问服务,详情参考OpenAI接口兼容。
{
"requestId": "468f22eb-673d-91ed-8b2c-51db041ef92d",
"usage": {
"input_tokens": 25,
"output_tokens": 68,
"total_tokens": 93
},
"output": {
"choices": [{
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "通义千问是阿里云自主研发的超大规模语言模型,能够回答问题、创作文字,还能表达观点、撰写代码。作为一个大型预训练语言模型,我能够根据您提出的指令产出相关的回复,并尽可能地提供准确和有用的信息。如果您有任何问题或需要帮助,请随时告诉我,我会尽力提供支持。"
}
}]
}
}
3、Gitlab调用代码review评论API
16.commint 提交信息 获取库的所有提交信息、创建多个文件的提交、单个文件提交、获取提交被推送到的branch、对给定分支的提交、还原至本次提交、提交与git文件的对比、查询提交评论信息、发表评论并提交、获取发表的评论、获取提交状态、查询提交合并信息、获取GPG签名
GitLab 提供了丰富的 API 来与 GitLab 实例进行交互,包括获取和创建 Merge Request(MR)的评论。以下是如何使用 GitLab API 来调用代码审查(Code Review)评论的相关步骤和示例。
1. 获取私有令牌(Private Token)
首先,你需要从 GitLab 用户设置中获取一个私有令牌(Private Token),这将用于 API 认证。
- 登录到 GitLab。
- 点击右上角的用户头像,选择“Settings”(设置)。
- 在左侧菜单中选择“Access Tokens”(访问令牌)。
- 输入一个名称,选择你需要的权限范围(对于代码审查,通常需要“api”权限),然后点击“Create personal access token”(创建个人访问令牌)。
- 复制生成的令牌并妥善保存,因为离开页面后你将无法再次查看它。
2. 调用 API
你可以使用任何支持 HTTP 请求的编程语言或工具来调用 GitLab API。以下是一个使用 curl
命令的示例,展示如何获取一个 Merge Request 的所有评论:
curl --header "Private-Token: YOUR_PRIVATE_TOKEN" "https://gitlab.example.com/api/v4/projects/PROJECT_ID/merge_requests/MERGE_REQUEST_IID/notes"
YOUR_PRIVATE_TOKEN
:替换为你的私有令牌。gitlab.example.com
:替换为你的 GitLab 实例的 URL。PROJECT_ID
:替换为你要获取评论的项目的 ID。MERGE_REQUEST_IID
:替换为你要获取评论的 Merge Request 的内部 ID(不是全局 ID)。
3. 创建评论
你也可以使用 API 来创建一个新的评论。以下是一个示例:
curl --request POST --header "Private-Token: YOUR_PRIVATE_TOKEN" --header "Content-Type: application/json" --data '{"body":"这是一个新的评论"}' "https://gitlab.example.com/api/v4/projects/PROJECT_ID/merge_requests/MERGE_REQUEST_IID/notes"
body
:是你要创建的评论的内容。
{
"id": 2,
"type": null,
"body": "这是一个新的评论",
"attachment": null,
"author": {
"id": 5,
"username": "project_2_bot1",
"name": "****",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0aaf8dea0d60673d2f8ee409dc2ec2af?s=80\u0026d=identicon",
"web_url": "http://localhost:8200/project_2_bot1"
},
"created_at": "2024-06-10T07:15:02.909Z",
"updated_at": "2024-06-10T07:15:02.909Z",
"system": false,
"noteable_id": 2,
"noteable_type": "MergeRequest",
"resolvable": false,
"confidential": false,
"noteable_iid": 2,
"commands_changes": {}
}
4. 注意事项
- 确保你的 GitLab 实例的 URL、项目 ID 和 Merge Request ID 都是正确的。
- GitLab API 遵循 RESTful 设计原则,因此你可以使用 GET、POST、PUT、DELETE 等 HTTP 方法来执行不同的操作。
- 始终在发送请求时包含你的私有令牌进行认证。
- GitLab API 文档是一个很好的资源,你可以在其中找到关于所有可用端点和参数的详细信息。你可以通过访问
https://gitlab.example.com/help/api/README.md
(将gitlab.example.com
替换为你的 GitLab 实例的 URL)来访问它。
参考资料
基于大模型 + 知识库的 Code Review 实践_大模型进行codereview结果对比-CSDN博客
AIGC 重塑软件工程 Code Review 篇:IDE 插件 + DevOps 平台协同组合-CSDN博客
🚀 CI+GPT双引擎驱动,🤖 开启AI代码评审新纪元 - 知乎 (zhihu.com)
基于大模型的 Code Review 实践 - 掘金 (juejin.cn)
玩转 ChatGPT+极狐GitLab|自动化的MR 变更评审来了 - 知乎 (zhihu.com)
GitLab的Webhook配置和开发_gitlab webhook-CSDN博客
GitLab的Webhook功能及自动触发流程-git-PHP中文网
Review Apps · Gitlab 中文文档 · 看云
Gitlab配置webhook报错:Urlis blocked: Requests to the local netwo..解决-腾讯云开发者社区-腾讯云