大家好,我是韩数,最近业余时间写了一个开源小工具 ChatGPT CodeReview, 目前已经开源至Github。
项目地址: github.com/hanshuaikan… (跪求各位客官姥爷Star一下)
由于我自己也基本上是 ChatGPT 的重度用户,因此自己在 github 上去开源一些代码的时候也不免会发给 AI 帮忙提出一些评审建议,有的评审建议还是比较有用的,于是就想到能不能使用 ChatGPT 去自动生成 github 的 review 评论呢,我期望的效果大概是这样:
说干就干,当然,今天这篇文章并不是主要推广我的这个小工具, 这个小工具更多的是验证了自己想法的可行性。
ChatGPT CodeReview 介绍
ChatGPT Code Review 是一个用Golang 编写的 AI Code Review 工具,它可以利用 AI 自动完成某个Github PR 的 Review 并 comment 到对应的代码段下。
目前主要提供了命令行和代码集成两个使用, 开发者可以通过实现对应的接口,自定义相关AI(文心一言) 和 集成(gitlab, 码云) 的实现。 默认集成了 ChatGPT 3.5 和 github 的实现。
package main
import (
"context"
"github.com/hanshuaikang/chatgpt-codereview/pkg"
"github.com/hanshuaikang/chatgpt-codereview/pkg/chatgpt"
"github.com/hanshuaikang/chatgpt-codereview/pkg/github"
"os"
)
func main() {
config := pkg.Config{
Owner: "",
Repo: "",
Pr: 0,
Prompt: "",
ApiKey: "",
Token: "",
}
defaultGptCli := chatgpt.NewGptClient(config)
githubCli := github.NewGithubCli(config.Token, config.Owner, config.Repo, config.Pr)
runner := pkg.NewCodeReviewRunner(&config, githubCli, defaultGptCli)
ctx := context.Background()
err := runner.RunCodeReview(ctx)
if err != nil {
os.Exit(1)
}
}
详细的实现方案
流程图
其实用 AI 进行 CodeReview 的思路并不复杂,网上的方案基本上都是通过 githu b的接口获取到某个 PR 的Diff, 并解析 AI 返回的结果,但是经过实操,发现这样准确率实在堪忧, 原因主要是因为 diff 拿到的是并不完整的代码段, AI 很难基于一个不完整的代码段给出什么精确的建议, 基于这个方案我发现 AI 很容易胡说八道, 比如提醒你的代码少了个括号什么的。
于是我换了一种思路,整个文件进行CodeReview, 只截取变动部分的评论,接下来是主要实现思路
取一个好的 prompt 很重要
在我的开源项目 chatgpt-codereview 中, 这个prompt 是这样的:
角色:你是一个非常超级高级的Golang工程师。
任务:请帮我review以下代码,重点审查是否存在非常严重的bug或代码逻辑错误。
只有在遇到[代码漏洞,逻辑漏洞,拼写错误,性能问题]等关键问题时才提出修改建议。请忽略补充日志、注释、优化错误,缺少结构体定义信息等常规性建议。
要求:
审查标准:严格遵循以下几点:
只提出关键性问题包括,代码漏洞、逻辑漏洞、拼写错误、性能问题。
建议格式:所有建议应按照“[行号] 建议内容”的格式提出,以便清晰地识别问题所在的具体位置。
返回内容限制:只返回有问题的行,不要返回任何标记为“无需修改”的行。
当然有这些还远远不够, 此时我们需要强制 AI 返回固定格式的数据, 最好能告诉我们哪行代码有需要改进的地方, 我们需要在末尾强制指定返回格式, 如果使用 chatgpt-codereview
, 会自动在你自定义的 prompt 后添加该 prompt 进行补充。
You must return it in this format, like [25] if err ! = nil { . instead of [Line 25] if err ! = nil
获取 github 某个 pr 的 commit 信息
这一步主要需要解析 github 的 pr 下每个 commit 的 git diff 信息, 大概长这样:
@@ -34,24 +34,14 @@ func (e syntaxExecutor) parseOutPut(path string, output string) (int, map[string
func (e syntaxExecutor) runVet(path string) (int, map[string]interface{}) {
- isDir, err := isDirectory(path)
- if err != nil {
- return 0, nil
- }
-
- pathArgs := "./..."
- if !isDir {
- pathArgs = path
- }
-
var out bytes.Buffer
var errOut bytes.Buffer
- cmd := exec.Command("go", "vet", pathArgs)
+ cmd := exec.Command("go", "vet", "./...")
cmd.Stdout = &out
cmd.Stderr = &errOut
- err = cmd.Run()
+ err := cmd.Run()
// go vet 命令如果 err, 则说明有语法错误
count, detail := e.parseOutPut(path, errOut.String())
if err != nil {
解析该信息,我们可以知道最终变化的部分在本次 commit 的 34 - 47 行, 也就是我们本次 commit 的范围,但是这样还不够。 把这段代码直接交给 AI 去 Review 是完全行不通的。
因为如果我们想要生成 comment 信息,我们必须知道两个信息 文件
和要comment的行号
。
获取 commit 该文件的全部内容,并按照行号编号。
由于给代码段会导致 AI
胡说八道, 因此会将需要 review 的文件的全部内容,编号发给 AI, 以获取更准确的结果。
为文件生成编号:
1 package engine
2
3 import (
4 "bytes"
5 "os"
6 "os/exec"
7 "path/filepath"
8 "regexp"
9 "strings"
10 )
11
12 type syntaxExecutor struct {
13 }
.....
将 prompt 和 处理过的文件 发送给 AI 进行 CodeReview。
剩下的工作就十分简单了,将 prompt 和 处理过的文件 发送给 AI 进行 CodeReview, 并获取到对应的结果, 进行解析, 解析完毕之后将内容转换成 github 的 comment。 调用 github 的 API 把 code reivew 结果提交。
总结
到这里, 已经把整个思路讲清楚了,由于 golang 并没有很好的实现, 市面上的开源项目也很少把详细的思路写出来, 所以写了今天这篇文章, 最后跪求各位客官点个小小的 star, 就好这口。
项目地址: github.com/hanshuaikan…
万水千山总是情,给个star行不行
欢迎点赞,关注我,有你好果子吃(滑稽)